mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Make critique storage the single owner of snapshot discovery and frontmatter parsing, and keep context signals focused on summarizing the canonical result. AI-assisted: Prepared by Codex under pbakaus's scheduled architecture-refactor authorization.
231 lines
8.4 KiB
JavaScript
231 lines
8.4 KiB
JavaScript
/**
|
|
* Tests for critique snapshot persistence.
|
|
* Run with: node --test tests/critique-storage.test.mjs
|
|
*/
|
|
|
|
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const SCRIPT = fileURLToPath(new URL('../skill/scripts/critique-storage.mjs', import.meta.url));
|
|
|
|
import {
|
|
slugFromTarget,
|
|
writeSnapshot,
|
|
readLatestSnapshot,
|
|
readLatestSnapshotAcrossTargets,
|
|
readTrend,
|
|
nowFilenameStamp,
|
|
} from '../skill/scripts/critique-storage.mjs';
|
|
|
|
let cwd;
|
|
beforeEach(() => { cwd = mkdtempSync(join(tmpdir(), 'imp-critique-')); });
|
|
afterEach(() => { rmSync(cwd, { recursive: true, force: true }); });
|
|
|
|
describe('slugFromTarget', () => {
|
|
it('kebabs a relative file path', () => {
|
|
assert.equal(slugFromTarget('site/pages/index.astro', { cwd }), 'site-pages-index-astro');
|
|
});
|
|
|
|
it('kebabs an absolute path inside cwd by relativizing', () => {
|
|
const abs = join(cwd, 'site/pages/index.astro');
|
|
assert.equal(slugFromTarget(abs, { cwd }), 'site-pages-index-astro');
|
|
});
|
|
|
|
it('uses basename for absolute paths outside cwd', () => {
|
|
// Sibling path, not under cwd
|
|
const abs = join(tmpdir(), 'somewhere', 'else', 'page.html');
|
|
assert.equal(slugFromTarget(abs, { cwd }), 'page-html');
|
|
});
|
|
|
|
it('drops port from URL', () => {
|
|
assert.equal(slugFromTarget('http://localhost:3000/pricing', { cwd }), 'localhost-pricing');
|
|
});
|
|
|
|
it('normalizes URL casing and trailing slash', () => {
|
|
assert.equal(
|
|
slugFromTarget('https://Impeccable.Style/docs/audit/', { cwd }),
|
|
'impeccable-style-docs-audit',
|
|
);
|
|
});
|
|
|
|
it('strips query strings', () => {
|
|
assert.equal(
|
|
slugFromTarget('https://example.com/x?utm=1&foo=bar', { cwd }),
|
|
'example-com-x',
|
|
);
|
|
});
|
|
|
|
it('returns null for empty / project-root inputs', () => {
|
|
assert.equal(slugFromTarget('', { cwd }), null);
|
|
assert.equal(slugFromTarget('.', { cwd }), null);
|
|
assert.equal(slugFromTarget(null, { cwd }), null);
|
|
});
|
|
|
|
it('caps overly long slugs from the tail', () => {
|
|
const longPath = 'a/'.repeat(60) + 'file.tsx'; // way over 50
|
|
const slug = slugFromTarget(longPath, { cwd });
|
|
assert.ok(slug.length <= 50);
|
|
assert.ok(slug.endsWith('file-tsx'));
|
|
});
|
|
|
|
it('is stable: same input → same slug', () => {
|
|
const a = slugFromTarget('site/pages/index.astro', { cwd });
|
|
const b = slugFromTarget('site/pages/index.astro', { cwd });
|
|
assert.equal(a, b);
|
|
});
|
|
});
|
|
|
|
describe('nowFilenameStamp', () => {
|
|
it('is windows-safe (no colons or dots in the time fragment)', () => {
|
|
const stamp = nowFilenameStamp(new Date('2026-05-12T18:30:00.123Z'));
|
|
assert.equal(stamp, '2026-05-12T18-30-00Z');
|
|
});
|
|
});
|
|
|
|
describe('writeSnapshot + readLatestSnapshot', () => {
|
|
it('round-trips body and frontmatter', () => {
|
|
const out = writeSnapshot({
|
|
slug: 'index-astro',
|
|
meta: { target: 'the homepage', total_score: 28, p0_count: 1, p1_count: 3 },
|
|
body: '# Critique\n\nP0: nested cards',
|
|
cwd,
|
|
});
|
|
assert.ok(out.endsWith('__index-astro.md'));
|
|
const latest = readLatestSnapshot('index-astro', { cwd });
|
|
assert.equal(latest.meta.slug, 'index-astro');
|
|
assert.equal(latest.meta.target, 'the homepage');
|
|
assert.equal(latest.meta.total_score, 28);
|
|
assert.match(latest.body, /P0: nested cards/);
|
|
});
|
|
|
|
it('returns null when no snapshot for slug', () => {
|
|
assert.equal(readLatestSnapshot('nope', { cwd }), null);
|
|
});
|
|
|
|
it('picks the newest by filename when multiple exist', () => {
|
|
writeSnapshot({ slug: 'index-astro', meta: { total_score: 22 }, body: 'old', cwd, now: new Date('2026-05-01T00:00:00Z') });
|
|
writeSnapshot({ slug: 'index-astro', meta: { total_score: 30 }, body: 'new', cwd, now: new Date('2026-05-12T00:00:00Z') });
|
|
const latest = readLatestSnapshot('index-astro', { cwd });
|
|
assert.equal(latest.meta.total_score, 30);
|
|
assert.match(latest.body, /new/);
|
|
});
|
|
|
|
it('picks the newest snapshot across target slugs', () => {
|
|
writeSnapshot({ slug: 'home', meta: {}, body: 'old', cwd, now: new Date('2026-05-01T00:00:00Z') });
|
|
writeSnapshot({ slug: 'pricing', meta: {}, body: 'new', cwd, now: new Date('2026-05-12T00:00:00Z') });
|
|
writeFileSync(join(cwd, '.impeccable', 'critique', 'ignore.md'), '# Critique ignores\n');
|
|
writeFileSync(join(cwd, '.impeccable', 'critique', '9999-not-a-snapshot.md'), '# Draft\n');
|
|
const latest = readLatestSnapshotAcrossTargets({ cwd });
|
|
assert.equal(latest.meta.slug, 'pricing');
|
|
assert.match(latest.body, /new/);
|
|
});
|
|
|
|
it('does not see snapshots for a different slug', () => {
|
|
writeSnapshot({ slug: 'pricing-astro', meta: { total_score: 10 }, body: 'b', cwd });
|
|
assert.equal(readLatestSnapshot('index-astro', { cwd }), null);
|
|
});
|
|
|
|
it('caller-supplied meta cannot override computed timestamp or slug', () => {
|
|
// Defends against a corrupt IMPECCABLE_CRITIQUE_META blob (parsed from
|
|
// an env var) silently rewriting fields that must agree with the
|
|
// filename. Otherwise readTrend would attribute scores to the wrong
|
|
// timestamps with no error.
|
|
const out = writeSnapshot({
|
|
slug: 'index-astro',
|
|
meta: { timestamp: 'NOT_A_REAL_STAMP', slug: 'somewhere-else', total_score: 50 },
|
|
body: 'b',
|
|
cwd,
|
|
now: new Date('2026-05-12T18:30:00Z'),
|
|
});
|
|
const latest = readLatestSnapshot('index-astro', { cwd });
|
|
assert.equal(latest.meta.slug, 'index-astro');
|
|
assert.equal(latest.meta.timestamp, '2026-05-12T18-30-00Z');
|
|
// The legit meta field still lands.
|
|
assert.equal(latest.meta.total_score, 50);
|
|
// The filename matches the computed slug.
|
|
assert.ok(out.endsWith('2026-05-12T18-30-00Z__index-astro.md'));
|
|
});
|
|
|
|
it('quotes values containing : or # to keep parsing simple', () => {
|
|
writeSnapshot({
|
|
slug: 'x',
|
|
meta: { target: 'docs: critique # main' },
|
|
body: '...',
|
|
cwd,
|
|
});
|
|
const latest = readLatestSnapshot('x', { cwd });
|
|
assert.equal(latest.meta.target, 'docs: critique # main');
|
|
});
|
|
});
|
|
|
|
describe('CLI entry point', () => {
|
|
// Why a subprocess test: the CLI guard at the bottom of the script
|
|
// previously compared import.meta.url to `file://${process.argv[1]}`,
|
|
// which silently broke on Windows (forward vs back slashes) — exit 0,
|
|
// no output, save skipped. The exported functions kept passing because
|
|
// tests never spawned the script as a process. See issue #155.
|
|
it('slug subcommand prints a slug and exits 0', () => {
|
|
const r = spawnSync(process.execPath, [SCRIPT, 'slug', 'site/pages/index.astro'], {
|
|
cwd,
|
|
encoding: 'utf-8',
|
|
});
|
|
assert.equal(r.status, 0, `stderr: ${r.stderr}`);
|
|
assert.equal(r.stdout.trim(), 'site-pages-index-astro');
|
|
});
|
|
|
|
it('slug subcommand exits 1 with a message for empty input', () => {
|
|
const r = spawnSync(process.execPath, [SCRIPT, 'slug', ''], { cwd, encoding: 'utf-8' });
|
|
assert.equal(r.status, 1);
|
|
assert.match(r.stderr, /no stable slug/);
|
|
});
|
|
|
|
it('runs when invoked through a symlinked harness path', () => {
|
|
const linkedScript = join(cwd, 'linked-critique-storage.mjs');
|
|
symlinkSync(SCRIPT, linkedScript);
|
|
|
|
const r = spawnSync(process.execPath, [linkedScript, 'slug', 'index.html'], {
|
|
cwd,
|
|
encoding: 'utf-8',
|
|
});
|
|
|
|
assert.equal(r.status, 0, `stderr: ${r.stderr}`);
|
|
assert.equal(r.stdout.trim(), 'index-html');
|
|
});
|
|
|
|
it('latest subcommand exits 2 when no snapshot exists', () => {
|
|
const r = spawnSync(process.execPath, [SCRIPT, 'latest', 'never-written'], {
|
|
cwd,
|
|
encoding: 'utf-8',
|
|
});
|
|
assert.equal(r.status, 2);
|
|
});
|
|
});
|
|
|
|
describe('readTrend', () => {
|
|
it('returns last N entries oldest → newest, filtered by slug', () => {
|
|
for (let i = 0; i < 6; i++) {
|
|
writeSnapshot({
|
|
slug: 'index-astro',
|
|
meta: { total_score: 20 + i },
|
|
body: `run ${i}`,
|
|
cwd,
|
|
now: new Date(2026, 4, i + 1),
|
|
});
|
|
}
|
|
writeSnapshot({ slug: 'pricing-astro', meta: { total_score: 99 }, body: 'unrelated', cwd });
|
|
const trend = readTrend('index-astro', { limit: 5, cwd });
|
|
assert.equal(trend.length, 5);
|
|
assert.equal(trend[0].total_score, 21); // dropped the oldest
|
|
assert.equal(trend[4].total_score, 25);
|
|
});
|
|
|
|
it('returns empty when no snapshots', () => {
|
|
assert.deepEqual(readTrend('nope', { cwd }), []);
|
|
});
|
|
});
|