import { it, expect } from 'vitest'; import { CORPUS, corpusSuite } from './corpusCheckout'; import { readdirSync, readFileSync, statSync } from 'node:fs'; import { join } from 'node:path'; import { parseDocument, isMap, isSeq, isScalar } from 'yaml'; import { findStepRanges, stepRangeKey } from '@beehexa/hexasync-template-index'; /** * `findStepRanges` against the REAL corpus (Story 4.5 AC-4b, review F10). * * The 28 unit cases are synthetic fixtures, and this repo's discipline for a measured claim is a corpus spec — every * headline number in Phase 2 earned one. AC-4b's claim is that *any* worker node carries its authored location, and the * only place "any" can be tested is the 7,000-odd authored files. * * Two independent things are checked, and the second is the point: * * 1. **Coverage** — every step a straightforward YAML walk can see is located. The walk here is deliberately NOT the one * under test: it re-derives the expected set from `parseDocument` directly, so a bug in `findStepRanges`'s own walk * cannot hide by also being in the oracle. * 2. **The range actually selects that step** — sliced out of the file and required to contain `key: `. A range * is a plausible pair of numbers whatever it points at, which is exactly how an off-by-one survives review. * * Asserted as floors rather than equalities: the corpus is a live checkout. A drop to zero, though, means this stopped * measuring anything and must be re-argued rather than relaxed. */ const suite = corpusSuite( 'step-ranges', "AC-4b's corpus assertion cannot run.", 'Every worker flow node\'s location comes from `findStepRanges`, and without the corpus "any node" is verified ' + 'only against synthetic fixtures.', ); const IGNORED = new Set(['.git', 'node_modules', '__configs']); /** Both collections `WORKER_STAGES` covers. Named here rather than imported: this file must not share a list with the code. */ const WORKER_COLLECTIONS = ['pullers', 'pushers'] as const; function* yamlFiles(at: string): Generator { for (const entry of readdirSync(at)) { if (IGNORED.has(entry)) continue; const full = join(at, entry); if (statSync(full).isDirectory()) yield* yamlFiles(full); else if ( (entry.endsWith('.yaml') || entry.endsWith('.yml')) && !entry.endsWith('output.yaml') ) yield full; } } /** * Every (collection, id, stage, stepKey) a plain parse can see — the ORACLE. * * Written against `parseDocument` and nothing else, so it shares no code with the function under test. It reads * sequence-valued properties of each component exactly as a reader would, which is the behaviour AC-4b promises. */ function stepsDeclaredIn(text: string): { components: Map>; } { const components = new Map>(); let doc; try { doc = parseDocument(text); } catch { return { components }; } if (!doc || doc.errors.length > 0 || !isMap(doc.contents)) return { components }; for (const collection of WORKER_COLLECTIONS) { const node = doc.contents.get(collection, true); if (!isSeq(node)) continue; for (const item of node.items) { if (!isMap(item)) continue; const id = item.get('id'); if (typeof id !== 'string' || id.startsWith('!')) continue; const keys = new Set(); for (const property of item.items) { if (!isScalar(property.key)) continue; const stage = String(property.key.value); const steps = property.value; if (!isSeq(steps)) continue; for (const step of steps.items) { if (!isMap(step)) continue; const key = step.get('key'); if (key === undefined || key === null || typeof key === 'object') continue; keys.add(stepRangeKey(stage, String(key))); } } if (keys.size > 0) components.set(`${collection}\u0000${id}`, keys); } } return { components }; } suite('every authored worker step in the corpus is located', () => { let components = 0; let expected = 0; let located = 0; let quoted = 0; const unlocated: string[] = []; const misquoted: string[] = []; for (const file of yamlFiles(CORPUS)) { let text: string; try { text = readFileSync(file, 'utf8'); } catch { continue; } for (const [identity, keys] of stepsDeclaredIn(text).components) { const [collection, id] = identity.split('\u0000') as [string, string]; const found = findStepRanges(text, collection, id); components += 1; expected += keys.size; for (const key of keys) { const step = found.get(key); if (!step) { if (unlocated.length < 10) unlocated.push(`${file} ${identity} ${key}`); continue; } located += 1; // The range must select the step, not merely exist. `sourceText` is what the range selects (asserted in the unit // spec), so checking it here checks the range. /** * Quoted or bare — the corpus writes both, and `- key: "GET_TAGS_ID"` is as valid as `- key: GET_TAGS_ID`. * * ⛔ My first version looked for the bare form only and reported 200-odd "misquoted" ranges that were every one * of them correct. Recorded because the lesson generalises: a corpus spec whose expectation is narrower than the * corpus reports the CORPUS as broken, and the first instinct on reading that report is to go looking for a bug * in the code it was measuring. */ const stepKey = key.split('\u0000')[1]!; const declares = step.sourceText.includes(`key: ${stepKey}`) || step.sourceText.includes(`key: "${stepKey}"`) || step.sourceText.includes(`key: '${stepKey}'`); if (declares) quoted += 1; else if (misquoted.length < 10) misquoted.push( `${file} ${identity} ${key} :: ${step.sourceText.slice(0, 60)}`, ); } } } it('locates every step a plain parse can see', () => { // eslint-disable-next-line no-console console.log( `[step-ranges] ${components} components, ${expected} steps, ${located} located, ${quoted} quoted`, ); // The floors: a corpus that stopped containing worker components would make the ratio below vacuous. expect( components, 'no worker components found — this is not the corpus', ).toBeGreaterThan(1500); expect(expected, 'no authored steps found').toBeGreaterThan(5000); expect( unlocated, 'steps a plain parse sees but `findStepRanges` does not', ).toEqual([]); expect(located).toBe(expected); }); it('every located range QUOTES its own step', () => { // The assertion an off-by-one fails and a "does it return something" check passes. expect(misquoted, 'ranges that do not select their step').toEqual([]); expect(quoted).toBe(located); }); });