Files
msd-core/tests/frontmatter-roundtrip.property.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

157 lines
7.5 KiB
JavaScript

'use strict';
/**
* ADR-3473 §8.1's mandated gate (#3881, phase test-matrix §E): "a property-based
* `fast-check` round-trip test is the gate."
*
* E1: parse(serialize(x)) === parse(serialize(parse(serialize(x)))) — idempotence after
* one round-trip cycle, the invariant #3349 violated (a value that changed shape on
* a SECOND write, even though the first write looked fine).
* E2: the #3257 full-line-comment channel survives a parse -> reconstruct -> re-parse ->
* reconstruct cycle intact and in place.
*
* `serialize` = reconstructFrontmatter (object -> YAML body text, no `---` delimiters).
* `parse` = extractFrontmatter (delimited document text -> object), applied to the body
* wrapped back in `---` fences the way every real caller round-trips it.
*
* fast-check v4 in this repo: no `fc.stringOf`; arbitraries live at module scope (never
* built inside a `describe` body) — see tests/frontmatter.property.test.cjs for the
* established pattern this file follows.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fc = require('./helpers/fast-check-setup.cjs');
const { extractFrontmatter, reconstructFrontmatter } = require('../msd-core/bin/lib/frontmatter.cjs');
// ─── Arbitraries — realistic key/value shapes, not free-form noise ────────────────────
// A real top-level frontmatter key: lower-kebab/snake identifiers as seen across the
// corpus (phase, key-decisions, tech-stack, human_verification, ...).
const realisticKey = fc.stringMatching(/^[a-z][a-z0-9_-]{0,19}$/);
// A plain scalar value shaped like real frontmatter content: printable, no raw control
// chars, no YAML-significant leading/trailing whitespace of its own (reconstructFrontmatter
// is exercised elsewhere, in tests/frontmatter.property.test.cjs, for the quoting/escaping
// contract itself — this file is about ROUND-TRIP IDENTITY, so values are drawn from the
// well-formed subset rather than re-testing escaping).
const realisticScalar = fc.stringMatching(/^[A-Za-z0-9][A-Za-z0-9 ._/:-]{0,60}$/);
// A short list of realistic scalars, the shape `tags:`/`requires:`/`key-decisions:` use.
const realisticList = fc.array(realisticScalar, { minLength: 0, maxLength: 5 });
// One frontmatter object: a flat dictionary whose values are either a scalar or a list of
// scalars — the two value shapes reconstructFrontmatter's top-level branch documents.
const frontmatterObject = fc.dictionary(
realisticKey,
fc.oneof(realisticScalar, realisticList),
{ minKeys: 1, maxKeys: 8 },
);
/** serialize -> wrap in fences -> parse, the shape every real caller round-trips through. */
function roundTripParse(obj) {
const body = reconstructFrontmatter(obj);
return extractFrontmatter(`---\n${body}\n---\n`);
}
describe('frontmatter round-trip idempotence — ADR-3473 §8.1 mandated gate (#3881, §E1)', () => {
test('property: parse(serialize(x)) is a fixed point of one more round-trip cycle', () => {
fc.assert(
fc.property(frontmatterObject, (x) => {
const once = roundTripParse(x);
const twice = roundTripParse(once);
assert.deepEqual(
twice,
once,
`round-trip is not idempotent for ${JSON.stringify(x)}: once=${JSON.stringify(once)} twice=${JSON.stringify(twice)}`,
);
}),
);
});
test('property: a single round-trip preserves every key and its scalar/list shape', () => {
fc.assert(
fc.property(frontmatterObject, (x) => {
const once = roundTripParse(x);
for (const [key, value] of Object.entries(x)) {
assert.ok(key in once, `key ${key} lost on round-trip`);
if (Array.isArray(value)) {
assert.ok(Array.isArray(once[key]), `key ${key} lost its list shape on round-trip`);
} else {
assert.equal(typeof once[key], 'string', `key ${key} lost its scalar shape on round-trip`);
}
}
}),
);
});
test('sensor: the idempotence check is not vacuous — a mutated second pass is caught', () => {
// Prove the deepEqual comparison above can actually fail: fabricate a "second pass"
// that differs from the first and confirm the property-style check rejects it.
const once = roundTripParse({ phase: 'p1', tags: ['a', 'b'] });
const mutatedTwice = { ...once, phase: `${once.phase}__MUTATED__` };
assert.notDeepEqual(mutatedTwice, once, 'sensor failed: a mutated second pass must not equal the first');
});
});
describe('frontmatter round-trip preserves the #3257 comment channel (#3881, §E2)', () => {
// A column-0 `# text` comment line, restricted to characters that cannot themselves be
// mistaken for the next key line by extractCommentChannel's own matcher.
const commentText = fc.stringMatching(/^# [A-Za-z0-9 .,'-]{1,40}$/);
test('property: a leading comment above a generated key survives a full round-trip cycle', () => {
fc.assert(
fc.property(realisticKey, realisticScalar, commentText, (key, value, comment) => {
const doc = `---\n${comment}\n${key}: ${JSON.stringify(value)}\n---\n`;
const extracted = extractFrontmatter(doc);
assert.equal(extracted[key], value, 'sanity: the key itself must parse before testing comment survival');
const reconstructed = reconstructFrontmatter(extracted);
assert.ok(
reconstructed.includes(comment),
`comment lost on first round-trip: ${JSON.stringify(reconstructed)}`,
);
// Full cycle: re-parse the reconstructed doc and reconstruct again — the comment
// must still be attached to the same key, not merely present anywhere.
const reExtracted = extractFrontmatter(`---\n${reconstructed}\n---\n`);
const reReconstructed = reconstructFrontmatter(reExtracted);
assert.ok(
reReconstructed.includes(comment),
`comment lost on second round-trip cycle: ${JSON.stringify(reReconstructed)}`,
);
// The comment must still immediately precede its key (attached, not orphaned).
const idx = reReconstructed.indexOf(comment);
const keyIdx = reReconstructed.indexOf(`${key}:`);
assert.ok(keyIdx > idx, `comment did not stay attached ahead of its key: ${JSON.stringify(reReconstructed)}`);
}),
);
});
test('property: a trailing comment after the last key survives a full round-trip cycle', () => {
fc.assert(
fc.property(realisticKey, realisticScalar, commentText, (key, value, comment) => {
const doc = `---\n${key}: ${JSON.stringify(value)}\n${comment}\n---\n`;
const extracted = extractFrontmatter(doc);
const reconstructed = reconstructFrontmatter(extracted);
assert.ok(reconstructed.includes(comment), `trailing comment lost: ${JSON.stringify(reconstructed)}`);
const reExtracted = extractFrontmatter(`---\n${reconstructed}\n---\n`);
const reReconstructed = reconstructFrontmatter(reExtracted);
assert.ok(
reReconstructed.includes(comment),
`trailing comment lost on second cycle: ${JSON.stringify(reReconstructed)}`,
);
}),
);
});
test('sensor: comment-channel preservation is not vacuous — a document with no comment carries none forward', () => {
const doc = '---\nkey: value\n---\n';
const extracted = extractFrontmatter(doc);
const reconstructed = reconstructFrontmatter(extracted);
assert.ok(!reconstructed.includes('#'), `expected no comment to appear from nowhere: ${JSON.stringify(reconstructed)}`);
});
});