Public: anyone can read this; the crew can change it.
Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:h503k9k7s7 is past its budget: trevato/forge: "apps, runs and the crew on the web > the runs list, one run's log page, and the short door to it" faile… #3006
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/2853-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 2 commits fork
1 file
changed +38
−27
test/web-apps.test.ts +38 −27
| @@ -334,32 +334,6 @@ describe("apps, runs and the crew on the web", () => { | ||
| 334 | 334 | test("the runs list, one run's log page, and the short door to it", async () => { |
| 335 | 335 | const [run] = await runsOf(hello); |
| 336 | 336 | expect(run).toBeDefined(); |
| 337 | // One list (#629): the crew's runs by default; the deploys are its | |
| 338 | // `?kind=deploy`, one quiet link under it. | |
| 339 | const crew = await bodyOf(alice, "/alice/hello/runs"); | |
| 340 | expect(crew).toContain('href="/alice/hello/runs?kind=deploy"'); | |
| 341 | // Whichever word the deploy has reached, it is under one of them. | |
| 342 | const list = ( | |
| 343 | await Promise.all( | |
| 344 | ["", "&word=shipped", "&word=stopped"].map((w) => | |
| 345 | bodyOf(alice, `/alice/hello/runs?kind=${run!.kind}${w}`), | |
| 346 | ), | |
| 347 | ) | |
| 348 | ).join(""); | |
| 349 | expect(list).toContain("Deploy production"); | |
| 350 | expect(list).toContain(`href="/alice/hello/runs/${run!.id}"`); | |
| 351 | expect(await bodyOf(alice, "/alice/hello/runs?kind=agent")).toContain( | |
| 352 | "Nothing open.", | |
| 353 | ); | |
| 354 | ||
| 355 | const page = await bodyOf(alice, `/alice/hello/runs/${run!.id}`); | |
| 356 | expect(page).toContain("<h1>Deploy production</h1>"); | |
| 357 | expect(page).toContain('id="run-log"'); | |
| 358 | expect(page).toContain('id="run-word"'); | |
| 359 | // The log is drawn line by line, each by what kind of line it is: the | |
| 360 | // forge's own words ("checking out …") are marked as such. | |
| 361 | expect(page).toContain('class="ln ln-forge"'); | |
| 362 | expect(page).toContain('id="run-elapsed"'); | |
| 363 | 337 | // The live log exists only while it is going, and the page never |
| 364 | 338 | // reloads itself either way (#2873). |
| 365 | 339 | // Match the real <meta> / data-stream= attributes — not the same |
| @@ -391,6 +365,43 @@ describe("apps, runs and the crew on the web", () => { | ||
| 391 | 365 | } |
| 392 | 366 | } |
| 393 | 367 | |
| 368 | // Let the deploy settle before the list and log are read: a queued run | |
| 369 | // has no log lines yet and moves between the list's words, so those | |
| 370 | // assertions would depend on how loaded the machine is. | |
| 371 | const settleBy = Date.now() + 20_000; | |
| 372 | while (isGoing()) { | |
| 373 | if (Date.now() >= settleBy) { | |
| 374 | throw new Error(`run ${run!.id} never settled`); | |
| 375 | } | |
| 376 | await Bun.sleep(50); | |
| 377 | } | |
| 378 | // One list (#629): the crew's runs by default; the deploys are its | |
| 379 | // `?kind=deploy`, one quiet link under it. | |
| 380 | const crew = await bodyOf(alice, "/alice/hello/runs"); | |
| 381 | expect(crew).toContain('href="/alice/hello/runs?kind=deploy"'); | |
| 382 | // Whichever word the deploy has reached, it is under one of them. | |
| 383 | const list = ( | |
| 384 | await Promise.all( | |
| 385 | ["", "&word=shipped", "&word=stopped"].map((w) => | |
| 386 | bodyOf(alice, `/alice/hello/runs?kind=${run!.kind}${w}`), | |
| 387 | ), | |
| 388 | ) | |
| 389 | ).join(""); | |
| 390 | expect(list).toContain("Deploy production"); | |
| 391 | expect(list).toContain(`href="/alice/hello/runs/${run!.id}"`); | |
| 392 | expect(await bodyOf(alice, "/alice/hello/runs?kind=agent")).toContain( | |
| 393 | "Nothing open.", | |
| 394 | ); | |
| 395 | ||
| 396 | const page = await bodyOf(alice, `/alice/hello/runs/${run!.id}`); | |
| 397 | expect(page).toContain("<h1>Deploy production</h1>"); | |
| 398 | expect(page).toContain('id="run-log"'); | |
| 399 | expect(page).toContain('id="run-word"'); | |
| 400 | // The log is drawn line by line, each by what kind of line it is: the | |
| 401 | // forge's own words ("checking out …") are marked as such. | |
| 402 | expect(page).toContain('class="ln ln-forge"'); | |
| 403 | expect(page).toContain('id="run-elapsed"'); | |
| 404 | ||
| 394 | 405 | const door = await web(alice, `/runs/${run!.id}`); |
| 395 | 406 | expect(door.status).toBe(303); |
| 396 | 407 | expect(door.headers.get("location")).toBe(`/alice/hello/runs/${run!.id}`); |
| @@ -402,7 +413,7 @@ describe("apps, runs and the crew on the web", () => { | ||
| 402 | 413 | } |
| 403 | 414 | expect((await web(alice, "/runs/nope")).status).toBe(404); |
| 404 | 415 | expect((await web(alice, "/alice/hello/runs/nope")).status).toBe(404); |
| 405 | }); | |
| 416 | }, 40_000); | |
| 406 | 417 | |
| 407 | 418 | test("cancelling a run: a writer may, a reader may not", async () => { |
| 408 | 419 | const [run] = await runsOf(hello); |
new above
offline