Files
stack/packages/cli/tests/config.test.mjs
T
jason.woltjeandClaude Opus 5.5 390d437ba2 feat(tasks): the adapter reads a project tree (#1555, row 62)
With tracker.descendants on, the tracker project is the root of a tree.
Each reconcile reads the sync bot's project list (archived projects and
negative pseudo-project ids left out) and walks it by parent_project_id.
A child that passes the install checks is read through its board, and
one that doesn't is watch-only (bucket 0) and never refuses the
business. A tick reads each project in the tree. A child answering 403
or 404 leaves the tree only if GET /projects/<id> then answers 403 or
404 too, and its open tasks get the one-by-one read. Task refs carry
the project, so a task moved inside the tree shows under its new ref
and the old ref gets its tombstone. tracker.descendants reaches the adapter
through the business var and packages/cli/src/config.mjs (lead decision
87). Off, the adapter reads as v0 did.

Darkwing built it (agents/darkwing/work/queue-62), in three rounds.
Round 3 was approved by Filbert (27317) and Dewey (27338) on manifest
8b81e836, 13 paths. Their non-blocking notes and Darkwing's follow-ups
(two surviving mutants in test 18, the fake's view-404 code) go to a
follow-up row.

Sage's gate on 918a4a19, sequential and teed: node suites tasks 71,
business 60, bus 80, cli 110 and queue 150, 0 failed; webui 39 of 40,
then 40 of 40 on three reruns (row 62 touches no webui file; a timing
test under load 17 with Dewey's gate running beside it); test-auth 15,
test-conductor 17, test-config 24, test-discord 66,
test-extension-package 18, test-foundation 44, test-queue 27,
test-release 14, test-task 98, all 0 failed (Docker on). edc5de71..HEAD
touches none of the 13 paths.

Live read, 2026-10-11T21:28:03Z, tasks.woltje.com v2.7.0, root #19,
descendants on, sync key only, GETs only (the startup scope probes not
sent): reconcile 15 GETs, 42 snapshots; tick 8 GETs, 0 snapshots.
Children 18, 20 and 21 are watch-only. A missing view id on #18 answers
404 (code 3014) while #18 reads 200, as the README says. Record:
agents/sage/work/vikunja-setup/2026-10-11_tick-62.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 16:29:13 -05:00

72 lines
4.0 KiB
JavaScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { join } from "node:path";
import { bootConfig, loadSystem } from "../src/config.mjs";
import { writeJson } from "../../business/tests/helpers.mjs";
import { fixture, tmp } from "./helpers.mjs";
test("bootConfig builds the broker's boot message for one business, with no trackers key when no project names one", (t) => {
const f = fixture(tmp(t));
const system = loadSystem({ env: f.env });
const warnings = [];
const boot = bootConfig({ system, businessId: "acme", env: f.env, warn: (w) => warnings.push(w) });
assert.equal(boot.dataRoot, f.dataRoot);
assert.deepEqual(Object.keys(boot.businesses), ["acme"]);
assert.deepEqual(boot.launches, []);
assert.deepEqual(boot.readers, ["acme"]);
assert.equal("trackers" in boot, false);
assert.match(warnings[0], /no project file sets tracker\.project/);
});
test("trackers come from the tracker.* variables of the one project that names a tracker project", (t) => {
const root = tmp(t);
const f = fixture(root);
writeJson(join(root, "project", ".mosaic", "project.json"), { projectVersion: 1, id: "stack", vars: { "tracker.project": 12 } });
const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env });
assert.deepEqual(boot.trackers, { acme: { baseUrl: "http://127.0.0.1:3456", project: 12, pollSeconds: 60, reconcileMinutes: 60, descendants: false } });
// Decision 87: the project's tracker.descendants reaches the adapter's boot entry.
writeJson(join(root, "project", ".mosaic", "project.json"), { projectVersion: 1, id: "stack", vars: { "tracker.project": 12, "tracker.descendants": true } });
const tree = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env });
assert.equal(tree.trackers.acme.descendants, true);
});
test("with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict", (t) => {
const root = tmp(t);
const f = fixture(root, "acme", (doc) => {
doc.projects.web = { root: join(root, "web") };
return doc;
});
writeJson(join(root, "project", ".mosaic", "project.json"), { projectVersion: 1, id: "stack", vars: { "tracker.project": 12 } });
writeJson(join(root, "web", ".mosaic", "project.json"), { projectVersion: 1, id: "web", vars: {} });
const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env });
assert.deepEqual(boot.trackers, { acme: { baseUrl: "http://127.0.0.1:3456", project: 12, pollSeconds: 60, reconcileMinutes: 60, descendants: false } });
});
test("two projects that each name a tracker project refuse, since the boot shape holds one", (t) => {
const root = tmp(t);
const f = fixture(root, "acme", (doc) => {
doc.projects.web = { root: join(root, "web") };
return doc;
});
writeJson(join(root, "project", ".mosaic", "project.json"), { projectVersion: 1, id: "stack", vars: { "tracker.project": 12 } });
writeJson(join(root, "web", ".mosaic", "project.json"), { projectVersion: 1, id: "web", vars: { "tracker.project": 13 } });
assert.throws(() => bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }), (e) => e.exitCode === 3 && /stack, web each set tracker\.project/.test(e.message));
});
test("a business without tracker.baseUrl gets no trackers entry", (t) => {
const f = fixture(tmp(t), "acme", (doc) => {
delete doc.vars["tracker.baseUrl"];
return doc;
});
const warnings = [];
const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env, warn: (w) => warnings.push(w) });
assert.equal("trackers" in boot, false);
assert.deepEqual(warnings, []);
});
test("an unknown business and a broken system config refuse with exit 3", (t) => {
const f = fixture(tmp(t));
assert.throws(() => bootConfig({ system: loadSystem({ env: f.env }), businessId: "nope", env: f.env }), (e) => e.exitCode === 3);
assert.throws(() => loadSystem({ env: { ...f.env, MOSAIC_CONFIG: join(f.dataRoot, "missing.json") } }), (e) => e.exitCode === 3);
});