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.
Files changed (291) hide show
  1. package/.github/workflows/codeql.yml +1 -6
  2. package/.github/workflows/docker-publish.yml +15 -49
  3. package/.github/workflows/lint-and-test.yml +1 -1
  4. package/.github/workflows/secrets-scanning.yml +4 -3
  5. package/.github/workflows/security-update.yml +3 -3
  6. package/CHANGELOG.md +213 -0
  7. package/CONTRIBUTING.md +132 -97
  8. package/DOCKER_README.md +184 -235
  9. package/Dockerfile +27 -24
  10. package/README.md +218 -190
  11. package/SECURITY.md +27 -35
  12. package/dist/cli.js +16 -1
  13. package/dist/cli.js.map +1 -1
  14. package/dist/constants/ServerInstructions.d.ts +5 -1
  15. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  16. package/dist/constants/ServerInstructions.js +133 -73
  17. package/dist/constants/ServerInstructions.js.map +1 -1
  18. package/dist/constants/icons.d.ts +2 -2
  19. package/dist/constants/icons.d.ts.map +1 -1
  20. package/dist/constants/icons.js +7 -6
  21. package/dist/constants/icons.js.map +1 -1
  22. package/dist/database/SqliteAdapter.d.ts +37 -24
  23. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  24. package/dist/database/SqliteAdapter.js +319 -157
  25. package/dist/database/SqliteAdapter.js.map +1 -1
  26. package/dist/database/schema.d.ts +45 -0
  27. package/dist/database/schema.d.ts.map +1 -0
  28. package/dist/database/schema.js +92 -0
  29. package/dist/database/schema.js.map +1 -0
  30. package/dist/filtering/ToolFilter.d.ts +1 -1
  31. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  32. package/dist/filtering/ToolFilter.js +13 -2
  33. package/dist/filtering/ToolFilter.js.map +1 -1
  34. package/dist/github/GitHubIntegration.d.ts.map +1 -1
  35. package/dist/github/GitHubIntegration.js +1 -3
  36. package/dist/github/GitHubIntegration.js.map +1 -1
  37. package/dist/handlers/prompts/github.d.ts +12 -0
  38. package/dist/handlers/prompts/github.d.ts.map +1 -0
  39. package/dist/handlers/prompts/github.js +178 -0
  40. package/dist/handlers/prompts/github.js.map +1 -0
  41. package/dist/handlers/prompts/index.d.ts +23 -2
  42. package/dist/handlers/prompts/index.d.ts.map +1 -1
  43. package/dist/handlers/prompts/index.js +7 -432
  44. package/dist/handlers/prompts/index.js.map +1 -1
  45. package/dist/handlers/prompts/workflow.d.ts +12 -0
  46. package/dist/handlers/prompts/workflow.d.ts.map +1 -0
  47. package/dist/handlers/prompts/workflow.js +277 -0
  48. package/dist/handlers/prompts/workflow.js.map +1 -0
  49. package/dist/handlers/resources/core.d.ts +11 -0
  50. package/dist/handlers/resources/core.d.ts.map +1 -0
  51. package/dist/handlers/resources/core.js +433 -0
  52. package/dist/handlers/resources/core.js.map +1 -0
  53. package/dist/handlers/resources/github.d.ts +11 -0
  54. package/dist/handlers/resources/github.d.ts.map +1 -0
  55. package/dist/handlers/resources/github.js +314 -0
  56. package/dist/handlers/resources/github.js.map +1 -0
  57. package/dist/handlers/resources/graph.d.ts +11 -0
  58. package/dist/handlers/resources/graph.d.ts.map +1 -0
  59. package/dist/handlers/resources/graph.js +204 -0
  60. package/dist/handlers/resources/graph.js.map +1 -0
  61. package/dist/handlers/resources/index.d.ts +5 -20
  62. package/dist/handlers/resources/index.d.ts.map +1 -1
  63. package/dist/handlers/resources/index.js +16 -1278
  64. package/dist/handlers/resources/index.js.map +1 -1
  65. package/dist/handlers/resources/shared.d.ts +60 -0
  66. package/dist/handlers/resources/shared.d.ts.map +1 -0
  67. package/dist/handlers/resources/shared.js +49 -0
  68. package/dist/handlers/resources/shared.js.map +1 -0
  69. package/dist/handlers/resources/team.d.ts +13 -0
  70. package/dist/handlers/resources/team.d.ts.map +1 -0
  71. package/dist/handlers/resources/team.js +119 -0
  72. package/dist/handlers/resources/team.js.map +1 -0
  73. package/dist/handlers/resources/templates.d.ts +13 -0
  74. package/dist/handlers/resources/templates.d.ts.map +1 -0
  75. package/dist/handlers/resources/templates.js +310 -0
  76. package/dist/handlers/resources/templates.js.map +1 -0
  77. package/dist/handlers/tools/admin.d.ts +8 -0
  78. package/dist/handlers/tools/admin.d.ts.map +1 -0
  79. package/dist/handlers/tools/admin.js +270 -0
  80. package/dist/handlers/tools/admin.js.map +1 -0
  81. package/dist/handlers/tools/analytics.d.ts +8 -0
  82. package/dist/handlers/tools/analytics.d.ts.map +1 -0
  83. package/dist/handlers/tools/analytics.js +256 -0
  84. package/dist/handlers/tools/analytics.js.map +1 -0
  85. package/dist/handlers/tools/backup.d.ts +8 -0
  86. package/dist/handlers/tools/backup.d.ts.map +1 -0
  87. package/dist/handlers/tools/backup.js +224 -0
  88. package/dist/handlers/tools/backup.js.map +1 -0
  89. package/dist/handlers/tools/core.d.ts +9 -0
  90. package/dist/handlers/tools/core.d.ts.map +1 -0
  91. package/dist/handlers/tools/core.js +326 -0
  92. package/dist/handlers/tools/core.js.map +1 -0
  93. package/dist/handlers/tools/export.d.ts +8 -0
  94. package/dist/handlers/tools/export.d.ts.map +1 -0
  95. package/dist/handlers/tools/export.js +89 -0
  96. package/dist/handlers/tools/export.js.map +1 -0
  97. package/dist/handlers/tools/github/helpers.d.ts +34 -0
  98. package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
  99. package/dist/handlers/tools/github/helpers.js +52 -0
  100. package/dist/handlers/tools/github/helpers.js.map +1 -0
  101. package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
  102. package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
  103. package/dist/handlers/tools/github/insights-tools.js +104 -0
  104. package/dist/handlers/tools/github/insights-tools.js.map +1 -0
  105. package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
  106. package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
  107. package/dist/handlers/tools/github/issue-tools.js +359 -0
  108. package/dist/handlers/tools/github/issue-tools.js.map +1 -0
  109. package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
  110. package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
  111. package/dist/handlers/tools/github/kanban-tools.js +108 -0
  112. package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
  113. package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
  114. package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
  115. package/dist/handlers/tools/github/milestone-tools.js +302 -0
  116. package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
  117. package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
  118. package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
  119. package/dist/handlers/tools/github/mutation-tools.js +15 -0
  120. package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
  121. package/dist/handlers/tools/github/read-tools.d.ts +8 -0
  122. package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
  123. package/dist/handlers/tools/github/read-tools.js +260 -0
  124. package/dist/handlers/tools/github/read-tools.js.map +1 -0
  125. package/dist/handlers/tools/github/schemas.d.ts +467 -0
  126. package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
  127. package/dist/handlers/tools/github/schemas.js +335 -0
  128. package/dist/handlers/tools/github/schemas.js.map +1 -0
  129. package/dist/handlers/tools/github.d.ts +14 -0
  130. package/dist/handlers/tools/github.d.ts.map +1 -0
  131. package/dist/handlers/tools/github.js +28 -0
  132. package/dist/handlers/tools/github.js.map +1 -0
  133. package/dist/handlers/tools/index.d.ts +15 -20
  134. package/dist/handlers/tools/index.d.ts.map +1 -1
  135. package/dist/handlers/tools/index.js +117 -2909
  136. package/dist/handlers/tools/index.js.map +1 -1
  137. package/dist/handlers/tools/relationships.d.ts +8 -0
  138. package/dist/handlers/tools/relationships.d.ts.map +1 -0
  139. package/dist/handlers/tools/relationships.js +308 -0
  140. package/dist/handlers/tools/relationships.js.map +1 -0
  141. package/dist/handlers/tools/schemas.d.ts +108 -0
  142. package/dist/handlers/tools/schemas.d.ts.map +1 -0
  143. package/dist/handlers/tools/schemas.js +122 -0
  144. package/dist/handlers/tools/schemas.js.map +1 -0
  145. package/dist/handlers/tools/search.d.ts +8 -0
  146. package/dist/handlers/tools/search.d.ts.map +1 -0
  147. package/dist/handlers/tools/search.js +282 -0
  148. package/dist/handlers/tools/search.js.map +1 -0
  149. package/dist/handlers/tools/team.d.ts +11 -0
  150. package/dist/handlers/tools/team.d.ts.map +1 -0
  151. package/dist/handlers/tools/team.js +239 -0
  152. package/dist/handlers/tools/team.js.map +1 -0
  153. package/dist/server/McpServer.d.ts +4 -0
  154. package/dist/server/McpServer.d.ts.map +1 -1
  155. package/dist/server/McpServer.js +48 -297
  156. package/dist/server/McpServer.js.map +1 -1
  157. package/dist/server/Scheduler.d.ts +91 -0
  158. package/dist/server/Scheduler.d.ts.map +1 -0
  159. package/dist/server/Scheduler.js +201 -0
  160. package/dist/server/Scheduler.js.map +1 -0
  161. package/dist/transports/http.d.ts +66 -0
  162. package/dist/transports/http.d.ts.map +1 -0
  163. package/dist/transports/http.js +519 -0
  164. package/dist/transports/http.js.map +1 -0
  165. package/dist/types/entities.d.ts +101 -0
  166. package/dist/types/entities.d.ts.map +1 -0
  167. package/dist/types/entities.js +5 -0
  168. package/dist/types/entities.js.map +1 -0
  169. package/dist/types/filtering.d.ts +34 -0
  170. package/dist/types/filtering.d.ts.map +1 -0
  171. package/dist/types/filtering.js +5 -0
  172. package/dist/types/filtering.js.map +1 -0
  173. package/dist/types/github.d.ts +166 -0
  174. package/dist/types/github.d.ts.map +1 -0
  175. package/dist/types/github.js +5 -0
  176. package/dist/types/github.js.map +1 -0
  177. package/dist/types/index.d.ts +35 -292
  178. package/dist/types/index.d.ts.map +1 -1
  179. package/dist/types/index.js +2 -2
  180. package/dist/types/index.js.map +1 -1
  181. package/dist/utils/error-helpers.d.ts +37 -0
  182. package/dist/utils/error-helpers.d.ts.map +1 -0
  183. package/dist/utils/error-helpers.js +47 -0
  184. package/dist/utils/error-helpers.js.map +1 -0
  185. package/dist/utils/logger.d.ts.map +1 -1
  186. package/dist/utils/logger.js +6 -3
  187. package/dist/utils/logger.js.map +1 -1
  188. package/dist/utils/security-utils.d.ts +0 -21
  189. package/dist/utils/security-utils.d.ts.map +1 -1
  190. package/dist/utils/security-utils.js +0 -47
  191. package/dist/utils/security-utils.js.map +1 -1
  192. package/dist/vector/VectorSearchManager.d.ts.map +1 -1
  193. package/dist/vector/VectorSearchManager.js +9 -32
  194. package/dist/vector/VectorSearchManager.js.map +1 -1
  195. package/docker-compose.yml +11 -2
  196. package/hooks/README.md +107 -0
  197. package/hooks/cursor/hooks.json +10 -0
  198. package/hooks/cursor/memory-journal.mdc +22 -0
  199. package/hooks/cursor/session-end.sh +19 -0
  200. package/hooks/kilo-code/session-end-mode.json +11 -0
  201. package/hooks/kiro/session-end.md +13 -0
  202. package/mcp-config-example.json +1 -0
  203. package/package.json +11 -9
  204. package/playwright.config.ts +29 -0
  205. package/releases/v4.5.0.md +116 -0
  206. package/releases/v5.0.0.md +105 -0
  207. package/scripts/generate-server-instructions.ts +176 -0
  208. package/scripts/server-instructions-function-body.ts +77 -0
  209. package/server.json +3 -3
  210. package/src/cli.ts +45 -1
  211. package/src/constants/ServerInstructions.ts +133 -73
  212. package/src/constants/icons.ts +8 -7
  213. package/src/constants/server-instructions.md +268 -0
  214. package/src/database/SqliteAdapter.ts +358 -192
  215. package/src/database/schema.ts +125 -0
  216. package/src/filtering/ToolFilter.ts +13 -2
  217. package/src/github/GitHubIntegration.ts +1 -3
  218. package/src/handlers/prompts/github.ts +209 -0
  219. package/src/handlers/prompts/index.ts +10 -499
  220. package/src/handlers/prompts/workflow.ts +314 -0
  221. package/src/handlers/resources/core.ts +528 -0
  222. package/src/handlers/resources/github.ts +358 -0
  223. package/src/handlers/resources/graph.ts +254 -0
  224. package/src/handlers/resources/index.ts +23 -1570
  225. package/src/handlers/resources/shared.ts +103 -0
  226. package/src/handlers/resources/team.ts +133 -0
  227. package/src/handlers/resources/templates.ts +374 -0
  228. package/src/handlers/tools/admin.ts +285 -0
  229. package/src/handlers/tools/analytics.ts +301 -0
  230. package/src/handlers/tools/backup.ts +242 -0
  231. package/src/handlers/tools/core.ts +350 -0
  232. package/src/handlers/tools/export.ts +115 -0
  233. package/src/handlers/tools/github/helpers.ts +86 -0
  234. package/src/handlers/tools/github/insights-tools.ts +119 -0
  235. package/src/handlers/tools/github/issue-tools.ts +439 -0
  236. package/src/handlers/tools/github/kanban-tools.ts +134 -0
  237. package/src/handlers/tools/github/milestone-tools.ts +392 -0
  238. package/src/handlers/tools/github/mutation-tools.ts +17 -0
  239. package/src/handlers/tools/github/read-tools.ts +328 -0
  240. package/src/handlers/tools/github/schemas.ts +369 -0
  241. package/src/handlers/tools/github.ts +36 -0
  242. package/src/handlers/tools/index.ts +144 -3325
  243. package/src/handlers/tools/relationships.ts +358 -0
  244. package/src/handlers/tools/schemas.ts +132 -0
  245. package/src/handlers/tools/search.ts +343 -0
  246. package/src/handlers/tools/team.ts +273 -0
  247. package/src/server/McpServer.ts +63 -358
  248. package/src/server/Scheduler.ts +278 -0
  249. package/src/transports/http.ts +635 -0
  250. package/src/types/entities.ts +145 -0
  251. package/src/types/filtering.ts +54 -0
  252. package/src/types/github.ts +180 -0
  253. package/src/types/index.ts +67 -375
  254. package/src/utils/error-helpers.ts +52 -0
  255. package/src/utils/logger.ts +6 -3
  256. package/src/utils/security-utils.ts +0 -52
  257. package/src/vector/VectorSearchManager.ts +9 -33
  258. package/tests/constants/icons.test.ts +1 -2
  259. package/tests/constants/server-instructions.test.ts +30 -4
  260. package/tests/database/sqlite-adapter.test.ts +91 -7
  261. package/tests/e2e/auth.spec.ts +154 -0
  262. package/tests/e2e/health.spec.ts +63 -0
  263. package/tests/e2e/protocols.spec.ts +134 -0
  264. package/tests/e2e/resources.spec.ts +103 -0
  265. package/tests/e2e/scheduler.spec.ts +79 -0
  266. package/tests/e2e/security.spec.ts +91 -0
  267. package/tests/e2e/sessions.spec.ts +95 -0
  268. package/tests/e2e/stateless.spec.ts +121 -0
  269. package/tests/e2e/tools.spec.ts +111 -0
  270. package/tests/filtering/tool-filter.test.ts +46 -0
  271. package/tests/handlers/error-path-coverage.test.ts +324 -0
  272. package/tests/handlers/github-resource-handlers.test.ts +453 -0
  273. package/tests/handlers/github-tool-handlers.test.ts +899 -0
  274. package/tests/handlers/prompt-handler-coverage.test.ts +106 -0
  275. package/tests/handlers/prompt-handlers.test.ts +40 -0
  276. package/tests/handlers/resource-handler-coverage.test.ts +181 -0
  277. package/tests/handlers/resource-handlers.test.ts +33 -9
  278. package/tests/handlers/search-tool-handlers.test.ts +272 -0
  279. package/tests/handlers/targeted-gap-closure.test.ts +387 -0
  280. package/tests/handlers/team-resource-handlers.test.ts +156 -0
  281. package/tests/handlers/team-tool-handlers.test.ts +301 -0
  282. package/tests/handlers/tool-handler-coverage.test.ts +469 -0
  283. package/tests/handlers/tool-handlers.test.ts +2 -2
  284. package/tests/security/sql-injection.test.ts +3 -54
  285. package/tests/server/mcp-server.test.ts +503 -8
  286. package/tests/server/scheduler.test.ts +400 -0
  287. package/tests/transports/http-transport.test.ts +620 -0
  288. package/tests/vector/vector-search-manager.test.ts +60 -0
  289. package/vitest.config.ts +4 -1
  290. package/.memory-journal-team.db +0 -0
  291. 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
  })
Binary file