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
|
// modules/test/test-memory-graph-recovery.js — run: node
// MAINT-003: a corrupt memory-graph store must not look like a fresh install. The graph still
// starts from what remains, but the unreadable file is kept aside and the user is told.
const fsMod = require('fs'), path = require('path');
const ROOT = path.join(__dirname, '..', 'core');
const store = {};
global.localStorage = { getItem: k => (k in store ? store[k] : null), setItem: (k, v) => { store[k] = String(v); }, removeItem: k => { delete store[k]; } };
global.indexedDB = { open() { throw new Error('no idb'); }, deleteDatabase() { throw new Error('no idb'); } };
global.window = global;
const load = (f, c) => new Function(fsMod.readFileSync(path.join(ROOT, f), 'utf8') + '\nreturn ' + c + ';')();
const VFS = load('virtual-fs.js', 'VirtualFileSystem');
const MG = load('module-memory-graph.js', 'MemoryGraphModule');
let fails = 0, passes = 0;
function ok(name, cond, detail) {
if (cond) { passes++; return; }
fails++;
console.log('FAIL ' + name + (detail ? ' — ' + detail : ''));
}
function mk() {
const out = [];
const vfs = new VFS();
vfs.saveToStorage = () => true;
const term = { print: (m, t) => out.push({ text: String(m), type: t || '' }), log: () => {}, fs: vfs };
const mg = new MG();
mg.init(term);
return { mg, vfs, out };
}
const text = out => out.map(o => o.text).join(' | ');
// A. a healthy store survives a reload
{
const a = mk();
for (let i = 0; i < 4; i++) a.mg.addNode('concept', 'keep-' + i, 'keep ' + i);
a.mg._saveNow();
const seeded = Object.keys(a.mg._nodes).length;
const out = [];
const mg2 = new MG();
mg2.init({ print: (m, t) => out.push({ text: String(m), type: t || '' }), log: () => {}, fs: a.vfs });
ok('A1 nodes survive a reload', Object.keys(mg2._nodes).length === seeded, 'seeded ' + seeded + ' reloaded ' + Object.keys(mg2._nodes).length);
ok('A2 no quarantine warning on a healthy store', !/unreadable/.test(text(out)), text(out));
}
// B. a corrupt store is quarantined and reported, not silently discarded
{
const a = mk();
for (let i = 0; i < 4; i++) a.mg.addNode('concept', 'lose-' + i, 'lose ' + i);
a.mg._saveNow();
const original = a.vfs.readFile('/.memory_graph/nodes.json');
a.vfs.writeFile('/.memory_graph/nodes.json', '{"nodes":['); // truncated write
a.vfs.writeFile('/.memory_graph/export.json', 'not json at all');
const out = [];
const mg2 = new MG();
mg2.init({ print: (m, t) => out.push({ text: String(m), type: t || '' }), log: () => {}, fs: a.vfs });
ok('B1 the user is warned', /unreadable/.test(text(out)), text(out).slice(0, 200));
ok('B2 the corrupt file is kept aside', a.vfs.readFile('/.memory_graph/nodes.json.corrupt') === '{"nodes":[',
String(a.vfs.readFile('/.memory_graph/nodes.json.corrupt')));
ok('B3 the quarantine copy held the broken text, not the old good one', original !== a.vfs.readFile('/.memory_graph/nodes.json.corrupt'));
ok('B4 the graph still starts', Object.keys(mg2._nodes).length >= 1, String(Object.keys(mg2._nodes).length));
}
// C. the export path is quarantined too, and the nodes.json fallback still loads
{
const a = mk();
a.mg.addNode('concept', 'fallback', 'fallback node');
a.mg._saveNow();
// the loader expects {nodes: [...], edges: [...]}
a.vfs.fsWriteJSON('/.memory_graph/export.json', { nodes: Object.values(a.mg._nodes), edges: [] });
a.vfs.writeFile('/.memory_graph/nodes.json', 'broken'); // corrupt fallback
const before = Object.keys(a.mg._nodes).length;
const out = [];
const mg2 = new MG();
mg2.init({ print: (m, t) => out.push({ text: String(m), type: t || '' }), log: () => {}, fs: a.vfs });
ok('C1 corrupt nodes.json is quarantined', !!a.vfs.readFile('/.memory_graph/nodes.json.corrupt'));
ok('C2 the valid export still loads the graph', Object.keys(mg2._nodes).length >= before,
'before ' + before + ' after ' + Object.keys(mg2._nodes).length);
}
if (fails === 0) {
console.log('test-memory-graph-recovery: ' + passes + ' passed, 0 failed');
} else {
console.log('test-memory-graph-recovery: ' + passes + ' passed, ' + fails + ' FAILED');
process.exit(1);
}
|