← back to Costa Rica
test/logo-agent.test.js
220 lines
'use strict';
// Coverage for the logo-agent tournament route (shipped to prod cycle: yoloforever C2).
// Two layers: (1) pure genetics/SVG invariants via router._internals, (2) full HTTP
// flow (session -> rank -> lock x6 -> finalize) over an ephemeral express server.
// Cleans up every file it writes (sessions + finalize artifacts) — zero leak.
const test = require('node:test');
const assert = require('node:assert');
const express = require('express');
const fs = require('fs');
const path = require('path');
const router = require('../routes/logo-agent');
const I = router._internals;
const ROOT = path.join(__dirname, '..');
const SVG_OUT = path.join(ROOT, 'public', 'img', 'cr-logo.svg');
const FINAL = path.join(ROOT, 'data', 'logo-agent-final.json');
const SESS_DIR = path.join(ROOT, 'data', 'logo-agent-sessions');
// ---- ephemeral server helper ----
let server, base;
test.before(async () => {
const app = express();
app.use(express.json());
app.use('/api/logo-agent', router);
await new Promise((r) => { server = app.listen(0, r); });
base = `http://127.0.0.1:${server.address().port}/api/logo-agent`;
});
test.after(() => {
if (server) server.close();
// clean any artifacts this test created
try { fs.rmSync(SVG_OUT, { force: true }); } catch {}
try { fs.rmSync(FINAL, { force: true }); } catch {}
try { fs.rmSync(SESS_DIR, { recursive: true, force: true }); } catch {}
// remove empty public/img if we created it
try { fs.rmdirSync(path.join(ROOT, 'public', 'img')); } catch {}
});
const P = (u, b) => fetch(base + u, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: b ? JSON.stringify(b) : undefined }).then((r) => r.json());
const G = (u) => fetch(base + u).then((r) => r.json());
// ---- 1. genetics invariants (deterministic assertions on a random op) ----
test('crossover glyph: motif+container inherited from a parent, weight/dominance within blended bounds', () => {
const a = { motif: 'volcano', container: 'circle', weight: 4, dominance: 40 };
const b = { motif: 'toucan', container: 'shield', weight: 12, dominance: 90 };
for (let i = 0; i < 200; i++) {
const c = I.crossover('glyph', a, b);
assert.ok([a.motif, b.motif].includes(c.motif), 'motif from a parent');
assert.ok([a.container, b.container].includes(c.container), 'container from a parent');
assert.ok(c.weight >= 3 && c.weight <= 14, `weight clamped 3..14 got ${c.weight}`);
assert.ok(c.dominance >= 30 && c.dominance <= 95, `dominance clamped 30..95 got ${c.dominance}`);
}
});
test('crossover openspace: density stays within clamp bounds', () => {
for (let i = 0; i < 100; i++) {
const c = I.crossover('openspace', { density: 20 }, { density: 100 });
assert.ok(c.density >= 10 && c.density <= 100, `density in 10..100 got ${c.density}`);
}
});
// HOLE 1 (Cody C2): drive the clamp with BOUNDARY inputs so a clamp-constant
// regression is actually caught — mid-range inputs never approach the clamp.
test('crossover clamps at the low/high edges (boundary inputs, not mid-range)', () => {
for (let i = 0; i < 300; i++) {
// low edge: both parents at/below the floor -> jitter must not escape below 3 / 30 / 10
const lo = I.crossover('glyph', { motif: 'sun', container: 'none', weight: 3, dominance: 30 },
{ motif: 'sun', container: 'none', weight: 3, dominance: 30 });
assert.ok(lo.weight >= 3, `weight floor held, got ${lo.weight}`);
assert.ok(lo.dominance >= 30, `dominance floor held, got ${lo.dominance}`);
// high edge: both parents at the ceiling -> must not escape above 14 / 95
const hi = I.crossover('glyph', { motif: 'sun', container: 'none', weight: 14, dominance: 95 },
{ motif: 'sun', container: 'none', weight: 14, dominance: 95 });
assert.ok(hi.weight <= 14, `weight ceiling held, got ${hi.weight}`);
assert.ok(hi.dominance <= 95, `dominance ceiling held, got ${hi.dominance}`);
// openspace edges
assert.ok(I.crossover('openspace', { density: 10 }, { density: 10 }).density >= 10, 'density floor');
assert.ok(I.crossover('openspace', { density: 100 }, { density: 100 }).density <= 100, 'density ceiling');
}
});
test('crossover categorical (palette/type/tagline/layout): child value comes from one parent', () => {
const pairs = [
['palette', { palette: 'pacific' }, { palette: 'sunset' }],
['typography', { type: 0 }, { type: 3 }],
['tagline', { tagline: 1 }, { tagline: 5 }],
['layout', { layout: 'icon-left' }, { layout: 'icon-only' }],
];
for (const [comp, a, b] of pairs) {
const key = Object.keys(a)[0];
for (let i = 0; i < 50; i++) {
const c = I.crossover(comp, a, b);
assert.ok([a[key], b[key]].includes(c[key]), `${comp} child ${key} from a parent`);
}
}
});
// ---- 2. glyph SVG generation ----
test('every motif produces a non-empty SVG with a stroke/shape', () => {
for (const motif of I.MOTIFS) {
const svg = I.buildSvg({ palette: 'rainforest', glyph: { motif, container: 'none', weight: 6 } });
assert.match(svg, /<svg[\s\S]*<\/svg>/, `${motif} wrapped in <svg>`);
assert.match(svg, /stroke|path|circle|line|ellipse/, `${motif} draws a shape`);
}
});
test('motifs render DISTINCT paths (not just any shape) — catches a motif path swap', () => {
// Each motif carries a characteristic signature; assert the RIGHT one renders.
const sig = {
volcano: 'M14 46 L32 16 L50 46', wave: 'M10 34 Q20 22', monstera: 'M32 12 C14 18',
toucan: 'Q52 20 50 30', sun: '<line', coffee: '<ellipse',
};
const seen = new Set();
for (const motif of I.MOTIFS) {
const svg = I.buildSvg({ glyph: { motif, container: 'none', weight: 6 } });
assert.ok(svg.includes(sig[motif]), `${motif} renders its own signature (${sig[motif]})`);
seen.add(I.motifPath(motif, 6));
}
assert.equal(seen.size, I.MOTIFS.length, 'all motif paths are unique');
});
test('containers wrap the motif (circle/shield add an outer shape)', () => {
const circle = I.buildSvg({ glyph: { motif: 'sun', container: 'circle', weight: 6 } });
const shield = I.buildSvg({ glyph: { motif: 'sun', container: 'shield', weight: 6 } });
assert.match(circle, /<circle cx="32" cy="32" r="30"/, 'circle container ring');
assert.match(shield, /<path d="M32 4/, 'shield container path');
});
test('assemble() returns a full brand object (svg+palette+type+tagline+layout+density)', () => {
const brand = I.assemble({ palette: 'pacific', type: 1, tagline: 2, layout: 'icon-top', density: 60, glyph: { motif: 'wave', container: 'none', weight: 5 } });
assert.ok(brand.svg.includes('<svg'));
assert.equal(brand.palette.accent, I.PALETTES.pacific.accent);
assert.equal(brand.type, I.TYPE[1]);
assert.equal(brand.tagline, I.TAGLINES[2]);
assert.equal(brand.layout, 'icon-top');
assert.equal(brand.density, 60);
});
// ---- 3. full HTTP tournament flow ----
test('session/start returns 3 glyph variants with valid svg', async () => {
const s = await P('/session/start');
assert.ok(s.session_id, 'has session_id');
assert.equal(s.component, 'glyph');
assert.equal(s.variants.length, 3);
assert.match(s.variants[0].svg, /<svg/);
});
test('rank breeds a new 3rd variant, keeps the top-2, bumps xp/streak', async () => {
const s = await P('/session/start');
const r = await P('/rank/' + s.session_id, { ranks: [s.variants[0].vid, s.variants[1].vid] });
assert.equal(r.variants.length, 3, 'still 3 variants');
assert.equal(r.variants[0].vid, s.variants[0].vid, 'champion retained');
assert.equal(r.variants[1].vid, s.variants[1].vid, 'runner-up retained');
assert.ok(!s.variants.map((v) => v.vid).includes(r.variants[2].vid), '3rd is a fresh bred variant');
assert.equal(r.xp, 10);
assert.equal(r.round, 2);
// HOLE 3 (Cody C2): the bred child must actually INHERIT genes from the two ranked
// parents over the HTTP path, not merely have a new vid. glyph is component 1, so the
// variants' genomes are exposed via assemble().genome.
const p1 = s.variants[0].genome, p2 = s.variants[1].genome, child = r.variants[2].genome;
assert.ok([p1.motif, p2.motif].includes(child.motif), 'child motif inherited from a ranked parent');
assert.ok([p1.container, p2.container].includes(child.container), 'child container inherited from a ranked parent');
const wLo = Math.min(p1.weight, p2.weight) - 1, wHi = Math.max(p1.weight, p2.weight) + 1;
assert.ok(child.weight >= Math.max(3, wLo) && child.weight <= Math.min(14, wHi),
`child weight blended from parents (${p1.weight},${p2.weight}) -> ${child.weight}`);
});
test('locking all 6 components finishes the tournament and awards tastemaker', async () => {
const s = await P('/session/start');
let cur = s;
const walked = [s.component];
for (let i = 0; i < 6; i++) {
const r = await P('/lock/' + s.session_id, { winner_id: cur.variants[0].vid });
if (r.done) {
assert.ok(r.badges.includes('tastemaker'), 'tastemaker on full completion');
assert.ok(r.brand && r.brand.svg.includes('<svg'), 'returns assembled brand');
return;
}
walked.push(r.component);
cur = r;
}
assert.fail('tournament did not complete after 6 locks; walked=' + walked.join(','));
});
test('lock rejects a winner_id not in the current variants', async () => {
const s = await P('/session/start');
const r = await P('/lock/' + s.session_id, { winner_id: 'deadbeef' });
assert.equal(r.error, 'winner_id not in current variants');
});
test('rank rejects fewer than 2 ranked vids', async () => {
const s = await P('/session/start');
const r = await P('/rank/' + s.session_id, { ranks: [s.variants[0].vid] });
assert.equal(r.error, 'need at least 2 ranked vids');
});
// ---- 4. finalize writes the SVG + brand spec + CSS vars ----
test('finalize writes cr-logo.svg + logo-agent-final.json with CSS vars', async () => {
const s = await P('/session/start');
let cur = s;
for (let i = 0; i < 6; i++) {
const r = await P('/lock/' + s.session_id, { winner_id: cur.variants[0].vid });
if (r.done) break;
cur = r;
}
const f = await P('/finalize/' + s.session_id);
assert.equal(f.ok, true);
assert.equal(f.svgPath, '/img/cr-logo.svg');
assert.match(f.cssVars, /--cr-accent:/);
assert.match(f.cssVars, /--cr-font-display:/);
assert.ok(fs.existsSync(SVG_OUT), 'cr-logo.svg written to disk');
assert.ok(fs.existsSync(FINAL), 'logo-agent-final.json written to disk');
const svg = fs.readFileSync(SVG_OUT, 'utf8');
assert.match(svg, /<svg[\s\S]*<\/svg>/, 'written svg is well-formed');
});
test('state/:sid returns 404 for an unknown session', async () => {
const r = await G('/state/nope-not-real');
assert.equal(r.error, 'session not found');
});