Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Repositories shard across git store namespaces, move between them, and can keep to the EU; a namespace can be served read-only from the self-hosted git store, rebuilt from the nightly backups (#20, #25) | 1 | // The namespace report, from configuration and table rows as the repos |
| 2 | // database holds them. | |
| 3 | ||
| 4 | import assert from "node:assert/strict"; | |
| 5 | import { readFileSync } from "node:fs"; | |
| 6 | import { test } from "node:test"; | |
| 7 | import { fileURLToPath } from "node:url"; | |
| 8 | ||
| 9 | import { parseJsonc } from "../deploy/stack.mjs"; | |
| 10 | import { LIMIT_PER_MINUTE, configured, namespaceOf, standings, table } from "./artifacts-namespaces.mjs"; | |
| 11 | ||
| 12 | const sharded = { | |
| 13 | artifacts: [ | |
| 14 | { binding: "ARTIFACTS", namespace: "g1t" }, | |
| 15 | { binding: "ARTIFACTS_1", namespace: "g1t-us-1" }, | |
| 16 | { binding: "ARTIFACTS_EU", namespace: "g1t-eu" }, | |
| 17 | ], | |
| 18 | vars: { | |
| 19 | ARTIFACTS_NAMESPACES: JSON.stringify({ ARTIFACTS: "g1t", ARTIFACTS_1: "g1t-us-1", ARTIFACTS_EU: "g1t-eu", ARTIFACTS_2: "g1t-us-2" }), | |
| 20 | ARTIFACTS_NEW_REPOS: "g1t,g1t-us-1,g1t-us-2", | |
| 21 | ARTIFACTS_EU_NAMESPACE: "g1t-eu", | |
| 22 | ARTIFACTS_NAMESPACE_LIMITS: JSON.stringify({ "g1t": { max_repos: 100 } }), | |
| 23 | }, | |
| 24 | }; | |
| 25 | ||
| 26 | test("today's configuration is one namespace that takes nothing new by name", () => { | |
| 27 | const today = parseJsonc(readFileSync(fileURLToPath(new URL("../../services/repos/wrangler.jsonc", import.meta.url)), "utf8")); | |
| 28 | const config = configured(today); | |
| 29 | assert.equal(config.length, 1); | |
| 30 | assert.deepEqual( | |
| 31 | { namespace: config[0].namespace, bound: config[0].bound, default: config[0].default, takes: config[0].takes_new_repos, eu: config[0].eu }, | |
| 32 | { namespace: "g1t", bound: true, default: true, takes: false, eu: false }, | |
| 33 | ); | |
| 34 | }); | |
| 35 | ||
| 36 | test("each namespace's binding, jurisdiction and part in placing are read from the configuration", () => { | |
| 37 | const config = configured(sharded); | |
| 38 | const by = Object.fromEntries(config.map((one) => [one.namespace, one])); | |
| 39 | // A binding never says its namespace's jurisdiction; Cloudflare does. | |
| 40 | assert.equal(by["g1t-eu"].jurisdiction, null); | |
| 41 | assert.ok(by["g1t-eu"].eu && !by["g1t-eu"].takes_new_repos); | |
| 42 | assert.ok(by["g1t-us-1"].bound && by["g1t-us-1"].takes_new_repos); | |
| 43 | // Named, but no binding for it: not bound. | |
| 44 | assert.equal(by["g1t-us-2"].bound, false); | |
| 45 | assert.equal(by.g1t.max_repos, 100); | |
| 46 | assert.equal(namespaceOf("acme--rocket"), "g1t"); | |
| 47 | assert.equal(namespaceOf("g1t-us-1/acme--rocket"), "g1t-us-1"); | |
| 48 | assert.equal(namespaceOf(null, "main"), "main"); | |
| 49 | }); | |
| 50 | ||
| 51 | test("standings add up holdings and health, and warn about what needs doing", () => { | |
| 52 | const held = [ | |
| 53 | { ns: "", repos: 90, forks: 10, stored_bytes: 2e9 }, | |
| 54 | { ns: "g1t", repos: 10, forks: 0, stored_bytes: 1e9 }, | |
| 55 | { ns: "g1t-us-1", repos: 3, forks: 1, stored_bytes: 5e8 }, | |
| 56 | ]; | |
| 57 | const health = [ | |
| 58 | { window: "day", store: "g1t", peak: LIMIT_PER_MINUTE * 0.8, calls: 900_000, errors: 4, rate_limited: 2, rejected: 0 }, | |
| 59 | { window: "hour", store: "g1t", peak: 100, calls: 5_000, errors: 0, rate_limited: 2, rejected: 0 }, | |
| 60 | { window: "hour", store: "g1t-us-1@fallback", peak: 3, calls: 40, errors: 0, rate_limited: 0, rejected: 0 }, | |
| 61 | ]; | |
| 62 | const rows = standings(configured(sharded), held, health, [{ namespace: "g1t", count: 7 }, { namespace: "g1t-eu", count: 1 }]); | |
| 63 | const by = Object.fromEntries(rows.map((row) => [row.namespace, row])); | |
| 64 | assert.equal(by.g1t.repos, 100); | |
| 65 | assert.equal(by.g1t.stored_bytes, 3e9); | |
| 66 | assert.equal(by.g1t.cloudflare_events, 7); | |
| 67 | assert.ok(by.g1t.warnings.some((w) => /80% of the limit/.test(w))); | |
| 68 | assert.ok(by.g1t.warnings.some((w) => /max_repos/.test(w))); | |
| 69 | assert.ok(by.g1t.warnings.some((w) => /rate limited/.test(w))); | |
| 70 | assert.ok(by["g1t-us-1"].warnings.some((w) => /fallback/.test(w))); | |
| 71 | assert.ok(by["g1t-us-2"].warnings.some((w) => /not bound/.test(w))); | |
| 72 | assert.deepEqual(by["g1t-eu"].warnings, []); | |
| 73 | const text = table(rows); | |
| 74 | assert.match(text, /^namespace/); | |
| 75 | assert.match(text, /g1t \*/); | |
| 76 | assert.match(text, /g1t-us-2 .*\(not bound\)/); | |
| 77 | }); | |
| 78 | ||
| 79 | test("what Cloudflare has is checked against what is bound", () => { | |
| 80 | const made = [ | |
| 81 | { namespace: "g1t", jurisdiction: null }, | |
| 82 | { namespace: "g1t-us-1", jurisdiction: null }, | |
| 83 | { namespace: "g1t-eu", jurisdiction: null }, | |
| 84 | ]; | |
| 85 | const rows = standings(configured(sharded), [], [], [], made); | |
| 86 | const by = Object.fromEntries(rows.map((row) => [row.namespace, row])); | |
| 87 | // Made without the EU jurisdiction: it cannot be changed, so it is said. | |
| 88 | assert.ok(by["g1t-eu"].warnings.some((w) => /jurisdiction is unrestricted/.test(w))); | |
| 89 | assert.equal(by.g1t.jurisdiction, "any"); | |
| 90 | // Named and bound nowhere, and not made: both said. | |
| 91 | assert.ok(by["g1t-us-2"].warnings.some((w) => /no namespace of this name/.test(w))); | |
| 92 | const right = standings(configured(sharded), [], [], [], made.map((one) => (one.namespace === "g1t-eu" ? { ...one, jurisdiction: "eu" } : one))); | |
| 93 | assert.deepEqual(right.find((row) => row.namespace === "g1t-eu").warnings, []); | |
| 94 | assert.equal(right.find((row) => row.namespace === "g1t-eu").jurisdiction, "eu"); | |
| 95 | }); |