mcp-memory-keeper 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +1051 -0
- package/bin/mcp-memory-keeper +52 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +938 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +407 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/git-integration.test.js +237 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +283 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +307 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4310 -0
- package/dist/index.phase1.backup.js +410 -0
- package/dist/index.phase2.backup.js +704 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +1873 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/server.js +384 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +731 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/package.json +84 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const database_1 = require("../../utils/database");
|
|
37
|
+
const git_1 = require("../../utils/git");
|
|
38
|
+
const validation_1 = require("../../utils/validation");
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const uuid_1 = require("uuid");
|
|
43
|
+
describe('Error Cases Integration Tests', () => {
|
|
44
|
+
let dbManager;
|
|
45
|
+
let tempDbPath;
|
|
46
|
+
let db;
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
tempDbPath = path.join(os.tmpdir(), `test-errors-${Date.now()}.db`);
|
|
49
|
+
dbManager = new database_1.DatabaseManager({
|
|
50
|
+
filename: tempDbPath,
|
|
51
|
+
maxSize: 10 * 1024 * 1024,
|
|
52
|
+
walMode: true,
|
|
53
|
+
});
|
|
54
|
+
db = dbManager.getDatabase();
|
|
55
|
+
});
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
dbManager.close();
|
|
58
|
+
try {
|
|
59
|
+
fs.unlinkSync(tempDbPath);
|
|
60
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
61
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
62
|
+
}
|
|
63
|
+
catch (_e) {
|
|
64
|
+
// Ignore
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
describe('Database errors', () => {
|
|
68
|
+
it('should handle database full errors', () => {
|
|
69
|
+
// Create a tiny database manager
|
|
70
|
+
const tinyDbPath = path.join(os.tmpdir(), `tiny-db-${Date.now()}.db`);
|
|
71
|
+
const tinyDb = new database_1.DatabaseManager({
|
|
72
|
+
filename: tinyDbPath,
|
|
73
|
+
maxSize: 1024, // 1KB - very small
|
|
74
|
+
walMode: true,
|
|
75
|
+
});
|
|
76
|
+
const tinyDbConn = tinyDb.getDatabase();
|
|
77
|
+
const sessionId = (0, uuid_1.v4)();
|
|
78
|
+
tinyDbConn.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test');
|
|
79
|
+
// Try to fill it up
|
|
80
|
+
let errorThrown = false;
|
|
81
|
+
try {
|
|
82
|
+
for (let i = 0; i < 1000; i++) {
|
|
83
|
+
if (tinyDb.isDatabaseFull()) {
|
|
84
|
+
errorThrown = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
tinyDbConn
|
|
88
|
+
.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)')
|
|
89
|
+
.run((0, uuid_1.v4)(), sessionId, `key${i}`, 'A'.repeat(100));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (_e) {
|
|
93
|
+
errorThrown = true;
|
|
94
|
+
}
|
|
95
|
+
expect(errorThrown).toBe(true);
|
|
96
|
+
tinyDb.close();
|
|
97
|
+
fs.unlinkSync(tinyDbPath);
|
|
98
|
+
});
|
|
99
|
+
it('should handle constraint violations', () => {
|
|
100
|
+
const sessionId = (0, uuid_1.v4)();
|
|
101
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test');
|
|
102
|
+
// Insert item
|
|
103
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, 'unique_key', 'value1');
|
|
104
|
+
// Try to insert duplicate key (violates unique constraint)
|
|
105
|
+
expect(() => {
|
|
106
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, 'unique_key', 'value2');
|
|
107
|
+
}).toThrow();
|
|
108
|
+
});
|
|
109
|
+
it('should handle foreign key violations', () => {
|
|
110
|
+
// Try to insert context item with non-existent session
|
|
111
|
+
expect(() => {
|
|
112
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), 'non-existent-session', 'key', 'value');
|
|
113
|
+
}).toThrow();
|
|
114
|
+
});
|
|
115
|
+
it('should handle transaction rollbacks', () => {
|
|
116
|
+
const sessionId = (0, uuid_1.v4)();
|
|
117
|
+
expect(() => {
|
|
118
|
+
dbManager.transaction(() => {
|
|
119
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test');
|
|
120
|
+
// This will fail due to foreign key constraint
|
|
121
|
+
db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)').run((0, uuid_1.v4)(), 'invalid-checkpoint', 'invalid-item');
|
|
122
|
+
});
|
|
123
|
+
}).toThrow();
|
|
124
|
+
// Verify session was not created (rolled back)
|
|
125
|
+
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(sessionId);
|
|
126
|
+
expect(session).toBeFalsy();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('Input validation errors', () => {
|
|
130
|
+
it('should reject invalid session names', () => {
|
|
131
|
+
const invalidNames = [
|
|
132
|
+
'',
|
|
133
|
+
' ',
|
|
134
|
+
'\n\t',
|
|
135
|
+
'a'.repeat(256), // Too long
|
|
136
|
+
'../../../etc/passwd',
|
|
137
|
+
'session\0name',
|
|
138
|
+
'session<script>alert(1)</script>',
|
|
139
|
+
];
|
|
140
|
+
invalidNames.forEach(name => {
|
|
141
|
+
expect(() => (0, validation_1.validateSessionName)(name)).toThrow();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
it('should reject invalid file paths', () => {
|
|
145
|
+
const invalidPaths = [
|
|
146
|
+
'../../../etc/passwd',
|
|
147
|
+
'/etc/passwd',
|
|
148
|
+
'C:\\Windows\\System32\\config\\sam',
|
|
149
|
+
'\\\\server\\share\\file',
|
|
150
|
+
'file\0name.txt',
|
|
151
|
+
'',
|
|
152
|
+
'con.txt', // Windows reserved name
|
|
153
|
+
'prn.txt', // Windows reserved name
|
|
154
|
+
];
|
|
155
|
+
invalidPaths.forEach(filePath => {
|
|
156
|
+
expect(() => (0, validation_1.validateFilePath)(filePath, 'read')).toThrow();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
it('should sanitize search queries', () => {
|
|
160
|
+
const dangerousQueries = [
|
|
161
|
+
"'; DROP TABLE sessions; --",
|
|
162
|
+
'%" OR "1"="1',
|
|
163
|
+
"' UNION SELECT * FROM sessions --",
|
|
164
|
+
'%_test_%',
|
|
165
|
+
];
|
|
166
|
+
dangerousQueries.forEach(query => {
|
|
167
|
+
const sanitized = (0, validation_1.validateSearchQuery)(query);
|
|
168
|
+
expect(sanitized).not.toContain("'");
|
|
169
|
+
expect(sanitized).not.toContain('"');
|
|
170
|
+
expect(sanitized).not.toContain(';');
|
|
171
|
+
expect(sanitized).not.toContain('--');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
it('should handle null and undefined inputs', () => {
|
|
175
|
+
expect(() => (0, validation_1.validateSessionName)(null)).toThrow();
|
|
176
|
+
expect(() => (0, validation_1.validateSessionName)(undefined)).toThrow();
|
|
177
|
+
expect(() => (0, validation_1.validateFilePath)(null, 'read')).toThrow();
|
|
178
|
+
expect(() => (0, validation_1.validateFilePath)(undefined, 'read')).toThrow();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
describe('File system errors', () => {
|
|
182
|
+
it('should handle non-existent file reads', () => {
|
|
183
|
+
const sessionId = (0, uuid_1.v4)();
|
|
184
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test');
|
|
185
|
+
const nonExistentPath = path.join(os.tmpdir(), 'non-existent-file.txt');
|
|
186
|
+
// Simulate file read error
|
|
187
|
+
let errorMessage = '';
|
|
188
|
+
try {
|
|
189
|
+
const _content = fs.readFileSync(nonExistentPath, 'utf-8');
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
errorMessage = e.message;
|
|
193
|
+
}
|
|
194
|
+
expect(errorMessage).toContain('ENOENT');
|
|
195
|
+
});
|
|
196
|
+
it('should handle permission errors', () => {
|
|
197
|
+
// This test is platform-specific and may need adjustment
|
|
198
|
+
const restrictedPath = '/root/test.txt'; // Usually no write permission
|
|
199
|
+
let errorThrown = false;
|
|
200
|
+
let errorCode = '';
|
|
201
|
+
try {
|
|
202
|
+
fs.writeFileSync(restrictedPath, 'test');
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
errorThrown = true;
|
|
206
|
+
errorCode = e.code;
|
|
207
|
+
}
|
|
208
|
+
expect(errorThrown).toBe(true);
|
|
209
|
+
// Accept ENOENT (parent dir doesn't exist) or EACCES/EPERM (no permission)
|
|
210
|
+
expect(['EACCES', 'EPERM', 'ENOENT']).toContain(errorCode);
|
|
211
|
+
});
|
|
212
|
+
it('should handle disk space errors', () => {
|
|
213
|
+
// This is difficult to test reliably across platforms
|
|
214
|
+
// We'll simulate the behavior
|
|
215
|
+
const mockWriteLargeFile = (path, size) => {
|
|
216
|
+
try {
|
|
217
|
+
// Check available space (platform specific)
|
|
218
|
+
const availableSpace = 1024 * 1024 * 100; // Mock 100MB available
|
|
219
|
+
if (size > availableSpace) {
|
|
220
|
+
return { success: false, error: 'ENOSPC: no space left on device' };
|
|
221
|
+
}
|
|
222
|
+
return { success: true };
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
return { success: false, error: e.message };
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const result = mockWriteLargeFile('/tmp/large.file', 1024 * 1024 * 1024); // 1GB
|
|
229
|
+
expect(result.success).toBe(false);
|
|
230
|
+
expect(result.error).toContain('ENOSPC');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
describe('Git operation errors', () => {
|
|
234
|
+
it('should handle corrupted git repository', async () => {
|
|
235
|
+
const corruptRepoPath = path.join(os.tmpdir(), `corrupt-repo-${Date.now()}`);
|
|
236
|
+
fs.mkdirSync(corruptRepoPath, { recursive: true });
|
|
237
|
+
// Create a fake .git directory with corrupted content
|
|
238
|
+
const gitDir = path.join(corruptRepoPath, '.git');
|
|
239
|
+
fs.mkdirSync(gitDir);
|
|
240
|
+
fs.writeFileSync(path.join(gitDir, 'HEAD'), 'corrupted content');
|
|
241
|
+
const gitOps = new git_1.GitOperations(corruptRepoPath);
|
|
242
|
+
const info = await gitOps.getGitInfo();
|
|
243
|
+
expect(info.isGitRepo).toBe(false);
|
|
244
|
+
expect(info.status).toContain('error');
|
|
245
|
+
fs.rmSync(corruptRepoPath, { recursive: true, force: true });
|
|
246
|
+
});
|
|
247
|
+
it('should handle git command timeouts', async () => {
|
|
248
|
+
// Mock a slow git operation
|
|
249
|
+
const mockSlowGitOp = async (timeout) => {
|
|
250
|
+
return new Promise(resolve => {
|
|
251
|
+
const timer = setTimeout(() => {
|
|
252
|
+
resolve({ success: true });
|
|
253
|
+
}, timeout + 1000);
|
|
254
|
+
setTimeout(() => {
|
|
255
|
+
clearTimeout(timer);
|
|
256
|
+
resolve({ success: false, error: 'Operation timed out' });
|
|
257
|
+
}, timeout);
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
const result = await mockSlowGitOp(1000);
|
|
261
|
+
expect(result.success).toBe(false);
|
|
262
|
+
expect(result.error).toContain('timed out');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
describe('Concurrent access errors', () => {
|
|
266
|
+
it('should handle database lock timeouts', async () => {
|
|
267
|
+
// Create two database connections
|
|
268
|
+
const db2Manager = new database_1.DatabaseManager({
|
|
269
|
+
filename: tempDbPath,
|
|
270
|
+
maxSize: 10 * 1024 * 1024,
|
|
271
|
+
walMode: true,
|
|
272
|
+
});
|
|
273
|
+
const db2 = db2Manager.getDatabase();
|
|
274
|
+
const sessionId = (0, uuid_1.v4)();
|
|
275
|
+
// Start a transaction in first connection
|
|
276
|
+
const _transaction1 = db.transaction(() => {
|
|
277
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test');
|
|
278
|
+
// Simulate long-running operation
|
|
279
|
+
const start = Date.now();
|
|
280
|
+
while (Date.now() - start < 100) {
|
|
281
|
+
// Busy wait
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// Try to write in second connection (should succeed with WAL mode)
|
|
285
|
+
let secondWriteSucceeded = false;
|
|
286
|
+
try {
|
|
287
|
+
db2.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run((0, uuid_1.v4)(), 'Test2');
|
|
288
|
+
secondWriteSucceeded = true;
|
|
289
|
+
}
|
|
290
|
+
catch (_e) {
|
|
291
|
+
// In WAL mode, this should not throw
|
|
292
|
+
}
|
|
293
|
+
expect(secondWriteSucceeded).toBe(true);
|
|
294
|
+
db2Manager.close();
|
|
295
|
+
});
|
|
296
|
+
it('should handle multiple readers', () => {
|
|
297
|
+
const sessionId = (0, uuid_1.v4)();
|
|
298
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test');
|
|
299
|
+
// Create multiple readers
|
|
300
|
+
const readers = [];
|
|
301
|
+
for (let i = 0; i < 5; i++) {
|
|
302
|
+
const reader = new database_1.DatabaseManager({
|
|
303
|
+
filename: tempDbPath,
|
|
304
|
+
maxSize: 10 * 1024 * 1024,
|
|
305
|
+
walMode: true,
|
|
306
|
+
});
|
|
307
|
+
readers.push(reader);
|
|
308
|
+
}
|
|
309
|
+
// All readers should be able to read simultaneously
|
|
310
|
+
const results = readers.map(reader => {
|
|
311
|
+
const db = reader.getDatabase();
|
|
312
|
+
return db.prepare('SELECT * FROM sessions WHERE id = ?').get(sessionId);
|
|
313
|
+
});
|
|
314
|
+
expect(results.every(r => r !== undefined)).toBe(true);
|
|
315
|
+
// Cleanup
|
|
316
|
+
readers.forEach(r => r.close());
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
describe('Memory errors', () => {
|
|
320
|
+
it('should handle large result sets', () => {
|
|
321
|
+
const sessionId = (0, uuid_1.v4)();
|
|
322
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Large Dataset');
|
|
323
|
+
// Insert many items
|
|
324
|
+
const stmt = db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)');
|
|
325
|
+
for (let i = 0; i < 10000; i++) {
|
|
326
|
+
stmt.run((0, uuid_1.v4)(), sessionId, `key${i}`, `value${i}`);
|
|
327
|
+
}
|
|
328
|
+
// Use iterating to handle large result sets
|
|
329
|
+
const stmt2 = db.prepare('SELECT * FROM context_items WHERE session_id = ?');
|
|
330
|
+
let count = 0;
|
|
331
|
+
for (const _row of stmt2.iterate(sessionId)) {
|
|
332
|
+
count++;
|
|
333
|
+
if (count > 5000)
|
|
334
|
+
break; // Limit iteration
|
|
335
|
+
}
|
|
336
|
+
expect(count).toBeGreaterThan(5000);
|
|
337
|
+
});
|
|
338
|
+
it('should handle very long strings', () => {
|
|
339
|
+
const sessionId = (0, uuid_1.v4)();
|
|
340
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Long String Test');
|
|
341
|
+
const veryLongString = 'A'.repeat(1024 * 1024); // 1MB string
|
|
342
|
+
// Should handle gracefully
|
|
343
|
+
let errorThrown = false;
|
|
344
|
+
try {
|
|
345
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, 'long_key', veryLongString);
|
|
346
|
+
}
|
|
347
|
+
catch (_e) {
|
|
348
|
+
errorThrown = true;
|
|
349
|
+
}
|
|
350
|
+
// SQLite can handle large strings, so this should work
|
|
351
|
+
expect(errorThrown).toBe(false);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
describe('Recovery scenarios', () => {
|
|
355
|
+
it('should recover from corrupted context items', () => {
|
|
356
|
+
const sessionId = (0, uuid_1.v4)();
|
|
357
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Recovery Test');
|
|
358
|
+
// Insert valid items
|
|
359
|
+
for (let i = 0; i < 5; i++) {
|
|
360
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, `key${i}`, `value${i}`);
|
|
361
|
+
}
|
|
362
|
+
// Simulate corrupted metadata
|
|
363
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, metadata) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, 'corrupted', 'value', '{invalid json');
|
|
364
|
+
// SQLite 3.9+ has json_valid function, check if available
|
|
365
|
+
let validItems;
|
|
366
|
+
try {
|
|
367
|
+
// Try with json_valid
|
|
368
|
+
validItems = db
|
|
369
|
+
.prepare(`SELECT * FROM context_items
|
|
370
|
+
WHERE session_id = ?
|
|
371
|
+
AND (metadata IS NULL OR json_valid(metadata))`)
|
|
372
|
+
.all(sessionId);
|
|
373
|
+
}
|
|
374
|
+
catch (_e) {
|
|
375
|
+
// Fallback without json_valid
|
|
376
|
+
validItems = db
|
|
377
|
+
.prepare(`SELECT * FROM context_items
|
|
378
|
+
WHERE session_id = ?
|
|
379
|
+
AND metadata IS NULL`)
|
|
380
|
+
.all(sessionId);
|
|
381
|
+
}
|
|
382
|
+
expect(validItems).toHaveLength(5); // Only valid items
|
|
383
|
+
});
|
|
384
|
+
it('should handle checkpoint restoration failures gracefully', () => {
|
|
385
|
+
const sessionId = (0, uuid_1.v4)();
|
|
386
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Checkpoint Recovery');
|
|
387
|
+
// Create checkpoint with missing references
|
|
388
|
+
const checkpointId = (0, uuid_1.v4)();
|
|
389
|
+
db.prepare('INSERT INTO checkpoints (id, session_id, name) VALUES (?, ?, ?)').run(checkpointId, sessionId, 'Broken Checkpoint');
|
|
390
|
+
// Temporarily disable foreign keys to simulate corruption
|
|
391
|
+
db.pragma('foreign_keys = OFF');
|
|
392
|
+
// Add checkpoint items referencing non-existent context items
|
|
393
|
+
db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)').run((0, uuid_1.v4)(), checkpointId, 'non-existent-item');
|
|
394
|
+
// Re-enable foreign keys
|
|
395
|
+
db.pragma('foreign_keys = ON');
|
|
396
|
+
// Restoration should handle missing items
|
|
397
|
+
const restorable = db
|
|
398
|
+
.prepare(`
|
|
399
|
+
SELECT ci.* FROM context_items ci
|
|
400
|
+
JOIN checkpoint_items cpi ON ci.id = cpi.context_item_id
|
|
401
|
+
WHERE cpi.checkpoint_id = ?
|
|
402
|
+
`)
|
|
403
|
+
.all(checkpointId);
|
|
404
|
+
expect(restorable).toHaveLength(0); // No items to restore
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
});
|