mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
ci: the E2E gate fails loudly when the release list cannot be read
Code review r3 (M1): realOps.releases() swallowed a failing `gh release list` into an empty list, so a fine-grained token scoped to houseplan-e2e alone would have dispatched with upgrade_from=stable — the tag onto itself — and the red run would look like the bug 4143f998 already fixed. The call now throws like dispatch() and lands in the same catch: result `error` with the token hint, which now names both repositories. L4: PROCESS.md says who dispatches and who waits. Issue: #514 User-Visible: no
This commit is contained in:
+3
-3
@@ -701,9 +701,9 @@ demo/docs/capture.mjs`, коммит вместе с задачей.
|
||||
достаточен для продолжения релиза, повторное код-ревью не требуется — §11.4.
|
||||
|
||||
**Гейт стабильного релиза:** полный локальный прогон плюс Validate и Full
|
||||
Performance зелёные на точном SHA, плюс зелёный E2E на реальном Home Assistant
|
||||
на теге (`houseplan-e2e`, запускает и ждёт `release.yml`, #514); статусов
|
||||
issue не касается.
|
||||
Performance зелёные на точном SHA, плюс зелёный E2E на реальном Home Assistant:
|
||||
`release.yml` сам запускает `e2e.yml` в `houseplan-e2e` на теге и ждёт его
|
||||
зелёного (#514); статусов issue не касается.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export const E2E_REPO = 'Matysh/houseplan-e2e';
|
||||
export const E2E_WORKFLOW = 'e2e.yml';
|
||||
/** Допуск на расхождение часов раннера и GitHub при отборе «свежих» прогонов. */
|
||||
export const CLOCK_SKEW_MS = 60_000;
|
||||
export const TOKEN_HINT = 'нужен секрет E2E_DISPATCH_TOKEN с правом Actions: write на houseplan-e2e';
|
||||
export const TOKEN_HINT = 'нужен секрет E2E_DISPATCH_TOKEN: Actions: write на houseplan-e2e И чтение релизов houseplan-card (fine-grained PAT — оба репозитория в списке)';
|
||||
export const CARD_REPO = 'Matysh/houseplan-card';
|
||||
|
||||
/**
|
||||
@@ -74,6 +74,9 @@ export function classifyRun(jobs, tag) {
|
||||
export async function e2eGate({ tag, ops, appearMs = VALIDATE_APPEAR_MS, totalMs = VALIDATE_TOTAL_MS, pollMs = POLL_MS }) {
|
||||
const started = ops.now();
|
||||
try {
|
||||
// Список релизов читается ДО dispatch и обязан падать громко (ревью r3 M1):
|
||||
// fine-grained токен «только houseplan-e2e» не видит houseplan-card, и
|
||||
// тихий пустой список дал бы upgrade_from=stable — тег сам на себя.
|
||||
await ops.dispatch(tag, previousStable(await ops.releases(), tag));
|
||||
} catch (error) {
|
||||
const message = String(error?.message || error);
|
||||
@@ -116,7 +119,11 @@ export function realOps({ repo = E2E_REPO, workflow = E2E_WORKFLOW, cardRepo = C
|
||||
const fields = 'databaseId,status,conclusion,url,createdAt';
|
||||
const parse = (r) => (r.status === 0 && r.stdout ? JSON.parse(r.stdout) : []);
|
||||
return {
|
||||
releases: async () => parse(exec('gh', ['release', 'list', '--repo', cardRepo, '--json', 'tagName,isDraft,isPrerelease', '--limit', '30'])),
|
||||
releases: async () => {
|
||||
const r = exec('gh', ['release', 'list', '--repo', cardRepo, '--json', 'tagName,isDraft,isPrerelease', '--limit', '30']);
|
||||
if (r.status !== 0) throw new Error(`gh release list ${cardRepo}: ${(r.stderr || r.stdout || '').trim()}`);
|
||||
return r.stdout ? JSON.parse(r.stdout) : [];
|
||||
},
|
||||
dispatch: async (tag, upgradeFrom = 'stable') => {
|
||||
const r = exec('gh', ['workflow', 'run', workflow, '--repo', repo, '--ref', 'main',
|
||||
'-f', `houseplan_ref=${tag}`, '-f', `upgrade_from=${upgradeFrom}`, '-f', 'ha_version=stable']);
|
||||
|
||||
@@ -114,6 +114,18 @@ test('#514 AC1: a dispatch refused by the token is an error that names the missi
|
||||
assert.equal(fake.calls(), 0, 'no polling after a failed dispatch');
|
||||
});
|
||||
|
||||
test('#514 r3 M1: a token that cannot read houseplan-card releases fails loudly with the token hint, never dispatches with upgrade_from=stable', async () => {
|
||||
const fake = fakeOps({ snapshots: [[]] });
|
||||
fake.ops.releases = async () => { throw new Error('gh release list Matysh/houseplan-card: HTTP 404: Not Found'); };
|
||||
const outcome = await e2eGate({ tag: TAG, ops: fake.ops, pollMs: 1000 });
|
||||
assert.equal(outcome.result, 'error');
|
||||
assert.deepEqual(fake.dispatched, [], 'no dispatch on a broken release list');
|
||||
assert.ok(outcome.note.includes('gh release list'), outcome.note);
|
||||
// realOps: a failing gh is an exception, not an empty list
|
||||
const exec = () => ({ status: 1, stdout: '', stderr: 'HTTP 403: Resource not accessible by personal access token' });
|
||||
await assert.rejects(() => realOps({ exec }).releases(), /403/);
|
||||
});
|
||||
|
||||
test('#514: the upgrade suite starts from the previous stable, never from the tag under test', () => {
|
||||
const releases = [
|
||||
{ tagName: 'v1.74.0', isDraft: false, isPrerelease: false },
|
||||
|
||||
Reference in New Issue
Block a user