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,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Resource Handler Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests GitHub-dependent resources using a mock GitHubIntegration object.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
|
|
8
|
+
import { readResource } from '../../src/handlers/resources/index.js'
|
|
9
|
+
import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
|
|
10
|
+
import type { GitHubIntegration } from '../../src/github/GitHubIntegration.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a minimal mock GitHubIntegration with sensible defaults.
|
|
14
|
+
*/
|
|
15
|
+
function createMockGitHub(overrides: Partial<Record<string, unknown>> = {}): GitHubIntegration {
|
|
16
|
+
const mock = {
|
|
17
|
+
isApiAvailable: vi.fn().mockReturnValue(true),
|
|
18
|
+
getRepoInfo: vi.fn().mockResolvedValue({
|
|
19
|
+
owner: 'testowner',
|
|
20
|
+
repo: 'testrepo',
|
|
21
|
+
branch: 'main',
|
|
22
|
+
remoteUrl: 'git@github.com:testowner/testrepo.git',
|
|
23
|
+
}),
|
|
24
|
+
getRepoContext: vi.fn().mockResolvedValue({
|
|
25
|
+
repoName: 'testrepo',
|
|
26
|
+
branch: 'main',
|
|
27
|
+
commit: 'abc1234',
|
|
28
|
+
remoteUrl: 'url',
|
|
29
|
+
projects: [],
|
|
30
|
+
issues: [],
|
|
31
|
+
pullRequests: [],
|
|
32
|
+
workflowRuns: [],
|
|
33
|
+
milestones: [],
|
|
34
|
+
}),
|
|
35
|
+
getIssues: vi
|
|
36
|
+
.fn()
|
|
37
|
+
.mockResolvedValue([
|
|
38
|
+
{ number: 1, title: 'Bug fix needed', url: 'url1', state: 'OPEN', milestone: null },
|
|
39
|
+
]),
|
|
40
|
+
getPullRequests: vi
|
|
41
|
+
.fn()
|
|
42
|
+
.mockResolvedValue([{ number: 10, title: 'Feature PR', url: 'url10', state: 'OPEN' }]),
|
|
43
|
+
getWorkflowRuns: vi.fn().mockResolvedValue([
|
|
44
|
+
{
|
|
45
|
+
id: 100,
|
|
46
|
+
name: 'CI',
|
|
47
|
+
status: 'completed',
|
|
48
|
+
conclusion: 'success',
|
|
49
|
+
url: 'url',
|
|
50
|
+
headBranch: 'main',
|
|
51
|
+
headSha: 'abc1234',
|
|
52
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
53
|
+
updatedAt: '2025-01-01T01:00:00Z',
|
|
54
|
+
},
|
|
55
|
+
]),
|
|
56
|
+
getProjectKanban: vi.fn().mockResolvedValue(null),
|
|
57
|
+
getMilestones: vi.fn().mockResolvedValue([
|
|
58
|
+
{
|
|
59
|
+
number: 1,
|
|
60
|
+
title: 'v1.0',
|
|
61
|
+
description: 'First release',
|
|
62
|
+
state: 'open',
|
|
63
|
+
url: 'url',
|
|
64
|
+
dueOn: '2025-06-01T00:00:00Z',
|
|
65
|
+
openIssues: 5,
|
|
66
|
+
closedIssues: 10,
|
|
67
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
68
|
+
updatedAt: '2025-01-02T00:00:00Z',
|
|
69
|
+
creator: 'owner1',
|
|
70
|
+
},
|
|
71
|
+
]),
|
|
72
|
+
getMilestone: vi.fn().mockResolvedValue({
|
|
73
|
+
number: 1,
|
|
74
|
+
title: 'v1.0',
|
|
75
|
+
description: 'First release',
|
|
76
|
+
state: 'open',
|
|
77
|
+
url: 'url',
|
|
78
|
+
dueOn: '2025-06-01T00:00:00Z',
|
|
79
|
+
openIssues: 5,
|
|
80
|
+
closedIssues: 10,
|
|
81
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
82
|
+
updatedAt: '2025-01-02T00:00:00Z',
|
|
83
|
+
creator: 'owner1',
|
|
84
|
+
}),
|
|
85
|
+
getCachedRepoInfo: vi.fn().mockReturnValue({
|
|
86
|
+
owner: 'testowner',
|
|
87
|
+
repo: 'testrepo',
|
|
88
|
+
branch: 'main',
|
|
89
|
+
remoteUrl: 'git@github.com:testowner/testrepo.git',
|
|
90
|
+
}),
|
|
91
|
+
getRepoStats: vi.fn().mockResolvedValue({
|
|
92
|
+
stars: 42,
|
|
93
|
+
forks: 7,
|
|
94
|
+
watchers: 3,
|
|
95
|
+
}),
|
|
96
|
+
getTrafficData: vi.fn().mockResolvedValue({
|
|
97
|
+
clones: { total: 120, uniqueCloners: 30 },
|
|
98
|
+
views: { total: 500, uniqueVisitors: 80 },
|
|
99
|
+
}),
|
|
100
|
+
...overrides,
|
|
101
|
+
}
|
|
102
|
+
return mock as unknown as GitHubIntegration
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe('GitHub Resource Handlers', () => {
|
|
106
|
+
let db: SqliteAdapter
|
|
107
|
+
const testDbPath = './test-gh-resources.db'
|
|
108
|
+
|
|
109
|
+
beforeAll(async () => {
|
|
110
|
+
db = new SqliteAdapter(testDbPath)
|
|
111
|
+
await db.initialize()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
afterAll(() => {
|
|
115
|
+
db.close()
|
|
116
|
+
try {
|
|
117
|
+
const fs = require('node:fs')
|
|
118
|
+
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
|
|
119
|
+
} catch {
|
|
120
|
+
// Ignore cleanup errors
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// ========================================================================
|
|
125
|
+
// memory://github/status
|
|
126
|
+
// ========================================================================
|
|
127
|
+
|
|
128
|
+
describe('memory://github/status', () => {
|
|
129
|
+
it('should return status with repo info, CI, issues, PRs', async () => {
|
|
130
|
+
const github = createMockGitHub()
|
|
131
|
+
const result = await readResource(
|
|
132
|
+
'memory://github/status',
|
|
133
|
+
db,
|
|
134
|
+
undefined,
|
|
135
|
+
undefined,
|
|
136
|
+
github
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
const data = result.data as {
|
|
140
|
+
repository: string
|
|
141
|
+
branch: string
|
|
142
|
+
ci: { status: string }
|
|
143
|
+
issues: { openCount: number }
|
|
144
|
+
pullRequests: { openCount: number }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
expect(data.repository).toBe('testowner/testrepo')
|
|
148
|
+
expect(data.branch).toBe('main')
|
|
149
|
+
expect(data.ci.status).toBe('passing')
|
|
150
|
+
expect(data.issues.openCount).toBe(1)
|
|
151
|
+
expect(data.pullRequests.openCount).toBe(1)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should return error when no github integration', async () => {
|
|
155
|
+
const result = await readResource(
|
|
156
|
+
'memory://github/status',
|
|
157
|
+
db,
|
|
158
|
+
undefined,
|
|
159
|
+
undefined,
|
|
160
|
+
null
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
const data = result.data as { error: string; hint: string }
|
|
164
|
+
expect(data.error).toContain('GitHub integration not available')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should handle missing owner/repo', async () => {
|
|
168
|
+
const github = createMockGitHub({
|
|
169
|
+
getRepoInfo: vi.fn().mockResolvedValue({
|
|
170
|
+
owner: null,
|
|
171
|
+
repo: null,
|
|
172
|
+
branch: 'main',
|
|
173
|
+
remoteUrl: null,
|
|
174
|
+
}),
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const result = await readResource(
|
|
178
|
+
'memory://github/status',
|
|
179
|
+
db,
|
|
180
|
+
undefined,
|
|
181
|
+
undefined,
|
|
182
|
+
github
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
const data = result.data as { error: string }
|
|
186
|
+
expect(data.error).toContain('Could not detect repository')
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// ========================================================================
|
|
191
|
+
// memory://github/milestones
|
|
192
|
+
// ========================================================================
|
|
193
|
+
|
|
194
|
+
describe('memory://github/milestones', () => {
|
|
195
|
+
it('should return milestones with completion percentages', async () => {
|
|
196
|
+
const github = createMockGitHub()
|
|
197
|
+
const result = await readResource(
|
|
198
|
+
'memory://github/milestones',
|
|
199
|
+
db,
|
|
200
|
+
undefined,
|
|
201
|
+
undefined,
|
|
202
|
+
github
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
const data = result.data as {
|
|
206
|
+
repository: string
|
|
207
|
+
milestones: { completionPercentage: number }[]
|
|
208
|
+
count: number
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
expect(data.repository).toBe('testowner/testrepo')
|
|
212
|
+
expect(data.count).toBe(1)
|
|
213
|
+
// 10 closed / (5 open + 10 closed) = 66.67% => 67%
|
|
214
|
+
expect(data.milestones[0]!.completionPercentage).toBe(67)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should return error when no github', async () => {
|
|
218
|
+
const result = await readResource(
|
|
219
|
+
'memory://github/milestones',
|
|
220
|
+
db,
|
|
221
|
+
undefined,
|
|
222
|
+
undefined,
|
|
223
|
+
null
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
const data = result.data as { error: string }
|
|
227
|
+
expect(data.error).toContain('GitHub integration not available')
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// ========================================================================
|
|
232
|
+
// memory://milestones/{number}
|
|
233
|
+
// ========================================================================
|
|
234
|
+
|
|
235
|
+
describe('memory://milestones/{number}', () => {
|
|
236
|
+
it('should return single milestone detail', async () => {
|
|
237
|
+
const github = createMockGitHub()
|
|
238
|
+
const result = await readResource(
|
|
239
|
+
'memory://milestones/1',
|
|
240
|
+
db,
|
|
241
|
+
undefined,
|
|
242
|
+
undefined,
|
|
243
|
+
github
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
const data = result.data as {
|
|
247
|
+
repository: string
|
|
248
|
+
milestone: { number: number; completionPercentage: number }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
expect(data.repository).toBe('testowner/testrepo')
|
|
252
|
+
expect(data.milestone.number).toBe(1)
|
|
253
|
+
expect(data.milestone.completionPercentage).toBe(67)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('should return error for not found milestone', async () => {
|
|
257
|
+
const github = createMockGitHub({
|
|
258
|
+
getMilestone: vi.fn().mockResolvedValue(null),
|
|
259
|
+
})
|
|
260
|
+
const result = await readResource(
|
|
261
|
+
'memory://milestones/999',
|
|
262
|
+
db,
|
|
263
|
+
undefined,
|
|
264
|
+
undefined,
|
|
265
|
+
github
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
const data = result.data as { error: string }
|
|
269
|
+
expect(data.error).toContain('not found')
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// ========================================================================
|
|
274
|
+
// memory://kanban/{project_number}
|
|
275
|
+
// ========================================================================
|
|
276
|
+
|
|
277
|
+
describe('memory://kanban/{project_number}', () => {
|
|
278
|
+
it('should return kanban board when available', async () => {
|
|
279
|
+
const github = createMockGitHub({
|
|
280
|
+
getProjectKanban: vi.fn().mockResolvedValue({
|
|
281
|
+
projectId: 'PVT_1',
|
|
282
|
+
projectTitle: 'My Board',
|
|
283
|
+
columns: [
|
|
284
|
+
{
|
|
285
|
+
status: 'Todo',
|
|
286
|
+
items: [{ id: 'I1', type: 'ISSUE', title: 'Task', number: 1 }],
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
statusOptions: [],
|
|
290
|
+
totalItems: 1,
|
|
291
|
+
}),
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const result = await readResource('memory://kanban/1', db, undefined, undefined, github)
|
|
295
|
+
|
|
296
|
+
const data = result.data as { projectTitle: string; totalItems: number }
|
|
297
|
+
expect(data.projectTitle).toBe('My Board')
|
|
298
|
+
expect(data.totalItems).toBe(1)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('should return error when project not found', async () => {
|
|
302
|
+
const github = createMockGitHub()
|
|
303
|
+
const result = await readResource(
|
|
304
|
+
'memory://kanban/999',
|
|
305
|
+
db,
|
|
306
|
+
undefined,
|
|
307
|
+
undefined,
|
|
308
|
+
github
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
const data = result.data as { error: string }
|
|
312
|
+
expect(data.error).toContain('not found')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('should return error when no github', async () => {
|
|
316
|
+
const result = await readResource('memory://kanban/1', db, undefined, undefined, null)
|
|
317
|
+
|
|
318
|
+
const data = result.data as { error: string }
|
|
319
|
+
expect(data.error).toContain('GitHub integration not available')
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// ========================================================================
|
|
324
|
+
// memory://kanban/{project_number}/diagram
|
|
325
|
+
// ========================================================================
|
|
326
|
+
|
|
327
|
+
describe('memory://kanban/{project_number}/diagram', () => {
|
|
328
|
+
it('should return mermaid diagram', async () => {
|
|
329
|
+
const github = createMockGitHub({
|
|
330
|
+
getProjectKanban: vi.fn().mockResolvedValue({
|
|
331
|
+
projectId: 'PVT_1',
|
|
332
|
+
projectTitle: 'Board',
|
|
333
|
+
columns: [
|
|
334
|
+
{
|
|
335
|
+
status: 'Done',
|
|
336
|
+
items: [
|
|
337
|
+
{
|
|
338
|
+
id: 'PVTITEM_A1B2C3D4',
|
|
339
|
+
type: 'ISSUE',
|
|
340
|
+
title: 'Completed task',
|
|
341
|
+
number: 5,
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
statusOptions: [],
|
|
347
|
+
totalItems: 1,
|
|
348
|
+
}),
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
const result = await readResource(
|
|
352
|
+
'memory://kanban/1/diagram',
|
|
353
|
+
db,
|
|
354
|
+
undefined,
|
|
355
|
+
undefined,
|
|
356
|
+
github
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
const data = result.data as {
|
|
360
|
+
format: string
|
|
361
|
+
diagram: string
|
|
362
|
+
projectNumber: number
|
|
363
|
+
totalItems: number
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
expect(data.format).toBe('mermaid')
|
|
367
|
+
expect(data.diagram).toContain('graph LR')
|
|
368
|
+
expect(data.diagram).toContain('Done')
|
|
369
|
+
expect(data.totalItems).toBe(1)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('should show fallback when no github', async () => {
|
|
373
|
+
const result = await readResource(
|
|
374
|
+
'memory://kanban/1/diagram',
|
|
375
|
+
db,
|
|
376
|
+
undefined,
|
|
377
|
+
undefined,
|
|
378
|
+
null
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
const data = result.data as { format: string; diagram: string }
|
|
382
|
+
expect(data.format).toBe('mermaid')
|
|
383
|
+
expect(data.diagram).toContain('NoGitHub')
|
|
384
|
+
})
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
// ========================================================================
|
|
388
|
+
// memory://briefing with GitHub insights
|
|
389
|
+
// ========================================================================
|
|
390
|
+
|
|
391
|
+
describe('memory://briefing with GitHub', () => {
|
|
392
|
+
it('should include insights with stars, forks, and traffic', async () => {
|
|
393
|
+
const github = createMockGitHub()
|
|
394
|
+
const result = await readResource('memory://briefing', db, undefined, undefined, github)
|
|
395
|
+
|
|
396
|
+
const data = result.data as {
|
|
397
|
+
github: {
|
|
398
|
+
repo: string
|
|
399
|
+
insights?: {
|
|
400
|
+
stars: number | null
|
|
401
|
+
forks: number | null
|
|
402
|
+
clones14d?: number
|
|
403
|
+
views14d?: number
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
userMessage: string
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
expect(data.github.repo).toBe('testowner/testrepo')
|
|
410
|
+
expect(data.github.insights).toBeDefined()
|
|
411
|
+
expect(data.github.insights!.stars).toBe(42)
|
|
412
|
+
expect(data.github.insights!.forks).toBe(7)
|
|
413
|
+
expect(data.github.insights!.clones14d).toBe(120)
|
|
414
|
+
expect(data.github.insights!.views14d).toBe(500)
|
|
415
|
+
expect(data.userMessage).toContain('stars')
|
|
416
|
+
expect(data.userMessage).toContain('forks')
|
|
417
|
+
expect(data.userMessage).toContain('clones')
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
it('should include insights without traffic when getTrafficData fails', async () => {
|
|
421
|
+
const github = createMockGitHub({
|
|
422
|
+
getTrafficData: vi.fn().mockRejectedValue(new Error('403 Forbidden')),
|
|
423
|
+
})
|
|
424
|
+
const result = await readResource('memory://briefing', db, undefined, undefined, github)
|
|
425
|
+
|
|
426
|
+
const data = result.data as {
|
|
427
|
+
github: {
|
|
428
|
+
insights?: {
|
|
429
|
+
stars: number | null
|
|
430
|
+
forks: number | null
|
|
431
|
+
clones14d?: number
|
|
432
|
+
views14d?: number
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
userMessage: string
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Stars and forks should still be present
|
|
439
|
+
expect(data.github.insights).toBeDefined()
|
|
440
|
+
expect(data.github.insights!.stars).toBe(42)
|
|
441
|
+
expect(data.github.insights!.forks).toBe(7)
|
|
442
|
+
// Traffic should be absent
|
|
443
|
+
expect(data.github.insights!.clones14d).toBeUndefined()
|
|
444
|
+
expect(data.github.insights!.views14d).toBeUndefined()
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it('should omit insights when getRepoStats fails', async () => {
|
|
448
|
+
const github = createMockGitHub({
|
|
449
|
+
getRepoStats: vi.fn().mockRejectedValue(new Error('API error')),
|
|
450
|
+
})
|
|
451
|
+
const result = await readResource('memory://briefing', db, undefined, undefined, github)
|
|
452
|
+
|
|
453
|
+
const data = result.data as {
|
|
454
|
+
github: {
|
|
455
|
+
insights?: unknown
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
expect(data.github.insights).toBeUndefined()
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it('should include repoInsights in more section', async () => {
|
|
463
|
+
const github = createMockGitHub()
|
|
464
|
+
const result = await readResource('memory://briefing', db, undefined, undefined, github)
|
|
465
|
+
|
|
466
|
+
const data = result.data as {
|
|
467
|
+
more: { repoInsights: string }
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
expect(data.more.repoInsights).toBe('memory://github/insights')
|
|
471
|
+
})
|
|
472
|
+
})
|
|
473
|
+
})
|