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,433 @@
|
|
|
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 RepositoryManager_1 = require("../../repositories/RepositoryManager");
|
|
39
|
+
const validation_1 = require("../../utils/validation");
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
(0, globals_1.describe)('Issue #13: Add validation for special characters in keys', () => {
|
|
44
|
+
let dbManager;
|
|
45
|
+
let repositories;
|
|
46
|
+
let tempDbPath;
|
|
47
|
+
let testSessionId;
|
|
48
|
+
(0, globals_1.beforeEach)(() => {
|
|
49
|
+
tempDbPath = path.join(os.tmpdir(), `test-context-${Date.now()}.db`);
|
|
50
|
+
dbManager = new database_1.DatabaseManager({
|
|
51
|
+
filename: tempDbPath,
|
|
52
|
+
maxSize: 10 * 1024 * 1024,
|
|
53
|
+
walMode: true,
|
|
54
|
+
});
|
|
55
|
+
repositories = new RepositoryManager_1.RepositoryManager(dbManager);
|
|
56
|
+
// Create test session
|
|
57
|
+
const session = repositories.sessions.create({
|
|
58
|
+
name: 'Test Session',
|
|
59
|
+
description: 'Session for key validation tests',
|
|
60
|
+
});
|
|
61
|
+
testSessionId = session.id;
|
|
62
|
+
});
|
|
63
|
+
(0, globals_1.afterEach)(() => {
|
|
64
|
+
dbManager.close();
|
|
65
|
+
try {
|
|
66
|
+
fs.unlinkSync(tempDbPath);
|
|
67
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
68
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
69
|
+
}
|
|
70
|
+
catch (_e) {
|
|
71
|
+
// Ignore
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
(0, globals_1.describe)('validateKey function', () => {
|
|
75
|
+
(0, globals_1.describe)('Invalid characters that should be rejected', () => {
|
|
76
|
+
(0, globals_1.it)('should reject keys with spaces', () => {
|
|
77
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key with spaces')).toThrow(validation_1.ValidationError);
|
|
78
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key with spaces')).toThrow(/special characters/i);
|
|
79
|
+
});
|
|
80
|
+
(0, globals_1.it)('should reject keys with tabs', () => {
|
|
81
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\twith\ttabs')).toThrow(validation_1.ValidationError);
|
|
82
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\twith\ttabs')).toThrow(/special characters/i);
|
|
83
|
+
});
|
|
84
|
+
(0, globals_1.it)('should reject keys with newlines', () => {
|
|
85
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\nwith\nnewlines')).toThrow(validation_1.ValidationError);
|
|
86
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\nwith\nnewlines')).toThrow(/special characters/i);
|
|
87
|
+
});
|
|
88
|
+
(0, globals_1.it)('should reject keys with carriage returns', () => {
|
|
89
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\rwith\rreturns')).toThrow(validation_1.ValidationError);
|
|
90
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\rwith\rreturns')).toThrow(/special characters/i);
|
|
91
|
+
});
|
|
92
|
+
(0, globals_1.it)('should reject keys with null bytes', () => {
|
|
93
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\0with\0null')).toThrow(validation_1.ValidationError);
|
|
94
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\0with\0null')).toThrow(/invalid characters/i);
|
|
95
|
+
});
|
|
96
|
+
(0, globals_1.it)('should reject keys with control characters', () => {
|
|
97
|
+
// Test various control characters
|
|
98
|
+
const controlChars = [
|
|
99
|
+
'\x00',
|
|
100
|
+
'\x01',
|
|
101
|
+
'\x02',
|
|
102
|
+
'\x03',
|
|
103
|
+
'\x04',
|
|
104
|
+
'\x05',
|
|
105
|
+
'\x06',
|
|
106
|
+
'\x07',
|
|
107
|
+
'\x08',
|
|
108
|
+
'\x0B',
|
|
109
|
+
'\x0C',
|
|
110
|
+
'\x0E',
|
|
111
|
+
'\x0F',
|
|
112
|
+
'\x10',
|
|
113
|
+
'\x11',
|
|
114
|
+
'\x12',
|
|
115
|
+
'\x13',
|
|
116
|
+
'\x14',
|
|
117
|
+
'\x15',
|
|
118
|
+
'\x16',
|
|
119
|
+
'\x17',
|
|
120
|
+
'\x18',
|
|
121
|
+
'\x19',
|
|
122
|
+
'\x1A',
|
|
123
|
+
'\x1B',
|
|
124
|
+
'\x1C',
|
|
125
|
+
'\x1D',
|
|
126
|
+
'\x1E',
|
|
127
|
+
'\x1F',
|
|
128
|
+
];
|
|
129
|
+
controlChars.forEach(char => {
|
|
130
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(`key${char}test`)).toThrow(validation_1.ValidationError);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
(0, globals_1.it)('should reject keys with backslashes', () => {
|
|
134
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\\with\\backslashes')).toThrow(validation_1.ValidationError);
|
|
135
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key\\with\\backslashes')).toThrow(/special characters/i);
|
|
136
|
+
});
|
|
137
|
+
(0, globals_1.it)('should reject keys with quotes', () => {
|
|
138
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key"with"quotes')).toThrow(validation_1.ValidationError);
|
|
139
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)("key'with'quotes")).toThrow(validation_1.ValidationError);
|
|
140
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key`with`backticks')).toThrow(validation_1.ValidationError);
|
|
141
|
+
});
|
|
142
|
+
(0, globals_1.it)('should reject keys with special shell characters', () => {
|
|
143
|
+
const shellChars = [
|
|
144
|
+
'|',
|
|
145
|
+
'&',
|
|
146
|
+
';',
|
|
147
|
+
'<',
|
|
148
|
+
'>',
|
|
149
|
+
'(',
|
|
150
|
+
')',
|
|
151
|
+
'{',
|
|
152
|
+
'}',
|
|
153
|
+
'[',
|
|
154
|
+
']',
|
|
155
|
+
'$',
|
|
156
|
+
'#',
|
|
157
|
+
'!',
|
|
158
|
+
'~',
|
|
159
|
+
];
|
|
160
|
+
shellChars.forEach(char => {
|
|
161
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(`key${char}test`)).toThrow(validation_1.ValidationError);
|
|
162
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(`key${char}test`)).toThrow(/special characters/i);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
(0, globals_1.it)('should reject keys with wildcards', () => {
|
|
166
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key*with*asterisks')).toThrow(validation_1.ValidationError);
|
|
167
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key?with?questions')).toThrow(validation_1.ValidationError);
|
|
168
|
+
});
|
|
169
|
+
(0, globals_1.it)('should reject keys that start or end with whitespace', () => {
|
|
170
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(' leading_space')).toThrow(validation_1.ValidationError);
|
|
171
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('trailing_space ')).toThrow(validation_1.ValidationError);
|
|
172
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('\tleading_tab')).toThrow(validation_1.ValidationError);
|
|
173
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('trailing_tab\t')).toThrow(validation_1.ValidationError);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
(0, globals_1.describe)('Valid characters that should be accepted', () => {
|
|
177
|
+
(0, globals_1.it)('should accept alphanumeric characters', () => {
|
|
178
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('simpleKey123')).toBe('simpleKey123');
|
|
179
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('UPPERCASE')).toBe('UPPERCASE');
|
|
180
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('lowercase')).toBe('lowercase');
|
|
181
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('MixedCase123')).toBe('MixedCase123');
|
|
182
|
+
});
|
|
183
|
+
(0, globals_1.it)('should accept underscores', () => {
|
|
184
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('key_with_underscores')).toBe('key_with_underscores');
|
|
185
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('_leading_underscore')).toBe('_leading_underscore');
|
|
186
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('trailing_underscore_')).toBe('trailing_underscore_');
|
|
187
|
+
});
|
|
188
|
+
(0, globals_1.it)('should accept hyphens', () => {
|
|
189
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('key-with-hyphens')).toBe('key-with-hyphens');
|
|
190
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('kebab-case-key')).toBe('kebab-case-key');
|
|
191
|
+
});
|
|
192
|
+
(0, globals_1.it)('should accept dots', () => {
|
|
193
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('key.with.dots')).toBe('key.with.dots');
|
|
194
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('namespace.key')).toBe('namespace.key');
|
|
195
|
+
});
|
|
196
|
+
(0, globals_1.it)('should accept forward slashes for path-like keys', () => {
|
|
197
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('path/to/key')).toBe('path/to/key');
|
|
198
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('feature/component/item')).toBe('feature/component/item');
|
|
199
|
+
});
|
|
200
|
+
(0, globals_1.it)('should accept colons for namespace separation', () => {
|
|
201
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('namespace:key')).toBe('namespace:key');
|
|
202
|
+
(0, globals_1.expect)((0, validation_1.validateKey)('module:component:item')).toBe('module:component:item');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
(0, globals_1.describe)('Edge cases', () => {
|
|
206
|
+
(0, globals_1.it)('should reject empty keys', () => {
|
|
207
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('')).toThrow(validation_1.ValidationError);
|
|
208
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('')).toThrow(/empty/i);
|
|
209
|
+
});
|
|
210
|
+
(0, globals_1.it)('should reject null or undefined', () => {
|
|
211
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(null)).toThrow(validation_1.ValidationError);
|
|
212
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(undefined)).toThrow(validation_1.ValidationError);
|
|
213
|
+
});
|
|
214
|
+
(0, globals_1.it)('should reject non-string values', () => {
|
|
215
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(123)).toThrow(validation_1.ValidationError);
|
|
216
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)({})).toThrow(validation_1.ValidationError);
|
|
217
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)([])).toThrow(validation_1.ValidationError);
|
|
218
|
+
});
|
|
219
|
+
(0, globals_1.it)('should reject keys that are too long', () => {
|
|
220
|
+
const longKey = 'a'.repeat(256);
|
|
221
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(longKey)).toThrow(validation_1.ValidationError);
|
|
222
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(longKey)).toThrow(/too long/i);
|
|
223
|
+
});
|
|
224
|
+
(0, globals_1.it)('should accept keys at maximum length', () => {
|
|
225
|
+
const maxKey = 'a'.repeat(255);
|
|
226
|
+
(0, globals_1.expect)((0, validation_1.validateKey)(maxKey)).toBe(maxKey);
|
|
227
|
+
});
|
|
228
|
+
(0, globals_1.it)('should handle Unicode characters appropriately', () => {
|
|
229
|
+
// Should reject emoji and other non-ASCII Unicode
|
|
230
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key_with_😀_emoji')).toThrow(validation_1.ValidationError);
|
|
231
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key_with_中文_characters')).toThrow(validation_1.ValidationError);
|
|
232
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key_with_🔥_fire')).toThrow(validation_1.ValidationError);
|
|
233
|
+
// Should reject other Unicode special characters
|
|
234
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key_with_©_copyright')).toThrow(validation_1.ValidationError);
|
|
235
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('key_with_™_trademark')).toThrow(validation_1.ValidationError);
|
|
236
|
+
});
|
|
237
|
+
(0, globals_1.it)('should properly validate keys that become empty after trimming', () => {
|
|
238
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(' ')).toThrow(validation_1.ValidationError);
|
|
239
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('\t\t\t')).toThrow(validation_1.ValidationError);
|
|
240
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)('\n\n\n')).toThrow(validation_1.ValidationError);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
(0, globals_1.describe)('context_save with key validation', () => {
|
|
245
|
+
(0, globals_1.it)('should reject invalid keys when saving context', () => {
|
|
246
|
+
const invalidKeys = [
|
|
247
|
+
'key with spaces',
|
|
248
|
+
'key\twith\ttabs',
|
|
249
|
+
'key\nwith\nnewlines',
|
|
250
|
+
'key|with|pipes',
|
|
251
|
+
'key;with;semicolons',
|
|
252
|
+
'key$with$dollars',
|
|
253
|
+
];
|
|
254
|
+
invalidKeys.forEach(invalidKey => {
|
|
255
|
+
(0, globals_1.expect)(() => {
|
|
256
|
+
repositories.contexts.save(testSessionId, {
|
|
257
|
+
key: invalidKey,
|
|
258
|
+
value: 'test value',
|
|
259
|
+
category: 'task',
|
|
260
|
+
priority: 'normal',
|
|
261
|
+
});
|
|
262
|
+
}).toThrow(validation_1.ValidationError);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
(0, globals_1.it)('should accept valid keys when saving context', () => {
|
|
266
|
+
const validKeys = [
|
|
267
|
+
'simple_key',
|
|
268
|
+
'key-with-hyphens',
|
|
269
|
+
'key.with.dots',
|
|
270
|
+
'path/to/key',
|
|
271
|
+
'namespace:key',
|
|
272
|
+
'MixedCase123',
|
|
273
|
+
];
|
|
274
|
+
validKeys.forEach(validKey => {
|
|
275
|
+
const result = repositories.contexts.save(testSessionId, {
|
|
276
|
+
key: validKey,
|
|
277
|
+
value: 'test value',
|
|
278
|
+
category: 'task',
|
|
279
|
+
priority: 'normal',
|
|
280
|
+
});
|
|
281
|
+
(0, globals_1.expect)(result.key).toBe(validKey);
|
|
282
|
+
(0, globals_1.expect)(result.value).toBe('test value');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
(0, globals_1.it)('should provide clear error messages for invalid keys', () => {
|
|
286
|
+
try {
|
|
287
|
+
repositories.contexts.save(testSessionId, {
|
|
288
|
+
key: 'key with spaces',
|
|
289
|
+
value: 'test value',
|
|
290
|
+
});
|
|
291
|
+
fail('Expected ValidationError to be thrown');
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
(0, globals_1.expect)(error).toBeInstanceOf(validation_1.ValidationError);
|
|
295
|
+
(0, globals_1.expect)(error.message).toMatch(/special characters/i);
|
|
296
|
+
(0, globals_1.expect)(error.message).toMatch(/spaces/i);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
(0, globals_1.describe)('context_batch_save with key validation', () => {
|
|
301
|
+
(0, globals_1.it)('should validate all keys in batch save', () => {
|
|
302
|
+
const items = [
|
|
303
|
+
{ key: 'valid_key_1', value: 'value1' },
|
|
304
|
+
{ key: 'key with spaces', value: 'value2' },
|
|
305
|
+
{ key: 'valid_key_3', value: 'value3' },
|
|
306
|
+
{ key: 'key\twith\ttabs', value: 'value4' },
|
|
307
|
+
];
|
|
308
|
+
// Note: Current implementation doesn't validate special characters in batch operations
|
|
309
|
+
// This test expects validation to be added
|
|
310
|
+
const result = repositories.contexts.batchSave(testSessionId, items);
|
|
311
|
+
// Once validation is implemented, we expect some items to fail
|
|
312
|
+
const failedItems = result.results.filter(r => !r.success);
|
|
313
|
+
(0, globals_1.expect)(failedItems).toHaveLength(2); // Should fail for items with spaces and tabs
|
|
314
|
+
// Check that the error messages mention special characters
|
|
315
|
+
(0, globals_1.expect)(failedItems[0].error).toMatch(/special characters/i);
|
|
316
|
+
(0, globals_1.expect)(failedItems[1].error).toMatch(/special characters/i);
|
|
317
|
+
});
|
|
318
|
+
(0, globals_1.it)('should accept batch with all valid keys', () => {
|
|
319
|
+
const items = [
|
|
320
|
+
{ key: 'valid_key_1', value: 'value1' },
|
|
321
|
+
{ key: 'valid-key-2', value: 'value2' },
|
|
322
|
+
{ key: 'valid.key.3', value: 'value3' },
|
|
323
|
+
{ key: 'valid/key/4', value: 'value4' },
|
|
324
|
+
];
|
|
325
|
+
const result = repositories.contexts.batchSave(testSessionId, items);
|
|
326
|
+
const successCount = result.results.filter(r => r.success).length;
|
|
327
|
+
(0, globals_1.expect)(successCount).toBe(4);
|
|
328
|
+
const savedItems = repositories.contexts.getBySessionId(testSessionId);
|
|
329
|
+
(0, globals_1.expect)(savedItems).toHaveLength(4);
|
|
330
|
+
});
|
|
331
|
+
(0, globals_1.it)('should provide detailed error report for batch validation failures', () => {
|
|
332
|
+
const items = [
|
|
333
|
+
{ key: 'valid_key', value: 'value1' },
|
|
334
|
+
{ key: 'invalid key', value: 'value2' },
|
|
335
|
+
{ key: 'another|invalid', value: 'value3' },
|
|
336
|
+
];
|
|
337
|
+
const result = repositories.contexts.batchSave(testSessionId, items);
|
|
338
|
+
// Once validation is implemented, we expect some items to fail
|
|
339
|
+
const failedItems = result.results.filter(r => !r.success);
|
|
340
|
+
(0, globals_1.expect)(failedItems).toHaveLength(2); // Should fail for items with special characters
|
|
341
|
+
// Check specific failures
|
|
342
|
+
const invalidKeyResult = result.results.find(r => r.key === 'invalid key');
|
|
343
|
+
(0, globals_1.expect)(invalidKeyResult?.success).toBe(false);
|
|
344
|
+
(0, globals_1.expect)(invalidKeyResult?.error).toMatch(/special characters/i);
|
|
345
|
+
const pipeKeyResult = result.results.find(r => r.key === 'another|invalid');
|
|
346
|
+
(0, globals_1.expect)(pipeKeyResult?.success).toBe(false);
|
|
347
|
+
(0, globals_1.expect)(pipeKeyResult?.error).toMatch(/special characters/i);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
(0, globals_1.describe)('Protection against malicious keys', () => {
|
|
351
|
+
(0, globals_1.it)('should prevent path traversal attempts in keys', () => {
|
|
352
|
+
const pathTraversalKeys = [
|
|
353
|
+
'../../../etc/passwd',
|
|
354
|
+
'..\\..\\..\\windows\\system32',
|
|
355
|
+
'key/../../../secret',
|
|
356
|
+
'key/../../admin',
|
|
357
|
+
];
|
|
358
|
+
pathTraversalKeys.forEach(key => {
|
|
359
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(key)).toThrow(validation_1.ValidationError);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
(0, globals_1.it)('should prevent SQL injection attempts in keys', () => {
|
|
363
|
+
const sqlInjectionKeys = [
|
|
364
|
+
"key'; DROP TABLE sessions; --",
|
|
365
|
+
'key" OR "1"="1',
|
|
366
|
+
'key`; DELETE FROM context_items; --',
|
|
367
|
+
];
|
|
368
|
+
sqlInjectionKeys.forEach(key => {
|
|
369
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(key)).toThrow(validation_1.ValidationError);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
(0, globals_1.it)('should prevent script injection attempts in keys', () => {
|
|
373
|
+
const scriptInjectionKeys = [
|
|
374
|
+
'<script>alert("xss")</script>',
|
|
375
|
+
'key<img src=x onerror=alert(1)>',
|
|
376
|
+
'javascript:alert(1)',
|
|
377
|
+
];
|
|
378
|
+
scriptInjectionKeys.forEach(key => {
|
|
379
|
+
(0, globals_1.expect)(() => (0, validation_1.validateKey)(key)).toThrow(validation_1.ValidationError);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
(0, globals_1.describe)('Performance considerations', () => {
|
|
384
|
+
(0, globals_1.it)('should validate keys efficiently for large batches', () => {
|
|
385
|
+
const largeItems = Array.from({ length: 100 }, (_, i) => ({
|
|
386
|
+
key: `valid_key_${i}`,
|
|
387
|
+
value: `value_${i}`,
|
|
388
|
+
}));
|
|
389
|
+
const startTime = Date.now();
|
|
390
|
+
const result = repositories.contexts.batchSave(testSessionId, largeItems);
|
|
391
|
+
const endTime = Date.now();
|
|
392
|
+
const successCount = result.results.filter(r => r.success).length;
|
|
393
|
+
(0, globals_1.expect)(successCount).toBe(100);
|
|
394
|
+
(0, globals_1.expect)(endTime - startTime).toBeLessThan(1000); // Should complete within 1 second
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
(0, globals_1.describe)('Error message quality', () => {
|
|
398
|
+
(0, globals_1.it)('should provide helpful error messages for common mistakes', () => {
|
|
399
|
+
const testCases = [
|
|
400
|
+
{
|
|
401
|
+
key: 'key with spaces',
|
|
402
|
+
expectedError: /spaces are not allowed/i,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
key: 'key\twith\ttabs',
|
|
406
|
+
expectedError: /tabs are not allowed/i,
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
key: 'key|pipe',
|
|
410
|
+
expectedError: /special characters/i,
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
key: '',
|
|
414
|
+
expectedError: /cannot be empty/i,
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
key: 'a'.repeat(256),
|
|
418
|
+
expectedError: /too long.*255 characters/i,
|
|
419
|
+
},
|
|
420
|
+
];
|
|
421
|
+
testCases.forEach(({ key, expectedError }) => {
|
|
422
|
+
try {
|
|
423
|
+
(0, validation_1.validateKey)(key);
|
|
424
|
+
fail(`Expected ValidationError for key: ${key}`);
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
(0, globals_1.expect)(error).toBeInstanceOf(validation_1.ValidationError);
|
|
428
|
+
(0, globals_1.expect)(error.message).toMatch(expectedError);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
});
|