Public: anyone can read this; the crew can change it.
Sentinel: red:01m23vygyffx5hg2f5vm8r74sr was degraded 84 minutes today: trevato/forge: main is red at 88e4bed — "reviews, threads and the merge gate > require-review refuses the merge until t… #6936
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6933-sentinel-red-01m23vygyffx5hg2f5vm8r74sr → main 3 commits fork 2 threads · 2 unresolved
1 file
changed +26
−14
test/tui-workflows.test.ts +26 −14
| @@ -203,11 +203,15 @@ describe("reviews, threads and the merge gate", () => { | ||
| 203 | 203 | email: "[email protected]", |
| 204 | 204 | password: "a-long-passphrase", |
| 205 | 205 | }); |
| 206 | const two = await signUp(forge, { | |
| 207 | username: "bob", | |
| 208 | email: "[email protected]", | |
| 209 | password: "another-long-passphrase", | |
| 210 | }, { slot: fixtureSlot(forge) }); | |
| 206 | const two = await signUp( | |
| 207 | forge, | |
| 208 | { | |
| 209 | username: "bob", | |
| 210 | email: "bob@forge.test", | |
| 211 | password: "another-long-passphrase", | |
| 212 | }, | |
| 213 | { slot: fixtureSlot(forge) }, | |
| 214 | ); | |
| 211 | 215 | if (!one.ok || !two.ok) throw new Error("signup failed"); |
| 212 | 216 | alice = one.value; |
| 213 | 217 | bob = two.value; |
| @@ -330,7 +334,7 @@ describe("reviews, threads and the merge gate", () => { | ||
| 330 | 334 | // wears it from the first paint, before the setting has loaded). |
| 331 | 335 | await admin.until("Needs an approval of this head", 30_000); |
| 332 | 336 | await admin.press("m"); |
| 333 | await admin.until("approval"); | |
| 337 | await admin.until("needs an approval of its current head first", 30_000); | |
Good: waits for the refusal text itself, which appears only when m is refused. |
||
| 334 | 338 | expect(getPull(forge.db, repo.id, 1)?.mergedAt).toBeNull(); |
| 335 | 339 | } finally { |
| 336 | 340 | admin.stop(); |
| @@ -348,8 +352,9 @@ describe("reviews, threads and the merge gate", () => { | ||
| 348 | 352 | |
| 349 | 353 | // The list wears the same verdict, in one tabular column. |
| 350 | 354 | await reviewer.press("q"); |
| 355 | // The title paints before the verdict column loads: wait for it. | |
| 351 | 356 | await reviewer.until("Tweak the lamp", 30_000); |
| 352 | expect(reviewer.text()).toContain("✓"); | |
| 357 | await reviewer.until("✓", 30_000); | |
| 353 | 358 | } finally { |
| 354 | 359 | reviewer.stop(); |
| 355 | 360 | } |
| @@ -475,7 +480,6 @@ describe("reviews, threads and the merge gate", () => { | ||
| 475 | 480 | await h.click(4, row); |
| 476 | 481 | await h.click(4, row); |
| 477 | 482 | await h.until("fix", 30_000); |
| 478 | ||
| 479 | 483 | } finally { |
| 480 | 484 | h.stop(); |
| 481 | 485 | } |
| @@ -523,16 +527,20 @@ describe("reviews, threads and the merge gate", () => { | ||
| 523 | 527 | } |
| 524 | 528 | }); |
| 525 | 529 | test("p on a sealed issue shows the private repository and its clone URL, with the same refusals", async () => { |
| 526 | const sealed = openIssue(forge, alice, repo, { title: "Token leak", sealed: true }); | |
| 530 | const sealed = openIssue(forge, alice, repo, { | |
| 531 | title: "Token leak", | |
| 532 | sealed: true, | |
| 533 | }); | |
| 527 | 534 | const plain = openIssue(forge, alice, repo, { title: "A typo" }); |
| 528 | 535 | if (!sealed.ok || !plain.ok) throw new Error("issues"); |
| 529 | const open = (user: User) => | |
| 530 | boot(forge, identityFor(user)); | |
| 536 | const open = (user: User) => boot(forge, identityFor(user)); | |
| 531 | 537 | |
| 532 | 538 | const h = open(alice); |
| 533 | 539 | try { |
| 534 | 540 | await h.settle(); |
| 535 | await h.push((s, ctx) => issueScreen(s, ctx, local(s, repo), sealed.value.number)); | |
| 541 | await h.push((s, ctx) => | |
| 542 | issueScreen(s, ctx, local(s, repo), sealed.value.number), | |
| 543 | ); | |
| 536 | 544 | await h.until("private repo"); |
| 537 | 545 | await h.press("p"); |
| 538 | 546 | await h.until("private repository alice/"); |
| @@ -546,7 +554,9 @@ describe("reviews, threads and the merge gate", () => { | ||
| 546 | 554 | const g = open(alice); |
| 547 | 555 | try { |
| 548 | 556 | await g.settle(); |
| 549 | await g.push((s, ctx) => issueScreen(s, ctx, local(s, repo), plain.value.number)); | |
| 557 | await g.push((s, ctx) => | |
| 558 | issueScreen(s, ctx, local(s, repo), plain.value.number), | |
| 559 | ); | |
| 550 | 560 | await g.until("A typo"); |
| 551 | 561 | expect(g.text()).not.toContain("private repo"); |
| 552 | 562 | await g.press("p"); |
| @@ -559,7 +569,9 @@ describe("reviews, threads and the merge gate", () => { | ||
| 559 | 569 | const b = open(bob); |
| 560 | 570 | try { |
| 561 | 571 | await b.settle(); |
| 562 | await b.push((s, ctx) => issueScreen(s, ctx, local(s, repo), sealed.value.number)); | |
| 572 | await b.push((s, ctx) => | |
| 573 | issueScreen(s, ctx, local(s, repo), sealed.value.number), | |
| 574 | ); | |
| 563 | 575 | await b.until("Token leak"); |
| 564 | 576 | await b.press("p"); |
| 565 | 577 | await b.until("Verify your email first"); |
new above
offline
High (the test proves nothing here): "approval" is already on screen from the merge box's "Needs an approval of this head…" line (waited for at 331), so this passes even if
mdoes nothing. Wait for the refusal's own text instead:await admin.until("needs an approval of its current head first", 30_000);(fromsrc/tui/screens/common.ts:872).