g1t/scripts/ops/artifacts-namespaces.test.mjs

95 lines4,809 bytesCodeBlame

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
4import assert from "node:assert/strict";
5import { readFileSync } from "node:fs";
6import { test } from "node:test";
7import { fileURLToPath } from "node:url";
8
9import { parseJsonc } from "../deploy/stack.mjs";
10import { LIMIT_PER_MINUTE, configured, namespaceOf, standings, table } from "./artifacts-namespaces.mjs";
11
12const 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
26test("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
36test("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
51test("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
79test("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});