mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-29 06:32:28 +00:00
175 lines
6.4 KiB
TypeScript
175 lines
6.4 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
import { mkdtempSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import {
|
|
readEmbeddingRecovery,
|
|
resolveEmbeddingRecovery,
|
|
} from '../../src/storage/embedding-recovery.js';
|
|
|
|
const stagingFile = 'lbug.staging.6e34c761-bf58-46cc-8b54-78d11607bc46';
|
|
const familySuffixes = [
|
|
'',
|
|
'.wal',
|
|
'.shadow',
|
|
'.wal.checkpoint',
|
|
'.lock',
|
|
'.checkpoint.intent.lock',
|
|
'.checkpoint.apply.lock',
|
|
];
|
|
const checkpoint = () => ({
|
|
kind: 'interrupted',
|
|
at: '2026-10-03T12:00:00.000Z',
|
|
nodesProcessed: 1,
|
|
totalNodes: 3,
|
|
chunksProcessed: 2,
|
|
model: 'test-model',
|
|
dimensions: 2,
|
|
provider: 'local',
|
|
pendingNodeIds: ['active'],
|
|
recovery: {
|
|
stagingFile,
|
|
schemaFingerprint: 'test-schema',
|
|
unsafeNodeIds: ['active', 'incomplete-restore'],
|
|
},
|
|
});
|
|
|
|
let dir: string;
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(path.join(os.tmpdir(), 'gnx-embedding-recovery-'));
|
|
writeFileSync(path.join(dir, stagingFile), 'stage');
|
|
});
|
|
afterEach(() => rmSync(dir, { recursive: true, force: true }));
|
|
|
|
describe('resolveEmbeddingRecovery', () => {
|
|
it('resolves the exact staged generation and carries all unsafe node IDs', () => {
|
|
expect(resolveEmbeddingRecovery(dir, checkpoint())).toEqual({
|
|
...checkpoint().recovery,
|
|
dbPath: path.join(dir, stagingFile),
|
|
familyFiles: familySuffixes.map((suffix) => stagingFile + suffix),
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
'../lbug.staging.6e34c761-bf58-46cc-8b54-78d11607bc46',
|
|
'/tmp/lbug.staging.6e34c761-bf58-46cc-8b54-78d11607bc46',
|
|
'branches/foreign/lbug.staging.6e34c761-bf58-46cc-8b54-78d11607bc46',
|
|
'..\\lbug.staging.6e34c761-bf58-46cc-8b54-78d11607bc46',
|
|
'lbug',
|
|
'lbug.new',
|
|
'lbug.staging.orphan',
|
|
`${stagingFile}.wal`,
|
|
'lbug.staging.00000000-0000-4000-8000-000000000000',
|
|
])('rejects a foreign, unrecognized or missing generation: %s', (filename) => {
|
|
const marker = checkpoint();
|
|
marker.recovery.stagingFile = filename;
|
|
expect(resolveEmbeddingRecovery(dir, marker)).toBeUndefined();
|
|
});
|
|
|
|
it.each([
|
|
null,
|
|
[],
|
|
{ recovery: checkpoint().recovery },
|
|
{ ...checkpoint(), kind: 'partial' },
|
|
{ ...checkpoint(), kind: 'unverified-count' },
|
|
{ ...checkpoint(), kind: 'unknown' },
|
|
{ ...checkpoint(), at: 'invalid-time' },
|
|
{ ...checkpoint(), nodesProcessed: -1 },
|
|
{ ...checkpoint(), nodesProcessed: 4 },
|
|
{ ...checkpoint(), totalNodes: 1.5 },
|
|
{ ...checkpoint(), chunksProcessed: Number.NaN },
|
|
{ ...checkpoint(), model: '' },
|
|
{ ...checkpoint(), provider: null },
|
|
{ ...checkpoint(), dimensions: 0 },
|
|
{ ...checkpoint(), pendingNodeIds: [42] },
|
|
{ ...checkpoint(), pendingNodeIds: ['unexcluded'] },
|
|
{ ...checkpoint(), recovery: { ...checkpoint().recovery, schemaFingerprint: '' } },
|
|
{ ...checkpoint(), recovery: { ...checkpoint().recovery, unsafeNodeIds: undefined } },
|
|
{ ...checkpoint(), recovery: { ...checkpoint().recovery, unsafeNodeIds: [''] } },
|
|
])('rejects malformed or incompatible checkpoint shape %#', (marker) => {
|
|
expect(resolveEmbeddingRecovery(dir, marker)).toBeUndefined();
|
|
});
|
|
|
|
it('accepts a durable restored stage even before a new embedding window completes', () => {
|
|
expect(
|
|
resolveEmbeddingRecovery(dir, {
|
|
...checkpoint(),
|
|
nodesProcessed: 0,
|
|
chunksProcessed: 0,
|
|
pendingNodeIds: [],
|
|
}),
|
|
).toBeDefined();
|
|
});
|
|
|
|
it('rejects a directory in place of the staged database', () => {
|
|
rmSync(path.join(dir, stagingFile));
|
|
mkdirSync(path.join(dir, stagingFile));
|
|
expect(resolveEmbeddingRecovery(dir, checkpoint())).toBeUndefined();
|
|
});
|
|
|
|
it.each(familySuffixes)('rejects a symlink in the staged family: %s', (suffix) => {
|
|
const target = path.join(dir, 'external-file');
|
|
writeFileSync(target, 'external');
|
|
const candidate = path.join(dir, stagingFile + suffix);
|
|
rmSync(candidate, { force: true });
|
|
symlinkSync(target, candidate);
|
|
expect(resolveEmbeddingRecovery(dir, checkpoint())).toBeUndefined();
|
|
});
|
|
|
|
it.each(familySuffixes.slice(1))('rejects a dangling staged sidecar symlink: %s', (suffix) => {
|
|
symlinkSync(path.join(dir, 'missing'), path.join(dir, stagingFile + suffix));
|
|
expect(resolveEmbeddingRecovery(dir, checkpoint())).toBeUndefined();
|
|
});
|
|
|
|
it.each(familySuffixes.slice(1))('rejects a non-file staged sidecar: %s', (suffix) => {
|
|
mkdirSync(path.join(dir, stagingFile + suffix));
|
|
expect(resolveEmbeddingRecovery(dir, checkpoint())).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('readEmbeddingRecovery', () => {
|
|
const writeMeta = (filename: string, marker: unknown = checkpoint()): void => {
|
|
writeFileSync(path.join(dir, filename), JSON.stringify({ embeddingCheckpoint: marker }));
|
|
};
|
|
|
|
it('prefers the primary metadata file over a stale legacy reference', () => {
|
|
writeMeta('gitnexus.json');
|
|
writeMeta('meta.json', null);
|
|
expect(readEmbeddingRecovery(dir)?.stagingFile).toBe(stagingFile);
|
|
});
|
|
|
|
it('loads the legacy mirror only when the primary metadata file is absent', () => {
|
|
writeMeta('meta.json');
|
|
expect(readEmbeddingRecovery(dir)?.stagingFile).toBe(stagingFile);
|
|
});
|
|
|
|
it('does not resurrect a legacy reference when the primary file is malformed', () => {
|
|
writeFileSync(path.join(dir, 'gitnexus.json'), '{');
|
|
writeMeta('meta.json');
|
|
expect(readEmbeddingRecovery(dir)).toBeUndefined();
|
|
});
|
|
|
|
it('does not fall back from valid primary metadata with no recovery reference', () => {
|
|
writeMeta('gitnexus.json', null);
|
|
writeMeta('meta.json');
|
|
expect(readEmbeddingRecovery(dir)).toBeUndefined();
|
|
});
|
|
|
|
it('rejects symlinked primary metadata rather than reading a foreign receipt', () => {
|
|
writeMeta('meta.json');
|
|
symlinkSync(path.join(dir, 'meta.json'), path.join(dir, 'gitnexus.json'));
|
|
expect(readEmbeddingRecovery(dir)).toBeUndefined();
|
|
});
|
|
|
|
it('resolves branch-slot provenance within that slot despite a flat storagePath', () => {
|
|
const branchSlot = path.join(dir, 'branches', 'feature');
|
|
mkdirSync(branchSlot, { recursive: true });
|
|
writeFileSync(path.join(branchSlot, stagingFile), 'branch-stage');
|
|
writeFileSync(
|
|
path.join(branchSlot, 'gitnexus.json'),
|
|
JSON.stringify({ storagePath: dir, embeddingCheckpoint: checkpoint() }),
|
|
);
|
|
expect(readEmbeddingRecovery(branchSlot)?.dbPath).toBe(path.join(branchSlot, stagingFile));
|
|
expect(readEmbeddingRecovery(dir)).toBeUndefined();
|
|
});
|
|
});
|