import { describe, it, expect } from 'vitest'; import { readFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { Dawg } from './dawg'; // Conformance gate for the ported dawg reader: it must agree with the // authoritative Go dafsa reader on indexOf for EVERY stored word and EVERY // negative, across all three shipped dictionaries. The golden vectors are // produced by `go run ./backend/cmd/dictgen`; point the test at them with: // DICT_DAWG_DIR=/dawg DICT_GOLD_DIR= // When those are unset the suite skips (kept out of the default unit run until // a committed sample fixture lands in Phase 1). const dawgDir = process.env.DICT_DAWG_DIR; const goldDir = process.env.DICT_GOLD_DIR; const ready = !!dawgDir && !!goldDir && existsSync(dawgDir) && existsSync(goldDir); const names = ['en_sowpods', 'ru_scrabble', 'ru_erudit']; // eachWord walks the [1-byte length][index bytes] framing emitted by dictgen. function* eachWord(buf: Buffer): Generator { let off = 0; while (off < buf.length) { const len = buf[off]; yield buf.subarray(off + 1, off + 1 + len); off += 1 + len; } } describe.skipIf(!ready)('dawg reader parity vs Go dafsa', () => { for (const name of names) { it( name, () => { const bytes = new Uint8Array(readFileSync(join(dawgDir!, `${name}.dawg`))); const meta = JSON.parse(readFileSync(join(goldDir!, `${name}.meta.json`), 'utf8')); const dawg = new Dawg(bytes); expect(dawg.numAdded).toBe(meta.numAdded); expect(dawg.numNodes).toBe(meta.numNodes); // Every stored word: indexOf(word) equals its position in index order. let idx = 0; let mismatches = 0; const firstBad: string[] = []; for (const word of eachWord(readFileSync(join(goldDir!, `${name}.words.bin`)))) { const got = dawg.indexOf(word); if (got !== idx) { mismatches++; if (firstBad.length < 5) firstBad.push(`idx ${idx}: got ${got} word [${Array.from(word)}]`); } idx++; } expect(idx).toBe(meta.numAdded); expect(mismatches, firstBad.join('; ')).toBe(0); // Negatives must resolve to -1. let negMismatches = 0; const firstNeg: string[] = []; for (const word of eachWord(readFileSync(join(goldDir!, `${name}.neg.bin`)))) { const got = dawg.indexOf(word); if (got !== -1) { negMismatches++; if (firstNeg.length < 5) firstNeg.push(`got ${got} word [${Array.from(word)}]`); } } expect(negMismatches, firstNeg.join('; ')).toBe(0); }, 120_000, ); } });