memory-journal-mcp 4.3.0 → 4.4.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/.dockerignore +131 -122
- package/.gitattributes +29 -0
- package/.github/workflows/docker-publish.yml +1 -1
- package/.github/workflows/lint-and-test.yml +1 -2
- package/.github/workflows/secrets-scanning.yml +0 -1
- package/.github/workflows/security-update.yml +6 -6
- package/.vscode/settings.json +17 -15
- package/CHANGELOG.md +1065 -11
- package/DOCKER_README.md +51 -33
- package/Dockerfile +14 -12
- package/README.md +68 -33
- package/SECURITY.md +225 -220
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +70 -26
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -0
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +6 -0
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +51 -10
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +143 -43
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +7 -1
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts +74 -2
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +508 -7
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/index.js +1 -0
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +257 -13
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +595 -8
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/server/McpServer.d.ts +2 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +69 -26
- package/dist/server/McpServer.js.map +1 -1
- package/dist/types/index.d.ts +97 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/progress-utils.d.ts +18 -3
- package/dist/utils/progress-utils.d.ts.map +1 -1
- package/dist/utils/progress-utils.js.map +1 -1
- package/dist/utils/security-utils.d.ts +91 -0
- package/dist/utils/security-utils.d.ts.map +1 -0
- package/dist/utils/security-utils.js +184 -0
- package/dist/utils/security-utils.js.map +1 -0
- package/dist/vector/VectorSearchManager.d.ts +2 -1
- package/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +100 -34
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +46 -37
- package/mcp-config-example.json +0 -2
- package/package.json +21 -14
- package/releases/v4.3.1.md +69 -0
- package/releases/v4.4.0.md +120 -0
- package/server.json +3 -3
- package/src/cli.ts +11 -0
- package/src/constants/ServerInstructions.ts +70 -26
- package/src/constants/icons.ts +7 -0
- package/src/database/SqliteAdapter.ts +165 -44
- package/src/filtering/ToolFilter.ts +7 -1
- package/src/github/GitHubIntegration.ts +588 -8
- package/src/handlers/prompts/index.ts +1 -0
- package/src/handlers/resources/index.ts +318 -12
- package/src/handlers/tools/index.ts +686 -13
- package/src/server/McpServer.ts +79 -37
- package/src/types/index.ts +98 -0
- package/src/utils/logger.ts +10 -1
- package/src/utils/progress-utils.ts +17 -6
- package/src/utils/security-utils.ts +205 -0
- package/src/vector/VectorSearchManager.ts +110 -39
- package/tests/constants/icons.test.ts +102 -0
- package/tests/constants/server-instructions.test.ts +549 -0
- package/tests/database/sqlite-adapter.bench.ts +63 -0
- package/tests/database/sqlite-adapter.test.ts +555 -0
- package/tests/filtering/tool-filter.test.ts +266 -0
- package/tests/github/github-integration.test.ts +1024 -0
- package/tests/handlers/github-resource-handlers.test.ts +473 -0
- package/tests/handlers/github-tool-handlers.test.ts +556 -0
- package/tests/handlers/prompt-handlers.test.ts +91 -0
- package/tests/handlers/resource-handlers.test.ts +339 -0
- package/tests/handlers/tool-handlers.test.ts +497 -0
- package/tests/handlers/vector-tool-handlers.test.ts +238 -0
- package/tests/security/sql-injection.test.ts +347 -0
- package/tests/server/mcp-server.bench.ts +55 -0
- package/tests/server/mcp-server.test.ts +675 -0
- package/tests/utils/logger.test.ts +180 -0
- package/tests/utils/mcp-logger.test.ts +212 -0
- package/tests/utils/progress-utils.test.ts +156 -0
- package/tests/utils/security-utils.test.ts +82 -0
- package/tests/vector/vector-search-manager.test.ts +335 -0
- package/tests/vector/vector-search.bench.ts +53 -0
- package/vitest.config.ts +15 -0
- package/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +0 -387
- package/.github/workflows/dependabot-auto-merge.yml +0 -42
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Handler Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests getTools listing, callTool for non-GitHub tools,
|
|
5
|
+
* and Zod output schema validation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
9
|
+
import { getTools, callTool } from '../../src/handlers/tools/index.js'
|
|
10
|
+
import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
|
|
11
|
+
|
|
12
|
+
describe('Tool Handlers', () => {
|
|
13
|
+
let db: SqliteAdapter
|
|
14
|
+
const testDbPath = './test-tools.db'
|
|
15
|
+
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
db = new SqliteAdapter(testDbPath)
|
|
18
|
+
await db.initialize()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
db.close()
|
|
23
|
+
try {
|
|
24
|
+
const fs = require('node:fs')
|
|
25
|
+
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
|
|
26
|
+
} catch {
|
|
27
|
+
// Ignore cleanup errors
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// ========================================================================
|
|
32
|
+
// getTools
|
|
33
|
+
// ========================================================================
|
|
34
|
+
|
|
35
|
+
describe('getTools', () => {
|
|
36
|
+
it('should return all tools when no filter', () => {
|
|
37
|
+
const tools = getTools(db, null)
|
|
38
|
+
expect(tools.length).toBeGreaterThan(30)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should have name, description, and inputSchema on each tool', () => {
|
|
42
|
+
const tools = getTools(db, null)
|
|
43
|
+
for (const t of tools) {
|
|
44
|
+
const tool = t as {
|
|
45
|
+
name: string
|
|
46
|
+
description: string
|
|
47
|
+
inputSchema: object
|
|
48
|
+
}
|
|
49
|
+
expect(typeof tool.name).toBe('string')
|
|
50
|
+
expect(typeof tool.description).toBe('string')
|
|
51
|
+
expect(tool.inputSchema).toBeDefined()
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should have outputSchema on each tool (MCP 2025-11-25)', () => {
|
|
56
|
+
const tools = getTools(db, null)
|
|
57
|
+
for (const t of tools) {
|
|
58
|
+
const tool = t as { name: string; outputSchema?: object }
|
|
59
|
+
expect(tool.outputSchema).toBeDefined()
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should include known tool names', () => {
|
|
64
|
+
const tools = getTools(db, null)
|
|
65
|
+
const names = tools.map((t) => (t as { name: string }).name)
|
|
66
|
+
|
|
67
|
+
expect(names).toContain('create_entry')
|
|
68
|
+
expect(names).toContain('search_entries')
|
|
69
|
+
expect(names).toContain('get_recent_entries')
|
|
70
|
+
expect(names).toContain('get_statistics')
|
|
71
|
+
expect(names).toContain('link_entries')
|
|
72
|
+
expect(names).toContain('backup_journal')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// ========================================================================
|
|
77
|
+
// callTool - core tools
|
|
78
|
+
// ========================================================================
|
|
79
|
+
|
|
80
|
+
describe('callTool - create_entry', () => {
|
|
81
|
+
it('should create a basic entry', async () => {
|
|
82
|
+
const result = (await callTool(
|
|
83
|
+
'create_entry',
|
|
84
|
+
{ content: 'Tool handler test entry' },
|
|
85
|
+
db
|
|
86
|
+
)) as { success: boolean; entry: { id: number; content: string } }
|
|
87
|
+
|
|
88
|
+
expect(result.success).toBe(true)
|
|
89
|
+
expect(result.entry).toBeDefined()
|
|
90
|
+
expect(result.entry.id).toBeGreaterThan(0)
|
|
91
|
+
expect(result.entry.content).toBe('Tool handler test entry')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should create entry with all fields', async () => {
|
|
95
|
+
const result = (await callTool(
|
|
96
|
+
'create_entry',
|
|
97
|
+
{
|
|
98
|
+
content: 'Full tool entry',
|
|
99
|
+
entry_type: 'decision',
|
|
100
|
+
tags: ['tool-tag-a', 'tool-tag-b'],
|
|
101
|
+
is_personal: false,
|
|
102
|
+
significance_type: 'milestone',
|
|
103
|
+
},
|
|
104
|
+
db
|
|
105
|
+
)) as { success: boolean; entry: { entryType: string; tags: string[] } }
|
|
106
|
+
|
|
107
|
+
expect(result.success).toBe(true)
|
|
108
|
+
expect(result.entry.entryType).toBe('decision')
|
|
109
|
+
expect(result.entry.tags).toContain('tool-tag-a')
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('callTool - create_entry_minimal', () => {
|
|
114
|
+
it('should create a minimal entry', async () => {
|
|
115
|
+
const result = (await callTool(
|
|
116
|
+
'create_entry_minimal',
|
|
117
|
+
{ content: 'Quick note' },
|
|
118
|
+
db
|
|
119
|
+
)) as { success: boolean; entry: { content: string } }
|
|
120
|
+
|
|
121
|
+
expect(result.success).toBe(true)
|
|
122
|
+
expect(result.entry.content).toBe('Quick note')
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('callTool - get_entry_by_id', () => {
|
|
127
|
+
it('should retrieve an entry by ID', async () => {
|
|
128
|
+
const created = (await callTool(
|
|
129
|
+
'create_entry',
|
|
130
|
+
{ content: 'Retrievable entry' },
|
|
131
|
+
db
|
|
132
|
+
)) as { entry: { id: number } }
|
|
133
|
+
|
|
134
|
+
const result = (await callTool(
|
|
135
|
+
'get_entry_by_id',
|
|
136
|
+
{ entry_id: created.entry.id },
|
|
137
|
+
db
|
|
138
|
+
)) as { entry: { content: string }; importance: number }
|
|
139
|
+
|
|
140
|
+
expect(result.entry.content).toBe('Retrievable entry')
|
|
141
|
+
expect(typeof result.importance).toBe('number')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should return error for nonexistent entry', async () => {
|
|
145
|
+
const result = (await callTool('get_entry_by_id', { entry_id: 99999 }, db)) as {
|
|
146
|
+
error: string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
expect(result.error).toContain('not found')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('callTool - get_recent_entries', () => {
|
|
154
|
+
it('should return recent entries', async () => {
|
|
155
|
+
const result = (await callTool('get_recent_entries', { limit: 3 }, db)) as {
|
|
156
|
+
entries: unknown[]
|
|
157
|
+
count: number
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
expect(result.entries.length).toBeLessThanOrEqual(3)
|
|
161
|
+
expect(result.count).toBeGreaterThan(0)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('callTool - test_simple', () => {
|
|
166
|
+
it('should echo message back', async () => {
|
|
167
|
+
const result = (await callTool('test_simple', { message: 'ping' }, db)) as {
|
|
168
|
+
message: string
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
expect(result.message).toContain('ping')
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// ========================================================================
|
|
176
|
+
// callTool - search tools
|
|
177
|
+
// ========================================================================
|
|
178
|
+
|
|
179
|
+
describe('callTool - search_entries', () => {
|
|
180
|
+
it('should search by content', async () => {
|
|
181
|
+
await callTool('create_entry', { content: 'Unique unicorn xyz99' }, db)
|
|
182
|
+
|
|
183
|
+
const result = (await callTool('search_entries', { query: 'xyz99', limit: 5 }, db)) as {
|
|
184
|
+
entries: unknown[]
|
|
185
|
+
count: number
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
expect(result.count).toBeGreaterThan(0)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('should return empty for non-matching query', async () => {
|
|
192
|
+
const result = (await callTool(
|
|
193
|
+
'search_entries',
|
|
194
|
+
{ query: 'nonexistent_term_abcxyz', limit: 5 },
|
|
195
|
+
db
|
|
196
|
+
)) as { entries: unknown[]; count: number }
|
|
197
|
+
|
|
198
|
+
expect(result.count).toBe(0)
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
describe('callTool - search_by_date_range', () => {
|
|
203
|
+
it('should search by date range', async () => {
|
|
204
|
+
const today = new Date().toISOString().split('T')[0]!
|
|
205
|
+
const result = (await callTool(
|
|
206
|
+
'search_by_date_range',
|
|
207
|
+
{ start_date: today, end_date: today },
|
|
208
|
+
db
|
|
209
|
+
)) as { entries: unknown[]; count: number }
|
|
210
|
+
|
|
211
|
+
expect(result.count).toBeGreaterThan(0)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// ========================================================================
|
|
216
|
+
// callTool - analytics tools
|
|
217
|
+
// ========================================================================
|
|
218
|
+
|
|
219
|
+
describe('callTool - get_statistics', () => {
|
|
220
|
+
it('should return statistics', async () => {
|
|
221
|
+
const result = (await callTool('get_statistics', {}, db)) as {
|
|
222
|
+
groupBy: string
|
|
223
|
+
totalEntries: number
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
expect(result.totalEntries).toBeGreaterThan(0)
|
|
227
|
+
expect(result.groupBy).toBe('week')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should accept group_by parameter', async () => {
|
|
231
|
+
const result = (await callTool('get_statistics', { group_by: 'day' }, db)) as {
|
|
232
|
+
groupBy: string
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
expect(result.groupBy).toBe('day')
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// ========================================================================
|
|
240
|
+
// callTool - relationship tools
|
|
241
|
+
// ========================================================================
|
|
242
|
+
|
|
243
|
+
describe('callTool - link_entries', () => {
|
|
244
|
+
it('should link two entries', async () => {
|
|
245
|
+
const e1 = (await callTool('create_entry', { content: 'Link source' }, db)) as {
|
|
246
|
+
entry: { id: number }
|
|
247
|
+
}
|
|
248
|
+
const e2 = (await callTool('create_entry', { content: 'Link target' }, db)) as {
|
|
249
|
+
entry: { id: number }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const result = (await callTool(
|
|
253
|
+
'link_entries',
|
|
254
|
+
{
|
|
255
|
+
from_entry_id: e1.entry.id,
|
|
256
|
+
to_entry_id: e2.entry.id,
|
|
257
|
+
relationship_type: 'references',
|
|
258
|
+
description: 'Test link',
|
|
259
|
+
},
|
|
260
|
+
db
|
|
261
|
+
)) as { success: boolean; relationship: { relationshipType: string } }
|
|
262
|
+
|
|
263
|
+
expect(result.success).toBe(true)
|
|
264
|
+
expect(result.relationship.relationshipType).toBe('references')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should return failure for nonexistent entry', async () => {
|
|
268
|
+
const result = (await callTool(
|
|
269
|
+
'link_entries',
|
|
270
|
+
{
|
|
271
|
+
from_entry_id: 99999,
|
|
272
|
+
to_entry_id: 99998,
|
|
273
|
+
relationship_type: 'references',
|
|
274
|
+
},
|
|
275
|
+
db
|
|
276
|
+
)) as { success: boolean; message?: string }
|
|
277
|
+
|
|
278
|
+
expect(result.success).toBe(false)
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// ========================================================================
|
|
283
|
+
// callTool - admin tools
|
|
284
|
+
// ========================================================================
|
|
285
|
+
|
|
286
|
+
describe('callTool - update_entry', () => {
|
|
287
|
+
it('should update entry content', async () => {
|
|
288
|
+
const created = (await callTool(
|
|
289
|
+
'create_entry',
|
|
290
|
+
{ content: 'Original content' },
|
|
291
|
+
db
|
|
292
|
+
)) as { entry: { id: number } }
|
|
293
|
+
|
|
294
|
+
const result = (await callTool(
|
|
295
|
+
'update_entry',
|
|
296
|
+
{ entry_id: created.entry.id, content: 'Updated content' },
|
|
297
|
+
db
|
|
298
|
+
)) as { success: boolean; entry: { content: string } }
|
|
299
|
+
|
|
300
|
+
expect(result.success).toBe(true)
|
|
301
|
+
expect(result.entry.content).toBe('Updated content')
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
describe('callTool - delete_entry', () => {
|
|
306
|
+
it('should soft delete an entry', async () => {
|
|
307
|
+
const created = (await callTool('create_entry', { content: 'To be deleted' }, db)) as {
|
|
308
|
+
entry: { id: number }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const result = (await callTool('delete_entry', { entry_id: created.entry.id }, db)) as {
|
|
312
|
+
success: boolean
|
|
313
|
+
entryId: number
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
expect(result.success).toBe(true)
|
|
317
|
+
expect(result.entryId).toBe(created.entry.id)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('should return error for nonexistent entry', async () => {
|
|
321
|
+
const result = (await callTool('delete_entry', { entry_id: 99999 }, db)) as {
|
|
322
|
+
success: boolean
|
|
323
|
+
error: string
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
expect(result.success).toBe(false)
|
|
327
|
+
expect(result.error).toContain('not found')
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// ========================================================================
|
|
332
|
+
// callTool - tag tools
|
|
333
|
+
// ========================================================================
|
|
334
|
+
|
|
335
|
+
describe('callTool - list_tags', () => {
|
|
336
|
+
it('should list tags with usage counts', async () => {
|
|
337
|
+
const result = (await callTool('list_tags', {}, db)) as {
|
|
338
|
+
tags: { name: string; count: number }[]
|
|
339
|
+
count: number
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
expect(result.tags).toBeDefined()
|
|
343
|
+
expect(result.count).toBeGreaterThan(0)
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
describe('callTool - merge_tags', () => {
|
|
348
|
+
it('should merge tags', async () => {
|
|
349
|
+
await callTool('create_entry', { content: 'Merge tag source', tags: ['merge-src'] }, db)
|
|
350
|
+
|
|
351
|
+
const result = (await callTool(
|
|
352
|
+
'merge_tags',
|
|
353
|
+
{ source_tag: 'merge-src', target_tag: 'merge-tgt' },
|
|
354
|
+
db
|
|
355
|
+
)) as { success: boolean; message: string }
|
|
356
|
+
|
|
357
|
+
expect(result.success).toBe(true)
|
|
358
|
+
expect(result.message).toContain('Merged')
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
// ========================================================================
|
|
363
|
+
// callTool - export tools
|
|
364
|
+
// ========================================================================
|
|
365
|
+
|
|
366
|
+
describe('callTool - export_entries', () => {
|
|
367
|
+
it('should export as JSON', async () => {
|
|
368
|
+
const result = (await callTool('export_entries', { format: 'json' }, db)) as {
|
|
369
|
+
format: string
|
|
370
|
+
entries: unknown[]
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
expect(result.format).toBe('json')
|
|
374
|
+
expect(result.entries.length).toBeGreaterThan(0)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('should export as markdown', async () => {
|
|
378
|
+
const result = (await callTool('export_entries', { format: 'markdown' }, db)) as {
|
|
379
|
+
format: string
|
|
380
|
+
content: string
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
expect(result.format).toBe('markdown')
|
|
384
|
+
expect(result.content.length).toBeGreaterThan(0)
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// ========================================================================
|
|
389
|
+
// callTool - backup tools
|
|
390
|
+
// ========================================================================
|
|
391
|
+
|
|
392
|
+
describe('callTool - backup_journal', () => {
|
|
393
|
+
it('should create a backup', async () => {
|
|
394
|
+
const result = (await callTool('backup_journal', { name: 'test-tool-backup' }, db)) as {
|
|
395
|
+
success: boolean
|
|
396
|
+
filename: string
|
|
397
|
+
path: string
|
|
398
|
+
sizeBytes: number
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
expect(result.success).toBe(true)
|
|
402
|
+
expect(result.filename).toContain('test-tool-backup')
|
|
403
|
+
|
|
404
|
+
// Cleanup backup file
|
|
405
|
+
try {
|
|
406
|
+
const fs = require('node:fs')
|
|
407
|
+
if (fs.existsSync(result.path)) {
|
|
408
|
+
fs.unlinkSync(result.path)
|
|
409
|
+
}
|
|
410
|
+
} catch {
|
|
411
|
+
// Ignore cleanup
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
describe('callTool - list_backups', () => {
|
|
417
|
+
it('should list backups', async () => {
|
|
418
|
+
const result = (await callTool('list_backups', {}, db)) as {
|
|
419
|
+
backups: unknown[]
|
|
420
|
+
total: number
|
|
421
|
+
backupsDirectory: string
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
expect(result.backups).toBeDefined()
|
|
425
|
+
expect(typeof result.total).toBe('number')
|
|
426
|
+
expect(result.backupsDirectory).toBeDefined()
|
|
427
|
+
})
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
// ========================================================================
|
|
431
|
+
// callTool - unknown tool
|
|
432
|
+
// ========================================================================
|
|
433
|
+
|
|
434
|
+
describe('callTool - error handling', () => {
|
|
435
|
+
it('should throw for unknown tool', async () => {
|
|
436
|
+
await expect(callTool('nonexistent_tool', {}, db)).rejects.toThrow('Unknown tool')
|
|
437
|
+
})
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
// ========================================================================
|
|
441
|
+
// callTool - visualize_relationships
|
|
442
|
+
// ========================================================================
|
|
443
|
+
|
|
444
|
+
describe('callTool - visualize_relationships', () => {
|
|
445
|
+
it('should generate mermaid diagram', async () => {
|
|
446
|
+
const e1 = (await callTool('create_entry', { content: 'Viz entry A' }, db)) as {
|
|
447
|
+
entry: { id: number }
|
|
448
|
+
}
|
|
449
|
+
const e2 = (await callTool('create_entry', { content: 'Viz entry B' }, db)) as {
|
|
450
|
+
entry: { id: number }
|
|
451
|
+
}
|
|
452
|
+
await callTool(
|
|
453
|
+
'link_entries',
|
|
454
|
+
{
|
|
455
|
+
from_entry_id: e1.entry.id,
|
|
456
|
+
to_entry_id: e2.entry.id,
|
|
457
|
+
relationship_type: 'implements',
|
|
458
|
+
},
|
|
459
|
+
db
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
const result = (await callTool(
|
|
463
|
+
'visualize_relationships',
|
|
464
|
+
{ entry_id: e1.entry.id },
|
|
465
|
+
db
|
|
466
|
+
)) as { mermaid: string | null; entry_count: number }
|
|
467
|
+
|
|
468
|
+
expect(result.entry_count).toBeGreaterThan(0)
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
// ========================================================================
|
|
473
|
+
// callTool - cross project insights
|
|
474
|
+
// ========================================================================
|
|
475
|
+
|
|
476
|
+
describe('callTool - get_cross_project_insights', () => {
|
|
477
|
+
it('should return insights structure', async () => {
|
|
478
|
+
// Need project entries for insights
|
|
479
|
+
await callTool('create_entry', { content: 'Insight entry 1', project_number: 100 }, db)
|
|
480
|
+
await callTool('create_entry', { content: 'Insight entry 2', project_number: 100 }, db)
|
|
481
|
+
await callTool('create_entry', { content: 'Insight entry 3', project_number: 100 }, db)
|
|
482
|
+
|
|
483
|
+
const result = (await callTool(
|
|
484
|
+
'get_cross_project_insights',
|
|
485
|
+
{ min_entries: 1 },
|
|
486
|
+
db
|
|
487
|
+
)) as {
|
|
488
|
+
project_count: number
|
|
489
|
+
total_entries: number
|
|
490
|
+
projects: unknown[]
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
expect(result.project_count).toBeGreaterThan(0)
|
|
494
|
+
expect(result.total_entries).toBeGreaterThan(0)
|
|
495
|
+
})
|
|
496
|
+
})
|
|
497
|
+
})
|