Public: anyone can read this; the crew can change it.
Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:swqri6g6e7 is past its budget: trevato/forge: "RepoSource: one repository, two places to read it from > the mirror and the origin answer the same ques… #7715
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/7686-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 2 commits fork 1 thread · 1 unresolved
6 files
changed +65
−8
src/origins/git-mirror.ts +3
| @@ -50,6 +50,9 @@ export function provideMirrorOutboundForTest(options: OutboundOptions): void { | ||
| 50 | 50 | outbound = options; |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | /** Test seam: what `vetHost` is told now, so a test that changes it can put it back. */ | |
| 54 | export const mirrorOutboundForTest = (): OutboundOptions => outbound; | |
| 55 | ||
| 53 | 56 | export const MIRROR_REFUSED = "refused: the remote's host is a private address"; |
| 54 | 57 | export const MIRROR_UNRESOLVED = "the remote's host could not be resolved"; |
| 55 | 58 |
test/helpers.test.ts +19 −1
| @@ -13,7 +13,17 @@ import { join } from "node:path"; | ||
| 13 | 13 | import { gitClientEnv } from "./helpers/git-client.ts"; |
| 14 | 14 | import { logLevel, setLogLevel, type LogLevel } from "../src/kernel/log.ts"; |
| 15 | 15 | import { firstFetchRetry } from "../src/origins/mirror.ts"; |
| 16 | import { fastFirstFetch, quietLogs, withoutEnv } from "./helpers/globals.ts"; | |
| 16 | import { | |
| 17 | mirrorOutboundForTest, | |
| 18 | provideMirrorOutboundForTest, | |
| 19 | } from "../src/origins/git-mirror.ts"; | |
| 20 | import type { OutboundOptions } from "../src/kernel/outbound.ts"; | |
| 21 | import { | |
| 22 | fastFirstFetch, | |
| 23 | mirrorOutbound, | |
| 24 | quietLogs, | |
| 25 | withoutEnv, | |
| 26 | } from "./helpers/globals.ts"; | |
| 17 | 27 | import { missing } from "./helpers/tools.ts"; |
| 18 | 28 | |
| 19 | 29 | describe("a git client's environment under test", () => { |
| @@ -87,22 +97,27 @@ describe("a global a file borrows is given back", () => { | ||
| 87 | 97 | // rather than a coincidence with the defaults. |
| 88 | 98 | const ASIDE = "https://machine.example.test"; |
| 89 | 99 | const WAITS: readonly number[] = [1, 2, 3]; |
| 100 | const VET: OutboundOptions = { allow: ["192.0.2.7"] }; | |
| 90 | 101 | let found: { |
| 91 | 102 | level: LogLevel; |
| 92 | 103 | waits: readonly number[]; |
| 93 | 104 | url: string | undefined; |
| 105 | vet: OutboundOptions; | |
| 94 | 106 | }; |
| 95 | 107 | beforeAll(() => { |
| 96 | 108 | found = { |
| 97 | 109 | level: logLevel(), |
| 98 | 110 | waits: firstFetchRetry.ms, |
| 99 | 111 | url: process.env.FORGE_URL, |
| 112 | vet: mirrorOutboundForTest(), | |
| 100 | 113 | }; |
| 101 | 114 | setLogLevel("warn"); |
| 102 | 115 | firstFetchRetry.ms = WAITS; |
| 103 | 116 | process.env.FORGE_URL = ASIDE; |
| 117 | provideMirrorOutboundForTest(VET); | |
| 104 | 118 | }); |
| 105 | 119 | afterAll(() => { |
| 120 | provideMirrorOutboundForTest(found.vet); | |
| 106 | 121 | setLogLevel(found.level); |
| 107 | 122 | firstFetchRetry.ms = found.waits; |
| 108 | 123 | if (found.url === undefined) delete process.env.FORGE_URL; |
| @@ -113,11 +128,13 @@ describe("a global a file borrows is given back", () => { | ||
| 113 | 128 | quietLogs(); |
| 114 | 129 | fastFirstFetch([50]); |
| 115 | 130 | withoutEnv("FORGE_URL"); |
| 131 | mirrorOutbound({}); | |
| 116 | 132 | |
| 117 | 133 | test("they hold what it asked for", () => { |
| 118 | 134 | expect(logLevel()).toBe("error"); |
| 119 | 135 | expect(firstFetchRetry.ms).toEqual([50]); |
| 120 | 136 | expect(process.env.FORGE_URL).toBeUndefined(); |
| 137 | expect(mirrorOutboundForTest()).toEqual({}); | |
| 121 | 138 | // What the file sets under a cleared name goes when it does. |
| 122 | 139 | process.env.FORGE_URL = "https://set.inside.test"; |
| 123 | 140 | }); |
| @@ -127,5 +144,6 @@ describe("a global a file borrows is given back", () => { | ||
| 127 | 144 | expect(logLevel()).toBe("warn"); |
| 128 | 145 | expect(firstFetchRetry.ms).toBe(WAITS); |
| 129 | 146 | expect(process.env.FORGE_URL).toBe(ASIDE); |
| 147 | expect(mirrorOutboundForTest()).toBe(VET); | |
| 130 | 148 | }); |
| 131 | 149 | }); |
test/helpers/globals.ts +19
| @@ -10,6 +10,11 @@ | ||
| 10 | 10 | import { afterAll, beforeAll } from "bun:test"; |
| 11 | 11 | import { logLevel, setLogLevel, type LogLevel } from "../../src/kernel/log.ts"; |
| 12 | 12 | import { firstFetchRetry } from "../../src/origins/mirror.ts"; |
| 13 | import type { OutboundOptions } from "../../src/kernel/outbound.ts"; | |
| 14 | import { | |
| 15 | mirrorOutboundForTest, | |
| 16 | provideMirrorOutboundForTest, | |
| 17 | } from "../../src/origins/git-mirror.ts"; | |
| 13 | 18 | |
| 14 | 19 | /** Raise the log floor (by default to errors only) while this scope runs. */ |
| 15 | 20 | export function quietLogs(level: LogLevel = "error"): void { |
| @@ -33,6 +38,20 @@ export function fastFirstFetch(ms: readonly number[] = [50]): void { | ||
| 33 | 38 | }); |
| 34 | 39 | } |
| 35 | 40 | |
| 41 | /** | |
| 42 | * Tell a mirror's outbound vet `options` while this scope runs. The preload | |
| 43 | * lets every file mirror from loopback; a file that tightens that and leaves | |
| 44 | * it tightened refuses the next file's fake origin in the same shard. | |
| 45 | */ | |
| 46 | export function mirrorOutbound(options: OutboundOptions): void { | |
| 47 | let found: OutboundOptions; | |
| 48 | beforeAll(() => { | |
| 49 | found = mirrorOutboundForTest(); | |
| 50 | provideMirrorOutboundForTest(options); | |
| 51 | }); | |
| 52 | afterAll(() => provideMirrorOutboundForTest(found)); | |
| 53 | } | |
| 54 | ||
| 36 | 55 | /** |
| 37 | 56 | * Unset environment variables the machine may carry while this scope runs. |
| 38 | 57 | * A crew machine sets `FORGE_URL` to its own forge; a test that registers an |
test/mirror-outbound.test.ts +2 −2
| @@ -10,14 +10,14 @@ import { | ||
| 10 | 10 | provideMirrorOutboundForTest, |
| 11 | 11 | } from "../src/origins/git-mirror.ts"; |
| 12 | 12 | import { remoteGitEnv, pinFor } from "../src/git/remote-env.ts"; |
| 13 | import { mirrorOutbound } from "./helpers/globals.ts"; | |
| 13 | 14 | |
| 14 | 15 | let dir: string; |
| 16 | mirrorOutbound({}); | |
| 15 | 17 | beforeAll(async () => { |
| 16 | 18 | dir = await mkdtemp(join(tmpdir(), "mirror-outbound-")); |
| 17 | provideMirrorOutboundForTest({}); | |
| 18 | 19 | }); |
| 19 | 20 | afterAll(async () => { |
| 20 | provideMirrorOutboundForTest({ allow: ["127.0.0.1", "::1"] }); | |
| 21 | 21 | await rm(dir, { recursive: true, force: true }); |
| 22 | 22 | }); |
| 23 | 23 |
test/upstream_follow.test.ts +4 −2
| @@ -32,6 +32,7 @@ import { runGit } from "../src/git/env.ts"; | ||
| 32 | 32 | import { REMOTE_PROTOCOLS } from "../src/git/remote-env.ts"; |
| 33 | 33 | import { repoDir } from "../src/git/repos.ts"; |
| 34 | 34 | import { MIRROR_REFUSED, provideMirrorOutboundForTest, vettedEnv } from "../src/origins/git-mirror.ts"; |
| 35 | import { mirrorOutbound } from "./helpers/globals.ts"; | |
| 35 | 36 | import { |
| 36 | 37 | getFollow, |
| 37 | 38 | listFollowing, |
| @@ -156,9 +157,10 @@ describe("checking an upstream (layer 2)", () => { | ||
| 156 | 157 | beforeAll(async () => { |
| 157 | 158 | target = ok(await createRepo(app, alice, { name: "checked", visibility: "public" })); |
| 158 | 159 | dir = repoDir(app.reposRoot, target.ownerName, target.name); |
| 159 | provideMirrorOutboundForTest({ allow: ["127.0.0.1"] }); | |
| 160 | 160 | }); |
| 161 | afterAll(() => provideMirrorOutboundForTest({})); | |
| 161 | // Given back as found: a bare `{}` here refused loopback to every file | |
| 162 | // after this one in its shard (#7686). | |
| 163 | mirrorOutbound({ allow: ["127.0.0.1"] }); | |
| 162 | 164 | /** A server that records every request and answers with `refs` as a dumb-http `info/refs`. */ |
| 163 | 165 | function upstream(refs: () => string, delayMs = 0) { |
| 164 | 166 | const seen: Request[] = []; |
test/view-source.test.ts +18 −3
| @@ -5,7 +5,12 @@ import { join } from "node:path"; | ||
| 5 | 5 | import { createApp, type App } from "../src/app.ts"; |
| 6 | 6 | import { serve, type Servers } from "../src/http/server.ts"; |
| 7 | 7 | import { getUserByName, type User } from "../src/core/users.ts"; |
| 8 | import { settled, subscribe, type MirrorStage } from "../src/origins/jobs.ts"; | |
| 8 | import { | |
| 9 | runningJob, | |
| 10 | settled, | |
| 11 | subscribe, | |
| 12 | type MirrorStage, | |
| 13 | } from "../src/origins/jobs.ts"; | |
| 9 | 14 | import { forgetProviders } from "../src/origins/providers/index.ts"; |
| 10 | 15 | import { |
| 11 | 16 | clearViewCache, |
| @@ -19,7 +24,7 @@ import { | ||
| 19 | 24 | serveFakeForge, |
| 20 | 25 | type FakeForge, |
| 21 | 26 | } from "./fake-origin.ts"; |
| 22 | import { fastFirstFetch } from "./helpers/globals.ts"; | |
| 27 | import { fastFirstFetch, mirrorOutbound } from "./helpers/globals.ts"; | |
| 23 | 28 | import { seedAdmin } from "./helpers/admin.ts"; |
| 24 | 29 | |
| 25 | 30 | /** |
| @@ -54,6 +59,9 @@ const text = (body: Uint8Array): string => new TextDecoder().decode(body); | ||
| 54 | 59 | |
| 55 | 60 | // A fetch that fails should say so now, not after the forge's patience. |
| 56 | 61 | fastFirstFetch(); |
| 62 | // Both origins are on loopback. Asked for here, not inherited, so a file | |
| 63 | // earlier in the shard that left the vet tightened cannot refuse them (#7686). | |
| 64 | mirrorOutbound({ allow: ["127.0.0.1", "::1"] }); | |
| 57 | 65 | |
| 58 | 66 | describe("RepoSource: one repository, two places to read it from", () => { |
| 59 | 67 | let dataDir: string; |
| @@ -653,6 +661,10 @@ describe("RepoSource: one repository, two places to read it from", () => { | ||
| 653 | 661 | // ---- the equivalence that makes the interface worth having ---- |
| 654 | 662 | |
| 655 | 663 | test("the mirror and the origin answer the same questions the same way", async () => { |
| 664 | // Compare against the finished mirror: if the test that started it ran | |
| 665 | // out of time on a loaded machine, its import may still be running. | |
| 666 | const importing = runningJob(originId, "octo/widgets"); | |
| 667 | if (importing !== undefined) await settled(importing.id); | |
| 656 | 668 | const here = local("alice", "widgets"); |
| 657 | 669 | const there = live("octo/widgets"); |
| 658 | 670 | expect(here.where.kind).toBe("local"); |
| @@ -796,7 +808,10 @@ describe("RepoSource: one repository, two places to read it from", () => { | ||
| 796 | 808 | // And the one thing that must differ: what you may do here. |
| 797 | 809 | expect(here.capabilities.write).toBe(true); |
| 798 | 810 | expect(there.capabilities.write).toBe(false); |
| 799 | }); | |
| 811 | // Eight rounds of git reads against HTTP reads: milliseconds when idle, | |
| 812 | // past bun's 5 s default on a loaded check machine (#7686). The verdict | |
| 813 | // is the answers', not the clock's. | |
| 814 | }, 60_000); | |
| 800 | 815 | |
| 801 | 816 | test("a local source refuses a repo the viewer cannot read", () => { |
| 802 | 817 | app.db |
new above
offline
This comment sits after the last assertion and reads as if it belongs to nothing. Put it above the test or beside the title, and use the shared MIRROR_MS once #7713 lands.