87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
|
|
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||
|
|
import { setupFileRig, type FileTestRig } from '../integration/helpers/rig.js';
|
||
|
|
import { FingerprintRequiredError, NotFoundError, type FileEntry } from '../../src/index.js';
|
||
|
|
|
||
|
|
describe('Fingerprint gate', () => {
|
||
|
|
let rig: FileTestRig;
|
||
|
|
const verifiedSet = new Set<string>();
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
verifiedSet.clear();
|
||
|
|
rig = await setupFileRig({
|
||
|
|
requireFingerprintVerifiedFor: (ctx) => {
|
||
|
|
// Mutations require verification; reads are optional.
|
||
|
|
const op = ctx.op;
|
||
|
|
if (op === 'mkdir' || op === 'delete' || op === 'move' || op === 'write') return 'required';
|
||
|
|
if (op === 'list') return 'optional';
|
||
|
|
return 'optional';
|
||
|
|
},
|
||
|
|
isFingerprintVerified: (sender) => verifiedSet.has(sender),
|
||
|
|
stat: async (ctx) => {
|
||
|
|
const e: FileEntry = {
|
||
|
|
name: ctx.path.split('/').filter(Boolean).pop() ?? '',
|
||
|
|
kind: 'file', size: 1, mtime: 0, metadata: {},
|
||
|
|
};
|
||
|
|
return e;
|
||
|
|
},
|
||
|
|
mkdir: async (ctx) => {
|
||
|
|
const e: FileEntry = {
|
||
|
|
name: ctx.path.split('/').filter(Boolean).pop() ?? '',
|
||
|
|
kind: 'dir', size: 0, mtime: 0, metadata: {},
|
||
|
|
};
|
||
|
|
return { entry: e };
|
||
|
|
},
|
||
|
|
delete: async () => ({ deletedCount: 1 }),
|
||
|
|
list: async () => ({ entries: [], hasMore: false }),
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
await rig.teardown();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('mutation without verification → FingerprintRequiredError', async () => {
|
||
|
|
verifiedSet.delete('alice');
|
||
|
|
await expect(rig.fs.mkdir('/locked')).rejects.toBeInstanceOf(FingerprintRequiredError);
|
||
|
|
await expect(rig.fs.delete('/locked')).rejects.toBeInstanceOf(FingerprintRequiredError);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('mutation after marking peer verified → succeeds', async () => {
|
||
|
|
verifiedSet.add('alice');
|
||
|
|
const result = await rig.fs.mkdir('/verified-dir');
|
||
|
|
expect(result.entry.name).toBe('verified-dir');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('optional ops (stat, list) work without verification', async () => {
|
||
|
|
verifiedSet.delete('alice');
|
||
|
|
const stat = await rig.fs.stat('/anything');
|
||
|
|
expect(stat.name).toBe('anything');
|
||
|
|
const list = await rig.fs.list('/anything');
|
||
|
|
expect(list.entries).toEqual([]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Fingerprint gate with reject policy', () => {
|
||
|
|
let rig: FileTestRig;
|
||
|
|
beforeAll(async () => {
|
||
|
|
rig = await setupFileRig({
|
||
|
|
requireFingerprintVerifiedFor: () => 'reject',
|
||
|
|
stat: async (ctx) => {
|
||
|
|
const e: FileEntry = {
|
||
|
|
name: 'x',
|
||
|
|
kind: 'file',
|
||
|
|
size: 1,
|
||
|
|
mtime: 0,
|
||
|
|
metadata: {},
|
||
|
|
};
|
||
|
|
return e;
|
||
|
|
},
|
||
|
|
});
|
||
|
|
});
|
||
|
|
afterAll(async () => { await rig.teardown(); });
|
||
|
|
|
||
|
|
test('all ops rejected outright', async () => {
|
||
|
|
await expect(rig.fs.stat('/x')).rejects.toBeInstanceOf(FingerprintRequiredError);
|
||
|
|
});
|
||
|
|
});
|