ahok-skill 1.3.1

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.
Files changed (141) hide show
  1. package/.prettierrc +8 -0
  2. package/Dockerfile +59 -0
  3. package/RAW_SKILL.md +219 -0
  4. package/README.md +277 -0
  5. package/SKILL.md +58 -0
  6. package/bin/opm.js +268 -0
  7. package/data/openmemory.sqlite +0 -0
  8. package/data/openmemory.sqlite-shm +0 -0
  9. package/data/openmemory.sqlite-wal +0 -0
  10. package/dist/ai/graph.js +293 -0
  11. package/dist/ai/mcp.js +397 -0
  12. package/dist/cli.js +78 -0
  13. package/dist/core/cfg.js +87 -0
  14. package/dist/core/db.js +636 -0
  15. package/dist/core/memory.js +116 -0
  16. package/dist/core/migrate.js +227 -0
  17. package/dist/core/models.js +105 -0
  18. package/dist/core/telemetry.js +57 -0
  19. package/dist/core/types.js +2 -0
  20. package/dist/core/vector/postgres.js +52 -0
  21. package/dist/core/vector/valkey.js +246 -0
  22. package/dist/core/vector_store.js +2 -0
  23. package/dist/index.js +44 -0
  24. package/dist/memory/decay.js +301 -0
  25. package/dist/memory/embed.js +675 -0
  26. package/dist/memory/hsg.js +959 -0
  27. package/dist/memory/reflect.js +131 -0
  28. package/dist/memory/user_summary.js +99 -0
  29. package/dist/migrate.js +9 -0
  30. package/dist/ops/compress.js +255 -0
  31. package/dist/ops/dynamics.js +189 -0
  32. package/dist/ops/extract.js +333 -0
  33. package/dist/ops/ingest.js +214 -0
  34. package/dist/server/index.js +109 -0
  35. package/dist/server/middleware/auth.js +137 -0
  36. package/dist/server/routes/auth.js +186 -0
  37. package/dist/server/routes/compression.js +108 -0
  38. package/dist/server/routes/dashboard.js +399 -0
  39. package/dist/server/routes/docs.js +241 -0
  40. package/dist/server/routes/dynamics.js +312 -0
  41. package/dist/server/routes/ide.js +280 -0
  42. package/dist/server/routes/index.js +33 -0
  43. package/dist/server/routes/keys.js +132 -0
  44. package/dist/server/routes/langgraph.js +61 -0
  45. package/dist/server/routes/memory.js +213 -0
  46. package/dist/server/routes/sources.js +140 -0
  47. package/dist/server/routes/system.js +63 -0
  48. package/dist/server/routes/temporal.js +293 -0
  49. package/dist/server/routes/users.js +101 -0
  50. package/dist/server/routes/vercel.js +57 -0
  51. package/dist/server/server.js +211 -0
  52. package/dist/server.js +3 -0
  53. package/dist/sources/base.js +223 -0
  54. package/dist/sources/github.js +171 -0
  55. package/dist/sources/google_drive.js +166 -0
  56. package/dist/sources/google_sheets.js +112 -0
  57. package/dist/sources/google_slides.js +139 -0
  58. package/dist/sources/index.js +34 -0
  59. package/dist/sources/notion.js +165 -0
  60. package/dist/sources/onedrive.js +143 -0
  61. package/dist/sources/web_crawler.js +166 -0
  62. package/dist/temporal_graph/index.js +20 -0
  63. package/dist/temporal_graph/query.js +240 -0
  64. package/dist/temporal_graph/store.js +116 -0
  65. package/dist/temporal_graph/timeline.js +241 -0
  66. package/dist/temporal_graph/types.js +2 -0
  67. package/dist/utils/chunking.js +60 -0
  68. package/dist/utils/index.js +31 -0
  69. package/dist/utils/keyword.js +94 -0
  70. package/dist/utils/text.js +120 -0
  71. package/nodemon.json +7 -0
  72. package/package.json +50 -0
  73. package/references/api_reference.md +66 -0
  74. package/references/examples.md +45 -0
  75. package/src/ai/graph.ts +363 -0
  76. package/src/ai/mcp.ts +494 -0
  77. package/src/cli.ts +94 -0
  78. package/src/core/cfg.ts +110 -0
  79. package/src/core/db.ts +1052 -0
  80. package/src/core/memory.ts +99 -0
  81. package/src/core/migrate.ts +302 -0
  82. package/src/core/models.ts +107 -0
  83. package/src/core/telemetry.ts +47 -0
  84. package/src/core/types.ts +130 -0
  85. package/src/core/vector/postgres.ts +61 -0
  86. package/src/core/vector/valkey.ts +261 -0
  87. package/src/core/vector_store.ts +9 -0
  88. package/src/index.ts +5 -0
  89. package/src/memory/decay.ts +427 -0
  90. package/src/memory/embed.ts +707 -0
  91. package/src/memory/hsg.ts +1245 -0
  92. package/src/memory/reflect.ts +158 -0
  93. package/src/memory/user_summary.ts +110 -0
  94. package/src/migrate.ts +8 -0
  95. package/src/ops/compress.ts +296 -0
  96. package/src/ops/dynamics.ts +272 -0
  97. package/src/ops/extract.ts +360 -0
  98. package/src/ops/ingest.ts +286 -0
  99. package/src/server/index.ts +159 -0
  100. package/src/server/middleware/auth.ts +156 -0
  101. package/src/server/routes/auth.ts +223 -0
  102. package/src/server/routes/compression.ts +106 -0
  103. package/src/server/routes/dashboard.ts +420 -0
  104. package/src/server/routes/docs.ts +380 -0
  105. package/src/server/routes/dynamics.ts +516 -0
  106. package/src/server/routes/ide.ts +283 -0
  107. package/src/server/routes/index.ts +32 -0
  108. package/src/server/routes/keys.ts +131 -0
  109. package/src/server/routes/langgraph.ts +71 -0
  110. package/src/server/routes/memory.ts +440 -0
  111. package/src/server/routes/sources.ts +111 -0
  112. package/src/server/routes/system.ts +68 -0
  113. package/src/server/routes/temporal.ts +335 -0
  114. package/src/server/routes/users.ts +111 -0
  115. package/src/server/routes/vercel.ts +55 -0
  116. package/src/server/server.js +215 -0
  117. package/src/server.ts +1 -0
  118. package/src/sources/base.ts +257 -0
  119. package/src/sources/github.ts +156 -0
  120. package/src/sources/google_drive.ts +144 -0
  121. package/src/sources/google_sheets.ts +85 -0
  122. package/src/sources/google_slides.ts +115 -0
  123. package/src/sources/index.ts +19 -0
  124. package/src/sources/notion.ts +148 -0
  125. package/src/sources/onedrive.ts +131 -0
  126. package/src/sources/web_crawler.ts +161 -0
  127. package/src/temporal_graph/index.ts +4 -0
  128. package/src/temporal_graph/query.ts +299 -0
  129. package/src/temporal_graph/store.ts +156 -0
  130. package/src/temporal_graph/timeline.ts +319 -0
  131. package/src/temporal_graph/types.ts +41 -0
  132. package/src/utils/chunking.ts +66 -0
  133. package/src/utils/index.ts +25 -0
  134. package/src/utils/keyword.ts +137 -0
  135. package/src/utils/text.ts +115 -0
  136. package/tests/test_api_workspace_management.ts +413 -0
  137. package/tests/test_bulk_delete.ts +267 -0
  138. package/tests/test_omnibus.ts +166 -0
  139. package/tests/test_workspace_management.ts +278 -0
  140. package/tests/verify.ts +104 -0
  141. package/tsconfig.json +15 -0
