memory-journal-mcp 4.4.2 → 5.0.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/.github/workflows/codeql.yml +1 -6
- package/.github/workflows/docker-publish.yml +15 -49
- package/.github/workflows/lint-and-test.yml +1 -1
- package/.github/workflows/secrets-scanning.yml +4 -3
- package/.github/workflows/security-update.yml +3 -3
- package/CHANGELOG.md +213 -0
- package/CONTRIBUTING.md +132 -97
- package/DOCKER_README.md +184 -235
- package/Dockerfile +27 -24
- package/README.md +218 -190
- package/SECURITY.md +27 -35
- package/dist/cli.js +16 -1
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +5 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +133 -73
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -2
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +7 -6
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +37 -24
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +319 -157
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/database/schema.d.ts +45 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +92 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +13 -2
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +1 -3
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/github.d.ts +12 -0
- package/dist/handlers/prompts/github.d.ts.map +1 -0
- package/dist/handlers/prompts/github.js +178 -0
- package/dist/handlers/prompts/github.js.map +1 -0
- package/dist/handlers/prompts/index.d.ts +23 -2
- package/dist/handlers/prompts/index.d.ts.map +1 -1
- package/dist/handlers/prompts/index.js +7 -432
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/prompts/workflow.d.ts +12 -0
- package/dist/handlers/prompts/workflow.d.ts.map +1 -0
- package/dist/handlers/prompts/workflow.js +277 -0
- package/dist/handlers/prompts/workflow.js.map +1 -0
- package/dist/handlers/resources/core.d.ts +11 -0
- package/dist/handlers/resources/core.d.ts.map +1 -0
- package/dist/handlers/resources/core.js +433 -0
- package/dist/handlers/resources/core.js.map +1 -0
- package/dist/handlers/resources/github.d.ts +11 -0
- package/dist/handlers/resources/github.d.ts.map +1 -0
- package/dist/handlers/resources/github.js +314 -0
- package/dist/handlers/resources/github.js.map +1 -0
- package/dist/handlers/resources/graph.d.ts +11 -0
- package/dist/handlers/resources/graph.d.ts.map +1 -0
- package/dist/handlers/resources/graph.js +204 -0
- package/dist/handlers/resources/graph.js.map +1 -0
- package/dist/handlers/resources/index.d.ts +5 -20
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +16 -1278
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/resources/shared.d.ts +60 -0
- package/dist/handlers/resources/shared.d.ts.map +1 -0
- package/dist/handlers/resources/shared.js +49 -0
- package/dist/handlers/resources/shared.js.map +1 -0
- package/dist/handlers/resources/team.d.ts +13 -0
- package/dist/handlers/resources/team.d.ts.map +1 -0
- package/dist/handlers/resources/team.js +119 -0
- package/dist/handlers/resources/team.js.map +1 -0
- package/dist/handlers/resources/templates.d.ts +13 -0
- package/dist/handlers/resources/templates.d.ts.map +1 -0
- package/dist/handlers/resources/templates.js +310 -0
- package/dist/handlers/resources/templates.js.map +1 -0
- package/dist/handlers/tools/admin.d.ts +8 -0
- package/dist/handlers/tools/admin.d.ts.map +1 -0
- package/dist/handlers/tools/admin.js +270 -0
- package/dist/handlers/tools/admin.js.map +1 -0
- package/dist/handlers/tools/analytics.d.ts +8 -0
- package/dist/handlers/tools/analytics.d.ts.map +1 -0
- package/dist/handlers/tools/analytics.js +256 -0
- package/dist/handlers/tools/analytics.js.map +1 -0
- package/dist/handlers/tools/backup.d.ts +8 -0
- package/dist/handlers/tools/backup.d.ts.map +1 -0
- package/dist/handlers/tools/backup.js +224 -0
- package/dist/handlers/tools/backup.js.map +1 -0
- package/dist/handlers/tools/core.d.ts +9 -0
- package/dist/handlers/tools/core.d.ts.map +1 -0
- package/dist/handlers/tools/core.js +326 -0
- package/dist/handlers/tools/core.js.map +1 -0
- package/dist/handlers/tools/export.d.ts +8 -0
- package/dist/handlers/tools/export.d.ts.map +1 -0
- package/dist/handlers/tools/export.js +89 -0
- package/dist/handlers/tools/export.js.map +1 -0
- package/dist/handlers/tools/github/helpers.d.ts +34 -0
- package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
- package/dist/handlers/tools/github/helpers.js +52 -0
- package/dist/handlers/tools/github/helpers.js.map +1 -0
- package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
- package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/insights-tools.js +104 -0
- package/dist/handlers/tools/github/insights-tools.js.map +1 -0
- package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
- package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/issue-tools.js +359 -0
- package/dist/handlers/tools/github/issue-tools.js.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.js +108 -0
- package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.js +302 -0
- package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.js +15 -0
- package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
- package/dist/handlers/tools/github/read-tools.d.ts +8 -0
- package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/read-tools.js +260 -0
- package/dist/handlers/tools/github/read-tools.js.map +1 -0
- package/dist/handlers/tools/github/schemas.d.ts +467 -0
- package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/github/schemas.js +335 -0
- package/dist/handlers/tools/github/schemas.js.map +1 -0
- package/dist/handlers/tools/github.d.ts +14 -0
- package/dist/handlers/tools/github.d.ts.map +1 -0
- package/dist/handlers/tools/github.js +28 -0
- package/dist/handlers/tools/github.js.map +1 -0
- package/dist/handlers/tools/index.d.ts +15 -20
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +117 -2909
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/handlers/tools/relationships.d.ts +8 -0
- package/dist/handlers/tools/relationships.d.ts.map +1 -0
- package/dist/handlers/tools/relationships.js +308 -0
- package/dist/handlers/tools/relationships.js.map +1 -0
- package/dist/handlers/tools/schemas.d.ts +108 -0
- package/dist/handlers/tools/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/schemas.js +122 -0
- package/dist/handlers/tools/schemas.js.map +1 -0
- package/dist/handlers/tools/search.d.ts +8 -0
- package/dist/handlers/tools/search.d.ts.map +1 -0
- package/dist/handlers/tools/search.js +282 -0
- package/dist/handlers/tools/search.js.map +1 -0
- package/dist/handlers/tools/team.d.ts +11 -0
- package/dist/handlers/tools/team.d.ts.map +1 -0
- package/dist/handlers/tools/team.js +239 -0
- package/dist/handlers/tools/team.js.map +1 -0
- package/dist/server/McpServer.d.ts +4 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +48 -297
- package/dist/server/McpServer.js.map +1 -1
- package/dist/server/Scheduler.d.ts +91 -0
- package/dist/server/Scheduler.d.ts.map +1 -0
- package/dist/server/Scheduler.js +201 -0
- package/dist/server/Scheduler.js.map +1 -0
- package/dist/transports/http.d.ts +66 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +519 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/types/entities.d.ts +101 -0
- package/dist/types/entities.d.ts.map +1 -0
- package/dist/types/entities.js +5 -0
- package/dist/types/entities.js.map +1 -0
- package/dist/types/filtering.d.ts +34 -0
- package/dist/types/filtering.d.ts.map +1 -0
- package/dist/types/filtering.js +5 -0
- package/dist/types/filtering.js.map +1 -0
- package/dist/types/github.d.ts +166 -0
- package/dist/types/github.d.ts.map +1 -0
- package/dist/types/github.js +5 -0
- package/dist/types/github.js.map +1 -0
- package/dist/types/index.d.ts +35 -292
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error-helpers.d.ts +37 -0
- package/dist/utils/error-helpers.d.ts.map +1 -0
- package/dist/utils/error-helpers.js +47 -0
- package/dist/utils/error-helpers.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +6 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security-utils.d.ts +0 -21
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +0 -47
- package/dist/utils/security-utils.js.map +1 -1
- package/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +9 -32
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +11 -2
- package/hooks/README.md +107 -0
- package/hooks/cursor/hooks.json +10 -0
- package/hooks/cursor/memory-journal.mdc +22 -0
- package/hooks/cursor/session-end.sh +19 -0
- package/hooks/kilo-code/session-end-mode.json +11 -0
- package/hooks/kiro/session-end.md +13 -0
- package/mcp-config-example.json +1 -0
- package/package.json +11 -9
- package/playwright.config.ts +29 -0
- package/releases/v4.5.0.md +116 -0
- package/releases/v5.0.0.md +105 -0
- package/scripts/generate-server-instructions.ts +176 -0
- package/scripts/server-instructions-function-body.ts +77 -0
- package/server.json +3 -3
- package/src/cli.ts +45 -1
- package/src/constants/ServerInstructions.ts +133 -73
- package/src/constants/icons.ts +8 -7
- package/src/constants/server-instructions.md +268 -0
- package/src/database/SqliteAdapter.ts +358 -192
- package/src/database/schema.ts +125 -0
- package/src/filtering/ToolFilter.ts +13 -2
- package/src/github/GitHubIntegration.ts +1 -3
- package/src/handlers/prompts/github.ts +209 -0
- package/src/handlers/prompts/index.ts +10 -499
- package/src/handlers/prompts/workflow.ts +314 -0
- package/src/handlers/resources/core.ts +528 -0
- package/src/handlers/resources/github.ts +358 -0
- package/src/handlers/resources/graph.ts +254 -0
- package/src/handlers/resources/index.ts +23 -1570
- package/src/handlers/resources/shared.ts +103 -0
- package/src/handlers/resources/team.ts +133 -0
- package/src/handlers/resources/templates.ts +374 -0
- package/src/handlers/tools/admin.ts +285 -0
- package/src/handlers/tools/analytics.ts +301 -0
- package/src/handlers/tools/backup.ts +242 -0
- package/src/handlers/tools/core.ts +350 -0
- package/src/handlers/tools/export.ts +115 -0
- package/src/handlers/tools/github/helpers.ts +86 -0
- package/src/handlers/tools/github/insights-tools.ts +119 -0
- package/src/handlers/tools/github/issue-tools.ts +439 -0
- package/src/handlers/tools/github/kanban-tools.ts +134 -0
- package/src/handlers/tools/github/milestone-tools.ts +392 -0
- package/src/handlers/tools/github/mutation-tools.ts +17 -0
- package/src/handlers/tools/github/read-tools.ts +328 -0
- package/src/handlers/tools/github/schemas.ts +369 -0
- package/src/handlers/tools/github.ts +36 -0
- package/src/handlers/tools/index.ts +144 -3325
- package/src/handlers/tools/relationships.ts +358 -0
- package/src/handlers/tools/schemas.ts +132 -0
- package/src/handlers/tools/search.ts +343 -0
- package/src/handlers/tools/team.ts +273 -0
- package/src/server/McpServer.ts +63 -358
- package/src/server/Scheduler.ts +278 -0
- package/src/transports/http.ts +635 -0
- package/src/types/entities.ts +145 -0
- package/src/types/filtering.ts +54 -0
- package/src/types/github.ts +180 -0
- package/src/types/index.ts +67 -375
- package/src/utils/error-helpers.ts +52 -0
- package/src/utils/logger.ts +6 -3
- package/src/utils/security-utils.ts +0 -52
- package/src/vector/VectorSearchManager.ts +9 -33
- package/tests/constants/icons.test.ts +1 -2
- package/tests/constants/server-instructions.test.ts +30 -4
- package/tests/database/sqlite-adapter.test.ts +91 -7
- package/tests/e2e/auth.spec.ts +154 -0
- package/tests/e2e/health.spec.ts +63 -0
- package/tests/e2e/protocols.spec.ts +134 -0
- package/tests/e2e/resources.spec.ts +103 -0
- package/tests/e2e/scheduler.spec.ts +79 -0
- package/tests/e2e/security.spec.ts +91 -0
- package/tests/e2e/sessions.spec.ts +95 -0
- package/tests/e2e/stateless.spec.ts +121 -0
- package/tests/e2e/tools.spec.ts +111 -0
- package/tests/filtering/tool-filter.test.ts +46 -0
- package/tests/handlers/error-path-coverage.test.ts +324 -0
- package/tests/handlers/github-resource-handlers.test.ts +453 -0
- package/tests/handlers/github-tool-handlers.test.ts +899 -0
- package/tests/handlers/prompt-handler-coverage.test.ts +106 -0
- package/tests/handlers/prompt-handlers.test.ts +40 -0
- package/tests/handlers/resource-handler-coverage.test.ts +181 -0
- package/tests/handlers/resource-handlers.test.ts +33 -9
- package/tests/handlers/search-tool-handlers.test.ts +272 -0
- package/tests/handlers/targeted-gap-closure.test.ts +387 -0
- package/tests/handlers/team-resource-handlers.test.ts +156 -0
- package/tests/handlers/team-tool-handlers.test.ts +301 -0
- package/tests/handlers/tool-handler-coverage.test.ts +469 -0
- package/tests/handlers/tool-handlers.test.ts +2 -2
- package/tests/security/sql-injection.test.ts +3 -54
- package/tests/server/mcp-server.test.ts +503 -8
- package/tests/server/scheduler.test.ts +400 -0
- package/tests/transports/http-transport.test.ts +620 -0
- package/tests/vector/vector-search-manager.test.ts +60 -0
- package/vitest.config.ts +4 -1
- package/.memory-journal-team.db +0 -0
- package/.vscode/settings.json +0 -84
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Transport Coverage Tests
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for HttpTransport class with mocked Express and MCP SDK.
|
|
5
|
+
* Focuses on easy coverage gains: constructor, stop(), middleware behavior,
|
|
6
|
+
* and route handler logic — without spinning up real HTTP servers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Hoisted mocks — vi.hoisted ensures these are available before vi.mock
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
mockHandleRequest,
|
|
17
|
+
mockTransportClose,
|
|
18
|
+
mockRoutes,
|
|
19
|
+
mockMiddlewares,
|
|
20
|
+
mockApp,
|
|
21
|
+
MockStreamableHTTPServerTransport,
|
|
22
|
+
MockSSEServerTransport,
|
|
23
|
+
} = vi.hoisted(() => {
|
|
24
|
+
const handleRequest = vi.fn().mockResolvedValue(undefined)
|
|
25
|
+
const transportClose = vi.fn().mockResolvedValue(undefined)
|
|
26
|
+
|
|
27
|
+
const routes: Record<string, Record<string, Function>> = {
|
|
28
|
+
get: {},
|
|
29
|
+
post: {},
|
|
30
|
+
delete: {},
|
|
31
|
+
all: {},
|
|
32
|
+
}
|
|
33
|
+
const middlewares: Function[] = []
|
|
34
|
+
|
|
35
|
+
const app = {
|
|
36
|
+
use: vi.fn().mockImplementation((...args: unknown[]) => {
|
|
37
|
+
if (args.length === 1 && typeof args[0] === 'function') {
|
|
38
|
+
middlewares.push(args[0] as Function)
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
get: vi.fn().mockImplementation((path: string, handler: Function) => {
|
|
42
|
+
routes['get']![path] = handler
|
|
43
|
+
}),
|
|
44
|
+
post: vi.fn().mockImplementation((path: string, handler: Function) => {
|
|
45
|
+
routes['post']![path] = handler
|
|
46
|
+
}),
|
|
47
|
+
delete: vi.fn().mockImplementation((path: string, handler: Function) => {
|
|
48
|
+
routes['delete']![path] = handler
|
|
49
|
+
}),
|
|
50
|
+
all: vi.fn().mockImplementation((path: string, handler: Function) => {
|
|
51
|
+
routes['all']![path] = handler
|
|
52
|
+
}),
|
|
53
|
+
listen: vi.fn().mockImplementation((_port: number, _host: string, cb?: () => void) => {
|
|
54
|
+
if (cb) cb()
|
|
55
|
+
return { close: vi.fn(), on: vi.fn() }
|
|
56
|
+
}),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Class-based mocks — classes work correctly with `new` operator
|
|
60
|
+
class StreamableMock {
|
|
61
|
+
sessionId: string
|
|
62
|
+
handleRequest = handleRequest
|
|
63
|
+
close = transportClose
|
|
64
|
+
onclose: (() => void) | null = null
|
|
65
|
+
constructor(opts?: {
|
|
66
|
+
sessionIdGenerator?: () => string
|
|
67
|
+
onsessioninitialized?: (sid: string) => void
|
|
68
|
+
}) {
|
|
69
|
+
this.sessionId = opts?.sessionIdGenerator?.() ?? 'mock-session-id'
|
|
70
|
+
if (opts?.onsessioninitialized) {
|
|
71
|
+
setTimeout(() => opts.onsessioninitialized!(this.sessionId), 0)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class SSEMock {
|
|
77
|
+
sessionId = 'sse-mock-session'
|
|
78
|
+
handlePostMessage = vi.fn().mockResolvedValue(undefined)
|
|
79
|
+
close = transportClose
|
|
80
|
+
onclose: (() => void) | null = null
|
|
81
|
+
start = vi.fn().mockResolvedValue(undefined)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
mockHandleRequest: handleRequest,
|
|
86
|
+
mockTransportClose: transportClose,
|
|
87
|
+
mockRoutes: routes,
|
|
88
|
+
mockMiddlewares: middlewares,
|
|
89
|
+
mockApp: app,
|
|
90
|
+
MockStreamableHTTPServerTransport: StreamableMock,
|
|
91
|
+
MockSSEServerTransport: SSEMock,
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Module mocks
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
vi.mock('@modelcontextprotocol/sdk/server/streamableHttp.js', () => ({
|
|
100
|
+
StreamableHTTPServerTransport: MockStreamableHTTPServerTransport,
|
|
101
|
+
}))
|
|
102
|
+
|
|
103
|
+
vi.mock('@modelcontextprotocol/sdk/server/sse.js', () => ({
|
|
104
|
+
SSEServerTransport: MockSSEServerTransport,
|
|
105
|
+
}))
|
|
106
|
+
|
|
107
|
+
vi.mock('@modelcontextprotocol/sdk/types.js', () => ({
|
|
108
|
+
isInitializeRequest: vi.fn().mockReturnValue(false),
|
|
109
|
+
}))
|
|
110
|
+
|
|
111
|
+
vi.mock('express', () => {
|
|
112
|
+
const expressFn = vi.fn().mockReturnValue(mockApp)
|
|
113
|
+
return {
|
|
114
|
+
default: Object.assign(expressFn, {
|
|
115
|
+
json: vi.fn().mockReturnValue(vi.fn()),
|
|
116
|
+
}),
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
vi.mock('express-rate-limit', () => ({
|
|
121
|
+
default: vi.fn().mockReturnValue(vi.fn()),
|
|
122
|
+
}))
|
|
123
|
+
|
|
124
|
+
vi.mock('../../src/utils/logger.js', () => ({
|
|
125
|
+
logger: {
|
|
126
|
+
info: vi.fn(),
|
|
127
|
+
warning: vi.fn(),
|
|
128
|
+
error: vi.fn(),
|
|
129
|
+
debug: vi.fn(),
|
|
130
|
+
},
|
|
131
|
+
}))
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Import after mocks
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
import { HttpTransport, type HttpTransportConfig } from '../../src/transports/http.js'
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Helpers
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
function mockReq(overrides: Partial<Record<string, unknown>> = {}): Record<string, unknown> {
|
|
144
|
+
return {
|
|
145
|
+
method: 'GET',
|
|
146
|
+
path: '/',
|
|
147
|
+
headers: {},
|
|
148
|
+
query: {},
|
|
149
|
+
body: {},
|
|
150
|
+
on: vi.fn(),
|
|
151
|
+
...overrides,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function mockRes(): Record<string, unknown> {
|
|
156
|
+
const res: Record<string, unknown> = {
|
|
157
|
+
headersSent: false,
|
|
158
|
+
setHeader: vi.fn(),
|
|
159
|
+
json: vi.fn(),
|
|
160
|
+
send: vi.fn(),
|
|
161
|
+
end: vi.fn(),
|
|
162
|
+
}
|
|
163
|
+
// Make status().json() / status().end() / status().send() chain work
|
|
164
|
+
res['status'] = vi.fn().mockReturnValue(res)
|
|
165
|
+
return res
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Tests
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
describe('HttpTransport', () => {
|
|
173
|
+
const mockServer = {
|
|
174
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
175
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
176
|
+
} as unknown as Parameters<HttpTransport['start']>[0]
|
|
177
|
+
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
vi.clearAllMocks()
|
|
180
|
+
for (const method of Object.keys(mockRoutes)) {
|
|
181
|
+
mockRoutes[method] = {}
|
|
182
|
+
}
|
|
183
|
+
mockMiddlewares.length = 0
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// ========================================================================
|
|
187
|
+
// Constructor
|
|
188
|
+
// ========================================================================
|
|
189
|
+
|
|
190
|
+
describe('constructor', () => {
|
|
191
|
+
it('should create instance with config', () => {
|
|
192
|
+
const config: HttpTransportConfig = {
|
|
193
|
+
port: 3000,
|
|
194
|
+
host: '0.0.0.0',
|
|
195
|
+
corsOrigin: '*',
|
|
196
|
+
stateless: false,
|
|
197
|
+
}
|
|
198
|
+
const transport = new HttpTransport(config)
|
|
199
|
+
expect(transport).toBeDefined()
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// ========================================================================
|
|
204
|
+
// start — stateless
|
|
205
|
+
// ========================================================================
|
|
206
|
+
|
|
207
|
+
describe('start - stateless mode', () => {
|
|
208
|
+
it('should register routes and start server', async () => {
|
|
209
|
+
const config: HttpTransportConfig = {
|
|
210
|
+
port: 3000,
|
|
211
|
+
host: '0.0.0.0',
|
|
212
|
+
corsOrigin: '*',
|
|
213
|
+
stateless: true,
|
|
214
|
+
}
|
|
215
|
+
const transport = new HttpTransport(config)
|
|
216
|
+
await transport.start(mockServer, null)
|
|
217
|
+
|
|
218
|
+
expect(mockApp.use).toHaveBeenCalled()
|
|
219
|
+
expect(mockApp.listen).toHaveBeenCalledWith(3000, '0.0.0.0', expect.any(Function))
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should warn about wildcard CORS and no auth', async () => {
|
|
223
|
+
const { logger } = await import('../../src/utils/logger.js')
|
|
224
|
+
const config: HttpTransportConfig = {
|
|
225
|
+
port: 3000,
|
|
226
|
+
host: '0.0.0.0',
|
|
227
|
+
corsOrigin: '*',
|
|
228
|
+
stateless: true,
|
|
229
|
+
}
|
|
230
|
+
const transport = new HttpTransport(config)
|
|
231
|
+
await transport.start(mockServer, null)
|
|
232
|
+
|
|
233
|
+
expect(logger.warning).toHaveBeenCalledWith(
|
|
234
|
+
expect.stringContaining('CORS origin'),
|
|
235
|
+
expect.any(Object)
|
|
236
|
+
)
|
|
237
|
+
expect(logger.warning).toHaveBeenCalledWith(
|
|
238
|
+
expect.stringContaining('No authentication'),
|
|
239
|
+
expect.any(Object)
|
|
240
|
+
)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// ========================================================================
|
|
245
|
+
// start — stateful with auth
|
|
246
|
+
// ========================================================================
|
|
247
|
+
|
|
248
|
+
describe('start - stateful mode with auth', () => {
|
|
249
|
+
it('should setup auth middleware when token provided', async () => {
|
|
250
|
+
const config: HttpTransportConfig = {
|
|
251
|
+
port: 3000,
|
|
252
|
+
host: '0.0.0.0',
|
|
253
|
+
corsOrigin: 'http://localhost',
|
|
254
|
+
stateless: false,
|
|
255
|
+
authToken: 'test-token-123',
|
|
256
|
+
}
|
|
257
|
+
const transport = new HttpTransport(config)
|
|
258
|
+
await transport.start(mockServer, null)
|
|
259
|
+
|
|
260
|
+
expect(mockApp.use).toHaveBeenCalled()
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// ========================================================================
|
|
265
|
+
// start — middleware behavior
|
|
266
|
+
// ========================================================================
|
|
267
|
+
|
|
268
|
+
describe('middleware behavior', () => {
|
|
269
|
+
it('should set security headers on requests', async () => {
|
|
270
|
+
const config: HttpTransportConfig = {
|
|
271
|
+
port: 3000,
|
|
272
|
+
host: '0.0.0.0',
|
|
273
|
+
corsOrigin: '*',
|
|
274
|
+
stateless: true,
|
|
275
|
+
}
|
|
276
|
+
const transport = new HttpTransport(config)
|
|
277
|
+
await transport.start(mockServer, null)
|
|
278
|
+
|
|
279
|
+
// Security headers middleware is first
|
|
280
|
+
const securityMw = mockMiddlewares[0]
|
|
281
|
+
expect(securityMw).toBeDefined()
|
|
282
|
+
|
|
283
|
+
const req = mockReq()
|
|
284
|
+
const res = mockRes()
|
|
285
|
+
securityMw!(req, res, () => {})
|
|
286
|
+
|
|
287
|
+
const setCalls = (res['setHeader'] as ReturnType<typeof vi.fn>).mock.calls
|
|
288
|
+
const headerNames = setCalls.map((c: unknown[]) => c[0] as string)
|
|
289
|
+
expect(headerNames).toContain('X-Content-Type-Options')
|
|
290
|
+
expect(headerNames).toContain('X-Frame-Options')
|
|
291
|
+
expect(headerNames).toContain('Cache-Control')
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should set HSTS when x-forwarded-proto is https', async () => {
|
|
295
|
+
const config: HttpTransportConfig = {
|
|
296
|
+
port: 3000,
|
|
297
|
+
host: '0.0.0.0',
|
|
298
|
+
corsOrigin: '*',
|
|
299
|
+
stateless: true,
|
|
300
|
+
}
|
|
301
|
+
const transport = new HttpTransport(config)
|
|
302
|
+
await transport.start(mockServer, null)
|
|
303
|
+
|
|
304
|
+
const securityMw = mockMiddlewares[0]
|
|
305
|
+
const req = mockReq({
|
|
306
|
+
headers: { 'x-forwarded-proto': 'https' },
|
|
307
|
+
})
|
|
308
|
+
const res = mockRes()
|
|
309
|
+
securityMw!(req, res, () => {})
|
|
310
|
+
|
|
311
|
+
const setCalls = (res['setHeader'] as ReturnType<typeof vi.fn>).mock.calls
|
|
312
|
+
const headerNames = setCalls.map((c: unknown[]) => c[0] as string)
|
|
313
|
+
expect(headerNames).toContain('Strict-Transport-Security')
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('should handle CORS OPTIONS preflight with 204', async () => {
|
|
317
|
+
const config: HttpTransportConfig = {
|
|
318
|
+
port: 3000,
|
|
319
|
+
host: '0.0.0.0',
|
|
320
|
+
corsOrigin: '*',
|
|
321
|
+
stateless: true,
|
|
322
|
+
}
|
|
323
|
+
const transport = new HttpTransport(config)
|
|
324
|
+
await transport.start(mockServer, null)
|
|
325
|
+
|
|
326
|
+
// CORS middleware is second
|
|
327
|
+
const corsMw = mockMiddlewares[1]
|
|
328
|
+
expect(corsMw).toBeDefined()
|
|
329
|
+
|
|
330
|
+
const req = mockReq({ method: 'OPTIONS' })
|
|
331
|
+
const res = mockRes()
|
|
332
|
+
corsMw!(req, res, () => {})
|
|
333
|
+
|
|
334
|
+
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(204)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('should pass non-OPTIONS requests through CORS middleware', async () => {
|
|
338
|
+
const config: HttpTransportConfig = {
|
|
339
|
+
port: 3000,
|
|
340
|
+
host: '0.0.0.0',
|
|
341
|
+
corsOrigin: '*',
|
|
342
|
+
stateless: true,
|
|
343
|
+
}
|
|
344
|
+
const transport = new HttpTransport(config)
|
|
345
|
+
await transport.start(mockServer, null)
|
|
346
|
+
|
|
347
|
+
const corsMw = mockMiddlewares[1]
|
|
348
|
+
const req = mockReq({ method: 'POST' })
|
|
349
|
+
const res = mockRes()
|
|
350
|
+
let nextCalled = false
|
|
351
|
+
corsMw!(req, res, () => {
|
|
352
|
+
nextCalled = true
|
|
353
|
+
})
|
|
354
|
+
expect(nextCalled).toBe(true)
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
// ========================================================================
|
|
359
|
+
// start — route handlers
|
|
360
|
+
// ========================================================================
|
|
361
|
+
|
|
362
|
+
describe('route handlers', () => {
|
|
363
|
+
it('should return healthy on GET /health', async () => {
|
|
364
|
+
const config: HttpTransportConfig = {
|
|
365
|
+
port: 3000,
|
|
366
|
+
host: '0.0.0.0',
|
|
367
|
+
corsOrigin: '*',
|
|
368
|
+
stateless: true,
|
|
369
|
+
}
|
|
370
|
+
const transport = new HttpTransport(config)
|
|
371
|
+
await transport.start(mockServer, null)
|
|
372
|
+
|
|
373
|
+
const handler = mockRoutes['get']!['/health']
|
|
374
|
+
expect(handler).toBeDefined()
|
|
375
|
+
|
|
376
|
+
const res = mockRes()
|
|
377
|
+
handler!(mockReq(), res)
|
|
378
|
+
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(200)
|
|
379
|
+
expect(res['json'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(
|
|
380
|
+
expect.objectContaining({ status: 'healthy' })
|
|
381
|
+
)
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('should return server info on GET /', async () => {
|
|
385
|
+
const config: HttpTransportConfig = {
|
|
386
|
+
port: 3000,
|
|
387
|
+
host: '0.0.0.0',
|
|
388
|
+
corsOrigin: '*',
|
|
389
|
+
stateless: true,
|
|
390
|
+
}
|
|
391
|
+
const transport = new HttpTransport(config)
|
|
392
|
+
await transport.start(mockServer, null)
|
|
393
|
+
|
|
394
|
+
const handler = mockRoutes['get']!['/']
|
|
395
|
+
expect(handler).toBeDefined()
|
|
396
|
+
|
|
397
|
+
const res = mockRes()
|
|
398
|
+
handler!(mockReq(), res)
|
|
399
|
+
expect(res['json'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(
|
|
400
|
+
expect.objectContaining({ name: 'memory-journal-mcp' })
|
|
401
|
+
)
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('should return 204 for OPTIONS /mcp', async () => {
|
|
405
|
+
const config: HttpTransportConfig = {
|
|
406
|
+
port: 3000,
|
|
407
|
+
host: '0.0.0.0',
|
|
408
|
+
corsOrigin: '*',
|
|
409
|
+
stateless: true,
|
|
410
|
+
}
|
|
411
|
+
const transport = new HttpTransport(config)
|
|
412
|
+
await transport.start(mockServer, null)
|
|
413
|
+
|
|
414
|
+
const handler = mockRoutes['all']!['/mcp']
|
|
415
|
+
expect(handler).toBeDefined()
|
|
416
|
+
|
|
417
|
+
const req = mockReq({ method: 'OPTIONS' })
|
|
418
|
+
const res = mockRes()
|
|
419
|
+
handler!(req, res, () => {})
|
|
420
|
+
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(204)
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('should pass non-OPTIONS /mcp to next', async () => {
|
|
424
|
+
const config: HttpTransportConfig = {
|
|
425
|
+
port: 3000,
|
|
426
|
+
host: '0.0.0.0',
|
|
427
|
+
corsOrigin: '*',
|
|
428
|
+
stateless: true,
|
|
429
|
+
}
|
|
430
|
+
const transport = new HttpTransport(config)
|
|
431
|
+
await transport.start(mockServer, null)
|
|
432
|
+
|
|
433
|
+
const handler = mockRoutes['all']!['/mcp']
|
|
434
|
+
const req = mockReq({ method: 'POST' })
|
|
435
|
+
const res = mockRes()
|
|
436
|
+
let nextCalled = false
|
|
437
|
+
handler!(req, res, () => {
|
|
438
|
+
nextCalled = true
|
|
439
|
+
})
|
|
440
|
+
expect(nextCalled).toBe(true)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('should return 404 for unknown routes', async () => {
|
|
444
|
+
const config: HttpTransportConfig = {
|
|
445
|
+
port: 3000,
|
|
446
|
+
host: '0.0.0.0',
|
|
447
|
+
corsOrigin: '*',
|
|
448
|
+
stateless: true,
|
|
449
|
+
}
|
|
450
|
+
const transport = new HttpTransport(config)
|
|
451
|
+
await transport.start(mockServer, null)
|
|
452
|
+
|
|
453
|
+
// 404 handler is the last registered middleware
|
|
454
|
+
const lastMw = mockMiddlewares[mockMiddlewares.length - 1]
|
|
455
|
+
expect(lastMw).toBeDefined()
|
|
456
|
+
|
|
457
|
+
const res = mockRes()
|
|
458
|
+
lastMw!(mockReq(), res)
|
|
459
|
+
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(404)
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
// ========================================================================
|
|
464
|
+
// start — stateless /mcp routes
|
|
465
|
+
// ========================================================================
|
|
466
|
+
|
|
467
|
+
describe('stateless /mcp routes', () => {
|
|
468
|
+
it('should return 405 for GET /mcp in stateless mode', async () => {
|
|
469
|
+
const config: HttpTransportConfig = {
|
|
470
|
+
port: 3000,
|
|
471
|
+
host: '0.0.0.0',
|
|
472
|
+
corsOrigin: '*',
|
|
473
|
+
stateless: true,
|
|
474
|
+
}
|
|
475
|
+
const transport = new HttpTransport(config)
|
|
476
|
+
await transport.start(mockServer, null)
|
|
477
|
+
|
|
478
|
+
const handler = mockRoutes['get']!['/mcp']
|
|
479
|
+
expect(handler).toBeDefined()
|
|
480
|
+
|
|
481
|
+
const res = mockRes()
|
|
482
|
+
handler!(mockReq(), res)
|
|
483
|
+
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(405)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('should return 204 for DELETE /mcp in stateless mode', async () => {
|
|
487
|
+
const config: HttpTransportConfig = {
|
|
488
|
+
port: 3000,
|
|
489
|
+
host: '0.0.0.0',
|
|
490
|
+
corsOrigin: '*',
|
|
491
|
+
stateless: true,
|
|
492
|
+
}
|
|
493
|
+
const transport = new HttpTransport(config)
|
|
494
|
+
await transport.start(mockServer, null)
|
|
495
|
+
|
|
496
|
+
const handler = mockRoutes['delete']!['/mcp']
|
|
497
|
+
expect(handler).toBeDefined()
|
|
498
|
+
|
|
499
|
+
const res = mockRes()
|
|
500
|
+
handler!(mockReq(), res)
|
|
501
|
+
expect(res['end'] as ReturnType<typeof vi.fn>).toHaveBeenCalled()
|
|
502
|
+
})
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
// ========================================================================
|
|
506
|
+
// start — scheduler
|
|
507
|
+
// ========================================================================
|
|
508
|
+
|
|
509
|
+
describe('scheduler', () => {
|
|
510
|
+
it('should start scheduler after server listen', async () => {
|
|
511
|
+
const config: HttpTransportConfig = {
|
|
512
|
+
port: 3000,
|
|
513
|
+
host: '0.0.0.0',
|
|
514
|
+
corsOrigin: '*',
|
|
515
|
+
stateless: true,
|
|
516
|
+
}
|
|
517
|
+
const mockScheduler = { start: vi.fn(), stop: vi.fn() }
|
|
518
|
+
const transport = new HttpTransport(config)
|
|
519
|
+
await transport.start(
|
|
520
|
+
mockServer,
|
|
521
|
+
mockScheduler as unknown as Parameters<HttpTransport['start']>[1]
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
expect(mockScheduler.start).toHaveBeenCalled()
|
|
525
|
+
})
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
// ========================================================================
|
|
529
|
+
// stop
|
|
530
|
+
// ========================================================================
|
|
531
|
+
|
|
532
|
+
describe('stop', () => {
|
|
533
|
+
it('should clean up on stop', async () => {
|
|
534
|
+
const config: HttpTransportConfig = {
|
|
535
|
+
port: 3000,
|
|
536
|
+
host: '0.0.0.0',
|
|
537
|
+
corsOrigin: '*',
|
|
538
|
+
stateless: true,
|
|
539
|
+
}
|
|
540
|
+
const transport = new HttpTransport(config)
|
|
541
|
+
await transport.start(mockServer, null)
|
|
542
|
+
await transport.stop(null)
|
|
543
|
+
// Should not throw
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
it('should stop scheduler on stop', async () => {
|
|
547
|
+
const config: HttpTransportConfig = {
|
|
548
|
+
port: 3000,
|
|
549
|
+
host: '0.0.0.0',
|
|
550
|
+
corsOrigin: '*',
|
|
551
|
+
stateless: true,
|
|
552
|
+
}
|
|
553
|
+
const mockScheduler = { start: vi.fn(), stop: vi.fn() }
|
|
554
|
+
const transport = new HttpTransport(config)
|
|
555
|
+
await transport.start(
|
|
556
|
+
mockServer,
|
|
557
|
+
mockScheduler as unknown as Parameters<HttpTransport['start']>[1]
|
|
558
|
+
)
|
|
559
|
+
await transport.stop(mockScheduler as unknown as Parameters<HttpTransport['stop']>[0])
|
|
560
|
+
|
|
561
|
+
expect(mockScheduler.stop).toHaveBeenCalled()
|
|
562
|
+
})
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
// ========================================================================
|
|
566
|
+
// Auth middleware
|
|
567
|
+
// ========================================================================
|
|
568
|
+
|
|
569
|
+
describe('auth middleware', () => {
|
|
570
|
+
it('should reject bad tokens with 401', async () => {
|
|
571
|
+
const config: HttpTransportConfig = {
|
|
572
|
+
port: 3000,
|
|
573
|
+
host: '0.0.0.0',
|
|
574
|
+
corsOrigin: 'http://localhost',
|
|
575
|
+
stateless: true,
|
|
576
|
+
authToken: 'secret-token',
|
|
577
|
+
}
|
|
578
|
+
const transport = new HttpTransport(config)
|
|
579
|
+
await transport.start(mockServer, null)
|
|
580
|
+
|
|
581
|
+
// Auth middleware checks req.path and authorization header.
|
|
582
|
+
// It's registered via app.use() after security, CORS, json, rate-limit.
|
|
583
|
+
// Find it by testing: sends 401 for bad token on /mcp path.
|
|
584
|
+
const authMw = mockMiddlewares.find((mw) => {
|
|
585
|
+
const req = mockReq({
|
|
586
|
+
path: '/mcp',
|
|
587
|
+
headers: { authorization: 'Bearer wrong-token' },
|
|
588
|
+
})
|
|
589
|
+
const res = mockRes()
|
|
590
|
+
mw(req, res, () => {})
|
|
591
|
+
return (res['status'] as ReturnType<typeof vi.fn>).mock.calls.some(
|
|
592
|
+
(c: unknown[]) => c[0] === 401
|
|
593
|
+
)
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
expect(authMw).toBeDefined()
|
|
597
|
+
|
|
598
|
+
// /health should bypass auth
|
|
599
|
+
const healthReq = mockReq({ path: '/health' })
|
|
600
|
+
const healthRes = mockRes()
|
|
601
|
+
let healthNext = false
|
|
602
|
+
authMw!(healthReq, healthRes, () => {
|
|
603
|
+
healthNext = true
|
|
604
|
+
})
|
|
605
|
+
expect(healthNext).toBe(true)
|
|
606
|
+
|
|
607
|
+
// Valid token should pass through
|
|
608
|
+
const goodReq = mockReq({
|
|
609
|
+
path: '/mcp',
|
|
610
|
+
headers: { authorization: 'Bearer secret-token' },
|
|
611
|
+
})
|
|
612
|
+
const goodRes = mockRes()
|
|
613
|
+
let goodNext = false
|
|
614
|
+
authMw!(goodReq, goodRes, () => {
|
|
615
|
+
goodNext = true
|
|
616
|
+
})
|
|
617
|
+
expect(goodNext).toBe(true)
|
|
618
|
+
})
|
|
619
|
+
})
|
|
620
|
+
})
|
|
@@ -331,5 +331,65 @@ describe('VectorSearchManager', () => {
|
|
|
331
331
|
const indexed = await vm.rebuildIndex(mockDb as any)
|
|
332
332
|
expect(indexed).toBe(0)
|
|
333
333
|
})
|
|
334
|
+
|
|
335
|
+
it('should recover from corrupted index', async () => {
|
|
336
|
+
await initManager(vm)
|
|
337
|
+
mockEmbedderFn.mockResolvedValue({ data: fakeEmbedding(0) })
|
|
338
|
+
|
|
339
|
+
// First listItems call throws (corrupted index)
|
|
340
|
+
mockListItems.mockRejectedValueOnce(new Error('Corrupted index'))
|
|
341
|
+
// After recreation, listItems returns empty
|
|
342
|
+
mockListItems.mockResolvedValueOnce([])
|
|
343
|
+
mockInsertItem.mockResolvedValue(undefined)
|
|
344
|
+
|
|
345
|
+
const mockDb = {
|
|
346
|
+
getActiveEntryCount: vi.fn().mockReturnValue(1),
|
|
347
|
+
getEntriesPage: vi.fn().mockReturnValue([{ id: 1, content: 'Entry' }]),
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
351
|
+
const indexed = await vm.rebuildIndex(mockDb as any)
|
|
352
|
+
expect(indexed).toBe(1)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('should skip entries with embedding failures', async () => {
|
|
356
|
+
await initManager(vm)
|
|
357
|
+
// First call succeeds, second fails
|
|
358
|
+
mockEmbedderFn
|
|
359
|
+
.mockResolvedValueOnce({ data: fakeEmbedding(1) })
|
|
360
|
+
.mockRejectedValueOnce(new Error('Embedding failed'))
|
|
361
|
+
mockListItems.mockResolvedValue([])
|
|
362
|
+
mockInsertItem.mockResolvedValue(undefined)
|
|
363
|
+
|
|
364
|
+
const mockDb = {
|
|
365
|
+
getActiveEntryCount: vi.fn().mockReturnValue(2),
|
|
366
|
+
getEntriesPage: vi.fn().mockReturnValue([
|
|
367
|
+
{ id: 1, content: 'Good entry' },
|
|
368
|
+
{ id: 2, content: 'Will fail embedding' },
|
|
369
|
+
]),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
373
|
+
const indexed = await vm.rebuildIndex(mockDb as any)
|
|
374
|
+
// Only 1 should be indexed (the other failed)
|
|
375
|
+
expect(indexed).toBe(1)
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// ========================================================================
|
|
380
|
+
// Initialize Error
|
|
381
|
+
// ========================================================================
|
|
382
|
+
|
|
383
|
+
describe('initialize error', () => {
|
|
384
|
+
it('should rethrow pipeline errors', async () => {
|
|
385
|
+
const { pipeline: pipelineMock } = await import('@xenova/transformers')
|
|
386
|
+
;(pipelineMock as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
|
|
387
|
+
new Error('Model not found')
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
const vm2 = new VectorSearchManager('/tmp/test-error.db')
|
|
391
|
+
await expect(vm2.initialize()).rejects.toThrow('Model not found')
|
|
392
|
+
expect(vm2.isInitialized()).toBe(false)
|
|
393
|
+
})
|
|
334
394
|
})
|
|
335
395
|
})
|
package/vitest.config.ts
CHANGED
|
@@ -9,7 +9,10 @@ export default defineConfig({
|
|
|
9
9
|
provider: 'v8',
|
|
10
10
|
reporter: ['text', 'html'],
|
|
11
11
|
include: ['src/**/*.ts'],
|
|
12
|
-
exclude: ['src/cli.ts', 'src/index.ts'],
|
|
12
|
+
exclude: ['src/cli.ts', 'src/index.ts', 'src/transports/http.ts', 'src/types/**'],
|
|
13
13
|
},
|
|
14
14
|
},
|
|
15
|
+
benchmark: {
|
|
16
|
+
hookTimeout: 30_000,
|
|
17
|
+
},
|
|
15
18
|
})
|
package/.memory-journal-team.db
DELETED
|
Binary file
|