qwen-code/scripts/tests/ci-flaky-rerun-workflow.test.js
易良 d4c15f05c5
feat(ci): add automated PR failure patrol (#6766)
* feat(ci): add stale failure patrol

* fix(ci): harden failure patrol

* refactor(ci): simplify flaky rerun patrol

* docs(ci): clarify flaky patrol skill boundary

* feat(ci): patrol stale PR failures

* fix(ci): prefilter failed PRs

* fix(ci): isolate patrol classification

* fix(ci): revalidate stale patrol actions

* fix(ci): verify main before branch update

* fix(ci): classify all stale PR failures

* fix(ci): bound patrol batches

* fix(ci): persist patrol failure state

* fix(ci): harden patrol state transitions

* fix(ci): harden stale failure patrol

* fix(ci): continue patrol after expired logs

* fix(ci): tighten patrol guardrails

* fix(ci): preserve failure context in patrol logs

* fix(ci): harden stale patrol closeout

* fix(ci): paginate patrol marker comments

* fix(ci): harden patrol action guards

* test(ci): cover patrol guard rails

* fix(ci): harden patrol marker parsing

* fix(ci): address patrol review followups

* test(ci): cover patrol review edges

* fix(ci): record patrol rerun marker first

* fix(ci): harden patrol review edge cases

* fix(ci): tighten stale failure patrol markers

* refactor(ci): simplify flaky rerun patrol (2838→1258 lines)

- Remove classification guards from actOnDecision (confidence check,
  action enum validation, boundedReason, update_branch multi-guard chain)
- Move classification rules to SKILL.md prompt
- Delete 40 source-code text matching tests, keep 20 behavior tests
- Merge identity job into classify, remove SHA verification
- Change scan sort order from oldest-first to newest-first
- Remove unused functions: writeSkillInputs, failureKey, boundedReason,
  canAct, skillCandidate, mainRunSucceeded

* fix(ci): show gh stderr in top-level error output

When gh CLI returns non-zero exit, execFile rejects with an error whose
.stderr contains the actual GitHub API diagnostic. Previously only one
of stderr or message was shown; now both are printed.

* fix(ci): address patrol review findings

* fix(ci): make stale patrol actions recoverable

* refactor(ci): simplify flaky rerun patrol

* fix(ci): close flaky patrol review gaps

* fix(ci): restore PR failure patrol actions

* fix(ci): harden failure patrol scanning

* fix(ci): address patrol review follow-ups

* fix(ci): harden patrol parsing and coverage

* fix(ci): classify failures against PR changes

* fix(ci): harden patrol script input handling

* fix(ci): remove unsafe auto branch update

* test(ci): exercise patrol action limit

* fix(ci): bind patrol actions to current evidence

* fix(ci): count patrol actions per PR

* fix(ci): redact quoted secret labels

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-15 00:51:11 +00:00

71 lines
2.7 KiB
JavaScript

import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
import { parse } from 'yaml';
const workflow = readFileSync(
'.github/workflows/qwen-ci-flaky-rerun.yml',
'utf8',
);
const yml = parse(workflow);
const skill = readFileSync('.qwen/skills/ci-flaky-patrol/SKILL.md', 'utf8');
describe('ci failure patrol workflow', () => {
it('runs every ten minutes with serialized configurable batches', () => {
expect(yml.on.schedule[0].cron).toBe('*/10 * * * *');
expect(yml.concurrency).toEqual({
group: 'qwen-ci-flaky-rerun',
'cancel-in-progress': false,
});
expect(yml.env.ACTIVE_DAYS).toBe('7');
expect(yml.env.MAX_CANDIDATES_PER_RUN).toBe('5');
expect(workflow).toContain('--active-days "${ACTIVE_DAYS}"');
expect(workflow).toContain('--max-candidates "${MAX_CANDIDATES_PER_RUN}"');
});
it('keeps classifier credentials isolated and PAT writes explicit', () => {
const classifier = yml.jobs.classify.steps.find((step) =>
step.uses?.includes('qwen-code-action'),
);
expect(classifier.env).toEqual({ GH_TOKEN: '', GITHUB_TOKEN: '' });
expect(JSON.stringify(classifier)).not.toContain('CI_BOT_PAT');
expect(yml.jobs.act.permissions).toEqual({
actions: 'read',
contents: 'read',
'pull-requests': 'read',
});
expect(JSON.stringify(yml.jobs.act)).toContain('CI_BOT_PAT');
});
it('passes a decision batch through a trusted act job and always resets', () => {
expect(yml.jobs.classify.outputs).toHaveProperty('bot_login');
expect(workflow).toContain('ci-flaky-decisions.json');
expect(workflow).toContain('--input-sha');
expect(workflow).toContain('--trusted-marker-login');
const reset = yml.jobs.act.steps.find(
(step) => step.name === 'Reset successful failure state',
);
expect(reset.if).toContain('always()');
expect(reset['continue-on-error']).toBe(true);
expect(reset.env.GH_TOKEN).toContain('CI_BOT_PAT');
});
it('runs scan and act with the same trusted event commit', () => {
for (const job of [yml.jobs.classify, yml.jobs.act]) {
const checkout = job.steps.find((step) =>
step.uses?.includes('actions/checkout'),
);
expect(checkout.with.ref).toBe('${{ github.sha }}');
expect(checkout.with['persist-credentials']).toBe(false);
}
});
it('keeps judgment in the skill and GitHub writes in the driver', () => {
for (const action of ['rerun', 'comment', 'no_action']) {
expect(skill).toContain(action);
}
expect(skill).toContain('maximum of 3 actions');
expect(skill).toContain('main-branch failures');
expect(skill).toContain('changedFiles');
expect(skill).toContain('ci-flaky-decisions.json');
});
});