397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53 lines
2.4 KiB
JavaScript
53 lines
2.4 KiB
JavaScript
// tools/judge-watermark.test.mjs
|
|
import { describe, it, expect } from 'vitest';
|
|
import { readWatermark, bumpWatermark, checkNoRollback } from './judge-watermark.mjs';
|
|
|
|
function memChain() {
|
|
const store = new Map(); // account -> value
|
|
return { store,
|
|
get: ({ account }) => (store.has(account) ? store.get(account) : null),
|
|
set: ({ account, value }) => store.set(account, String(value)) };
|
|
}
|
|
const kc = (c) => ({ keychainGet: c.get, keychainSet: c.set });
|
|
|
|
describe('judge-watermark (K6 anti-rollback, per-session)', () => {
|
|
it('fresh session reads genesis 0 (no mark yet → not a rollback)', () => {
|
|
const c = memChain();
|
|
expect(readWatermark({ sessionId: 'S', ...kc(c) })).toBe(0);
|
|
});
|
|
it('bump only increases; lower bump is ignored (monotonic)', () => {
|
|
const c = memChain();
|
|
bumpWatermark({ sessionId: 'S', value: 5, ...kc(c) });
|
|
expect(readWatermark({ sessionId: 'S', ...kc(c) })).toBe(5);
|
|
bumpWatermark({ sessionId: 'S', value: 3, ...kc(c) }); // lower → ignored
|
|
expect(readWatermark({ sessionId: 'S', ...kc(c) })).toBe(5);
|
|
bumpWatermark({ sessionId: 'S', value: 9, ...kc(c) });
|
|
expect(readWatermark({ sessionId: 'S', ...kc(c) })).toBe(9);
|
|
});
|
|
it('checkNoRollback: current >= mark → ok', () => {
|
|
const c = memChain();
|
|
bumpWatermark({ sessionId: 'S', value: 7, ...kc(c) });
|
|
expect(checkNoRollback({ sessionId: 'S', current: 7, ...kc(c) })).toEqual({ ok: true, rolledBack: false, seen: 7 });
|
|
expect(checkNoRollback({ sessionId: 'S', current: 8, ...kc(c) }).ok).toBe(true);
|
|
});
|
|
it('checkNoRollback: current < mark → ROLLBACK detected (block)', () => {
|
|
const c = memChain();
|
|
bumpWatermark({ sessionId: 'S', value: 10, ...kc(c) });
|
|
const r = checkNoRollback({ sessionId: 'S', current: 4, ...kc(c) });
|
|
expect(r.ok).toBe(false);
|
|
expect(r.rolledBack).toBe(true);
|
|
expect(r.seen).toBe(10);
|
|
});
|
|
it('watermark is per-session (different sessionId independent)', () => {
|
|
const c = memChain();
|
|
bumpWatermark({ sessionId: 'A', value: 9, ...kc(c) });
|
|
expect(readWatermark({ sessionId: 'B', ...kc(c) })).toBe(0); // B fresh genesis
|
|
expect(checkNoRollback({ sessionId: 'B', current: 0, ...kc(c) }).ok).toBe(true);
|
|
});
|
|
it('corrupt/non-numeric stored value → treated as genesis 0 (fail-safe read)', () => {
|
|
const c = memChain();
|
|
c.store.set('wm:S', 'garbage');
|
|
expect(readWatermark({ sessionId: 'S', ...kc(c) })).toBe(0);
|
|
});
|
|
});
|