File size: 2,731 Bytes
4e23b01 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | // test/e2e/fuzz-model.test.js
//
// Model-based fuzz: feed identical random op sequences to MiniDb and a tiny
// reference model, and assert they always agree. Seeded so any failure is
// reproducible by re-running with the same seed.
import { expect, test } from 'vitest';
import assert from 'node:assert/strict';
import { MiniDb } from '../../src/index.js';
import { Model } from './helpers/model.js';
import { mulberry32, randInt, pick } from './helpers/prng.js';
import { tmpDir, rmrf } from './helpers/tmp.js';
const KEYS = Array.from({ length: 24 }, (_, i) => 'k' + i);
const VALUES = [{ a: 1 }, { b: [1, 2, 3] }, { s: 'hello' }, { n: 42 }, null, { nested: { x: 1, y: [2] } }];
async function runSeed(seed, steps) {
const rng = mulberry32(seed >>> 0);
const dir = await tmpDir();
let db = await MiniDb.open({ dir, valueCodec: 'json', fsyncPolicy: 'no', autoCompact: false });
const model = new Model();
const ctx = () => `seed=${seed} step`;
try {
for (let i = 0; i < steps; i++) {
const op = randInt(rng, 5);
const key = pick(rng, KEYS);
if (op === 0) {
// set (no expiry, or far-future expiry so nothing auto-expires mid-run)
const value = pick(rng, VALUES);
const ttl = rng() < 0.2 ? 60000 : 0;
await db.set(key, value, ttl ? { ttl } : {});
model.set(key, value, ttl);
} else if (op === 1) {
await db.del(key);
model.del(key);
} else if (op === 2) {
assert.deepEqual(db.get(key), model.get(key), `${ctx()} ${i}: get(${key})`);
} else if (op === 3) {
const ttl = 60000;
const a = await db.expire(key, ttl);
const e = model.expire(key, ttl);
assert.equal(a, e, `${ctx()} ${i}: expire(${key})`);
} else {
// reopen, then compare everything
await db.close();
db = await MiniDb.open({ dir, valueCodec: 'json', fsyncPolicy: 'no', autoCompact: false });
for (const k of KEYS) {
assert.deepEqual(db.get(k), model.get(k), `${ctx()} ${i}: after reopen get(${k})`);
}
}
}
// final full compare
for (const k of KEYS) assert.deepEqual(db.get(k), model.get(k), `final get(${k}) seed=${seed}`);
} finally {
await db.close().catch(() => {});
await rmrf(dir);
}
}
test('fuzz-model: random op sequences match a reference model (many seeds)', { timeout: 60_000 }, async () => {
// Mix of small and large seeds. 6 seeds × 250 steps ≈ 1.5k ops total, still
// hitting every op branch (including reopen + full compare) many times per seed.
const seeds = [1, 2, 3, 99999, 0xdeadbeef, 20240625];
for (const seed of seeds) {
await expect(runSeed(seed, 250)).resolves.toBeUndefined();
}
});
|