@@ -0,0 +1,413 @@
1
+ /**
2
+ * Extensive API Tests for Memory Workspace Management
3
+ * Tests the deployed backend endpoints via HTTP
4
+ */
5
+
6
+ // Configuration - update this to your deployed API URL
7
+ const API_URL = process.env.API_URL || "https://zqmt62peqz.us-east-1.awsapprunner.com";
8
+ const API_KEY = process.env.API_KEY || "napi_ce3w2t2k6vjmms1j7hnc8j0o0owgje9t65opzmp8cc3b6tr4nyxnhi2lun7lc90f";
9
+
10
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
11
+
12
+ let testWorkspaceIds: string[] = [];
13
+ let testMemoryIds: string[] = [];
14
+
15
+ async function apiRequest(method: string, path: string, body?: any): Promise<{ status: number; data: any }> {
16
+ const url = `${API_URL}${path}`;
17
+ const options: RequestInit = {
18
+ method,
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ "x-api-key": API_KEY,
22
+ },
23
+ };
24
+
25
+ if (body) {
26
+ options.body = JSON.stringify(body);
27
+ }
28
+
29
+ try {
30
+ const response = await fetch(url, options);
31
+ const data = await response.json().catch(() => ({}));
32
+ return { status: response.status, data };
33
+ } catch (error: any) {
34
+ return { status: 0, data: { error: error.message } };
35
+ }
36
+ }
37
+
38
+ async function setup() {
39
+ console.log("=".repeat(70));
40
+ console.log("Extensive API Tests for Memory Workspace Management");
41
+ console.log("=".repeat(70));
42
+ console.log(`\nAPI URL: ${API_URL}`);
43
+ console.log(`API Key: ${API_KEY.substring(0, 20)}...`);
44
+ }
45
+
46
+ /**
47
+ * Test 0: Health check
48
+ */
49
+ async function test_health_check() {
50
+ console.log("\n[Test 0] Health Check...");
51
+
52
+ const { status } = await apiRequest("GET", "/health");
53
+ if (status === 200) {
54
+ console.log(" ✓ API is healthy");
55
+ } else {
56
+ console.log(` ⚠ Health check returned status ${status} (continuing anyway)`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Test 1: Create workspaces via POST /keys
62
+ */
63
+ async function test_create_workspaces() {
64
+ console.log("\n[Test 1] Creating workspaces via POST /keys...");
65
+
66
+ const workspaces = [
67
+ { label: "Test-Development" },
68
+ { label: "Test-Production" },
69
+ { label: "Test-Staging" },
70
+ ];
71
+
72
+ for (const ws of workspaces) {
73
+ const { status, data } = await apiRequest("POST", "/keys", ws);
74
+
75
+ if (status === 200 && data.id) {
76
+ testWorkspaceIds.push(data.id);
77
+ console.log(` ✓ Created workspace: ${ws.label} (${data.id})`);
78
+ console.log(` Secret key: ${data.secret_key?.substring(0, 25)}...`);
79
+ } else {
80
+ throw new Error(`Failed to create workspace: ${status} ${JSON.stringify(data)}`);
81
+ }
82
+ }
83
+
84
+ console.log(` ✓ Total workspaces created: ${testWorkspaceIds.length}`);
85
+ }
86
+
87
+ /**
88
+ * Test 2: List workspaces via GET /keys
89
+ */
90
+ async function test_list_workspaces() {
91
+ console.log("\n[Test 2] Listing workspaces via GET /keys...");
92
+
93
+ const { status, data } = await apiRequest("GET", "/keys");
94
+
95
+ if (status !== 200) {
96
+ throw new Error(`Failed to list workspaces: ${status} ${JSON.stringify(data)}`);
97
+ }
98
+
99
+ const keys = data.keys || [];
100
+ console.log(` ✓ Found ${keys.length} workspaces`);
101
+
102
+ // Verify our test workspaces exist
103
+ const foundIds = keys.map((k: any) => k.id);
104
+ for (const id of testWorkspaceIds) {
105
+ if (!foundIds.includes(id)) {
106
+ throw new Error(`Workspace ${id} not found in list`);
107
+ }
108
+ }
109
+
110
+ console.log(" ✓ All test workspaces found in list");
111
+ }
112
+
113
+ /**
114
+ * Test 3: Get workspace statistics via GET /keys/stats
115
+ */
116
+ async function test_workspace_stats_empty() {
117
+ console.log("\n[Test 3] Getting workspace statistics (empty) via GET /keys/stats...");
118
+
119
+ const { status, data } = await apiRequest("GET", "/keys/stats");
120
+
121
+ if (status !== 200) {
122
+ throw new Error(`Failed to get stats: ${status} ${JSON.stringify(data)}`);
123
+ }
124
+
125
+ const workspaces = data.workspaces || [];
126
+ console.log(` ✓ Stats returned for ${workspaces.length} workspaces`);
127
+
128
+ // Verify memory counts are 0 for new workspaces
129
+ for (const ws of workspaces) {
130
+ if (testWorkspaceIds.includes(ws.id)) {
131
+ console.log(` ${ws.label}: ${ws.memory_count} memories`);
132
+ }
133
+ }
134
+
135
+ console.log(" ✓ Workspace statistics retrieved successfully");
136
+ }
137
+
138
+ /**
139
+ * Test 4: Rename workspace via PATCH /keys/:id
140
+ */
141
+ async function test_rename_workspace() {
142
+ console.log("\n[Test 4] Renaming workspace via PATCH /keys/:id...");
143
+
144
+ const workspaceId = testWorkspaceIds[0];
145
+ const newLabel = "Test-Development-RENAMED";
146
+
147
+ const { status, data } = await apiRequest("PATCH", `/keys/${workspaceId}`, { label: newLabel });
148
+
149
+ if (status !== 200) {
150
+ throw new Error(`Failed to rename workspace: ${status} ${JSON.stringify(data)}`);
151
+ }
152
+
153
+ if (data.label !== newLabel) {
154
+ throw new Error(`Rename failed: expected "${newLabel}", got "${data.label}"`);
155
+ }
156
+
157
+ console.log(` ✓ Workspace renamed to: ${newLabel}`);
158
+ console.log(` ✓ Updated at: ${new Date(data.updated_at).toISOString()}`);
159
+ }
160
+
161
+ /**
162
+ * Test 5: Rename validation - empty label should fail
163
+ */
164
+ async function test_rename_validation() {
165
+ console.log("\n[Test 5] Testing rename validation (empty label)...");
166
+
167
+ const workspaceId = testWorkspaceIds[0];
168
+
169
+ // Test empty string
170
+ let { status } = await apiRequest("PATCH", `/keys/${workspaceId}`, { label: "" });
171
+
172
+ if (status === 400) {
173
+ console.log(" ✓ Empty label correctly rejected with 400");
174
+ } else {
175
+ console.log(` ⚠ Expected 400 for empty label, got ${status}`);
176
+ }
177
+
178
+ // Test whitespace only
179
+ ({ status } = await apiRequest("PATCH", `/keys/${workspaceId}`, { label: " " }));
180
+
181
+ if (status === 400) {
182
+ console.log(" ✓ Whitespace-only label correctly rejected with 400");
183
+ } else {
184
+ console.log(` ⚠ Expected 400 for whitespace label, got ${status}`);
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Test 6: Create memories in workspaces via POST /memory/add
190
+ * Note: Using memory_key_id in body (requires updated backend) or workspace secret key
191
+ */
192
+ async function test_create_memories() {
193
+ console.log("\n[Test 6] Creating memories in workspaces via POST /memory/add...");
194
+
195
+ const memories = [
196
+ { content: "Test memory 1 in Development workspace - unique " + Date.now(), workspace_idx: 0 },
197
+ { content: "Test memory 2 in Development workspace - unique " + Date.now(), workspace_idx: 0 },
198
+ { content: "Test memory 3 in Production workspace - unique " + Date.now(), workspace_idx: 1 },
199
+ { content: "Test memory 4 in Production workspace - unique " + Date.now(), workspace_idx: 1 },
200
+ { content: "Test memory 5 in Production workspace - unique " + Date.now(), workspace_idx: 1 },
201
+ { content: "Test memory 6 in Staging workspace - unique " + Date.now(), workspace_idx: 2 },
202
+ ];
203
+
204
+ for (const mem of memories) {
205
+ // Try with memory_key_id in body (works with updated backend)
206
+ const { status, data } = await apiRequest("POST", "/memory/add", {
207
+ content: mem.content,
208
+ memory_key_id: testWorkspaceIds[mem.workspace_idx]
209
+ });
210
+
211
+ if (status === 200 && data.id) {
212
+ testMemoryIds.push(data.id);
213
+ console.log(` ✓ Created memory: ${data.id.substring(0, 8)}... in workspace ${mem.workspace_idx + 1}`);
214
+ } else {
215
+ console.log(` ⚠ Failed to create memory: ${status} ${JSON.stringify(data)}`);
216
+ }
217
+
218
+ await sleep(300); // Small delay between creates
219
+ }
220
+
221
+ console.log(` ✓ Total memories created: ${testMemoryIds.length}`);
222
+ }
223
+
224
+ /**
225
+ * Test 7: Verify workspace statistics with memories
226
+ */
227
+ async function test_workspace_stats_with_memories() {
228
+ console.log("\n[Test 7] Verifying workspace statistics with memories...");
229
+
230
+ await sleep(1000); // Wait for indexing
231
+
232
+ const { status, data } = await apiRequest("GET", "/keys/stats");
233
+
234
+ if (status !== 200) {
235
+ throw new Error(`Failed to get stats: ${status}`);
236
+ }
237
+
238
+ const workspaces = data.workspaces || [];
239
+ const statsMap = new Map<string, number>();
240
+
241
+ for (const ws of workspaces) {
242
+ if (testWorkspaceIds.includes(ws.id)) {
243
+ statsMap.set(ws.id, ws.memory_count);
244
+ console.log(` ${ws.label}: ${ws.memory_count} memories`);
245
+ }
246
+ }
247
+
248
+ // Expected: ws1=2, ws2=3, ws3=1
249
+ const ws1Count = statsMap.get(testWorkspaceIds[0]) || 0;
250
+ const ws2Count = statsMap.get(testWorkspaceIds[1]) || 0;
251
+ const ws3Count = statsMap.get(testWorkspaceIds[2]) || 0;
252
+
253
+ console.log(` Expected: Development=2, Production=3, Staging=1`);
254
+ console.log(` Actual: Development=${ws1Count}, Production=${ws2Count}, Staging=${ws3Count}`);
255
+
256
+ if (ws1Count === 2 && ws2Count === 3 && ws3Count === 1) {
257
+ console.log(" ✓ All memory counts are correct!");
258
+ } else {
259
+ console.log(" ⚠ Memory counts don't match expected values (may need more time for indexing)");
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Test 8: Bulk move memories via POST /memory/bulk-move
265
+ */
266
+ async function test_bulk_move() {
267
+ console.log("\n[Test 8] Testing bulk move via POST /memory/bulk-move...");
268
+
269
+ if (testMemoryIds.length < 2) {
270
+ console.log(" ⚠ Not enough memories to test bulk move");
271
+ return;
272
+ }
273
+
274
+ // Move first 2 memories (from Development) to Staging
275
+ const idsToMove = testMemoryIds.slice(0, 2);
276
+ const targetWorkspace = testWorkspaceIds[2]; // Staging
277
+
278
+ console.log(` Moving ${idsToMove.length} memories to Staging workspace...`);
279
+
280
+ const { status, data } = await apiRequest("POST", "/memory/bulk-move", {
281
+ ids: idsToMove,
282
+ target_workspace_id: targetWorkspace
283
+ });
284
+
285
+ console.log(` Response: ${status} - moved: ${data.moved}, failed: ${data.failed?.length || 0}`);
286
+
287
+ if (status === 200 || status === 207) {
288
+ console.log(` ✓ Bulk move completed: ${data.moved} moved`);
289
+
290
+ // Verify by checking stats
291
+ await sleep(500);
292
+ const statsRes = await apiRequest("GET", "/keys/stats");
293
+
294
+ if (statsRes.status === 200) {
295
+ const workspaces = statsRes.data.workspaces || [];
296
+ for (const ws of workspaces) {
297
+ if (testWorkspaceIds.includes(ws.id)) {
298
+ console.log(` ${ws.label}: ${ws.memory_count} memories`);
299
+ }
300
+ }
301
+ }
302
+ } else {
303
+ console.log(` ⚠ Bulk move failed: ${JSON.stringify(data)}`);
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Test 9: Bulk delete memories via POST /memory/bulk-delete
309
+ */
310
+ async function test_bulk_delete() {
311
+ console.log("\n[Test 9] Testing bulk delete via POST /memory/bulk-delete...");
312
+
313
+ if (testMemoryIds.length < 2) {
314
+ console.log(" ⚠ Not enough memories to test bulk delete");
315
+ return;
316
+ }
317
+
318
+ // Delete last 2 memories
319
+ const idsToDelete = testMemoryIds.slice(-2);
320
+
321
+ console.log(` Deleting ${idsToDelete.length} memories...`);
322
+
323
+ const { status, data } = await apiRequest("POST", "/memory/bulk-delete", {
324
+ ids: idsToDelete
325
+ });
326
+
327
+ console.log(` Response: ${status} - deleted: ${data.deleted}, failed: ${data.failed?.length || 0}`);
328
+
329
+ if (status === 200 || status === 207) {
330
+ console.log(` ✓ Bulk delete completed: ${data.deleted} deleted`);
331
+
332
+ // Remove from our tracking array
333
+ testMemoryIds = testMemoryIds.slice(0, -2);
334
+ } else {
335
+ console.log(` ⚠ Bulk delete failed: ${JSON.stringify(data)}`);
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Test 10: Delete workspace via DELETE /keys/:id
341
+ */
342
+ async function test_delete_workspace() {
343
+ console.log("\n[Test 10] Deleting workspace via DELETE /keys/:id...");
344
+
345
+ const workspaceId = testWorkspaceIds[2]; // Delete Staging
346
+
347
+ const { status, data } = await apiRequest("DELETE", `/keys/${workspaceId}`);
348
+
349
+ if (status === 200) {
350
+ console.log(` ✓ Workspace deleted successfully`);
351
+ testWorkspaceIds = testWorkspaceIds.filter(id => id !== workspaceId);
352
+ } else {
353
+ console.log(` ⚠ Delete failed: ${status} ${JSON.stringify(data)}`);
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Cleanup: Delete all test data
359
+ */
360
+ async function cleanup() {
361
+ console.log("\n[Cleanup] Removing test data...");
362
+
363
+ // Delete remaining memories
364
+ if (testMemoryIds.length > 0) {
365
+ console.log(` Deleting ${testMemoryIds.length} remaining memories...`);
366
+ await apiRequest("POST", "/memory/bulk-delete", { ids: testMemoryIds });
367
+ }
368
+
369
+ // Delete remaining workspaces
370
+ for (const id of testWorkspaceIds) {
371
+ await apiRequest("DELETE", `/keys/${id}`);
372
+ }
373
+
374
+ console.log(" ✓ Cleanup complete");
375
+ }
376
+
377
+ async function run_all_tests() {
378
+ try {
379
+ await setup();
380
+ await test_health_check();
381
+ await test_create_workspaces();
382
+ await test_list_workspaces();
383
+ await test_workspace_stats_empty();
384
+ await test_rename_workspace();
385
+ await test_rename_validation();
386
+ await test_create_memories();
387
+ await test_workspace_stats_with_memories();
388
+ await test_bulk_move();
389
+ await test_bulk_delete();
390
+ await test_delete_workspace();
391
+ await cleanup();
392
+
393
+ console.log("\n" + "=".repeat(70));
394
+ console.log("✅ ALL API TESTS COMPLETED SUCCESSFULLY");
395
+ console.log("=".repeat(70));
396
+ process.exit(0);
397
+ } catch (error: any) {
398
+ console.error("\n" + "=".repeat(70));
399
+ console.error("❌ TEST FAILED:", error.message);
400
+ console.error("=".repeat(70));
401
+
402
+ // Attempt cleanup
403
+ try {
404
+ await cleanup();
405
+ } catch (e) {
406
+ console.error("Cleanup also failed:", e);
407
+ }
408
+
409
+ process.exit(1);
410
+ }
411
+ }
412
+
413
+ run_all_tests();
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Unit tests for POST /memory/bulk-delete endpoint
3
+ * Requirements: 6.2, 6.3
4
+ */
5
+
6
+ import { q, run_async, vector_store } from "../src/core/db";
7
+ import { add_hsg_memory } from "../src/memory/hsg";
8
+ import { j } from "../src/utils";
9
+
10
+ // Force synthetic embeddings for reliability
11
+ process.env.OM_EMBEDDINGS = "synthetic";
12
+
13
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
14
+
15
+ async function cleanup(user_id: string) {
16
+ await run_async(`DELETE FROM memories WHERE user_id = '${user_id}'`);
17
+ try { await run_async(`DELETE FROM vectors`); } catch { }
18
+ try { await run_async(`DELETE FROM openmemory_vectors`); } catch { }
19
+ try { await run_async(`DELETE FROM waypoints`); } catch { }
20
+ if (global.gc) global.gc();
21
+ }
22
+
23
+ /**
24
+ * Test: Bulk delete successfully removes all specified memories
25
+ * Validates: Requirement 6.2 - Backend_API SHALL delete all selected memories
26
+ */
27
+ async function test_bulk_delete_success() {
28
+ console.log("\n[Test 1] Bulk Delete - All Memories Deleted Successfully");
29
+ const uid = "bulk_delete_test_user_1";
30
+ await cleanup(uid);
31
+ await sleep(500);
32
+
33
+ // Create test memories with unique content to avoid deduplication
34
+ const timestamp = Date.now();
35
+ const mem1 = await add_hsg_memory(`Memory 1 for bulk delete - unique ${timestamp}-1`, j([]), {}, uid, null);
36
+ await sleep(200);
37
+ const mem2 = await add_hsg_memory(`Memory 2 for bulk delete - unique ${timestamp}-2`, j([]), {}, uid, null);
38
+ await sleep(200);
39
+ const mem3 = await add_hsg_memory(`Memory 3 for bulk delete - unique ${timestamp}-3`, j([]), {}, uid, null);
40
+
41
+ await sleep(500);
42
+
43
+ // Verify memories exist
44
+ const before1 = await q.get_mem.get(mem1.id);
45
+ const before2 = await q.get_mem.get(mem2.id);
46
+ const before3 = await q.get_mem.get(mem3.id);
47
+
48
+ if (!before1 || !before2 || !before3) {
49
+ throw new Error("FAIL: Test memories were not created properly");
50
+ }
51
+
52
+ // Perform bulk delete
53
+ const ids = [mem1.id, mem2.id, mem3.id];
54
+ const deleted: string[] = [];
55
+ const failed: string[] = [];
56
+
57
+ for (const id of ids) {
58
+ try {
59
+ const m = await q.get_mem.get(id);
60
+ if (!m) {
61
+ failed.push(id);
62
+ continue;
63
+ }
64
+ if (uid && m.user_id !== uid) {
65
+ failed.push(id);
66
+ continue;
67
+ }
68
+ await q.del_mem.run(id);
69
+ await vector_store.deleteVectors(id);
70
+ await q.del_waypoints.run(id, id);
71
+ deleted.push(id);
72
+ } catch (e) {
73
+ failed.push(id);
74
+ }
75
+ }
76
+
77
+ // Verify all deleted
78
+ if (deleted.length !== 3) {
79
+ throw new Error(`FAIL: Expected 3 deleted, got ${deleted.length}`);
80
+ }
81
+ if (failed.length !== 0) {
82
+ throw new Error(`FAIL: Expected 0 failed, got ${failed.length}`);
83
+ }
84
+
85
+ // Verify memories no longer exist
86
+ const after1 = await q.get_mem.get(mem1.id);
87
+ const after2 = await q.get_mem.get(mem2.id);
88
+ const after3 = await q.get_mem.get(mem3.id);
89
+
90
+ if (after1 || after2 || after3) {
91
+ throw new Error("FAIL: Memories still exist after bulk delete");
92
+ }
93
+
94
+ console.log(` -> Deleted: ${deleted.length}, Failed: ${failed.length}`);
95
+ console.log(" -> PASS: All memories deleted successfully");
96
+ }
97
+
98
+ /**
99
+ * Test: Bulk delete with non-existent IDs reports failures
100
+ * Validates: Requirement 6.3 - Backend_API SHALL continue deleting remaining memories and report partial success
101
+ */
102
+ async function test_bulk_delete_partial_failure() {
103
+ console.log("\n[Test 2] Bulk Delete - Partial Failure with Non-existent IDs");
104
+ const uid = "bulk_delete_test_user_2";
105
+ await cleanup(uid);
106
+ await sleep(500);
107
+
108
+ // Create one real memory
109
+ const mem1 = await add_hsg_memory("Real memory for partial test", j([]), {}, uid, null);
110
+ await sleep(500);
111
+
112
+ // Mix real and fake IDs
113
+ const ids = [mem1.id, "non-existent-id-1", "non-existent-id-2"];
114
+ const deleted: string[] = [];
115
+ const failed: string[] = [];
116
+
117
+ for (const id of ids) {
118
+ try {
119
+ const m = await q.get_mem.get(id);
120
+ if (!m) {
121
+ failed.push(id);
122
+ continue;
123
+ }
124
+ if (uid && m.user_id !== uid) {
125
+ failed.push(id);
126
+ continue;
127
+ }
128
+ await q.del_mem.run(id);
129
+ await vector_store.deleteVectors(id);
130
+ await q.del_waypoints.run(id, id);
131
+ deleted.push(id);
132
+ } catch (e) {
133
+ failed.push(id);
134
+ }
135
+ }
136
+
137
+ // Verify partial success
138
+ if (deleted.length !== 1) {
139
+ throw new Error(`FAIL: Expected 1 deleted, got ${deleted.length}`);
140
+ }
141
+ if (failed.length !== 2) {
142
+ throw new Error(`FAIL: Expected 2 failed, got ${failed.length}`);
143
+ }
144
+
145
+ // Verify the real memory was deleted
146
+ const after = await q.get_mem.get(mem1.id);
147
+ if (after) {
148
+ throw new Error("FAIL: Real memory still exists after bulk delete");
149
+ }
150
+
151
+ console.log(` -> Deleted: ${deleted.length}, Failed: ${failed.length}`);
152
+ console.log(` -> Failed IDs: ${failed.join(", ")}`);
153
+ console.log(" -> PASS: Partial failure handled correctly");
154
+ }
155
+
156
+ /**
157
+ * Test: Bulk delete with ownership verification
158
+ * Validates: Requirement 6.2 - Delete each memory with ownership verification
159
+ */
160
+ async function test_bulk_delete_ownership_verification() {
161
+ console.log("\n[Test 3] Bulk Delete - Ownership Verification");
162
+ const uid1 = "bulk_delete_owner_1";
163
+ const uid2 = "bulk_delete_owner_2";
164
+ await cleanup(uid1);
165
+ await cleanup(uid2);
166
+ await sleep(500);
167
+
168
+ // Create memories for different users
169
+ const mem1 = await add_hsg_memory("Memory owned by user 1", j([]), {}, uid1, null);
170
+ const mem2 = await add_hsg_memory("Memory owned by user 2", j([]), {}, uid2, null);
171
+ await sleep(500);
172
+
173
+ // Try to delete both as user 1
174
+ const ids = [mem1.id, mem2.id];
175
+ const deleted: string[] = [];
176
+ const failed: string[] = [];
177
+
178
+ for (const id of ids) {
179
+ try {
180
+ const m = await q.get_mem.get(id);
181
+ if (!m) {
182
+ failed.push(id);
183
+ continue;
184
+ }
185
+ // Check ownership - user 1 trying to delete
186
+ if (uid1 && m.user_id !== uid1) {
187
+ failed.push(id);
188
+ continue;
189
+ }
190
+ await q.del_mem.run(id);
191
+ await vector_store.deleteVectors(id);
192
+ await q.del_waypoints.run(id, id);
193
+ deleted.push(id);
194
+ } catch (e) {
195
+ failed.push(id);
196
+ }
197
+ }
198
+
199
+ // User 1 should only be able to delete their own memory
200
+ if (deleted.length !== 1) {
201
+ throw new Error(`FAIL: Expected 1 deleted (own memory), got ${deleted.length}`);
202
+ }
203
+ if (failed.length !== 1) {
204
+ throw new Error(`FAIL: Expected 1 failed (other user's memory), got ${failed.length}`);
205
+ }
206
+
207
+ // Verify user 1's memory was deleted
208
+ const after1 = await q.get_mem.get(mem1.id);
209
+ if (after1) {
210
+ throw new Error("FAIL: User 1's memory still exists");
211
+ }
212
+
213
+ // Verify user 2's memory still exists
214
+ const after2 = await q.get_mem.get(mem2.id);
215
+ if (!after2) {
216
+ throw new Error("FAIL: User 2's memory was incorrectly deleted");
217
+ }
218
+
219
+ console.log(` -> Deleted: ${deleted.length}, Failed: ${failed.length}`);
220
+ console.log(" -> PASS: Ownership verification working correctly");
221
+
222
+ // Cleanup user 2's memory
223
+ await cleanup(uid2);
224
+ }
225
+
226
+ /**
227
+ * Test: Bulk delete with empty array
228
+ */
229
+ async function test_bulk_delete_empty_array() {
230
+ console.log("\n[Test 4] Bulk Delete - Empty Array");
231
+
232
+ const ids: string[] = [];
233
+ const deleted: string[] = [];
234
+ const failed: string[] = [];
235
+
236
+ // Empty array should return immediately with 0 deleted
237
+ for (const id of ids) {
238
+ // This loop won't execute
239
+ deleted.push(id);
240
+ }
241
+
242
+ if (deleted.length !== 0) {
243
+ throw new Error(`FAIL: Expected 0 deleted for empty array, got ${deleted.length}`);
244
+ }
245
+ if (failed.length !== 0) {
246
+ throw new Error(`FAIL: Expected 0 failed for empty array, got ${failed.length}`);
247
+ }
248
+
249
+ console.log(` -> Deleted: ${deleted.length}, Failed: ${failed.length}`);
250
+ console.log(" -> PASS: Empty array handled correctly");
251
+ }
252
+
253
+ async function run_all() {
254
+ try {
255
+ await test_bulk_delete_success();
256
+ await test_bulk_delete_partial_failure();
257
+ await test_bulk_delete_ownership_verification();
258
+ await test_bulk_delete_empty_array();
259
+ console.log("\n[BULK DELETE TESTS] ALL TESTS PASSED");
260
+ process.exit(0);
261
+ } catch (e) {
262
+ console.error("\n[BULK DELETE TESTS] TEST FAILED:", e);
263
+ process.exit(1);
264
+ }
265
+ }
266
+
267
+ run_all();