@smallironman/mcp-memory-keeper 0.12.2-fork1
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 +542 -0
- package/LICENSE +21 -0
- package/README.md +1281 -0
- package/bin/mcp-memory-keeper +54 -0
- package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
- package/dist/__tests__/e2e/server-e2e.test.js +341 -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 +1054 -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/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 +411 -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/filterBySessionId.test.js +251 -0
- package/dist/__tests__/integration/git-integration.test.js +241 -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/issue24-final-fix.test.js +241 -0
- package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
- package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
- package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
- package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -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 +291 -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 +305 -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/tokenLimitEnforcement.test.js +134 -0
- package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -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 +192 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/token-limits.test.js +225 -0
- package/dist/__tests__/utils/tool-profiles.test.js +374 -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 +4425 -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 +2017 -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/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 +780 -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/token-limits.js +350 -0
- package/dist/utils/tool-profiles.js +242 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/examples/config.json +31 -0
- package/examples/project-directory-setup.md +114 -0
- package/package.json +85 -0
|
@@ -0,0 +1,264 @@
|
|
|
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 os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const uuid_1 = require("uuid");
|
|
42
|
+
describe('File Operations Integration Tests', () => {
|
|
43
|
+
let dbManager;
|
|
44
|
+
let tempDbPath;
|
|
45
|
+
let tempFileDir;
|
|
46
|
+
let db;
|
|
47
|
+
let testSessionId;
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
tempDbPath = path.join(os.tmpdir(), `test-files-${Date.now()}.db`);
|
|
50
|
+
tempFileDir = path.join(os.tmpdir(), `test-files-${Date.now()}`);
|
|
51
|
+
dbManager = new database_1.DatabaseManager({
|
|
52
|
+
filename: tempDbPath,
|
|
53
|
+
maxSize: 10 * 1024 * 1024,
|
|
54
|
+
walMode: true,
|
|
55
|
+
});
|
|
56
|
+
db = dbManager.getDatabase();
|
|
57
|
+
// Create test directory
|
|
58
|
+
fs.mkdirSync(tempFileDir, { recursive: true });
|
|
59
|
+
// Create test session
|
|
60
|
+
testSessionId = (0, uuid_1.v4)();
|
|
61
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'File Test Session');
|
|
62
|
+
});
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
dbManager.close();
|
|
65
|
+
try {
|
|
66
|
+
fs.unlinkSync(tempDbPath);
|
|
67
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
68
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
69
|
+
fs.rmSync(tempFileDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
catch (_e) {
|
|
72
|
+
// Ignore
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
describe('context_cache_file', () => {
|
|
76
|
+
it('should cache file content with hash', () => {
|
|
77
|
+
const filePath = path.join(tempFileDir, 'test.txt');
|
|
78
|
+
const content = 'This is test file content';
|
|
79
|
+
fs.writeFileSync(filePath, content);
|
|
80
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
81
|
+
const result = db
|
|
82
|
+
.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)')
|
|
83
|
+
.run((0, uuid_1.v4)(), testSessionId, filePath, content, hash);
|
|
84
|
+
expect(result.changes).toBe(1);
|
|
85
|
+
const cached = db
|
|
86
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
87
|
+
.get(testSessionId, filePath);
|
|
88
|
+
expect(cached).toBeDefined();
|
|
89
|
+
expect(cached.content).toBe(content);
|
|
90
|
+
expect(cached.hash).toBe(hash);
|
|
91
|
+
});
|
|
92
|
+
it('should update existing file cache', () => {
|
|
93
|
+
const filePath = path.join(tempFileDir, 'update.txt');
|
|
94
|
+
const originalContent = 'Original content';
|
|
95
|
+
const updatedContent = 'Updated content';
|
|
96
|
+
// Cache original
|
|
97
|
+
const originalHash = crypto.createHash('sha256').update(originalContent).digest('hex');
|
|
98
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, filePath, originalContent, originalHash);
|
|
99
|
+
// Update cache
|
|
100
|
+
const updatedHash = crypto.createHash('sha256').update(updatedContent).digest('hex');
|
|
101
|
+
db.prepare('UPDATE file_cache SET content = ?, hash = ?, last_read = CURRENT_TIMESTAMP WHERE session_id = ? AND file_path = ?').run(updatedContent, updatedHash, testSessionId, filePath);
|
|
102
|
+
const cached = db
|
|
103
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
104
|
+
.get(testSessionId, filePath);
|
|
105
|
+
expect(cached.content).toBe(updatedContent);
|
|
106
|
+
expect(cached.hash).toBe(updatedHash);
|
|
107
|
+
});
|
|
108
|
+
it('should handle binary file content', () => {
|
|
109
|
+
const filePath = path.join(tempFileDir, 'binary.bin');
|
|
110
|
+
const binaryContent = Buffer.from([0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd]);
|
|
111
|
+
fs.writeFileSync(filePath, binaryContent);
|
|
112
|
+
const base64Content = binaryContent.toString('base64');
|
|
113
|
+
const hash = crypto.createHash('sha256').update(binaryContent).digest('hex');
|
|
114
|
+
const result = db
|
|
115
|
+
.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)')
|
|
116
|
+
.run((0, uuid_1.v4)(), testSessionId, filePath, base64Content, hash);
|
|
117
|
+
expect(result.changes).toBe(1);
|
|
118
|
+
const cached = db
|
|
119
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
120
|
+
.get(testSessionId, filePath);
|
|
121
|
+
expect(cached.content).toBe(base64Content);
|
|
122
|
+
// Verify we can decode back
|
|
123
|
+
const decoded = Buffer.from(cached.content, 'base64');
|
|
124
|
+
expect(decoded.equals(binaryContent)).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
it('should handle very large files', () => {
|
|
127
|
+
const filePath = path.join(tempFileDir, 'large.txt');
|
|
128
|
+
const largeContent = 'x'.repeat(1024 * 1024); // 1MB
|
|
129
|
+
fs.writeFileSync(filePath, largeContent);
|
|
130
|
+
const hash = crypto.createHash('sha256').update(largeContent).digest('hex');
|
|
131
|
+
const result = db
|
|
132
|
+
.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)')
|
|
133
|
+
.run((0, uuid_1.v4)(), testSessionId, filePath, largeContent, hash);
|
|
134
|
+
expect(result.changes).toBe(1);
|
|
135
|
+
const cached = db
|
|
136
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
137
|
+
.get(testSessionId, filePath);
|
|
138
|
+
expect(cached.content.length).toBe(1024 * 1024);
|
|
139
|
+
expect(cached.hash).toBe(hash);
|
|
140
|
+
});
|
|
141
|
+
it('should track last read timestamp', async () => {
|
|
142
|
+
const filePath = path.join(tempFileDir, 'timestamp.txt');
|
|
143
|
+
const content = 'Test content';
|
|
144
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, filePath, content, 'hash');
|
|
145
|
+
const cached = db
|
|
146
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
147
|
+
.get(testSessionId, filePath);
|
|
148
|
+
// Verify we have a last_read timestamp
|
|
149
|
+
expect(cached.last_read).toBeDefined();
|
|
150
|
+
const lastRead = new Date(cached.last_read);
|
|
151
|
+
expect(lastRead).toBeInstanceOf(Date);
|
|
152
|
+
// The timestamp should be a valid date (not NaN)
|
|
153
|
+
expect(lastRead.getTime()).not.toBeNaN();
|
|
154
|
+
// SQLite timestamps can have timezone issues, so we just verify it's reasonable
|
|
155
|
+
// (within 24 hours of now in either direction)
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
const dayInMs = 24 * 60 * 60 * 1000;
|
|
158
|
+
expect(Math.abs(lastRead.getTime() - now)).toBeLessThan(dayInMs);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe('context_file_changed', () => {
|
|
162
|
+
it('should detect no change when hash matches', () => {
|
|
163
|
+
const filePath = path.join(tempFileDir, 'unchanged.txt');
|
|
164
|
+
const content = 'Unchanged content';
|
|
165
|
+
fs.writeFileSync(filePath, content);
|
|
166
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
167
|
+
// Cache the file
|
|
168
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, filePath, content, hash);
|
|
169
|
+
// Check if changed (using same hash)
|
|
170
|
+
const cached = db
|
|
171
|
+
.prepare('SELECT hash FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
172
|
+
.get(testSessionId, filePath);
|
|
173
|
+
const currentHash = crypto.createHash('sha256').update(content).digest('hex');
|
|
174
|
+
expect(cached.hash).toBe(currentHash);
|
|
175
|
+
expect(cached.hash === currentHash).toBe(true); // Not changed
|
|
176
|
+
});
|
|
177
|
+
it('should detect change when hash differs', () => {
|
|
178
|
+
const filePath = path.join(tempFileDir, 'changed.txt');
|
|
179
|
+
const originalContent = 'Original content';
|
|
180
|
+
const newContent = 'Modified content';
|
|
181
|
+
const originalHash = crypto.createHash('sha256').update(originalContent).digest('hex');
|
|
182
|
+
// Cache original
|
|
183
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, filePath, originalContent, originalHash);
|
|
184
|
+
// Simulate file change
|
|
185
|
+
fs.writeFileSync(filePath, newContent);
|
|
186
|
+
const newHash = crypto.createHash('sha256').update(newContent).digest('hex');
|
|
187
|
+
const cached = db
|
|
188
|
+
.prepare('SELECT hash FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
189
|
+
.get(testSessionId, filePath);
|
|
190
|
+
expect(cached.hash).toBe(originalHash);
|
|
191
|
+
expect(cached.hash === newHash).toBe(false); // Changed
|
|
192
|
+
});
|
|
193
|
+
it('should handle file not in cache', () => {
|
|
194
|
+
const filePath = path.join(tempFileDir, 'notcached.txt');
|
|
195
|
+
const cached = db
|
|
196
|
+
.prepare('SELECT hash FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
197
|
+
.get(testSessionId, filePath);
|
|
198
|
+
expect(cached).toBeUndefined();
|
|
199
|
+
});
|
|
200
|
+
it('should handle file deletion', () => {
|
|
201
|
+
const filePath = path.join(tempFileDir, 'deleted.txt');
|
|
202
|
+
const content = 'To be deleted';
|
|
203
|
+
fs.writeFileSync(filePath, content);
|
|
204
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
205
|
+
// Cache the file
|
|
206
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, filePath, content, hash);
|
|
207
|
+
// Delete the file
|
|
208
|
+
fs.unlinkSync(filePath);
|
|
209
|
+
// Check existence
|
|
210
|
+
expect(fs.existsSync(filePath)).toBe(false);
|
|
211
|
+
// Cache still exists
|
|
212
|
+
const cached = db
|
|
213
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
214
|
+
.get(testSessionId, filePath);
|
|
215
|
+
expect(cached).toBeDefined();
|
|
216
|
+
expect(cached.content).toBe(content); // Can still retrieve content
|
|
217
|
+
});
|
|
218
|
+
it('should handle multiple files efficiently', () => {
|
|
219
|
+
const files = [];
|
|
220
|
+
// Create and cache multiple files
|
|
221
|
+
for (let i = 0; i < 10; i++) {
|
|
222
|
+
const filePath = path.join(tempFileDir, `file${i}.txt`);
|
|
223
|
+
const content = `Content for file ${i}`;
|
|
224
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
225
|
+
fs.writeFileSync(filePath, content);
|
|
226
|
+
files.push({ path: filePath, content, hash });
|
|
227
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, filePath, content, hash);
|
|
228
|
+
}
|
|
229
|
+
// Check all files
|
|
230
|
+
const cachedFiles = db
|
|
231
|
+
.prepare('SELECT file_path, hash FROM file_cache WHERE session_id = ?')
|
|
232
|
+
.all(testSessionId);
|
|
233
|
+
expect(cachedFiles).toHaveLength(10);
|
|
234
|
+
// Verify each file
|
|
235
|
+
cachedFiles.forEach((cached) => {
|
|
236
|
+
const original = files.find(f => f.path === cached.file_path);
|
|
237
|
+
expect(original).toBeDefined();
|
|
238
|
+
expect(cached.hash).toBe(original.hash);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe('File operations with context', () => {
|
|
243
|
+
it('should link file cache with context items', () => {
|
|
244
|
+
const filePath = path.join(tempFileDir, 'linked.txt');
|
|
245
|
+
const content = 'Linked file content';
|
|
246
|
+
fs.writeFileSync(filePath, content);
|
|
247
|
+
// Cache file
|
|
248
|
+
const fileId = (0, uuid_1.v4)();
|
|
249
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
250
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run(fileId, testSessionId, filePath, content, hash);
|
|
251
|
+
// Save related context
|
|
252
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'working_file', filePath, 'file_reference');
|
|
253
|
+
// Query both
|
|
254
|
+
const context = db
|
|
255
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
256
|
+
.get(testSessionId, 'working_file');
|
|
257
|
+
const file = db
|
|
258
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
259
|
+
.get(testSessionId, context.value);
|
|
260
|
+
expect(file).toBeDefined();
|
|
261
|
+
expect(file.content).toBe(content);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
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 globals_1 = require("@jest/globals");
|
|
37
|
+
const database_1 = require("../../utils/database");
|
|
38
|
+
const ContextRepository_1 = require("../../repositories/ContextRepository");
|
|
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
|
+
/**
|
|
44
|
+
* Tests for filterBySessionId parameter in queryEnhanced
|
|
45
|
+
*
|
|
46
|
+
* Bug: When user specifies sessionId in context_get, the API should return
|
|
47
|
+
* only items from that specific session (respecting privacy rules).
|
|
48
|
+
*
|
|
49
|
+
* Previous behavior: sessionId was only used for privacy checking, not filtering.
|
|
50
|
+
* All public items from ALL sessions were returned regardless of sessionId.
|
|
51
|
+
*
|
|
52
|
+
* Fixed behavior: When sessionId is specified, only items from that session
|
|
53
|
+
* are returned (public items always visible, private items only if current session).
|
|
54
|
+
*/
|
|
55
|
+
(0, globals_1.describe)('filterBySessionId parameter in queryEnhanced', () => {
|
|
56
|
+
let dbManager;
|
|
57
|
+
let tempDbPath;
|
|
58
|
+
let db;
|
|
59
|
+
let contextRepo;
|
|
60
|
+
let sessionA;
|
|
61
|
+
let sessionB;
|
|
62
|
+
let sessionC;
|
|
63
|
+
(0, globals_1.beforeEach)(() => {
|
|
64
|
+
tempDbPath = path.join(os.tmpdir(), `test-filter-session-${Date.now()}.db`);
|
|
65
|
+
dbManager = new database_1.DatabaseManager({
|
|
66
|
+
filename: tempDbPath,
|
|
67
|
+
maxSize: 10 * 1024 * 1024,
|
|
68
|
+
walMode: true,
|
|
69
|
+
});
|
|
70
|
+
db = dbManager.getDatabase();
|
|
71
|
+
contextRepo = new ContextRepository_1.ContextRepository(dbManager);
|
|
72
|
+
// Create three test sessions
|
|
73
|
+
sessionA = (0, uuid_1.v4)();
|
|
74
|
+
sessionB = (0, uuid_1.v4)();
|
|
75
|
+
sessionC = (0, uuid_1.v4)();
|
|
76
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionA, 'Session A - Main');
|
|
77
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionB, 'Session B - Other');
|
|
78
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionC, 'Session C - Third');
|
|
79
|
+
// Create test items in different sessions
|
|
80
|
+
const testItems = [
|
|
81
|
+
// Session A items
|
|
82
|
+
{
|
|
83
|
+
id: (0, uuid_1.v4)(),
|
|
84
|
+
session_id: sessionA,
|
|
85
|
+
key: 'session-a-public-1',
|
|
86
|
+
value: 'Public item 1 in session A',
|
|
87
|
+
category: 'note',
|
|
88
|
+
priority: 'normal',
|
|
89
|
+
channel: 'main',
|
|
90
|
+
created_at: new Date().toISOString(),
|
|
91
|
+
is_private: 0,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: (0, uuid_1.v4)(),
|
|
95
|
+
session_id: sessionA,
|
|
96
|
+
key: 'session-a-private-1',
|
|
97
|
+
value: 'Private item 1 in session A',
|
|
98
|
+
category: 'note',
|
|
99
|
+
priority: 'high',
|
|
100
|
+
channel: 'main',
|
|
101
|
+
created_at: new Date().toISOString(),
|
|
102
|
+
is_private: 1,
|
|
103
|
+
},
|
|
104
|
+
// Session B items
|
|
105
|
+
{
|
|
106
|
+
id: (0, uuid_1.v4)(),
|
|
107
|
+
session_id: sessionB,
|
|
108
|
+
key: 'session-b-public-1',
|
|
109
|
+
value: 'Public item 1 in session B',
|
|
110
|
+
category: 'task',
|
|
111
|
+
priority: 'normal',
|
|
112
|
+
channel: 'main',
|
|
113
|
+
created_at: new Date().toISOString(),
|
|
114
|
+
is_private: 0,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: (0, uuid_1.v4)(),
|
|
118
|
+
session_id: sessionB,
|
|
119
|
+
key: 'session-b-private-1',
|
|
120
|
+
value: 'Private item 1 in session B',
|
|
121
|
+
category: 'task',
|
|
122
|
+
priority: 'normal',
|
|
123
|
+
channel: 'main',
|
|
124
|
+
created_at: new Date().toISOString(),
|
|
125
|
+
is_private: 1,
|
|
126
|
+
},
|
|
127
|
+
// Session C items
|
|
128
|
+
{
|
|
129
|
+
id: (0, uuid_1.v4)(),
|
|
130
|
+
session_id: sessionC,
|
|
131
|
+
key: 'session-c-public-1',
|
|
132
|
+
value: 'Public item 1 in session C',
|
|
133
|
+
category: 'reference',
|
|
134
|
+
priority: 'low',
|
|
135
|
+
channel: 'main',
|
|
136
|
+
created_at: new Date().toISOString(),
|
|
137
|
+
is_private: 0,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
const insertStmt = db.prepare(`
|
|
141
|
+
INSERT INTO context_items (id, session_id, key, value, category, priority, channel, created_at, is_private)
|
|
142
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
143
|
+
`);
|
|
144
|
+
for (const item of testItems) {
|
|
145
|
+
insertStmt.run(item.id, item.session_id, item.key, item.value, item.category, item.priority, item.channel, item.created_at, item.is_private);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
(0, globals_1.afterEach)(() => {
|
|
149
|
+
dbManager.close();
|
|
150
|
+
if (fs.existsSync(tempDbPath)) {
|
|
151
|
+
fs.unlinkSync(tempDbPath);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
(0, globals_1.describe)('Without filterBySessionId (default behavior)', () => {
|
|
155
|
+
(0, globals_1.it)('should return all accessible items (public from all + private from current)', () => {
|
|
156
|
+
const result = contextRepo.queryEnhanced({
|
|
157
|
+
sessionId: sessionA, // Current session for privacy check
|
|
158
|
+
// No filterBySessionId - should return all accessible items
|
|
159
|
+
});
|
|
160
|
+
// Should see:
|
|
161
|
+
// - All public items (3 items from sessions A, B, C)
|
|
162
|
+
// - Private items from sessionA (1 item)
|
|
163
|
+
// Total: 4 items
|
|
164
|
+
(0, globals_1.expect)(result.items.length).toBe(4);
|
|
165
|
+
const publicItems = result.items.filter(item => item.is_private === 0);
|
|
166
|
+
const privateItems = result.items.filter(item => item.is_private === 1);
|
|
167
|
+
(0, globals_1.expect)(publicItems.length).toBe(3); // Public from A, B, C
|
|
168
|
+
(0, globals_1.expect)(privateItems.length).toBe(1); // Only private from A (current session)
|
|
169
|
+
(0, globals_1.expect)(privateItems[0].session_id).toBe(sessionA);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
(0, globals_1.describe)('With filterBySessionId', () => {
|
|
173
|
+
(0, globals_1.it)('should return only items from the specified session', () => {
|
|
174
|
+
const result = contextRepo.queryEnhanced({
|
|
175
|
+
sessionId: sessionA, // Current session for privacy check
|
|
176
|
+
filterBySessionId: sessionA, // Filter to only session A
|
|
177
|
+
});
|
|
178
|
+
// Should only see items from session A
|
|
179
|
+
(0, globals_1.expect)(result.items.length).toBe(2); // 1 public + 1 private
|
|
180
|
+
(0, globals_1.expect)(result.items.every(item => item.session_id === sessionA)).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
(0, globals_1.it)('should respect privacy when filtering another session', () => {
|
|
183
|
+
const result = contextRepo.queryEnhanced({
|
|
184
|
+
sessionId: sessionA, // Current session for privacy check
|
|
185
|
+
filterBySessionId: sessionB, // Filter to session B
|
|
186
|
+
});
|
|
187
|
+
// Should only see public items from session B
|
|
188
|
+
// (private items from B are not visible to A)
|
|
189
|
+
(0, globals_1.expect)(result.items.length).toBe(1);
|
|
190
|
+
(0, globals_1.expect)(result.items[0].key).toBe('session-b-public-1');
|
|
191
|
+
(0, globals_1.expect)(result.items[0].is_private).toBe(0);
|
|
192
|
+
});
|
|
193
|
+
(0, globals_1.it)('should see all items when filtering own session', () => {
|
|
194
|
+
const result = contextRepo.queryEnhanced({
|
|
195
|
+
sessionId: sessionA, // Current session
|
|
196
|
+
filterBySessionId: sessionA, // Filter to same session
|
|
197
|
+
});
|
|
198
|
+
// Should see both public and private items from session A
|
|
199
|
+
(0, globals_1.expect)(result.items.length).toBe(2);
|
|
200
|
+
const keys = result.items.map(i => i.key).sort();
|
|
201
|
+
(0, globals_1.expect)(keys).toEqual(['session-a-private-1', 'session-a-public-1']);
|
|
202
|
+
});
|
|
203
|
+
(0, globals_1.it)('should return empty when filtering session with only private items from another session', () => {
|
|
204
|
+
// Create a session with only private items
|
|
205
|
+
const privateOnlySession = (0, uuid_1.v4)();
|
|
206
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(privateOnlySession, 'Private Only Session');
|
|
207
|
+
db.prepare(`
|
|
208
|
+
INSERT INTO context_items (id, session_id, key, value, category, priority, channel, created_at, is_private)
|
|
209
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
210
|
+
`).run((0, uuid_1.v4)(), privateOnlySession, 'secret-item', 'This is a secret', 'note', 'normal', 'main', new Date().toISOString(), 1 // Private
|
|
211
|
+
);
|
|
212
|
+
const result = contextRepo.queryEnhanced({
|
|
213
|
+
sessionId: sessionA, // Current session (not privateOnlySession)
|
|
214
|
+
filterBySessionId: privateOnlySession, // Filter to private-only session
|
|
215
|
+
});
|
|
216
|
+
// Should return no items (the only item is private and belongs to another session)
|
|
217
|
+
(0, globals_1.expect)(result.items.length).toBe(0);
|
|
218
|
+
});
|
|
219
|
+
(0, globals_1.it)('should work with other filters combined', () => {
|
|
220
|
+
const result = contextRepo.queryEnhanced({
|
|
221
|
+
sessionId: sessionA,
|
|
222
|
+
filterBySessionId: sessionA,
|
|
223
|
+
category: 'note',
|
|
224
|
+
});
|
|
225
|
+
// Should return only note items from session A
|
|
226
|
+
(0, globals_1.expect)(result.items.length).toBe(2); // Both session A items are notes
|
|
227
|
+
(0, globals_1.expect)(result.items.every(item => item.category === 'note')).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
(0, globals_1.describe)('Edge cases', () => {
|
|
231
|
+
(0, globals_1.it)('should handle non-existent session gracefully', () => {
|
|
232
|
+
const nonExistentSession = (0, uuid_1.v4)();
|
|
233
|
+
const result = contextRepo.queryEnhanced({
|
|
234
|
+
sessionId: sessionA,
|
|
235
|
+
filterBySessionId: nonExistentSession,
|
|
236
|
+
});
|
|
237
|
+
(0, globals_1.expect)(result.items.length).toBe(0);
|
|
238
|
+
(0, globals_1.expect)(result.totalCount).toBe(0);
|
|
239
|
+
});
|
|
240
|
+
(0, globals_1.it)('should handle undefined filterBySessionId same as not provided', () => {
|
|
241
|
+
const resultWithUndefined = contextRepo.queryEnhanced({
|
|
242
|
+
sessionId: sessionA,
|
|
243
|
+
filterBySessionId: undefined,
|
|
244
|
+
});
|
|
245
|
+
const resultWithout = contextRepo.queryEnhanced({
|
|
246
|
+
sessionId: sessionA,
|
|
247
|
+
});
|
|
248
|
+
(0, globals_1.expect)(resultWithUndefined.items.length).toBe(resultWithout.items.length);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|