mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
The body of _process.yml is read from dev (@dev, #623), so the flags and formats it passes to scripts are dev's. After "Опубликовать документ ревью" the working copy of job integrate is the task branch, and a show/ship branch with a clean merge is not rebased before review: its scripts/ may lag dev by days. review-doc-guard.mjs silently ignores unknown flags (the anchor lost #726 route and #737 usage), and a stale merge-candidate.mjs merges the old way. Only two calls (#723 push refusal, #726 route) were taken from dev, each with its own extraction, and on ship/reuse the remaining ones ran dev's version anyway: the script version depended on the path. Now one step right after setup-node extracts `git archive origin/dev scripts .github/workflows/validate.yml` into $RUNNER_TEMP/dev-tools and every repo script of the job runs from there via TOOLS (review-result-gate, review-doc-guard, reviews-index, merge-candidate, process-track route, status-label). validate.yml is part of the snapshot because workflow-jobs.mjs reads it relative to itself; without it ci-proof answers `failed (#622)` and every code merge would return to S6. The working copy stays the material: git, the document and paths are judged there. PROCESS.md §10.4 gets the paragraph "Скрипты конвейера — из dev": the model_review exception, merges of pipeline changes judged by dev's version, and compatible edits of the Validate proof contract. Tests: test/process-integrate-tools.test.mjs is the job contract (no step calls scripts/ from the working copy, every call goes through the snapshot, one archive from origin/dev with validate.yml, and the step as is yields a directory where ci-proof resolves the job contract); publish-push-refusal runs the publish step and the #413 step on real bash with a task branch whose review-doc-guard.mjs exits 7 (red with the old call). Existing harnesses take the snapshot step before the publish and decide steps; the #706 mutant anchor follows the status-label call. Issue: #749 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
58 lines
3.9 KiB
JavaScript
58 lines
3.9 KiB
JavaScript
// #706: исход rereview возвращает задачу в ту же метку `S7-code-review`, и
|
|
// одним вызовом `gh` она добавлялась и тут же снималась — задача оставалась без
|
|
// статуса, новый заход не стартовал.
|
|
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { readFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { moveStatusLabel } from '../scripts/status-label.mjs';
|
|
|
|
const REPO = 'Matysh/houseplan-card';
|
|
const recorder = (fail = () => false) => {
|
|
const calls = [];
|
|
const execute = (args, opts = {}) => {
|
|
calls.push(args.slice(5).join(' '));
|
|
const status = fail(args, calls.length) ? 1 : 0;
|
|
if (status && !opts.allowFailure) throw new Error(`gh ${args.join(' ')} → boom`);
|
|
return { status, stderr: status ? 'boom' : '' };
|
|
};
|
|
return { calls, execute };
|
|
};
|
|
|
|
test('#706 rereview: та же метка снимается и ставится заново отдельными вызовами', () => {
|
|
const { calls, execute } = recorder();
|
|
const how = moveStatusLabel({ repo: REPO, issue: 699, from: 'S7-code-review', to: 'S7-code-review', execute });
|
|
assert.equal(how, 'relabeled');
|
|
assert.deepEqual(calls, ['--remove-label S7-code-review', '--add-label S7-code-review'],
|
|
'снятие, затем постановка — событие labeled запускает новый заход');
|
|
});
|
|
|
|
test('#706 обычный исход — одна правка: новая метка ставится, прежняя снимается', () => {
|
|
const { calls, execute } = recorder();
|
|
assert.equal(moveStatusLabel({ repo: REPO, issue: 700, from: 'S7-code-review', to: 'S8-merged', execute }), 'moved');
|
|
assert.deepEqual(calls, ['--add-label S8-merged --remove-label S7-code-review']);
|
|
const none = recorder();
|
|
moveStatusLabel({ repo: REPO, issue: 700, from: '', to: 'S6-in-progress', execute: none.execute });
|
|
assert.deepEqual(none.calls, ['--add-label S6-in-progress'], 'без исходной метки снимать нечего');
|
|
});
|
|
|
|
test('#706 сбой повторной постановки роняет шаг, а не оставляет задачу без статуса молча', () => {
|
|
const once = recorder((args, n) => n === 2);
|
|
moveStatusLabel({ repo: REPO, issue: 699, from: 'S7-code-review', to: 'S7-code-review', execute: once.execute });
|
|
assert.deepEqual(once.calls, ['--remove-label S7-code-review', '--add-label S7-code-review', '--add-label S7-code-review'],
|
|
'одна попытка восстановления, как в автосверке (#555)');
|
|
const always = recorder((args) => args.includes('--add-label'));
|
|
assert.throws(() => moveStatusLabel({ repo: REPO, issue: 699, from: 'S7-code-review', to: 'S7-code-review', execute: always.execute }),
|
|
/could not restore S7-code-review/);
|
|
assert.throws(() => moveStatusLabel({ repo: REPO, issue: 699, from: 'S7-code-review', to: '', execute: always.execute }), /usage/);
|
|
});
|
|
|
|
test('#706 шаг конвейера переставляет метку через скрипт, а не одним вызовом gh', () => {
|
|
const workflow = readFileSync(fileURLToPath(new URL('../.github/workflows/_process.yml', import.meta.url)), 'utf8');
|
|
const step = workflow.slice(workflow.indexOf('- name: Переставить метку'), workflow.indexOf('- name: Сводка длительности стадий'));
|
|
assert.ok(step.length > 0, 'шаг найден');
|
|
// #749: скрипт — из снимка dev job integrate, не из рабочей копии ветки задачи.
|
|
assert.match(step, /node "\$TOOLS\/scripts\/status-label\.mjs" --repo="\$\{\{ github\.repository \}\}" \\\n\s+--issue="\$NUM" --from="\$FROM" --to="\$TO"/);
|
|
assert.doesNotMatch(step, /gh issue edit/, 'совмещённый вызов снимал ту же метку, которую ставил');
|
|
});
|