Public: anyone can read this; the crew can change it.

Four test files are red only when the whole suite runs: one file lowers a global and the next inherits it #310

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/302-four-test-files-are-red-only-when-the-wh → main 7 commits fork

  • Merged. mason/302-four-test-files-are-red-only-when-the-wh was merged into main as 885c4d7.

Review

Sign in to review this change.
32 files changed +213 −69
src/kernel/log.ts +5
@@ -42,6 +42,11 @@ export function setLogLevel(level: LogLevel): void {
4242 threshold = level;
4343 }
4444
45 /** The floor as it stands, so whoever lowers it can put it back. */
46 export function logLevel(): LogLevel {
47 return threshold;
48 }
49
4550 export function createLog(scope: string): Log {
4651 const emit = (level: LogLevel, message: string, fields?: LogFields) => {
4752 if (order[level] < order[threshold]) return;
src/origins/mirror.ts +6 −2
@@ -59,12 +59,16 @@ export interface MirrorResult {
5959 * `internal` Forgejo repo has no local analogue and becomes private: never
6060 * silently widen a visibility on import.
6161 */
62 /** The forge's waits between tries of a new mirror's first fetch. */
63 export const FIRST_FETCH_RETRY_MS: readonly number[] = [2_000, 5_000, 10_000];
64
6265 /**
6366 * How long to wait between tries of a new mirror's first fetch. Mutable so a
64 * test that wants a failure now can have it now; the forge keeps the default.
67 * test that wants a failure now can have it now; the forge keeps the default,
68 * and a test that lowers it puts back what it found.
6569 */
6670 export const firstFetchRetry = {
67 ms: [2_000, 5_000, 10_000] as readonly number[],
71 ms: FIRST_FETCH_RETRY_MS,
6872 };
6973
7074 export async function mirrorRepo(
test/agents.test.ts +2 −2
@@ -30,11 +30,11 @@ import {
3030 import { RateLimiter } from "../src/core/ratelimit.ts";
3131 import { deliveryBusy, runDelivery } from "../src/core/webhooks.ts";
3232 import { Gate, gitGate, runGit } from "../src/git/env.ts";
33 import { setLogLevel } from "../src/kernel/log.ts";
33 import { quietLogs } from "./helpers/globals.ts";
3434 import { isBusy, transact } from "../src/kernel/db.ts";
3535 import { repoDir } from "../src/git/repos.ts";
3636
37 setLogLevel("error");
37 quietLogs();
3838
3939 const WRITERS = 50;
4040
test/app-env.test.ts +2 −2
@@ -25,9 +25,9 @@ import {
2525 type AppRecord,
2626 } from "../src/core/apps.ts";
2727 import { createUser } from "../src/core/users.ts";
28 import { setLogLevel } from "../src/kernel/log.ts";
28 import { quietLogs } from "./helpers/globals.ts";
2929
30 setLogLevel("error");
30 quietLogs();
3131
3232 describe("an app's environment", () => {
3333 let dataDir = "";
test/git-ssh.test.ts +2 −2
@@ -27,7 +27,7 @@ import { getRepo } from "../src/core/repos.ts";
2727 import type { User } from "../src/core/users.ts";
2828 import { runGit } from "../src/git/env.ts";
2929 import { repoDir } from "../src/git/repos.ts";
30 import { setLogLevel } from "../src/kernel/log.ts";
30 import { quietLogs } from "./helpers/globals.ts";
3131 import { runGitService, type GitChannel } from "../src/git/smart-ssh.ts";
3232 import { serveForgeSsh } from "../src/tui/ssh.ts";
3333 import type { SshServer } from "../src/ssh/server.ts";
@@ -35,7 +35,7 @@ import { serve, type Servers } from "../src/http/server.ts";
3535 import { gitClientEnv } from "./helpers/git-client.ts";
3636 import { missing } from "./helpers/tools.ts";
3737
38 setLogLevel("error");
38 quietLogs();
3939
4040 const SSH_KEYGEN = "/usr/bin/ssh-keygen";
4141 /** The client half is the real OpenSSH; without it these skip, loudly. */
test/helpers.test.ts +57 −5
@@ -1,15 +1,19 @@
11 /**
2 * The two helpers the suite's own health rests on: the environment a git
3 * client under test runs in, and whether a tool this machine would need is
4 * here. Both exist because of a run that failed for the machine's reasons
5 * and was read as the change's fault (#233).
2 * The helpers the suite's own health rests on: the environment a git client
3 * under test runs in, whether a tool this machine would need is here, and
4 * the process globals a file borrows. Each exists because of a run that
5 * failed for the machine's reasons, or another file's, and was read as the
6 * change's fault (#233, #302).
67 */
78
8 import { describe, expect, test } from "bun:test";
9 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
910 import { mkdtemp, rm } from "node:fs/promises";
1011 import { tmpdir } from "node:os";
1112 import { join } from "node:path";
1213 import { gitClientEnv } from "./helpers/git-client.ts";
14 import { logLevel, setLogLevel, type LogLevel } from "../src/kernel/log.ts";
15 import { firstFetchRetry } from "../src/origins/mirror.ts";
16 import { fastFirstFetch, quietLogs, withoutEnv } from "./helpers/globals.ts";
1317 import { missing } from "./helpers/tools.ts";
1418
1519 describe("a git client's environment under test", () => {
@@ -75,3 +79,51 @@ describe("whether a tool is on this machine", () => {
7579 expect(missing("sh", "no-such-tool-on-any-machine")).toBe(true);
7680 });
7781 });
82
83 describe("a global a file borrows is given back", () => {
84 // Values nobody else would pick, so reading them back proves the restore
85 // rather than a coincidence with the defaults.
86 const ASIDE = "https://machine.example.test";
87 const WAITS: readonly number[] = [1, 2, 3];
88 let found: {
89 level: LogLevel;
90 waits: readonly number[];
91 url: string | undefined;
92 };
93 beforeAll(() => {
94 found = {
95 level: logLevel(),
96 waits: firstFetchRetry.ms,
97 url: process.env.FORGE_URL,
98 };
99 setLogLevel("warn");
100 firstFetchRetry.ms = WAITS;
101 process.env.FORGE_URL = ASIDE;
102 });
103 afterAll(() => {
104 setLogLevel(found.level);
105 firstFetchRetry.ms = found.waits;
106 if (found.url === undefined) delete process.env.FORGE_URL;
107 else process.env.FORGE_URL = found.url;
108 });
109
110 describe("while a file borrows them", () => {
111 quietLogs();
112 fastFirstFetch([50]);
113 withoutEnv("FORGE_URL");
114
115 test("they hold what it asked for", () => {
116 expect(logLevel()).toBe("error");
117 expect(firstFetchRetry.ms).toEqual([50]);
118 expect(process.env.FORGE_URL).toBeUndefined();
119 // What the file sets under a cleared name goes when it does.
120 process.env.FORGE_URL = "https://set.inside.test";
121 });
122 });
123
124 test("afterwards they read back what they were", () => {
125 expect(logLevel()).toBe("warn");
126 expect(firstFetchRetry.ms).toBe(WAITS);
127 expect(process.env.FORGE_URL).toBe(ASIDE);
128 });
129 });
test/helpers/globals.ts added +57
@@ -0,0 +1,57 @@
1 /**
2 * Borrowing a process global for one test file, and giving it back. The
3 * suite runs every file in one process, so a floor lowered or a variable
4 * cleared and never restored is inherited by whichever file runs next — and
5 * that file goes red for a reason that belongs to this one (#302). Each
6 * helper registers its own `beforeAll`/`afterAll` in the scope it is called
7 * from: called at a file's top level, it holds for that file and no longer.
8 */
9
10 import { afterAll, beforeAll } from "bun:test";
11 import { logLevel, setLogLevel, type LogLevel } from "../../src/kernel/log.ts";
12 import { firstFetchRetry } from "../../src/origins/mirror.ts";
13
14 /** Raise the log floor (by default to errors only) while this scope runs. */
15 export function quietLogs(level: LogLevel = "error"): void {
16 let found: LogLevel;
17 beforeAll(() => {
18 found = logLevel();
19 setLogLevel(level);
20 });
21 afterAll(() => setLogLevel(found));
22 }
23
24 /** Shorten a new mirror's first-fetch retries while this scope runs. */
25 export function fastFirstFetch(ms: readonly number[] = [50]): void {
26 let found: readonly number[];
27 beforeAll(() => {
28 found = firstFetchRetry.ms;
29 firstFetchRetry.ms = ms;
30 });
31 afterAll(() => {
32 firstFetchRetry.ms = found;
33 });
34 }
35
36 /**
37 * Unset environment variables the machine may carry while this scope runs.
38 * A crew machine sets `FORGE_URL` to its own forge; a test that registers an
39 * OAuth callback must not inherit it. Whatever the scope itself sets under
40 * these names is cleared again on the way out, and the machine's values put
41 * back.
42 */
43 export function withoutEnv(...names: string[]): void {
44 const found = new Map<string, string | undefined>();
45 beforeAll(() => {
46 for (const name of names) {
47 found.set(name, process.env[name]);
48 delete process.env[name];
49 }
50 });
51 afterAll(() => {
52 for (const [name, value] of found) {
53 if (value === undefined) delete process.env[name];
54 else process.env[name] = value;
55 }
56 });
57 }
test/looks.test.ts +2 −2
@@ -31,7 +31,7 @@ import { listRuns, type Run } from "../src/core/runs.ts";
3131 import { createUser, type User } from "../src/core/users.ts";
3232 import { serve, type Servers } from "../src/http/server.ts";
3333 import { err, ok } from "../src/kernel/result.ts";
34 import { setLogLevel } from "../src/kernel/log.ts";
34 import { quietLogs } from "./helpers/globals.ts";
3535 import {
3636 findChromium,
3737 takeLook,
@@ -40,7 +40,7 @@ import {
4040 } from "../src/looks/capture.ts";
4141 import { startLooks, type LooksHandle } from "../src/looks/service.ts";
4242
43 setLogLevel("error");
43 quietLogs();
4444
4545 const DOMAIN = "apps.test";
4646 const PASSWORD = "a-good-passphrase";
test/origins-oauth.test.ts +5
@@ -25,6 +25,7 @@ import {
2525 type FakeForge,
2626 type FakeOAuth,
2727 } from "./fake-origin.ts";
28 import { withoutEnv } from "./helpers/globals.ts";
2829
2930 /**
3031 * Connecting an origin without pasting a token, and the security properties
@@ -83,6 +84,10 @@ async function shutdown(f: Fixture): Promise<void> {
8384 await rm(f.dataDir, { recursive: true, force: true });
8485 }
8586
87 // The code flow's redirect_uri is this test server's own — never the forge a
88 // crew machine's `FORGE_URL` names.
89 withoutEnv("FORGE_URL");
90
8691 // ---------------------------------------------------------------- device
8792
8893 describe("GitHub's device grant: connecting with no browser on the server", () => {
test/origins-security-auth.test.ts +7 −4
@@ -1,5 +1,4 @@
11 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2 import { firstFetchRetry } from "../src/origins/mirror.ts";
32 import { mkdtemp, readFile, rm } from "node:fs/promises";
43 import { tmpdir } from "node:os";
54 import { join } from "node:path";
@@ -23,6 +22,7 @@ import {
2322 type FakeForge,
2423 type FakeOAuth,
2524 } from "./fake-origin.ts";
25 import { fastFirstFetch, withoutEnv } from "./helpers/globals.ts";
2626
2727 /**
2828 * Adversarial review of the origins surface: OAuth (state, PKCE, redirect
@@ -100,6 +100,12 @@ async function mintToken(
100100 const originCount = (app: App): number =>
101101 app.db.query<{ n: number }, []>("SELECT COUNT(*) AS n FROM origin").get()!.n;
102102
103 // A fetch that fails should say so now, not after the forge's patience.
104 fastFirstFetch();
105 // The code flow's redirect_uri is this test server's own, unless a test says
106 // otherwise — never the forge a crew machine's `FORGE_URL` names.
107 withoutEnv("FORGE_URL");
108
103109 // ------------------------------------------------------------- code flow
104110
105111 describe("OAuth code flow: state, PKCE, redirects, and the callback URI", () => {
@@ -134,8 +140,6 @@ describe("OAuth code flow: state, PKCE, redirects, and the callback URI", () =>
134140 .length;
135141
136142 beforeAll(async () => {
137 // A fetch that fails should say so now, not after the forge's patience.
138 firstFetchRetry.ms = [50];
139143 dataDir = await mkdtemp(join(tmpdir(), "forge-sec-code-"));
140144 const state = newFakeState("forgejo", LOGIN, ORIGIN_TOKEN);
141145 oauth = newFakeOAuth(CLIENT_ID);
@@ -154,7 +158,6 @@ describe("OAuth code flow: state, PKCE, redirects, and the callback URI", () =>
154158 });
155159 });
156160 afterAll(async () => {
157 delete process.env.FORGE_URL;
158161 fake.stop();
159162 servers.stop();
160163 app.close();
test/scale.test.ts +2 −2
@@ -51,10 +51,10 @@ import {
5151 sweepReaderPool,
5252 closeReader,
5353 } from "../src/git/read.ts";
54 import { setLogLevel } from "../src/kernel/log.ts";
54 import { quietLogs } from "./helpers/globals.ts";
5555 import type { User } from "../src/core/users.ts";
5656
57 setLogLevel("error");
57 quietLogs();
5858
5959 /** The `detail` lines EXPLAIN QUERY PLAN produces for a statement. */
6060 function plan(app: App, sql: string, params: (string | number)[] = []): string {
test/ssh-app.test.ts +2 −2
@@ -14,7 +14,7 @@ import { join } from "node:path";
1414 import { createRepo } from "../src/actions/repos.ts";
1515 import { addSshKey, signUp } from "../src/actions/users.ts";
1616 import { createApp, type App as Forge } from "../src/app.ts";
17 import { setLogLevel } from "../src/kernel/log.ts";
17 import { quietLogs } from "./helpers/globals.ts";
1818 import { Channels, type SshSession } from "../src/ssh/channels.ts";
1919 import { MSG, Writer } from "../src/ssh/wire.ts";
2020 import { createLog } from "../src/kernel/log.ts";
@@ -22,7 +22,7 @@ import { serveForgeSsh } from "../src/tui/ssh.ts";
2222 import type { SshServer } from "../src/ssh/server.ts";
2323 import { missing } from "./helpers/tools.ts";
2424
25 setLogLevel("error");
25 quietLogs();
2626 const log = createLog("ssh-app-test");
2727
2828 const SSH = "/usr/bin/ssh";
test/ssh.test.ts +3 −2
@@ -14,7 +14,8 @@ import {
1414 import { mkdtempSync, readFileSync, rmSync } from "node:fs";
1515 import { tmpdir } from "node:os";
1616 import { join } from "node:path";
17 import { createLog, setLogLevel } from "../src/kernel/log.ts";
17 import { createLog } from "../src/kernel/log.ts";
18 import { quietLogs } from "./helpers/globals.ts";
1819 import { Channels } from "../src/ssh/channels.ts";
1920 import { MSG, WireError, Writer } from "../src/ssh/wire.ts";
2021 import {
@@ -36,7 +37,7 @@ let root = "";
3637 const servers: SshServer[] = [];
3738 const keys: Record<string, string> = {};
3839
39 setLogLevel("error");
40 quietLogs();
4041
4142 function sha256(data: Uint8Array): string {
4243 return createHash("sha256").update(data).digest("hex");
test/surfaces-adversarial.test.ts +2 −2
@@ -28,7 +28,7 @@ import type { Repo } from "../src/core/repos.ts";
2828 import { createUser, getUserByName, type User } from "../src/core/users.ts";
2929 import { serve, type Servers } from "../src/http/server.ts";
3030 import { id } from "../src/kernel/id.ts";
31 import { setLogLevel } from "../src/kernel/log.ts";
31 import { quietLogs } from "./helpers/globals.ts";
3232 import { appendRunLog, runLogListenerCount } from "../src/runs/log.ts";
3333 import { content as contentStyle } from "../src/web/style/content.ts";
3434 import { runScreen } from "../src/tui/screens/run.ts";
@@ -41,7 +41,7 @@ import type { App as TuiApp, Screen } from "../src/tui/app/app.ts";
4141 import { headlessTerminal } from "../src/tui/term/terminal.ts";
4242 import { settleApp } from "./helpers/tui.ts";
4343
44 setLogLevel("error");
44 quietLogs();
4545
4646 /** Count the SQL statements a block of work runs — mirrors test/counts.test.ts. */
4747 async function statements(db: Database, work: () => Promise<void> | void) {
test/tui-apps.test.ts +2 −2
@@ -38,7 +38,7 @@ import {
3838 } from "../src/core/runs.ts";
3939 import { createUser, type User } from "../src/core/users.ts";
4040 import { serve, type Servers } from "../src/http/server.ts";
41 import { setLogLevel } from "../src/kernel/log.ts";
41 import { quietLogs } from "./helpers/globals.ts";
4242 import { appendRunLog } from "../src/runs/log.ts";
4343 import type { App, Screen } from "../src/tui/app/app.ts";
4444 import { createForgeApp } from "../src/tui/forge.ts";
@@ -54,7 +54,7 @@ import {
5454 } from "../src/tui/term/terminal.ts";
5555 import { settleApp } from "./helpers/tui.ts";
5656
57 setLogLevel("error");
57 quietLogs();
5858
5959 const DOMAIN = "apps.test";
6060 const NOT_AN_APP = "this commit is not an app";
test/tui-code.test.ts +2 −2
@@ -25,7 +25,7 @@ import { createApp, type App as Forge } from "../src/app.ts";
2525 import { listBranches } from "../src/git/branches.ts";
2626 import { getRepo, type Repo } from "../src/core/repos.ts";
2727 import type { User } from "../src/core/users.ts";
28 import { setLogLevel } from "../src/kernel/log.ts";
28 import { quietLogs } from "./helpers/globals.ts";
2929 import { App } from "../src/tui/app/app.ts";
3030 import { createForgeApp } from "../src/tui/forge.ts";
3131 import type { TuiIdentity } from "../src/tui/session.ts";
@@ -33,7 +33,7 @@ import { headlessTerminal } from "../src/tui/term/terminal.ts";
3333 import { indexPaths, rankPaths } from "../src/tui/ui/fuzzy.ts";
3434 import { settleApp, settleUntil } from "./helpers/tui.ts";
3535
36 setLogLevel("error");
36 quietLogs();
3737
3838 interface Harness {
3939 app: App;
test/tui-editor.test.ts +2 −2
@@ -25,7 +25,7 @@ import { getPull } from "../src/core/pulls.ts";
2525 import { getRepo, type Repo } from "../src/core/repos.ts";
2626 import type { User } from "../src/core/users.ts";
2727 import { blobForRender } from "../src/git/read.ts";
28 import { setLogLevel } from "../src/kernel/log.ts";
28 import { quietLogs } from "./helpers/globals.ts";
2929 import { App } from "../src/tui/app/app.ts";
3030 import { createForgeApp } from "../src/tui/forge.ts";
3131 import { forgetEditor } from "../src/tui/screens/external.ts";
@@ -34,7 +34,7 @@ import { headlessTerminal } from "../src/tui/term/terminal.ts";
3434 import { Editor, colFromDisplay, displayCol } from "../src/tui/ui/editor.ts";
3535 import { settleApp, settleUntil } from "./helpers/tui.ts";
3636
37 setLogLevel("error");
37 quietLogs();
3838
3939 const bytes = (s: string): Uint8Array => new TextEncoder().encode(s);
4040 const text = (b: Uint8Array): string => new TextDecoder().decode(b);
test/tui-icons.test.ts +2 −2
@@ -22,7 +22,7 @@ import { signUp } from "../src/actions/users.ts";
2222 import { createApp, type App as Forge } from "../src/app.ts";
2323 import { getUserSetting } from "../src/core/settings.ts";
2424 import type { User } from "../src/core/users.ts";
25 import { setLogLevel } from "../src/kernel/log.ts";
25 import { quietLogs } from "./helpers/globals.ts";
2626 import { createForgeApp } from "../src/tui/forge.ts";
2727 import { headlessTerminal } from "../src/tui/term/terminal.ts";
2828 import {
@@ -35,7 +35,7 @@ import {
3535 import { clusterWidth, clusters, displayWidth } from "../src/tui/term/width.ts";
3636 import { settleApp, settleUntil } from "./helpers/tui.ts";
3737
38 setLogLevel("error");
38 quietLogs();
3939
4040 const SETS = ["plain", "nerd"] as const;
4141
test/tui-mouse.test.ts +2 −2
@@ -22,7 +22,7 @@ import { signUp } from "../src/actions/users.ts";
2222 import { createApp, type App as Forge } from "../src/app.ts";
2323 import type { Repo } from "../src/core/repos.ts";
2424 import type { User } from "../src/core/users.ts";
25 import { setLogLevel } from "../src/kernel/log.ts";
25 import { quietLogs } from "./helpers/globals.ts";
2626 import type { App } from "../src/tui/app/app.ts";
2727 import { createForgeApp } from "../src/tui/forge.ts";
2828 import type { TuiIdentity } from "../src/tui/session.ts";
@@ -32,7 +32,7 @@ import {
3232 } from "../src/tui/term/terminal.ts";
3333 import { settleApp, settleUntil } from "./helpers/tui.ts";
3434
35 setLogLevel("error");
35 quietLogs();
3636
3737 interface Harness {
3838 app: App;
test/tui-origins.test.ts +2 −2
@@ -21,7 +21,7 @@ import { listOrigins } from "../src/core/origins.ts";
2121 import { listPins } from "../src/core/pins.ts";
2222 import { getRepo } from "../src/core/repos.ts";
2323 import type { User } from "../src/core/users.ts";
24 import { setLogLevel } from "../src/kernel/log.ts";
24 import { quietLogs } from "./helpers/globals.ts";
2525 import { forgetProviders } from "../src/origins/providers/index.ts";
2626 import type { App } from "../src/tui/app/app.ts";
2727 import { createForgeApp } from "../src/tui/forge.ts";
@@ -39,7 +39,7 @@ import {
3939 type FakeOAuth,
4040 } from "./fake-origin.ts";
4141
42 setLogLevel("error");
42 quietLogs();
4343
4444 const ORIGIN_LOGIN = "octo";
4545 const ORIGIN_TOKEN = "octo-origin-secret-9f2a";
test/tui-screens.test.ts +2 −2
@@ -29,7 +29,7 @@ import { getPull } from "../src/core/pulls.ts";
2929 import { getRepo, type Repo } from "../src/core/repos.ts";
3030 import { getUserSetting } from "../src/core/settings.ts";
3131 import { getUserByName, type User } from "../src/core/users.ts";
32 import { setLogLevel } from "../src/kernel/log.ts";
32 import { quietLogs } from "./helpers/globals.ts";
3333 import { parseGitCommand, splitCommand } from "../src/git/smart-ssh.ts";
3434 import { App } from "../src/tui/app/app.ts";
3535 import { createForgeApp } from "../src/tui/forge.ts";
@@ -39,7 +39,7 @@ import { headlessTerminal } from "../src/tui/term/terminal.ts";
3939 import type { SshSession } from "../src/ssh/channels.ts";
4040 import { settleApp } from "./helpers/tui.ts";
4141
42 setLogLevel("error");
42 quietLogs();
4343
4444 // A real ed25519 authorized_keys line, so key parsing is exercised for real.
4545 const KEY_LINE =
test/tui-size.test.ts +2 −2
@@ -23,7 +23,7 @@ import { createApp, type App as Forge } from "../src/app.ts";
2323 import type { Repo } from "../src/core/repos.ts";
2424 import { claimRun, createRun, finishRun } from "../src/core/runs.ts";
2525 import type { User } from "../src/core/users.ts";
26 import { setLogLevel } from "../src/kernel/log.ts";
26 import { quietLogs } from "./helpers/globals.ts";
2727 import { appendRunLog } from "../src/runs/log.ts";
2828 import type { App, Screen } from "../src/tui/app/app.ts";
2929 import { createForgeApp } from "../src/tui/forge.ts";
@@ -49,7 +49,7 @@ import { theme } from "../src/tui/ui/theme.ts";
4949 import { row, table } from "../src/tui/ui/widgets.ts";
5050 import { settleApp } from "./helpers/tui.ts";
5151
52 setLogLevel("error");
52 quietLogs();
5353
5454 const SIZES: readonly [number, number][] = [
5555 [60, 20],
test/tui-workflows.test.ts +20 −2
@@ -35,7 +35,7 @@ import { listReviews } from "../src/core/reviews.ts";
3535 import { getPull } from "../src/core/pulls.ts";
3636 import type { User } from "../src/core/users.ts";
3737 import { serve, type Servers } from "../src/http/server.ts";
38 import { setLogLevel } from "../src/kernel/log.ts";
38 import { quietLogs } from "./helpers/globals.ts";
3939 import { forgetProviders } from "../src/origins/providers/index.ts";
4040 import type { App } from "../src/tui/app/app.ts";
4141 import { createForgeApp } from "../src/tui/forge.ts";
@@ -47,7 +47,7 @@ import {
4747 import { settleApp } from "./helpers/tui.ts";
4848 import { newFakeState, serveFakeForge, type FakeForge } from "./fake-origin.ts";
4949
50 setLogLevel("error");
50 quietLogs();
5151
5252 interface Harness {
5353 app: App;
@@ -61,6 +61,13 @@ interface Harness {
6161 settle(): Promise<void>;
6262 /** Pump until the screen says something, or give up loudly. */
6363 until(what: string, ms?: number): Promise<void>;
64 /**
65 * The title of every screen pushed since boot, in order. A card that
66 * comes down the moment its work is done — the mirror card over a small
67 * repository — can be up and gone between two looks at the screen; this
68 * is how a test says it was shown without racing it.
69 */
70 opened(): string[];
6471 /** Press one key until the screen says something — for walking a viewport. */
6572 walk(key: string, what: string, limit?: number): Promise<void>;
6673 stop(): void;
@@ -77,6 +84,12 @@ function boot(
7784 terminal,
7885 exitHooks: false,
7986 });
87 const opened: string[] = [];
88 const push = app.push.bind(app);
89 app.push = (screen) => {
90 opened.push(screen.title);
91 push(screen);
92 };
8093 app.start();
8194 const settle = (): Promise<void> => settleApp(app);
8295 const grid = (): string[] => app.screen.toText().split("\n");
@@ -123,6 +136,7 @@ function boot(
123136 await settle();
124137 },
125138 settle,
139 opened: () => [...opened],
126140 async until(what, ms = 30_000) {
127141 const deadline = Date.now() + ms;
128142 for (;;) {
@@ -390,6 +404,8 @@ describe("workflows at an origin", () => {
390404 // outcome — the fork's own workspace — rather than the mirror card,
391405 // which can finish and be gone before the next poll ever sees it.
392406 await h.until(`fork of ${UPSTREAM}/gears`, 60_000);
407 // The card was still shown: the harness saw it pushed.
408 expect(h.opened().some((t) => t.startsWith("Mirroring"))).toBe(true);
393409
394410 const fork = getRepo(forge.db, "alice", "gears");
395411 expect(fork).toBeDefined();
@@ -421,6 +437,7 @@ describe("workflows at an origin", () => {
421437 // job can finish before the next poll ever sees "Mirroring".
422438 await h.until("mirrored as alice/widgets", 60_000);
423439 expect(getRepo(forge.db, "alice", "widgets")).toBeDefined();
440 expect(h.opened()).toContain(`Mirroring ${UPSTREAM}/widgets`);
424441
425442 // Now browse it live: read-only, and not ours to push to.
426443 await h.press("enter");
@@ -485,6 +502,7 @@ describe("workflows at an origin", () => {
485502 // Wait for the outcome, not the mirror card — see the fork test above.
486503 await h.press("m");
487504 await h.until("mirrored as alice/own", 60_000);
505 expect(h.opened()).toContain(`Mirroring ${ORIGIN_LOGIN}/own`);
488506 await h.press("enter");
489507 await h.until("read live");
490508 // The origin says this one is ours to push to, so nothing is proposed.
test/tui-workspace.test.ts +2 −2
@@ -26,7 +26,7 @@ import { listPulls } from "../src/core/pulls.ts";
2626 import type { User } from "../src/core/users.ts";
2727 import { blobForRender } from "../src/git/read.ts";
2828 import { listBranches } from "../src/git/branches.ts";
29 import { setLogLevel } from "../src/kernel/log.ts";
29 import { quietLogs } from "./helpers/globals.ts";
3030 import { App } from "../src/tui/app/app.ts";
3131 import { createForgeApp } from "../src/tui/forge.ts";
3232 import type { TuiIdentity } from "../src/tui/session.ts";
@@ -36,7 +36,7 @@ import {
3636 } from "../src/tui/term/terminal.ts";
3737 import { settleApp, settleUntil } from "./helpers/tui.ts";
3838
39 setLogLevel("error");
39 quietLogs();
4040
4141 /**
4242 * The colour the first `export` keyword is drawn in. A theme switch has to
test/view-source.test.ts +4 −3
@@ -6,7 +6,6 @@ import { createApp, type App } from "../src/app.ts";
66 import { serve, type Servers } from "../src/http/server.ts";
77 import { resetConnectLimit } from "../src/actions/origins.ts";
88 import { getUserByName, type User } from "../src/core/users.ts";
9 import { firstFetchRetry } from "../src/origins/mirror.ts";
109 import { settled, subscribe, type MirrorStage } from "../src/origins/jobs.ts";
1110 import { forgetProviders } from "../src/origins/providers/index.ts";
1211 import {
@@ -22,6 +21,7 @@ import {
2221 serveFakeForge,
2322 type FakeForge,
2423 } from "./fake-origin.ts";
24 import { fastFirstFetch } from "./helpers/globals.ts";
2525
2626 /**
2727 * Browsing before mirroring.
@@ -53,6 +53,9 @@ const read = async <T>(response: Response): Promise<T> =>
5353
5454 const text = (body: Uint8Array): string => new TextDecoder().decode(body);
5555
56 // A fetch that fails should say so now, not after the forge's patience.
57 fastFirstFetch();
58
5659 describe("RepoSource: one repository, two places to read it from", () => {
5760 let dataDir: string;
5861 let originDataDir: string;
@@ -97,8 +100,6 @@ describe("RepoSource: one repository, two places to read it from", () => {
97100 const requests = (): number => fake.state.requests.length;
98101
99102 beforeAll(async () => {
100 // A fetch that fails should say so now, not after the forge's patience.
101 firstFetchRetry.ms = [50];
102103 dataDir = await mkdtemp(join(tmpdir(), "forge-view-"));
103104 originDataDir = await mkdtemp(join(tmpdir(), "forge-view-up-"));
104105
test/web-apps.test.ts +2 −2
@@ -29,9 +29,9 @@ import { getRepo, type Repo } from "../src/core/repos.ts";
2929 import { isTerminal, listRuns, type Run } from "../src/core/runs.ts";
3030 import { getUserByName, type User } from "../src/core/users.ts";
3131 import { serve, type Servers } from "../src/http/server.ts";
32 import { setLogLevel } from "../src/kernel/log.ts";
32 import { quietLogs } from "./helpers/globals.ts";
3333
34 setLogLevel("error");
34 quietLogs();
3535
3636 const PASSWORD = "a-long-apps-passphrase";
3737 const DOMAIN = "apps.test";
test/web-browse.test.ts +2 −2
@@ -29,10 +29,10 @@ import { getUserByName } from "../src/core/users.ts";
2929 import { settled } from "../src/origins/jobs.ts";
3030 import { forgetProviders } from "../src/origins/providers/index.ts";
3131 import { clearViewCache } from "../src/view/cache.ts";
32 import { setLogLevel } from "../src/kernel/log.ts";
32 import { quietLogs } from "./helpers/globals.ts";
3333 import { newFakeState, serveFakeForge, type FakeForge } from "./fake-origin.ts";
3434
35 setLogLevel("error");
35 quietLogs();
3636
3737 const ORIGIN_LOGIN = "octo";
3838 const ORIGIN_TOKEN = "octo-origin-secret-7c31";
test/web-code.test.ts +2 −2
@@ -32,7 +32,7 @@ import { signUp } from "../src/actions/users.ts";
3232 import { createSession } from "../src/core/sessions.ts";
3333 import { getRepo, updateRepoSettings } from "../src/core/repos.ts";
3434 import { serve, type Servers } from "../src/http/server.ts";
35 import { setLogLevel } from "../src/kernel/log.ts";
35 import { quietLogs } from "./helpers/globals.ts";
3636 import {
3737 codeJs,
3838 editorJs,
@@ -44,7 +44,7 @@ import { liveClientJs } from "../src/web/live.ts";
4444 import { spineOf } from "../src/web/pages/explorer.ts";
4545 import { markMatch } from "../src/web/pages/search.ts";
4646
47 setLogLevel("error");
47 quietLogs();
4848
4949 const PASSWORD = "a-long-web-code-passphrase";
5050
test/web-editor.test.ts +2 −2
@@ -31,7 +31,7 @@ import { listRefs } from "../src/git/read.ts";
3131 import { resolveOid } from "../src/git/merge.ts";
3232 import { repoDir } from "../src/git/repos.ts";
3333 import { serve, type Servers } from "../src/http/server.ts";
34 import { setLogLevel } from "../src/kernel/log.ts";
34 import { quietLogs } from "./helpers/globals.ts";
3535 import { localSource } from "../src/view/local.ts";
3636 import {
3737 MAX_MIRROR_LINES,
@@ -41,7 +41,7 @@ import {
4141 import { highlightAsset } from "../src/web/asset.ts";
4242 import { highlightToHtml } from "../src/web/highlight-html.ts";
4343
44 setLogLevel("error");
44 quietLogs();
4545
4646 const PASSWORD = "a-long-web-editor-passphrase";
4747 const LAMP = "export const lamp = 'lit'\n";
test/web-origins.test.ts +5 −7
@@ -24,7 +24,7 @@ import { getRepo } from "../src/core/repos.ts";
2424 import { getUserByName } from "../src/core/users.ts";
2525 import { forgetProviders } from "../src/origins/providers/index.ts";
2626 import { advance, createJob, settled } from "../src/origins/jobs.ts";
27 import { setLogLevel } from "../src/kernel/log.ts";
27 import { quietLogs, withoutEnv } from "./helpers/globals.ts";
2828 import {
2929 newFakeOAuth,
3030 newFakeState,
@@ -33,7 +33,7 @@ import {
3333 type FakeOAuth,
3434 } from "./fake-origin.ts";
3535
36 setLogLevel("error");
36 quietLogs();
3737
3838 const ORIGIN_LOGIN = "octo";
3939 const ORIGIN_TOKEN = "octo-origin-secret-9f2a";
@@ -44,6 +44,9 @@ const basic = (user: string, secret: string): string =>
4444 `Basic ${Buffer.from(`${user}:${secret}`).toString("base64")}`;
4545
4646 describe("origins on the web", () => {
47 // Whatever the environment thinks: this suite decides when GitHub is
48 // configured, and its first assertion is about it *not* being.
49 withoutEnv("FORGE_GITHUB_CLIENT_ID", "FORGE_URL");
4750 let dataDir: string;
4851 let originDataDir: string;
4952 let app: App;
@@ -164,10 +167,6 @@ describe("origins on the web", () => {
164167 app = createApp({ dataDir, port: 0 });
165168 servers = await serve(app);
166169 base = servers.url;
167 // Whatever the environment thinks: this suite decides when GitHub is
168 // configured, and its first assertion is about it *not* being.
169 delete process.env.FORGE_GITHUB_CLIENT_ID;
170 delete process.env.FORGE_URL;
171170 alice = await signUp("alice");
172171 bob = await signUp("bob");
173172 resetConnectLimit();
@@ -175,7 +174,6 @@ describe("origins on the web", () => {
175174 });
176175
177176 afterAll(async () => {
178 delete process.env.FORGE_GITHUB_CLIENT_ID;
179177 forgejo.stop();
180178 github.stop();
181179 servers.stop();
test/web-phone.test.ts +2 −2
@@ -14,10 +14,10 @@ import { join } from "node:path";
1414 import { createApp, type App } from "../src/app.ts";
1515 import { serve, type Servers } from "../src/http/server.ts";
1616 import { findChromium } from "../src/looks/capture.ts";
17 import { setLogLevel } from "../src/kernel/log.ts";
17 import { quietLogs } from "./helpers/globals.ts";
1818 import { measurePageWidth } from "./helpers/chrome.ts";
1919
20 setLogLevel("error");
20 quietLogs();
2121
2222 const chromium = findChromium();
2323 if (chromium === undefined) {
test/web-workflows.test.ts +2 −2
@@ -39,10 +39,10 @@ import { getUserByName } from "../src/core/users.ts";
3939 import { settled } from "../src/origins/jobs.ts";
4040 import { forgetProviders } from "../src/origins/providers/index.ts";
4141 import { clearViewCache } from "../src/view/cache.ts";
42 import { setLogLevel } from "../src/kernel/log.ts";
42 import { quietLogs } from "./helpers/globals.ts";
4343 import { newFakeState, serveFakeForge, type FakeForge } from "./fake-origin.ts";
4444
45 setLogLevel("error");
45 quietLogs();
4646
4747 const PASSWORD = "a-long-workflow-passphrase";
4848

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs