Files
mesh-catalog/modules/gitea/test/views.test.ts
T
jschoubben 32655b95f6 GIT1, GIT2, GIT3, GIT10: the forge module serves the seat git's verbs and says its events, retires its acting tools, follows the catalog half
The four share files (index.ts, module.json, guard.test.ts), so they land as one commit.
GIT1: seat.ts serves pull, open-pulls, tags, trees, file, contains, statuses, status, comment, view, note, merge-pull
as tools on the seat, each answering the contract's shape and an error of kind refused, not-found or failed; a comment,
view or note whose act id is held is answered held (note matched by id). merge-pull merges the head the caller names
(its from-G judgements are GIT6, GIT7, GIT9).
GIT2: seatevents.ts says pull-updated, pull-merged, pull-closed, forge-pull, forge-status, forge-delete and repo-writable on
mesh.seat.git.event.<event>; the module's emits drop them.
GIT3: merge.ts, check.ts, the acting tools, the owed watch, the gate's source read and check-gates-merge are gone.
GIT10: catalog.ts follows the seat mesh-catalog (revision, catalog-revision); no write is granted before the first view.

Builds: design 47 G §310, §311, §312, §313, §314, §163, §317, §278, §321, §196

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 11:31:24 +01:00

96 lines
5.4 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { viewWaitsFinding } from "@novox/mesh-sdk/views";
import { CatalogView, REVISION_VERB, findingValue, revisionAnswer, unansweredFinding } from "../catalog.ts";
// Design 47 *Every part takes a first view, then follows the events*: the forge module asks the seat mesh-catalog's verb
// `revision` for the catalog half (every page of one trunk revision), then hears its event `catalog-revision`. A candidate's
// revision moves nothing; a newer trunk revision is waited for, said, and asked; waited for past the bound, view-waits is
// written, and cleared once held; an owner that does not answer is asked again and raised past its bound.
const entry = (module: string, repository: string) => ({ module, repository, dir: ".", manifest: "m", fingerprint: "f", verdict: "accepted" });
const revisionOf = (revision: number, kind = "trunk") => ({ revision, kind, source: revision, entries: [] });
/** A holder of the seat whose trunk revisions are pages of entries, and who answers what is asked of it. */
function aHolder() {
const asked: Record<string, unknown>[] = [];
const trunk = new Map<number, { module: string; repository: string }[][]>([
[7, [[entry("a", "u/a"), entry("b", "u/b")], [entry("c", "u/c")]]],
[8, [[entry("a", "u/a"), entry("c", "u/c2")]]],
]);
let newest = 7;
let down = 0;
return {
asked,
set newest(n: number) { newest = n; },
set down(n: number) { down = n; },
ask: async (verb: string, input: { after?: string; revision?: number }) => {
assert.equal(verb, REVISION_VERB);
asked.push({ ...input });
if (down > 0) { down--; return { error: "no responders" }; }
const rev = input.revision ?? newest;
const pages = trunk.get(rev)!;
const at = input.after ? pages.findIndex((p) => p[0].module > input.after!) : 0;
return { revision: revisionOf(rev), entries: pages[at], ...(at < pages.length - 1 ? { next: pages[at].at(-1)!.module } : {}) };
},
};
}
test("the catalog view takes the trunk's first view page by page, then a newer trunk revision, and a candidate's moves nothing", async () => {
const holder = aHolder();
const changes: string[] = [];
const view = new CatalogView(holder.ask, { pause: async () => {}, changed: (k, v, deleted) => { changes.push(deleted ? `-${k}` : `${k}=${(v as any).repository}`); } });
await view.follower.start();
assert.deepEqual(holder.asked, [{}, { after: "b", revision: 7 }], "the second page names the first page's revision");
assert.deepEqual([view.held, view.follower.status().view, changes.sort()], [7, "7", ["a=u/a", "b=u/b", "c=u/c"]]);
const rows: { name: string; ev: Record<string, unknown>; asks: number; held: number }[] = [
{ name: "a candidate's revision", ev: { v: 1, kind: "candidate", revision: 9, source: 8, group: "g", heads: "h" }, asks: 0, held: 7 },
{ name: "the revision it holds", ev: { v: 1, kind: "trunk", revision: 7, source: 7 }, asks: 0, held: 7 },
{ name: "a newer trunk revision", ev: { v: 1, kind: "trunk", revision: 8, source: 8 }, asks: 1, held: 8 },
];
for (const r of rows) {
holder.newest = 8;
holder.asked.splice(0);
changes.splice(0);
await view.hear(r.ev as any);
await view.asked;
assert.deepEqual([holder.asked.length, view.held], [r.asks, r.held], r.name);
}
assert.deepEqual(changes.sort(), ["-b", "c=u/c2"], "the newer revision hands what changed");
const s = view.follower.status();
assert.deepEqual([s.view, s["waits-for"]], ["8", undefined], "it holds what it waited for");
await assert.rejects(view.hear({ v: 1, kind: "trunk" } as any), /not a mesh-catalog.catalog-revision event/);
assert.throws(() => revisionAnswer({ error: "refused for gitea" }), /answered no revision: refused for gitea/);
});
test("a revision waited for past the bound writes view-waits, cleared once held; an unanswered first view is raised, then cleared", async () => {
const holder = aHolder();
const said: string[] = [];
let bound = () => {};
let clock = 0;
const view = new CatalogView(holder.ask, {
pause: () => new Promise((r) => setImmediate(r)), bound: 1000, now: () => new Date(Date.parse("2026-11-09T22:00:00Z") + (clock += 600)),
waitsBound: () => new Promise<void>((r) => (bound = r)),
raise: (s) => { said.push(`raised ${findingValue(unansweredFinding("gitea", s, new Date())).key}`); },
clear: () => { said.push("cleared"); },
raiseWaits: (s) => { said.push(`waits ${findingValue(viewWaitsFinding("gitea", s, new Date())).key}`); },
clearWaits: () => { said.push("waits cleared"); },
});
holder.down = 3;
await view.follower.start();
assert.deepEqual(said, ["raised module.gitea.mesh-catalog.catalog-revisions.first-view-unanswered", "cleared"]);
said.splice(0);
holder.newest = 8;
holder.down = 100; // the holder is away when the newer revision is said
const heard = view.hear({ v: 1, kind: "trunk", revision: 8, source: 8 } as any);
await heard;
assert.equal(view.follower.status()["waits-for"], "8", "it says what it waits for");
bound();
await new Promise((r) => setImmediate(r));
assert.deepEqual(said.filter((l) => l.startsWith("waits")), ["waits module.gitea.mesh-catalog.catalog-revisions.view-waits"]);
holder.down = 0;
await view.follower.gap("the holder is back");
await view.asked;
assert.deepEqual([view.held, view.follower.status()["waits-for"], said.includes("waits cleared")], [8, undefined, true]);
});