-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocalCache.test.ts
More file actions
104 lines (93 loc) · 4 KB
/
Copy pathlocalCache.test.ts
File metadata and controls
104 lines (93 loc) · 4 KB
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// localCache.test.ts — the repo-local two-tier cache: schema I/O + atomic,
// section-scoped, fail-open reads/writes.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, readFileSync, existsSync, rmSync, mkdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
CACHE_SCHEMA_VERSION,
cacheDir,
cachePath,
readCache,
writeCacheSection,
resolveRepoRoot,
type StructureSection,
type DecisionsSection,
} from './localCache.js';
function tmpRepo(): string {
return mkdtempSync(join(tmpdir(), 'bt-cache-'));
}
const sampleStructure: StructureSection = {
refreshedAt: '2026-07-11T00:00:00.000Z',
root: '/x',
extractorVersion: '0.1.0',
fileHashes: { 'src/a.ts': '1:2' },
fileGraph: { version: 3, headSha: 'worktree', files: {} },
modules: [{ id: 'auth', kind: 'internal', godNode: false, loc: 10, fileCount: 1, fileIds: ['src/a.ts'], subsystem: null }],
edges: [],
};
const sampleDecisions: DecisionsSection = {
syncedAt: '2026-07-11T00:00:00.000Z',
ttlHours: 6,
repo: 'o/r',
items: [],
};
test('readCache returns null when the file is absent', async () => {
const repo = tmpRepo();
assert.equal(await readCache(repo), null);
rmSync(repo, { recursive: true, force: true });
});
test('readCache returns null on corrupt JSON', async () => {
const repo = tmpRepo();
mkdirSync(cacheDir(repo), { recursive: true });
writeFileSync(cachePath(repo), '{ not json');
assert.equal(await readCache(repo), null);
rmSync(repo, { recursive: true, force: true });
});
test('readCache returns null on a mismatched schema version (→ rebuild)', async () => {
const repo = tmpRepo();
mkdirSync(cacheDir(repo), { recursive: true });
writeFileSync(cachePath(repo), JSON.stringify({ schemaVersion: 999, repo: null, structure: null, decisions: null }));
assert.equal(await readCache(repo), null);
rmSync(repo, { recursive: true, force: true });
});
test('writeCacheSection creates the dir, a self-ignoring .gitignore, and the file', async () => {
const repo = tmpRepo();
await writeCacheSection(repo, { structure: sampleStructure, repo: 'o/r' });
const gi = join(cacheDir(repo), '.gitignore');
assert.ok(existsSync(gi), '.gitignore is written');
assert.equal(readFileSync(gi, 'utf8'), '*\n', 'ignores the whole dir');
const cache = await readCache(repo);
assert.ok(cache);
assert.equal(cache!.schemaVersion, CACHE_SCHEMA_VERSION);
assert.equal(cache!.repo, 'o/r');
assert.deepEqual(cache!.structure, sampleStructure);
assert.equal(cache!.decisions, null);
rmSync(repo, { recursive: true, force: true });
});
test('writeCacheSection is section-scoped: writing decisions preserves structure', async () => {
const repo = tmpRepo();
await writeCacheSection(repo, { structure: sampleStructure, repo: 'o/r' });
await writeCacheSection(repo, { decisions: sampleDecisions });
const cache = await readCache(repo);
assert.ok(cache);
assert.deepEqual(cache!.structure, sampleStructure, 'structure survived a decisions-only write');
assert.deepEqual(cache!.decisions, sampleDecisions);
assert.equal(cache!.repo, 'o/r', 'repo survived (not in the second patch)');
rmSync(repo, { recursive: true, force: true });
});
test('writeCacheSection does not clobber an existing user .gitignore', async () => {
const repo = tmpRepo();
mkdirSync(cacheDir(repo), { recursive: true });
const gi = join(cacheDir(repo), '.gitignore');
writeFileSync(gi, '# custom\n');
await writeCacheSection(repo, { decisions: sampleDecisions });
assert.equal(readFileSync(gi, 'utf8'), '# custom\n', 'existing .gitignore left alone');
rmSync(repo, { recursive: true, force: true });
});
test('resolveRepoRoot uses the injected git top-level, falling back to cwd', () => {
assert.equal(resolveRepoRoot('/x/y/z', () => '/x'), '/x');
assert.equal(resolveRepoRoot('/x/y/z', () => null), '/x/y/z', 'non-git cwd → cwd itself');
assert.equal(resolveRepoRoot('/x/y/z', () => ''), '/x/y/z', 'empty top-level → cwd');
});