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,915 @@
|
|
|
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
|
+
(0, globals_1.describe)('Enhanced Context Get Handler Integration Tests', () => {
|
|
44
|
+
let dbManager;
|
|
45
|
+
let tempDbPath;
|
|
46
|
+
let db;
|
|
47
|
+
let contextRepo;
|
|
48
|
+
let testSessionId;
|
|
49
|
+
let secondSessionId;
|
|
50
|
+
(0, globals_1.beforeEach)(() => {
|
|
51
|
+
tempDbPath = path.join(os.tmpdir(), `test-enhanced-context-get-${Date.now()}.db`);
|
|
52
|
+
dbManager = new database_1.DatabaseManager({
|
|
53
|
+
filename: tempDbPath,
|
|
54
|
+
maxSize: 10 * 1024 * 1024,
|
|
55
|
+
walMode: true,
|
|
56
|
+
});
|
|
57
|
+
db = dbManager.getDatabase();
|
|
58
|
+
contextRepo = new ContextRepository_1.ContextRepository(dbManager);
|
|
59
|
+
// Create test sessions
|
|
60
|
+
testSessionId = (0, uuid_1.v4)();
|
|
61
|
+
secondSessionId = (0, uuid_1.v4)();
|
|
62
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'Test Session');
|
|
63
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(secondSessionId, 'Second Session');
|
|
64
|
+
});
|
|
65
|
+
(0, globals_1.afterEach)(() => {
|
|
66
|
+
dbManager.close();
|
|
67
|
+
try {
|
|
68
|
+
fs.unlinkSync(tempDbPath);
|
|
69
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
70
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
71
|
+
}
|
|
72
|
+
catch (_e) {
|
|
73
|
+
// Ignore
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
function createTestData() {
|
|
77
|
+
const now = new Date();
|
|
78
|
+
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
|
|
79
|
+
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
80
|
+
const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000);
|
|
81
|
+
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
82
|
+
// Create a timestamp that's definitely yesterday
|
|
83
|
+
const yesterday = new Date();
|
|
84
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
85
|
+
yesterday.setHours(12, 0, 0, 0); // Noon yesterday
|
|
86
|
+
const items = [
|
|
87
|
+
{
|
|
88
|
+
id: (0, uuid_1.v4)(),
|
|
89
|
+
session_id: testSessionId,
|
|
90
|
+
key: 'config.database.url',
|
|
91
|
+
value: 'postgresql://localhost:5432/myapp',
|
|
92
|
+
category: 'config',
|
|
93
|
+
priority: 'high',
|
|
94
|
+
channel: 'main',
|
|
95
|
+
created_at: now.toISOString(),
|
|
96
|
+
updated_at: now.toISOString(),
|
|
97
|
+
metadata: JSON.stringify({ environment: 'production' }),
|
|
98
|
+
size: 35,
|
|
99
|
+
},
|
|
100
|
+
// Add an item from yesterday
|
|
101
|
+
{
|
|
102
|
+
id: (0, uuid_1.v4)(),
|
|
103
|
+
session_id: testSessionId,
|
|
104
|
+
key: 'config.yesterday.item',
|
|
105
|
+
value: 'Created yesterday',
|
|
106
|
+
category: 'config',
|
|
107
|
+
priority: 'normal',
|
|
108
|
+
channel: 'main',
|
|
109
|
+
created_at: yesterday.toISOString(),
|
|
110
|
+
updated_at: yesterday.toISOString(),
|
|
111
|
+
size: 17,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: (0, uuid_1.v4)(),
|
|
115
|
+
session_id: testSessionId,
|
|
116
|
+
key: 'config.cache.ttl',
|
|
117
|
+
value: '3600',
|
|
118
|
+
category: 'config',
|
|
119
|
+
priority: 'normal',
|
|
120
|
+
channel: 'main',
|
|
121
|
+
created_at: oneHourAgo.toISOString(),
|
|
122
|
+
updated_at: oneHourAgo.toISOString(),
|
|
123
|
+
metadata: JSON.stringify({ unit: 'seconds' }),
|
|
124
|
+
size: 4,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: (0, uuid_1.v4)(),
|
|
128
|
+
session_id: testSessionId,
|
|
129
|
+
key: 'task.deploy.status',
|
|
130
|
+
value: 'completed',
|
|
131
|
+
category: 'task',
|
|
132
|
+
priority: 'high',
|
|
133
|
+
channel: 'deployment',
|
|
134
|
+
created_at: oneDayAgo.toISOString(),
|
|
135
|
+
updated_at: oneHourAgo.toISOString(),
|
|
136
|
+
size: 9,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: (0, uuid_1.v4)(),
|
|
140
|
+
session_id: testSessionId,
|
|
141
|
+
key: 'task.backup.status',
|
|
142
|
+
value: 'pending',
|
|
143
|
+
category: 'task',
|
|
144
|
+
priority: 'low',
|
|
145
|
+
channel: 'maintenance',
|
|
146
|
+
created_at: twoDaysAgo.toISOString(),
|
|
147
|
+
updated_at: twoDaysAgo.toISOString(),
|
|
148
|
+
size: 7,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: (0, uuid_1.v4)(),
|
|
152
|
+
session_id: testSessionId,
|
|
153
|
+
key: 'note.architecture',
|
|
154
|
+
value: 'The system uses a microservices architecture with Docker containers',
|
|
155
|
+
category: 'note',
|
|
156
|
+
priority: 'normal',
|
|
157
|
+
channel: 'documentation',
|
|
158
|
+
created_at: oneWeekAgo.toISOString(),
|
|
159
|
+
updated_at: oneWeekAgo.toISOString(),
|
|
160
|
+
size: 67,
|
|
161
|
+
},
|
|
162
|
+
// Private item
|
|
163
|
+
{
|
|
164
|
+
id: (0, uuid_1.v4)(),
|
|
165
|
+
session_id: testSessionId,
|
|
166
|
+
key: 'secret.api.key',
|
|
167
|
+
value: 'sk-1234567890abcdef',
|
|
168
|
+
category: 'config',
|
|
169
|
+
priority: 'high',
|
|
170
|
+
channel: 'secure',
|
|
171
|
+
created_at: now.toISOString(),
|
|
172
|
+
updated_at: now.toISOString(),
|
|
173
|
+
is_private: 1,
|
|
174
|
+
size: 18,
|
|
175
|
+
},
|
|
176
|
+
// Item from another session (public)
|
|
177
|
+
{
|
|
178
|
+
id: (0, uuid_1.v4)(),
|
|
179
|
+
session_id: secondSessionId,
|
|
180
|
+
key: 'shared.resource',
|
|
181
|
+
value: 'This is a shared resource',
|
|
182
|
+
category: 'shared',
|
|
183
|
+
priority: 'normal',
|
|
184
|
+
channel: 'public',
|
|
185
|
+
created_at: oneDayAgo.toISOString(),
|
|
186
|
+
is_private: 0,
|
|
187
|
+
size: 25,
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
// Insert test data
|
|
191
|
+
const stmt = db.prepare(`
|
|
192
|
+
INSERT INTO context_items (
|
|
193
|
+
id, session_id, key, value, category, priority, channel,
|
|
194
|
+
created_at, updated_at, metadata, size, is_private
|
|
195
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
196
|
+
`);
|
|
197
|
+
items.forEach(item => {
|
|
198
|
+
stmt.run(item.id, item.session_id, item.key, item.value, item.category || null, item.priority || 'normal', item.channel || 'general', item.created_at || new Date().toISOString(), item.updated_at || item.created_at || new Date().toISOString(), item.metadata || null, item.size || Buffer.byteLength(item.value, 'utf8'), item.is_private || 0);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
(0, globals_1.describe)('Backward Compatibility Tests', () => {
|
|
202
|
+
(0, globals_1.beforeEach)(() => {
|
|
203
|
+
createTestData();
|
|
204
|
+
});
|
|
205
|
+
(0, globals_1.it)('should return single value when requesting by key (backward compatible)', () => {
|
|
206
|
+
const result = contextRepo.getByKey(testSessionId, 'config.database.url');
|
|
207
|
+
(0, globals_1.expect)(result).toBeTruthy();
|
|
208
|
+
(0, globals_1.expect)(result.value).toBe('postgresql://localhost:5432/myapp');
|
|
209
|
+
// Handler should return just the value for single key requests
|
|
210
|
+
// This simulates the handler behavior
|
|
211
|
+
const handlerResponse = {
|
|
212
|
+
content: [{ type: 'text', text: result.value }],
|
|
213
|
+
};
|
|
214
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toBe('postgresql://localhost:5432/myapp');
|
|
215
|
+
});
|
|
216
|
+
(0, globals_1.it)('should return text format for multiple items by default', () => {
|
|
217
|
+
const items = contextRepo.getByCategory(testSessionId, 'config');
|
|
218
|
+
(0, globals_1.expect)(items.length).toBeGreaterThan(1);
|
|
219
|
+
// Simulate handler formatting for multiple items
|
|
220
|
+
const formattedItems = items
|
|
221
|
+
.filter(item => !item.is_private || item.session_id === testSessionId)
|
|
222
|
+
.map(r => `• [${r.priority}] ${r.key}: ${r.value.substring(0, 100)}${r.value.length > 100 ? '...' : ''}`)
|
|
223
|
+
.join('\n');
|
|
224
|
+
const handlerResponse = {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: 'text',
|
|
228
|
+
text: `Found ${items.length} context items:\n\n${formattedItems}`,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('Found');
|
|
233
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('config.database.url');
|
|
234
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('[high]');
|
|
235
|
+
});
|
|
236
|
+
(0, globals_1.it)('should handle category parameter (existing functionality)', () => {
|
|
237
|
+
const items = contextRepo.getByCategory(testSessionId, 'task');
|
|
238
|
+
(0, globals_1.expect)(items.length).toBe(2);
|
|
239
|
+
(0, globals_1.expect)(items.every(item => item.category === 'task')).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
(0, globals_1.it)('should respect session isolation by default', () => {
|
|
242
|
+
const items = contextRepo.getBySessionId(testSessionId);
|
|
243
|
+
(0, globals_1.expect)(items.every(item => item.session_id === testSessionId)).toBe(true);
|
|
244
|
+
(0, globals_1.expect)(items.some(item => item.key === 'shared.resource')).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
(0, globals_1.describe)('Metadata Inclusion Tests', () => {
|
|
248
|
+
(0, globals_1.beforeEach)(() => {
|
|
249
|
+
createTestData();
|
|
250
|
+
});
|
|
251
|
+
(0, globals_1.it)('should include metadata when includeMetadata is true', () => {
|
|
252
|
+
const options = {
|
|
253
|
+
sessionId: testSessionId,
|
|
254
|
+
key: 'config.database.url',
|
|
255
|
+
includeMetadata: true,
|
|
256
|
+
};
|
|
257
|
+
const result = contextRepo.queryEnhanced(options);
|
|
258
|
+
(0, globals_1.expect)(result.items.length).toBe(1);
|
|
259
|
+
const item = result.items[0];
|
|
260
|
+
// Verify all metadata fields are present
|
|
261
|
+
(0, globals_1.expect)(item).toHaveProperty('size');
|
|
262
|
+
(0, globals_1.expect)(item).toHaveProperty('created_at');
|
|
263
|
+
(0, globals_1.expect)(item).toHaveProperty('updated_at');
|
|
264
|
+
(0, globals_1.expect)(item).toHaveProperty('metadata');
|
|
265
|
+
(0, globals_1.expect)(item.size).toBe(35);
|
|
266
|
+
// Verify metadata can be parsed
|
|
267
|
+
if (item.metadata) {
|
|
268
|
+
const parsed = JSON.parse(item.metadata);
|
|
269
|
+
(0, globals_1.expect)(parsed).toHaveProperty('environment', 'production');
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
(0, globals_1.it)('should return metadata structure for multiple items', () => {
|
|
273
|
+
const options = {
|
|
274
|
+
sessionId: testSessionId,
|
|
275
|
+
category: 'config',
|
|
276
|
+
includeMetadata: true,
|
|
277
|
+
};
|
|
278
|
+
const result = contextRepo.queryEnhanced(options);
|
|
279
|
+
// Should return object with items array and totalCount
|
|
280
|
+
(0, globals_1.expect)(result).toHaveProperty('items');
|
|
281
|
+
(0, globals_1.expect)(result).toHaveProperty('totalCount');
|
|
282
|
+
(0, globals_1.expect)(result.totalCount).toBeGreaterThanOrEqual(result.items.length);
|
|
283
|
+
result.items.forEach(item => {
|
|
284
|
+
(0, globals_1.expect)(item).toHaveProperty('size');
|
|
285
|
+
(0, globals_1.expect)(item).toHaveProperty('created_at');
|
|
286
|
+
(0, globals_1.expect)(item).toHaveProperty('updated_at');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
(0, globals_1.describe)('Sorting Tests', () => {
|
|
291
|
+
(0, globals_1.beforeEach)(() => {
|
|
292
|
+
createTestData();
|
|
293
|
+
});
|
|
294
|
+
(0, globals_1.it)('should sort by created_at descending (default)', () => {
|
|
295
|
+
const result = contextRepo.queryEnhanced({
|
|
296
|
+
sessionId: testSessionId,
|
|
297
|
+
sort: 'created_desc',
|
|
298
|
+
});
|
|
299
|
+
for (let i = 1; i < result.items.length; i++) {
|
|
300
|
+
const prevDate = new Date(result.items[i - 1].created_at);
|
|
301
|
+
const currDate = new Date(result.items[i].created_at);
|
|
302
|
+
(0, globals_1.expect)(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime());
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
(0, globals_1.it)('should sort by created_at ascending', () => {
|
|
306
|
+
const result = contextRepo.queryEnhanced({
|
|
307
|
+
sessionId: testSessionId,
|
|
308
|
+
sort: 'created_asc',
|
|
309
|
+
});
|
|
310
|
+
for (let i = 1; i < result.items.length; i++) {
|
|
311
|
+
const prevDate = new Date(result.items[i - 1].created_at);
|
|
312
|
+
const currDate = new Date(result.items[i].created_at);
|
|
313
|
+
(0, globals_1.expect)(prevDate.getTime()).toBeLessThanOrEqual(currDate.getTime());
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
(0, globals_1.it)('should sort by updated_at descending', () => {
|
|
317
|
+
const result = contextRepo.queryEnhanced({
|
|
318
|
+
sessionId: testSessionId,
|
|
319
|
+
sort: 'updated_desc',
|
|
320
|
+
});
|
|
321
|
+
// Verify items are sorted by updated_at in descending order
|
|
322
|
+
for (let i = 1; i < result.items.length; i++) {
|
|
323
|
+
const prevDate = new Date(result.items[i - 1].updated_at);
|
|
324
|
+
const currDate = new Date(result.items[i].updated_at);
|
|
325
|
+
(0, globals_1.expect)(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime());
|
|
326
|
+
}
|
|
327
|
+
// The most recently updated items should be first
|
|
328
|
+
// config.database.url and secret.api.key both have updated_at = now
|
|
329
|
+
(0, globals_1.expect)(['config.database.url', 'secret.api.key']).toContain(result.items[0].key);
|
|
330
|
+
});
|
|
331
|
+
(0, globals_1.it)('should sort by key ascending', () => {
|
|
332
|
+
const result = contextRepo.queryEnhanced({
|
|
333
|
+
sessionId: testSessionId,
|
|
334
|
+
sort: 'key_asc',
|
|
335
|
+
});
|
|
336
|
+
for (let i = 1; i < result.items.length; i++) {
|
|
337
|
+
(0, globals_1.expect)(result.items[i - 1].key.localeCompare(result.items[i].key)).toBeLessThanOrEqual(0);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
(0, globals_1.it)('should sort by key descending', () => {
|
|
341
|
+
const result = contextRepo.queryEnhanced({
|
|
342
|
+
sessionId: testSessionId,
|
|
343
|
+
sort: 'key_desc',
|
|
344
|
+
});
|
|
345
|
+
for (let i = 1; i < result.items.length; i++) {
|
|
346
|
+
(0, globals_1.expect)(result.items[i - 1].key.localeCompare(result.items[i].key)).toBeGreaterThanOrEqual(0);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
(0, globals_1.it)('should handle priority-based sorting when no sort specified', () => {
|
|
350
|
+
const result = contextRepo.queryEnhanced({
|
|
351
|
+
sessionId: testSessionId,
|
|
352
|
+
category: 'task',
|
|
353
|
+
});
|
|
354
|
+
// High priority items should come first
|
|
355
|
+
(0, globals_1.expect)(result.items[0].priority).toBe('high');
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
(0, globals_1.describe)('Pagination Tests', () => {
|
|
359
|
+
(0, globals_1.beforeEach)(() => {
|
|
360
|
+
createTestData();
|
|
361
|
+
// Add more items for pagination testing
|
|
362
|
+
for (let i = 0; i < 10; i++) {
|
|
363
|
+
db.prepare(`
|
|
364
|
+
INSERT INTO context_items (id, session_id, key, value, category, priority)
|
|
365
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
366
|
+
`).run((0, uuid_1.v4)(), testSessionId, `test.item.${i}`, `Test value ${i}`, 'test', 'normal');
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
(0, globals_1.it)('should limit results correctly', () => {
|
|
370
|
+
const result = contextRepo.queryEnhanced({
|
|
371
|
+
sessionId: testSessionId,
|
|
372
|
+
limit: 5,
|
|
373
|
+
});
|
|
374
|
+
(0, globals_1.expect)(result.items.length).toBe(5);
|
|
375
|
+
(0, globals_1.expect)(result.totalCount).toBeGreaterThan(5);
|
|
376
|
+
});
|
|
377
|
+
(0, globals_1.it)('should handle offset correctly', () => {
|
|
378
|
+
const resultPage1 = contextRepo.queryEnhanced({
|
|
379
|
+
sessionId: testSessionId,
|
|
380
|
+
limit: 5,
|
|
381
|
+
offset: 0,
|
|
382
|
+
});
|
|
383
|
+
const resultPage2 = contextRepo.queryEnhanced({
|
|
384
|
+
sessionId: testSessionId,
|
|
385
|
+
limit: 5,
|
|
386
|
+
offset: 5,
|
|
387
|
+
});
|
|
388
|
+
// Ensure no overlap between pages
|
|
389
|
+
const page1Keys = new Set(resultPage1.items.map(item => item.key));
|
|
390
|
+
const page2Keys = new Set(resultPage2.items.map(item => item.key));
|
|
391
|
+
page2Keys.forEach(key => {
|
|
392
|
+
(0, globals_1.expect)(page1Keys.has(key)).toBe(false);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
(0, globals_1.it)('should calculate correct pagination metadata', () => {
|
|
396
|
+
const pageSize = 3;
|
|
397
|
+
const page = 2;
|
|
398
|
+
const offset = (page - 1) * pageSize;
|
|
399
|
+
const result = contextRepo.queryEnhanced({
|
|
400
|
+
sessionId: testSessionId,
|
|
401
|
+
limit: pageSize,
|
|
402
|
+
offset: offset,
|
|
403
|
+
});
|
|
404
|
+
// Handler would calculate these values
|
|
405
|
+
const totalPages = Math.ceil(result.totalCount / pageSize);
|
|
406
|
+
const hasNextPage = page < totalPages;
|
|
407
|
+
const hasPreviousPage = page > 1;
|
|
408
|
+
(0, globals_1.expect)(result.items.length).toBeLessThanOrEqual(pageSize);
|
|
409
|
+
(0, globals_1.expect)(hasNextPage).toBe(true);
|
|
410
|
+
(0, globals_1.expect)(hasPreviousPage).toBe(true);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
(0, globals_1.describe)('Time Filtering Tests', () => {
|
|
414
|
+
(0, globals_1.beforeEach)(() => {
|
|
415
|
+
createTestData();
|
|
416
|
+
});
|
|
417
|
+
(0, globals_1.it)('should filter by createdAfter with ISO date', () => {
|
|
418
|
+
const twoDaysAgo = new Date();
|
|
419
|
+
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
|
|
420
|
+
const result = contextRepo.queryEnhanced({
|
|
421
|
+
sessionId: testSessionId,
|
|
422
|
+
createdAfter: twoDaysAgo.toISOString(),
|
|
423
|
+
});
|
|
424
|
+
result.items.forEach(item => {
|
|
425
|
+
(0, globals_1.expect)(new Date(item.created_at).getTime()).toBeGreaterThan(twoDaysAgo.getTime());
|
|
426
|
+
});
|
|
427
|
+
// Should include recent items but not older ones
|
|
428
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'config.database.url')).toBe(true);
|
|
429
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'note.architecture')).toBe(false);
|
|
430
|
+
});
|
|
431
|
+
(0, globals_1.it)('should filter by createdBefore with ISO date', () => {
|
|
432
|
+
const oneDayAgo = new Date();
|
|
433
|
+
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
|
|
434
|
+
const result = contextRepo.queryEnhanced({
|
|
435
|
+
sessionId: testSessionId,
|
|
436
|
+
createdBefore: oneDayAgo.toISOString(),
|
|
437
|
+
});
|
|
438
|
+
result.items.forEach(item => {
|
|
439
|
+
(0, globals_1.expect)(new Date(item.created_at).getTime()).toBeLessThan(oneDayAgo.getTime());
|
|
440
|
+
});
|
|
441
|
+
// Should include older items but not recent ones
|
|
442
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'note.architecture')).toBe(true);
|
|
443
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'config.database.url')).toBe(false);
|
|
444
|
+
});
|
|
445
|
+
(0, globals_1.it)('should handle relative time for createdAfter', () => {
|
|
446
|
+
// Test "2 hours ago"
|
|
447
|
+
const result = contextRepo.queryEnhanced({
|
|
448
|
+
sessionId: testSessionId,
|
|
449
|
+
createdAfter: '2 hours ago',
|
|
450
|
+
});
|
|
451
|
+
// Should include items created within last 2 hours
|
|
452
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'config.database.url')).toBe(true);
|
|
453
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'config.cache.ttl')).toBe(true);
|
|
454
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'task.deploy.status')).toBe(false);
|
|
455
|
+
});
|
|
456
|
+
(0, globals_1.it)('should handle "today" relative time', () => {
|
|
457
|
+
const result = contextRepo.queryEnhanced({
|
|
458
|
+
sessionId: testSessionId,
|
|
459
|
+
createdAfter: 'today',
|
|
460
|
+
});
|
|
461
|
+
const today = new Date();
|
|
462
|
+
today.setHours(0, 0, 0, 0);
|
|
463
|
+
result.items.forEach(item => {
|
|
464
|
+
const itemDate = new Date(item.created_at);
|
|
465
|
+
(0, globals_1.expect)(itemDate.getTime()).toBeGreaterThanOrEqual(today.getTime());
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
(0, globals_1.it)('should handle "yesterday" relative time', () => {
|
|
469
|
+
const result = contextRepo.queryEnhanced({
|
|
470
|
+
sessionId: testSessionId,
|
|
471
|
+
createdAfter: 'yesterday',
|
|
472
|
+
createdBefore: 'today',
|
|
473
|
+
});
|
|
474
|
+
const today = new Date();
|
|
475
|
+
today.setHours(0, 0, 0, 0);
|
|
476
|
+
const yesterday = new Date(today);
|
|
477
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
478
|
+
result.items.forEach(item => {
|
|
479
|
+
const itemDate = new Date(item.created_at);
|
|
480
|
+
(0, globals_1.expect)(itemDate.getTime()).toBeGreaterThanOrEqual(yesterday.getTime());
|
|
481
|
+
(0, globals_1.expect)(itemDate.getTime()).toBeLessThan(today.getTime());
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
(0, globals_1.it)('should handle combined date range', () => {
|
|
485
|
+
// Use a slightly larger range to account for timing
|
|
486
|
+
const result = contextRepo.queryEnhanced({
|
|
487
|
+
sessionId: testSessionId,
|
|
488
|
+
createdAfter: '8 days ago',
|
|
489
|
+
createdBefore: '1 hour ago',
|
|
490
|
+
});
|
|
491
|
+
// Should include items from the past week but not the most recent items
|
|
492
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'note.architecture')).toBe(true);
|
|
493
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'task.backup.status')).toBe(true);
|
|
494
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'task.deploy.status')).toBe(true);
|
|
495
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'config.database.url')).toBe(false); // created "now"
|
|
496
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'secret.api.key')).toBe(false); // created "now"
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
(0, globals_1.describe)('Priority Filtering Tests', () => {
|
|
500
|
+
(0, globals_1.beforeEach)(() => {
|
|
501
|
+
createTestData();
|
|
502
|
+
});
|
|
503
|
+
(0, globals_1.it)('should filter by single priority', () => {
|
|
504
|
+
const result = contextRepo.queryEnhanced({
|
|
505
|
+
sessionId: testSessionId,
|
|
506
|
+
priorities: ['high'],
|
|
507
|
+
});
|
|
508
|
+
(0, globals_1.expect)(result.items.every(item => item.priority === 'high')).toBe(true);
|
|
509
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'config.database.url')).toBe(true);
|
|
510
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'task.deploy.status')).toBe(true);
|
|
511
|
+
});
|
|
512
|
+
(0, globals_1.it)('should filter by multiple priorities', () => {
|
|
513
|
+
const result = contextRepo.queryEnhanced({
|
|
514
|
+
sessionId: testSessionId,
|
|
515
|
+
priorities: ['high', 'low'],
|
|
516
|
+
});
|
|
517
|
+
(0, globals_1.expect)(result.items.every(item => item.priority === 'high' || item.priority === 'low')).toBe(true);
|
|
518
|
+
(0, globals_1.expect)(result.items.some(item => item.priority === 'normal')).toBe(false);
|
|
519
|
+
});
|
|
520
|
+
(0, globals_1.it)('should handle empty priorities array', () => {
|
|
521
|
+
const result = contextRepo.queryEnhanced({
|
|
522
|
+
sessionId: testSessionId,
|
|
523
|
+
priorities: [],
|
|
524
|
+
});
|
|
525
|
+
// Should return all items when no priority filter
|
|
526
|
+
(0, globals_1.expect)(result.items.length).toBeGreaterThan(0);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
(0, globals_1.describe)('Pattern Matching Tests', () => {
|
|
530
|
+
(0, globals_1.beforeEach)(() => {
|
|
531
|
+
createTestData();
|
|
532
|
+
});
|
|
533
|
+
(0, globals_1.it)('should match keys with simple pattern', () => {
|
|
534
|
+
const result = contextRepo.queryEnhanced({
|
|
535
|
+
sessionId: testSessionId,
|
|
536
|
+
keyPattern: 'config.*',
|
|
537
|
+
});
|
|
538
|
+
(0, globals_1.expect)(result.items.every(item => item.key.startsWith('config.'))).toBe(true);
|
|
539
|
+
(0, globals_1.expect)(result.items.length).toBe(3); // config.database.url, config.cache.ttl, and config.yesterday.item
|
|
540
|
+
});
|
|
541
|
+
(0, globals_1.it)('should match keys ending with pattern', () => {
|
|
542
|
+
const result = contextRepo.queryEnhanced({
|
|
543
|
+
sessionId: testSessionId,
|
|
544
|
+
keyPattern: '*.status',
|
|
545
|
+
});
|
|
546
|
+
(0, globals_1.expect)(result.items.every(item => item.key.endsWith('.status'))).toBe(true);
|
|
547
|
+
(0, globals_1.expect)(result.items.length).toBe(2); // task.deploy.status and task.backup.status
|
|
548
|
+
});
|
|
549
|
+
(0, globals_1.it)('should match keys with middle wildcard', () => {
|
|
550
|
+
const result = contextRepo.queryEnhanced({
|
|
551
|
+
sessionId: testSessionId,
|
|
552
|
+
keyPattern: 'task.*.status',
|
|
553
|
+
});
|
|
554
|
+
(0, globals_1.expect)(result.items.every(item => item.key.startsWith('task.') && item.key.endsWith('.status'))).toBe(true);
|
|
555
|
+
});
|
|
556
|
+
(0, globals_1.it)('should handle regex special characters in pattern', () => {
|
|
557
|
+
// Add item with special characters
|
|
558
|
+
db.prepare(`
|
|
559
|
+
INSERT INTO context_items (id, session_id, key, value)
|
|
560
|
+
VALUES (?, ?, ?, ?)
|
|
561
|
+
`).run((0, uuid_1.v4)(), testSessionId, 'config.db[prod].host', 'localhost');
|
|
562
|
+
// Pattern should handle [ ] characters
|
|
563
|
+
// In GLOB, we need to match literal brackets and any content between them
|
|
564
|
+
const result = contextRepo.queryEnhanced({
|
|
565
|
+
sessionId: testSessionId,
|
|
566
|
+
keyPattern: 'config.db*prod*.host',
|
|
567
|
+
});
|
|
568
|
+
(0, globals_1.expect)(result.items.length).toBe(1);
|
|
569
|
+
(0, globals_1.expect)(result.items[0].key).toBe('config.db[prod].host');
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
(0, globals_1.describe)('Channel Filtering Tests', () => {
|
|
573
|
+
(0, globals_1.beforeEach)(() => {
|
|
574
|
+
createTestData();
|
|
575
|
+
});
|
|
576
|
+
(0, globals_1.it)('should filter by single channel', () => {
|
|
577
|
+
const result = contextRepo.queryEnhanced({
|
|
578
|
+
sessionId: testSessionId,
|
|
579
|
+
channel: 'main',
|
|
580
|
+
});
|
|
581
|
+
(0, globals_1.expect)(result.items.every(item => item.channel === 'main')).toBe(true);
|
|
582
|
+
(0, globals_1.expect)(result.items.length).toBe(3); // includes config.yesterday.item
|
|
583
|
+
});
|
|
584
|
+
(0, globals_1.it)('should filter by multiple channels', () => {
|
|
585
|
+
const result = contextRepo.queryEnhanced({
|
|
586
|
+
sessionId: testSessionId,
|
|
587
|
+
channels: ['main', 'deployment'],
|
|
588
|
+
});
|
|
589
|
+
(0, globals_1.expect)(result.items.every(item => item.channel === 'main' || item.channel === 'deployment')).toBe(true);
|
|
590
|
+
(0, globals_1.expect)(result.items.length).toBe(4); // 3 from main + 1 from deployment
|
|
591
|
+
});
|
|
592
|
+
(0, globals_1.it)('should handle both channel and channels parameters', () => {
|
|
593
|
+
// When both are provided, channels array should take precedence
|
|
594
|
+
const result = contextRepo.queryEnhanced({
|
|
595
|
+
sessionId: testSessionId,
|
|
596
|
+
channel: 'main',
|
|
597
|
+
channels: ['deployment', 'maintenance'],
|
|
598
|
+
});
|
|
599
|
+
// Should only return items from channels array
|
|
600
|
+
(0, globals_1.expect)(result.items.every(item => item.channel === 'deployment' || item.channel === 'maintenance')).toBe(true);
|
|
601
|
+
(0, globals_1.expect)(result.items.some(item => item.channel === 'main')).toBe(false);
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
(0, globals_1.describe)('Combined Parameter Tests', () => {
|
|
605
|
+
(0, globals_1.beforeEach)(() => {
|
|
606
|
+
createTestData();
|
|
607
|
+
});
|
|
608
|
+
(0, globals_1.it)('should handle multiple filters together', () => {
|
|
609
|
+
const result = contextRepo.queryEnhanced({
|
|
610
|
+
sessionId: testSessionId,
|
|
611
|
+
category: 'task',
|
|
612
|
+
priorities: ['high', 'normal'],
|
|
613
|
+
createdAfter: '3 days ago',
|
|
614
|
+
sort: 'created_desc',
|
|
615
|
+
limit: 10,
|
|
616
|
+
});
|
|
617
|
+
(0, globals_1.expect)(result.items.every(item => item.category === 'task' && (item.priority === 'high' || item.priority === 'normal'))).toBe(true);
|
|
618
|
+
// Should include task.deploy.status but not task.backup.status (low priority)
|
|
619
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'task.deploy.status')).toBe(true);
|
|
620
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'task.backup.status')).toBe(false);
|
|
621
|
+
});
|
|
622
|
+
(0, globals_1.it)('should handle pattern with other filters', () => {
|
|
623
|
+
const result = contextRepo.queryEnhanced({
|
|
624
|
+
sessionId: testSessionId,
|
|
625
|
+
keyPattern: '*.status',
|
|
626
|
+
priorities: ['high'],
|
|
627
|
+
includeMetadata: true,
|
|
628
|
+
});
|
|
629
|
+
(0, globals_1.expect)(result.items.length).toBe(1);
|
|
630
|
+
(0, globals_1.expect)(result.items[0].key).toBe('task.deploy.status');
|
|
631
|
+
(0, globals_1.expect)(result.items[0]).toHaveProperty('size');
|
|
632
|
+
(0, globals_1.expect)(result.items[0]).toHaveProperty('created_at');
|
|
633
|
+
});
|
|
634
|
+
(0, globals_1.it)('should handle time range with channels and pagination', () => {
|
|
635
|
+
const result = contextRepo.queryEnhanced({
|
|
636
|
+
sessionId: testSessionId,
|
|
637
|
+
channels: ['main', 'deployment', 'maintenance'],
|
|
638
|
+
createdAfter: '7 days ago',
|
|
639
|
+
createdBefore: 'today',
|
|
640
|
+
sort: 'key_asc',
|
|
641
|
+
limit: 2,
|
|
642
|
+
offset: 0,
|
|
643
|
+
});
|
|
644
|
+
(0, globals_1.expect)(result.items.length).toBeLessThanOrEqual(2);
|
|
645
|
+
(0, globals_1.expect)(result.totalCount).toBeGreaterThanOrEqual(result.items.length);
|
|
646
|
+
// Verify sorting
|
|
647
|
+
if (result.items.length > 1) {
|
|
648
|
+
(0, globals_1.expect)(result.items[0].key.localeCompare(result.items[1].key)).toBeLessThanOrEqual(0);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
(0, globals_1.describe)('Privacy and Access Control Tests', () => {
|
|
653
|
+
(0, globals_1.beforeEach)(() => {
|
|
654
|
+
createTestData();
|
|
655
|
+
});
|
|
656
|
+
(0, globals_1.it)('should include own private items', () => {
|
|
657
|
+
const result = contextRepo.queryEnhanced({
|
|
658
|
+
sessionId: testSessionId,
|
|
659
|
+
category: 'config',
|
|
660
|
+
});
|
|
661
|
+
// Should include private secret.api.key since it's from the same session
|
|
662
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'secret.api.key')).toBe(true);
|
|
663
|
+
});
|
|
664
|
+
(0, globals_1.it)('should include public items from other sessions', () => {
|
|
665
|
+
const result = contextRepo.getAccessibleItems(testSessionId);
|
|
666
|
+
// Should include shared.resource from another session
|
|
667
|
+
(0, globals_1.expect)(result.some(item => item.key === 'shared.resource')).toBe(true);
|
|
668
|
+
});
|
|
669
|
+
(0, globals_1.it)('should not include private items from other sessions', () => {
|
|
670
|
+
// Add private item in second session
|
|
671
|
+
db.prepare(`
|
|
672
|
+
INSERT INTO context_items (id, session_id, key, value, is_private)
|
|
673
|
+
VALUES (?, ?, ?, ?, ?)
|
|
674
|
+
`).run((0, uuid_1.v4)(), secondSessionId, 'other.private', 'Private data', 1);
|
|
675
|
+
const result = contextRepo.getAccessibleItems(testSessionId);
|
|
676
|
+
// Should not include other session's private item
|
|
677
|
+
(0, globals_1.expect)(result.some(item => item.key === 'other.private')).toBe(false);
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
(0, globals_1.describe)('Error Handling Tests', () => {
|
|
681
|
+
(0, globals_1.beforeEach)(() => {
|
|
682
|
+
createTestData();
|
|
683
|
+
});
|
|
684
|
+
(0, globals_1.it)('should handle non-existent session gracefully', () => {
|
|
685
|
+
const result = contextRepo.queryEnhanced({
|
|
686
|
+
sessionId: 'non-existent-session',
|
|
687
|
+
});
|
|
688
|
+
// When session doesn't exist, it returns public items from other sessions
|
|
689
|
+
(0, globals_1.expect)(result.items.length).toBeGreaterThan(0);
|
|
690
|
+
(0, globals_1.expect)(result.items.every(item => item.is_private === 0)).toBe(true);
|
|
691
|
+
(0, globals_1.expect)(result.items.some(item => item.key === 'shared.resource')).toBe(true);
|
|
692
|
+
});
|
|
693
|
+
(0, globals_1.it)('should handle invalid sort parameter', () => {
|
|
694
|
+
const result = contextRepo.queryEnhanced({
|
|
695
|
+
sessionId: testSessionId,
|
|
696
|
+
sort: 'invalid_sort',
|
|
697
|
+
});
|
|
698
|
+
// Should fall back to default sort
|
|
699
|
+
(0, globals_1.expect)(result.items.length).toBeGreaterThan(0);
|
|
700
|
+
});
|
|
701
|
+
(0, globals_1.it)('should handle invalid date formats', () => {
|
|
702
|
+
const result = contextRepo.queryEnhanced({
|
|
703
|
+
sessionId: testSessionId,
|
|
704
|
+
createdAfter: 'invalid date',
|
|
705
|
+
});
|
|
706
|
+
// Should treat as literal string and likely return no results
|
|
707
|
+
(0, globals_1.expect)(result.items).toEqual([]);
|
|
708
|
+
});
|
|
709
|
+
(0, globals_1.it)('should handle negative offset', () => {
|
|
710
|
+
const result = contextRepo.queryEnhanced({
|
|
711
|
+
sessionId: testSessionId,
|
|
712
|
+
limit: 5,
|
|
713
|
+
offset: -1,
|
|
714
|
+
});
|
|
715
|
+
// Should treat negative offset as 0
|
|
716
|
+
(0, globals_1.expect)(result.items.length).toBeGreaterThan(0);
|
|
717
|
+
});
|
|
718
|
+
(0, globals_1.it)('should handle very large limit', () => {
|
|
719
|
+
const result = contextRepo.queryEnhanced({
|
|
720
|
+
sessionId: testSessionId,
|
|
721
|
+
limit: 999999,
|
|
722
|
+
});
|
|
723
|
+
// Should return all available items
|
|
724
|
+
(0, globals_1.expect)(result.items.length).toBe(result.totalCount);
|
|
725
|
+
});
|
|
726
|
+
(0, globals_1.it)('should handle SQL injection attempts in keyPattern', () => {
|
|
727
|
+
const maliciousPattern = "'; DROP TABLE context_items; --";
|
|
728
|
+
// Should not throw and should not damage database
|
|
729
|
+
const result = contextRepo.queryEnhanced({
|
|
730
|
+
sessionId: testSessionId,
|
|
731
|
+
keyPattern: maliciousPattern,
|
|
732
|
+
});
|
|
733
|
+
// Verify table still exists
|
|
734
|
+
const tableExists = db
|
|
735
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='context_items'")
|
|
736
|
+
.get();
|
|
737
|
+
(0, globals_1.expect)(tableExists).toBeTruthy();
|
|
738
|
+
(0, globals_1.expect)(result.items).toEqual([]);
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
(0, globals_1.describe)('Handler Response Format Tests', () => {
|
|
742
|
+
(0, globals_1.beforeEach)(() => {
|
|
743
|
+
createTestData();
|
|
744
|
+
});
|
|
745
|
+
(0, globals_1.it)('should format single item response correctly', () => {
|
|
746
|
+
const item = contextRepo.getByKey(testSessionId, 'config.database.url');
|
|
747
|
+
// Handler returns just the value for single key request
|
|
748
|
+
const handlerResponse = {
|
|
749
|
+
content: [
|
|
750
|
+
{
|
|
751
|
+
type: 'text',
|
|
752
|
+
text: item.value,
|
|
753
|
+
},
|
|
754
|
+
],
|
|
755
|
+
};
|
|
756
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toBe('postgresql://localhost:5432/myapp');
|
|
757
|
+
});
|
|
758
|
+
(0, globals_1.it)('should format multiple items without metadata', () => {
|
|
759
|
+
const result = contextRepo.queryEnhanced({
|
|
760
|
+
sessionId: testSessionId,
|
|
761
|
+
category: 'config',
|
|
762
|
+
});
|
|
763
|
+
// Handler formats as text list
|
|
764
|
+
const formattedItems = result.items
|
|
765
|
+
.map(r => `• [${r.priority}] ${r.key}: ${r.value.substring(0, 100)}${r.value.length > 100 ? '...' : ''}`)
|
|
766
|
+
.join('\n');
|
|
767
|
+
const handlerResponse = {
|
|
768
|
+
content: [
|
|
769
|
+
{
|
|
770
|
+
type: 'text',
|
|
771
|
+
text: `Found ${result.items.length} context items:\n\n${formattedItems}`,
|
|
772
|
+
},
|
|
773
|
+
],
|
|
774
|
+
};
|
|
775
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('Found');
|
|
776
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('• [');
|
|
777
|
+
(0, globals_1.expect)(handlerResponse.content[0].text.split('\n').length).toBeGreaterThan(2);
|
|
778
|
+
});
|
|
779
|
+
(0, globals_1.it)('should format response with metadata as JSON', () => {
|
|
780
|
+
const result = contextRepo.queryEnhanced({
|
|
781
|
+
sessionId: testSessionId,
|
|
782
|
+
category: 'config',
|
|
783
|
+
includeMetadata: true,
|
|
784
|
+
limit: 10,
|
|
785
|
+
offset: 0,
|
|
786
|
+
});
|
|
787
|
+
// Handler returns structured data with metadata
|
|
788
|
+
const handlerResponse = {
|
|
789
|
+
content: [
|
|
790
|
+
{
|
|
791
|
+
type: 'text',
|
|
792
|
+
text: JSON.stringify({
|
|
793
|
+
items: result.items.map(item => ({
|
|
794
|
+
key: item.key,
|
|
795
|
+
value: item.value,
|
|
796
|
+
category: item.category,
|
|
797
|
+
priority: item.priority,
|
|
798
|
+
channel: item.channel,
|
|
799
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
800
|
+
size: item.size,
|
|
801
|
+
created_at: item.created_at,
|
|
802
|
+
updated_at: item.updated_at,
|
|
803
|
+
})),
|
|
804
|
+
pagination: {
|
|
805
|
+
totalCount: result.totalCount,
|
|
806
|
+
page: 1,
|
|
807
|
+
pageSize: 10,
|
|
808
|
+
totalPages: Math.ceil(result.totalCount / 10),
|
|
809
|
+
hasNextPage: result.totalCount > 10,
|
|
810
|
+
hasPreviousPage: false,
|
|
811
|
+
},
|
|
812
|
+
}, null, 2),
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
};
|
|
816
|
+
const parsed = JSON.parse(handlerResponse.content[0].text);
|
|
817
|
+
(0, globals_1.expect)(parsed).toHaveProperty('items');
|
|
818
|
+
(0, globals_1.expect)(parsed).toHaveProperty('pagination');
|
|
819
|
+
(0, globals_1.expect)(parsed.pagination).toHaveProperty('totalCount');
|
|
820
|
+
(0, globals_1.expect)(parsed.items[0]).toHaveProperty('size');
|
|
821
|
+
(0, globals_1.expect)(parsed.items[0]).toHaveProperty('created_at');
|
|
822
|
+
});
|
|
823
|
+
(0, globals_1.it)('should handle empty results gracefully', () => {
|
|
824
|
+
const _result = contextRepo.queryEnhanced({
|
|
825
|
+
sessionId: testSessionId,
|
|
826
|
+
key: 'non-existent-key',
|
|
827
|
+
});
|
|
828
|
+
const handlerResponse = {
|
|
829
|
+
content: [
|
|
830
|
+
{
|
|
831
|
+
type: 'text',
|
|
832
|
+
text: 'No matching context found',
|
|
833
|
+
},
|
|
834
|
+
],
|
|
835
|
+
};
|
|
836
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toBe('No matching context found');
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
(0, globals_1.describe)('Performance and Edge Cases', () => {
|
|
840
|
+
(0, globals_1.it)('should handle large number of items efficiently', () => {
|
|
841
|
+
// Add 100 items
|
|
842
|
+
for (let i = 0; i < 100; i++) {
|
|
843
|
+
db.prepare(`
|
|
844
|
+
INSERT INTO context_items (id, session_id, key, value, priority)
|
|
845
|
+
VALUES (?, ?, ?, ?, ?)
|
|
846
|
+
`).run((0, uuid_1.v4)(), testSessionId, `perf.test.${i.toString().padStart(3, '0')}`, `Performance test value ${i}`, i % 3 === 0 ? 'high' : 'normal');
|
|
847
|
+
}
|
|
848
|
+
const startTime = Date.now();
|
|
849
|
+
const result = contextRepo.queryEnhanced({
|
|
850
|
+
sessionId: testSessionId,
|
|
851
|
+
keyPattern: 'perf.test.*',
|
|
852
|
+
priorities: ['high'],
|
|
853
|
+
sort: 'key_desc',
|
|
854
|
+
limit: 20,
|
|
855
|
+
});
|
|
856
|
+
const endTime = Date.now();
|
|
857
|
+
(0, globals_1.expect)(result.items.length).toBe(20);
|
|
858
|
+
(0, globals_1.expect)(result.totalCount).toBeGreaterThan(20);
|
|
859
|
+
(0, globals_1.expect)(endTime - startTime).toBeLessThan(100); // Should complete within 100ms
|
|
860
|
+
});
|
|
861
|
+
(0, globals_1.it)('should handle unicode and special characters in values', () => {
|
|
862
|
+
const unicodeKey = 'unicode.test';
|
|
863
|
+
const unicodeValue = '🚀 Unicode test with émojis and spëcial çharacters';
|
|
864
|
+
db.prepare(`
|
|
865
|
+
INSERT INTO context_items (id, session_id, key, value)
|
|
866
|
+
VALUES (?, ?, ?, ?)
|
|
867
|
+
`).run((0, uuid_1.v4)(), testSessionId, unicodeKey, unicodeValue);
|
|
868
|
+
const result = contextRepo.queryEnhanced({
|
|
869
|
+
sessionId: testSessionId,
|
|
870
|
+
key: unicodeKey,
|
|
871
|
+
});
|
|
872
|
+
(0, globals_1.expect)(result.items.length).toBe(1);
|
|
873
|
+
(0, globals_1.expect)(result.items[0].value).toBe(unicodeValue);
|
|
874
|
+
});
|
|
875
|
+
(0, globals_1.it)('should handle very long values', () => {
|
|
876
|
+
const longValue = 'A'.repeat(10000);
|
|
877
|
+
db.prepare(`
|
|
878
|
+
INSERT INTO context_items (id, session_id, key, value, size)
|
|
879
|
+
VALUES (?, ?, ?, ?, ?)
|
|
880
|
+
`).run((0, uuid_1.v4)(), testSessionId, 'long.value', longValue, Buffer.byteLength(longValue, 'utf8'));
|
|
881
|
+
const result = contextRepo.queryEnhanced({
|
|
882
|
+
sessionId: testSessionId,
|
|
883
|
+
key: 'long.value',
|
|
884
|
+
includeMetadata: true,
|
|
885
|
+
});
|
|
886
|
+
(0, globals_1.expect)(result.items.length).toBe(1);
|
|
887
|
+
(0, globals_1.expect)(result.items[0].value.length).toBe(10000);
|
|
888
|
+
(0, globals_1.expect)(result.items[0].size).toBe(10000);
|
|
889
|
+
});
|
|
890
|
+
(0, globals_1.it)('should handle all parameters at once', () => {
|
|
891
|
+
const result = contextRepo.queryEnhanced({
|
|
892
|
+
sessionId: testSessionId,
|
|
893
|
+
key: undefined,
|
|
894
|
+
category: 'config',
|
|
895
|
+
channel: undefined,
|
|
896
|
+
channels: ['main', 'secure'],
|
|
897
|
+
sort: 'created_desc',
|
|
898
|
+
limit: 5,
|
|
899
|
+
offset: 0,
|
|
900
|
+
createdAfter: '7 days ago',
|
|
901
|
+
createdBefore: 'now',
|
|
902
|
+
keyPattern: 'config.*',
|
|
903
|
+
priorities: ['high', 'normal'],
|
|
904
|
+
includeMetadata: true,
|
|
905
|
+
});
|
|
906
|
+
// Should apply all filters correctly
|
|
907
|
+
(0, globals_1.expect)(result.items.every(item => item.category === 'config' &&
|
|
908
|
+
item.key.startsWith('config.') &&
|
|
909
|
+
(item.channel === 'main' || item.channel === 'secure') &&
|
|
910
|
+
(item.priority === 'high' || item.priority === 'normal'))).toBe(true);
|
|
911
|
+
(0, globals_1.expect)(result).toHaveProperty('totalCount');
|
|
912
|
+
(0, globals_1.expect)(result.items.length).toBeLessThanOrEqual(5);
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
});
|