memory-journal-mcp 4.4.2 → 4.5.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 (73) hide show
  1. package/.github/workflows/lint-and-test.yml +1 -1
  2. package/.github/workflows/security-update.yml +1 -1
  3. package/CHANGELOG.md +81 -1
  4. package/DOCKER_README.md +57 -7
  5. package/Dockerfile +17 -17
  6. package/README.md +65 -6
  7. package/SECURITY.md +27 -35
  8. package/dist/cli.js +10 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/constants/ServerInstructions.d.ts +5 -1
  11. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  12. package/dist/constants/ServerInstructions.js +137 -83
  13. package/dist/constants/ServerInstructions.js.map +1 -1
  14. package/dist/database/SqliteAdapter.d.ts +2 -1
  15. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  16. package/dist/database/SqliteAdapter.js +15 -8
  17. package/dist/database/SqliteAdapter.js.map +1 -1
  18. package/dist/handlers/resources/index.d.ts +3 -1
  19. package/dist/handlers/resources/index.d.ts.map +1 -1
  20. package/dist/handlers/resources/index.js +5 -2
  21. package/dist/handlers/resources/index.js.map +1 -1
  22. package/dist/handlers/tools/index.d.ts.map +1 -1
  23. package/dist/handlers/tools/index.js +63 -16
  24. package/dist/handlers/tools/index.js.map +1 -1
  25. package/dist/server/McpServer.d.ts +2 -0
  26. package/dist/server/McpServer.d.ts.map +1 -1
  27. package/dist/server/McpServer.js +43 -2
  28. package/dist/server/McpServer.js.map +1 -1
  29. package/dist/server/Scheduler.d.ts +91 -0
  30. package/dist/server/Scheduler.d.ts.map +1 -0
  31. package/dist/server/Scheduler.js +201 -0
  32. package/dist/server/Scheduler.js.map +1 -0
  33. package/dist/utils/logger.d.ts.map +1 -1
  34. package/dist/utils/logger.js +6 -3
  35. package/dist/utils/logger.js.map +1 -1
  36. package/dist/utils/security-utils.d.ts +0 -21
  37. package/dist/utils/security-utils.d.ts.map +1 -1
  38. package/dist/utils/security-utils.js +0 -47
  39. package/dist/utils/security-utils.js.map +1 -1
  40. package/hooks/README.md +107 -0
  41. package/hooks/cursor/hooks.json +10 -0
  42. package/hooks/cursor/memory-journal.mdc +22 -0
  43. package/hooks/cursor/session-end.sh +19 -0
  44. package/hooks/kilo-code/session-end-mode.json +11 -0
  45. package/hooks/kiro/session-end.md +13 -0
  46. package/package.json +8 -8
  47. package/releases/v4.5.0.md +116 -0
  48. package/scripts/generate-server-instructions.ts +176 -0
  49. package/scripts/server-instructions-function-body.ts +77 -0
  50. package/server.json +3 -3
  51. package/src/cli.ts +26 -0
  52. package/src/constants/ServerInstructions.ts +137 -83
  53. package/src/constants/server-instructions.md +262 -0
  54. package/src/database/SqliteAdapter.ts +22 -8
  55. package/src/handlers/resources/index.ts +8 -2
  56. package/src/handlers/tools/index.ts +70 -20
  57. package/src/server/McpServer.ts +60 -2
  58. package/src/server/Scheduler.ts +278 -0
  59. package/src/utils/logger.ts +6 -3
  60. package/src/utils/security-utils.ts +0 -52
  61. package/tests/constants/server-instructions.test.ts +26 -0
  62. package/tests/database/sqlite-adapter.test.ts +84 -0
  63. package/tests/filtering/tool-filter.test.ts +46 -0
  64. package/tests/handlers/github-resource-handlers.test.ts +453 -0
  65. package/tests/handlers/github-tool-handlers.test.ts +899 -0
  66. package/tests/handlers/prompt-handlers.test.ts +40 -0
  67. package/tests/handlers/resource-handlers.test.ts +32 -0
  68. package/tests/handlers/tool-handlers.test.ts +13 -2
  69. package/tests/security/sql-injection.test.ts +3 -54
  70. package/tests/server/mcp-server.test.ts +491 -5
  71. package/tests/server/scheduler.test.ts +400 -0
  72. package/tests/vector/vector-search-manager.test.ts +60 -0
  73. package/.vscode/settings.json +0 -84
@@ -30,6 +30,7 @@ const {
30
30
  mockStdioTransport,
31
31
  mockListTags,
32
32
  mockHandlers,
33
+ mockSigintHandlers,
33
34
  } = vi.hoisted(() => ({
34
35
  mockRegisterTool: vi.fn(),
35
36
  mockRegisterResource: vi.fn(),
@@ -75,8 +76,9 @@ const {
75
76
  post: {} as Record<string, Function>,
76
77
  delete: {} as Record<string, Function>,
77
78
  all: {} as Record<string, Function>,
78
- use: {} as Record<string, Function>,
79
+ useMiddlewares: [] as Function[],
79
80
  },
81
+ mockSigintHandlers: [] as Function[],
80
82
  }))
81
83
 
82
84
  // ============================================================================
@@ -204,7 +206,7 @@ vi.mock('express', () => {
204
206
  const mockApp = {
205
207
  use: vi.fn().mockImplementation((...args: unknown[]) => {
206
208
  if (args.length === 1 && typeof args[0] === 'function') {
207
- mockHandlers.use['*'] = args[0] as () => void
209
+ mockHandlers.useMiddlewares.push(args[0] as Function)
208
210
  }
209
211
  }),
210
212
  get: vi.fn().mockImplementation((path: string, handler: unknown) => {
@@ -235,8 +237,13 @@ vi.mock('express', () => {
235
237
  }
236
238
  })
237
239
 
238
- // Suppress process.on/exit in tests
239
- vi.spyOn(process, 'on').mockImplementation(() => process)
240
+ // Capture process.on('SIGINT') handlers for testing
241
+ vi.spyOn(process, 'on').mockImplementation((event: string, handler: Function) => {
242
+ if (event === 'SIGINT') {
243
+ mockSigintHandlers.push(handler)
244
+ }
245
+ return process
246
+ })
240
247
  vi.spyOn(process, 'exit').mockImplementation((() => {}) as never)
241
248
 
242
249
  // ============================================================================
@@ -247,7 +254,24 @@ import { createServer, type ServerOptions } from '../../src/server/McpServer.js'
247
254
 
248
255
  describe('McpServer', () => {
249
256
  beforeEach(() => {
250
- vi.clearAllMocks()
257
+ // Reset call counts but preserve mock implementations
258
+ // (vi.clearAllMocks() would wipe .mockImplementation() on express mock)
259
+ mockRegisterTool.mockClear()
260
+ mockRegisterResource.mockClear()
261
+ mockRegisterPrompt.mockClear()
262
+ mockConnect.mockClear()
263
+ mockDbInitialize.mockClear()
264
+ mockDbClose.mockClear()
265
+ mockVectorInitialize.mockClear()
266
+ mockVectorRebuildIndex.mockClear()
267
+ mockCreateEntry.mockClear()
268
+ // Clear captured handler references
269
+ mockHandlers.get = {}
270
+ mockHandlers.post = {}
271
+ mockHandlers.delete = {}
272
+ mockHandlers.all = {}
273
+ mockHandlers.useMiddlewares.length = 0
274
+ mockSigintHandlers.length = 0
251
275
  })
252
276
 
253
277
  // ========================================================================
@@ -671,5 +695,467 @@ describe('McpServer', () => {
671
695
  if (allHandler) await allHandler(mockReqGet, mockResOptions, nextFn)
672
696
  expect(nextFn).toHaveBeenCalled()
673
697
  })
698
+
699
+ it('should invoke security headers middleware', async () => {
700
+ const middlewareFns: Function[] = []
701
+ const { default: expressMod } = await import('express')
702
+ const app = expressMod()
703
+ // Capture all middleware registered via app.use()
704
+ ;(app.use as ReturnType<typeof vi.fn>).mockImplementation((...args: unknown[]) => {
705
+ if (args.length === 1 && typeof args[0] === 'function') {
706
+ middlewareFns.push(args[0] as Function)
707
+ }
708
+ })
709
+
710
+ await createServer({
711
+ transport: 'http',
712
+ dbPath: './test-server.db',
713
+ statelessHttp: true,
714
+ })
715
+
716
+ // The first middleware (after express.json) should set security headers
717
+ // Find a middleware that calls setHeader for security headers
718
+ let securityMiddlewareFound = false
719
+ for (const mw of middlewareFns) {
720
+ const mockRes = {
721
+ setHeader: vi.fn(),
722
+ status: vi.fn().mockReturnThis(),
723
+ end: vi.fn(),
724
+ }
725
+ const nextFn = vi.fn()
726
+ mw({}, mockRes, nextFn)
727
+ const calls = mockRes.setHeader.mock.calls as [string, string][]
728
+ const headerNames = calls.map((c) => c[0])
729
+ if (headerNames.includes('X-Content-Type-Options')) {
730
+ securityMiddlewareFound = true
731
+ expect(headerNames).toContain('X-Frame-Options')
732
+ expect(headerNames).toContain('Content-Security-Policy')
733
+ expect(headerNames).toContain('Cache-Control')
734
+ expect(headerNames).toContain('Referrer-Policy')
735
+ expect(nextFn).toHaveBeenCalled()
736
+ break
737
+ }
738
+ }
739
+ expect(securityMiddlewareFound).toBe(true)
740
+ })
741
+
742
+ it('should invoke CORS middleware and handle OPTIONS', async () => {
743
+ const middlewareFns: Function[] = []
744
+ const { default: expressMod } = await import('express')
745
+ const app = expressMod()
746
+ ;(app.use as ReturnType<typeof vi.fn>).mockImplementation((...args: unknown[]) => {
747
+ if (args.length === 1 && typeof args[0] === 'function') {
748
+ middlewareFns.push(args[0] as Function)
749
+ }
750
+ })
751
+
752
+ await createServer({
753
+ transport: 'http',
754
+ dbPath: './test-server.db',
755
+ statelessHttp: true,
756
+ corsOrigin: 'https://test.example.com',
757
+ })
758
+
759
+ // Find the CORS middleware (sets Access-Control-Allow-Origin)
760
+ let corsMiddlewareFound = false
761
+ for (const mw of middlewareFns) {
762
+ const mockRes = {
763
+ setHeader: vi.fn(),
764
+ status: vi.fn().mockReturnThis(),
765
+ end: vi.fn(),
766
+ }
767
+
768
+ // Test with OPTIONS request
769
+ const mockReqOptions = { method: 'OPTIONS' }
770
+ const noopNext = vi.fn()
771
+ mw(mockReqOptions, mockRes, noopNext)
772
+ const calls = mockRes.setHeader.mock.calls as [string, string][]
773
+ const headerNames = calls.map((c) => c[0])
774
+ if (headerNames.includes('Access-Control-Allow-Origin')) {
775
+ corsMiddlewareFound = true
776
+ expect(headerNames).toContain('Access-Control-Allow-Methods')
777
+ expect(headerNames).toContain('Access-Control-Allow-Headers')
778
+ expect(headerNames).toContain('Access-Control-Expose-Headers')
779
+ // OPTIONS should return 204
780
+ expect(mockRes.status).toHaveBeenCalledWith(204)
781
+ expect(mockRes.end).toHaveBeenCalled()
782
+ break
783
+ }
784
+ }
785
+ expect(corsMiddlewareFound).toBe(true)
786
+
787
+ // Test CORS middleware with non-OPTIONS request (calls next)
788
+ for (const mw of middlewareFns) {
789
+ const mockRes = {
790
+ setHeader: vi.fn(),
791
+ status: vi.fn().mockReturnThis(),
792
+ end: vi.fn(),
793
+ }
794
+ const nextFn = vi.fn()
795
+ mw({ method: 'POST' }, mockRes, nextFn)
796
+ const calls = mockRes.setHeader.mock.calls as [string, string][]
797
+ const headerNames = calls.map((c) => c[0])
798
+ if (headerNames.includes('Access-Control-Allow-Origin')) {
799
+ expect(nextFn).toHaveBeenCalled()
800
+ break
801
+ }
802
+ }
803
+ })
804
+
805
+ it('should handle stateful GET /mcp without session', async () => {
806
+ await createServer({
807
+ transport: 'http',
808
+ dbPath: './test-server.db',
809
+ statelessHttp: false,
810
+ })
811
+
812
+ const getHandler = mockHandlers.get['/mcp']
813
+ expect(getHandler).toBeDefined()
814
+
815
+ // No session ID
816
+ const mockReq = { headers: {} }
817
+ const mockRes = {
818
+ status: vi.fn().mockReturnThis(),
819
+ send: vi.fn(),
820
+ }
821
+
822
+ if (getHandler) await getHandler(mockReq, mockRes)
823
+ expect(mockRes.status).toHaveBeenCalledWith(400)
824
+ expect(mockRes.send).toHaveBeenCalledWith(
825
+ expect.stringContaining('Invalid or missing session ID')
826
+ )
827
+ })
828
+
829
+ it('should handle stateful DELETE /mcp without session', async () => {
830
+ await createServer({
831
+ transport: 'http',
832
+ dbPath: './test-server.db',
833
+ statelessHttp: false,
834
+ })
835
+
836
+ const deleteHandler = mockHandlers.delete['/mcp']
837
+ expect(deleteHandler).toBeDefined()
838
+
839
+ const mockReq = { headers: {} }
840
+ const mockRes = {
841
+ status: vi.fn().mockReturnThis(),
842
+ send: vi.fn(),
843
+ }
844
+
845
+ if (deleteHandler) await deleteHandler(mockReq, mockRes)
846
+ expect(mockRes.status).toHaveBeenCalledWith(400)
847
+ })
848
+
849
+ it('should handle stateful GET /mcp with invalid session', async () => {
850
+ await createServer({
851
+ transport: 'http',
852
+ dbPath: './test-server.db',
853
+ statelessHttp: false,
854
+ })
855
+
856
+ const getHandler = mockHandlers.get['/mcp']
857
+ const mockReq = { headers: { 'mcp-session-id': 'nonexistent-session' } }
858
+ const mockRes = {
859
+ status: vi.fn().mockReturnThis(),
860
+ send: vi.fn(),
861
+ }
862
+
863
+ if (getHandler) await getHandler(mockReq, mockRes)
864
+ expect(mockRes.status).toHaveBeenCalledWith(400)
865
+ })
866
+
867
+ it('should handle stateful DELETE /mcp with invalid session', async () => {
868
+ await createServer({
869
+ transport: 'http',
870
+ dbPath: './test-server.db',
871
+ statelessHttp: false,
872
+ })
873
+
874
+ const deleteHandler = mockHandlers.delete['/mcp']
875
+ const mockReq = { headers: { 'mcp-session-id': 'nonexistent-session' } }
876
+ const mockRes = {
877
+ status: vi.fn().mockReturnThis(),
878
+ send: vi.fn(),
879
+ }
880
+
881
+ if (deleteHandler) await deleteHandler(mockReq, mockRes)
882
+ expect(mockRes.status).toHaveBeenCalledWith(400)
883
+ })
884
+
885
+ it('should handle stateful POST /mcp with initialization request', async () => {
886
+ // Make isInitializeRequest return true for this test
887
+ const { isInitializeRequest: mockIsInit } =
888
+ await import('@modelcontextprotocol/sdk/types.js')
889
+ ;(mockIsInit as ReturnType<typeof vi.fn>).mockReturnValueOnce(true)
890
+
891
+ await createServer({
892
+ transport: 'http',
893
+ dbPath: './test-server.db',
894
+ statelessHttp: false,
895
+ })
896
+
897
+ const postHandler = mockHandlers.post['/mcp']
898
+ expect(postHandler).toBeDefined()
899
+
900
+ // Simulate initialization request (no session ID, isInitializeRequest returns true)
901
+ const mockReq = {
902
+ headers: {},
903
+ body: { jsonrpc: '2.0', id: 1, method: 'initialize' },
904
+ }
905
+ const mockRes = {
906
+ status: vi.fn().mockReturnThis(),
907
+ json: vi.fn(),
908
+ headersSent: false,
909
+ }
910
+
911
+ if (postHandler) {
912
+ await postHandler(mockReq, mockRes)
913
+ // Wait for async void handler to complete
914
+ await new Promise((r) => setTimeout(r, 50))
915
+ }
916
+
917
+ // The transport should have been created and connected
918
+ // (StreamableHTTPServerTransport mock handles the request)
919
+ expect(mockConnect).toHaveBeenCalled()
920
+ })
921
+
922
+ it('should handle stateful POST /mcp error with 500 response', async () => {
923
+ // Create a transport mock that throws
924
+ const StreamableTransportMod =
925
+ await import('@modelcontextprotocol/sdk/server/streamableHttp.js')
926
+ const OrigConstructor = StreamableTransportMod.StreamableHTTPServerTransport
927
+ const throwingConstructor = vi.fn().mockImplementation(() => {
928
+ return {
929
+ handleRequest: vi.fn().mockRejectedValue(new Error('Transport failure')),
930
+ close: vi.fn().mockResolvedValue(undefined),
931
+ sessionId: 'fail-session',
932
+ }
933
+ })
934
+ ;(StreamableTransportMod as Record<string, unknown>)['StreamableHTTPServerTransport'] =
935
+ throwingConstructor
936
+
937
+ await createServer({
938
+ transport: 'http',
939
+ dbPath: './test-server.db',
940
+ statelessHttp: false,
941
+ })
942
+
943
+ const postHandler = mockHandlers.post['/mcp']
944
+ expect(postHandler).toBeDefined()
945
+
946
+ // Request with existing session ID that throws during handleRequest
947
+ const mockReq = {
948
+ headers: { 'mcp-session-id': 'fail-session' },
949
+ body: { jsonrpc: '2.0', id: 1, method: 'test' },
950
+ }
951
+ const mockRes = {
952
+ status: vi.fn().mockReturnThis(),
953
+ json: vi.fn(),
954
+ headersSent: false,
955
+ }
956
+
957
+ if (postHandler) {
958
+ await postHandler(mockReq, mockRes)
959
+ await new Promise((r) => setTimeout(r, 50))
960
+ }
961
+
962
+ // Restore original
963
+ ;(StreamableTransportMod as Record<string, unknown>)['StreamableHTTPServerTransport'] =
964
+ OrigConstructor
965
+ })
966
+ })
967
+
968
+ // ========================================================================
969
+ // Scheduler
970
+ // ========================================================================
971
+
972
+ describe('createServer - scheduler', () => {
973
+ it('should warn and not start scheduler on stdio transport', async () => {
974
+ await createServer({
975
+ transport: 'stdio',
976
+ dbPath: './test-server.db',
977
+ scheduler: {
978
+ backupIntervalMinutes: 60,
979
+ vacuumIntervalMinutes: 120,
980
+ rebuildIndexIntervalMinutes: 180,
981
+ },
982
+ })
983
+
984
+ // Scheduler should NOT be started for stdio
985
+ // (no way to directly check but the code path is covered)
986
+ expect(mockDbInitialize).toHaveBeenCalled()
987
+ })
988
+
989
+ it('should start scheduler on HTTP transport', async () => {
990
+ await createServer({
991
+ transport: 'http',
992
+ dbPath: './test-server.db',
993
+ statelessHttp: true,
994
+ scheduler: {
995
+ backupIntervalMinutes: 60,
996
+ vacuumIntervalMinutes: 0,
997
+ rebuildIndexIntervalMinutes: 0,
998
+ },
999
+ })
1000
+
1001
+ expect(mockDbInitialize).toHaveBeenCalled()
1002
+ })
1003
+
1004
+ it('should not create scheduler when all intervals are 0', async () => {
1005
+ await createServer({
1006
+ transport: 'http',
1007
+ dbPath: './test-server.db',
1008
+ scheduler: {
1009
+ backupIntervalMinutes: 0,
1010
+ vacuumIntervalMinutes: 0,
1011
+ rebuildIndexIntervalMinutes: 0,
1012
+ },
1013
+ })
1014
+
1015
+ expect(mockDbInitialize).toHaveBeenCalled()
1016
+ })
1017
+ })
1018
+
1019
+ // ========================================================================
1020
+ // Tool handler with outputSchema
1021
+ // ========================================================================
1022
+
1023
+ describe('tool handler structuredContent', () => {
1024
+ it('should return structuredContent for tools with outputSchema', async () => {
1025
+ await createServer({
1026
+ transport: 'stdio',
1027
+ dbPath: './test-server.db',
1028
+ })
1029
+
1030
+ // get_recent_entries has an outputSchema
1031
+ const calls = mockRegisterTool.mock.calls.filter(
1032
+ (call: unknown[]) => call[0] === 'get_recent_entries'
1033
+ ) as unknown[][]
1034
+
1035
+ expect(calls.length).toBe(1)
1036
+ const handler = calls[0]![2] as (
1037
+ args: Record<string, unknown>,
1038
+ extra: Record<string, unknown>
1039
+ ) => Promise<{
1040
+ content: { type: string; text: string }[]
1041
+ structuredContent?: Record<string, unknown>
1042
+ }>
1043
+
1044
+ const result = await handler({ limit: 5 }, { _meta: {} })
1045
+
1046
+ // Should have both content and structuredContent
1047
+ expect(result.content).toBeDefined()
1048
+ expect(result.structuredContent).toBeDefined()
1049
+ })
1050
+
1051
+ it('should pass progressToken to tool handler when provided', async () => {
1052
+ await createServer({
1053
+ transport: 'stdio',
1054
+ dbPath: './test-server.db',
1055
+ })
1056
+
1057
+ const calls = mockRegisterTool.mock.calls.filter(
1058
+ (call: unknown[]) => call[0] === 'create_entry'
1059
+ ) as unknown[][]
1060
+
1061
+ const handler = calls[0]![2] as (
1062
+ args: Record<string, unknown>,
1063
+ extra: Record<string, unknown>
1064
+ ) => Promise<unknown>
1065
+
1066
+ // Invoke with a progressToken in _meta
1067
+ const result = await handler(
1068
+ { content: 'Progress test' },
1069
+ { _meta: { progressToken: 'tok-123' } }
1070
+ )
1071
+
1072
+ expect(result).toBeDefined()
1073
+ })
1074
+ })
1075
+
1076
+ // ========================================================================
1077
+ // Environment-based tool filter
1078
+ // ========================================================================
1079
+
1080
+ describe('createServer - env tool filter', () => {
1081
+ it('should use MEMORY_JOURNAL_MCP_TOOL_FILTER env var when no explicit filter', async () => {
1082
+ process.env['MEMORY_JOURNAL_MCP_TOOL_FILTER'] = 'core'
1083
+
1084
+ await createServer({
1085
+ transport: 'stdio',
1086
+ dbPath: './test-server.db',
1087
+ })
1088
+
1089
+ // Tools should be filtered from env
1090
+ expect(mockRegisterTool).toHaveBeenCalled()
1091
+ const toolCount = mockRegisterTool.mock.calls.length
1092
+
1093
+ delete process.env['MEMORY_JOURNAL_MCP_TOOL_FILTER']
1094
+
1095
+ // Reset only the specific mock call counts we care about
1096
+ mockRegisterTool.mockClear()
1097
+ await createServer({
1098
+ transport: 'stdio',
1099
+ dbPath: './test-server.db',
1100
+ })
1101
+
1102
+ const unfilteredCount = mockRegisterTool.mock.calls.length
1103
+ // Env-filtered should have fewer tools than unfiltered
1104
+ expect(toolCount).toBeLessThan(unfilteredCount)
1105
+ })
1106
+ })
1107
+
1108
+ // ========================================================================
1109
+ // SIGINT shutdown handlers
1110
+ // ========================================================================
1111
+
1112
+ describe('createServer - shutdown handlers', () => {
1113
+ it('should register SIGINT handler for stdio transport', async () => {
1114
+ await createServer({
1115
+ transport: 'stdio',
1116
+ dbPath: './test-server.db',
1117
+ })
1118
+
1119
+ expect(mockSigintHandlers.length).toBe(1)
1120
+ })
1121
+
1122
+ it('should register SIGINT handler for stateless HTTP', async () => {
1123
+ await createServer({
1124
+ transport: 'http',
1125
+ dbPath: './test-server.db',
1126
+ statelessHttp: true,
1127
+ })
1128
+
1129
+ expect(mockSigintHandlers.length).toBe(1)
1130
+
1131
+ // Exercise the SIGINT handler
1132
+ const sigintHandler = mockSigintHandlers[0]
1133
+ if (sigintHandler) {
1134
+ sigintHandler()
1135
+ // Wait for async void
1136
+ await new Promise((r) => setTimeout(r, 50))
1137
+ }
1138
+
1139
+ expect(mockDbClose).toHaveBeenCalled()
1140
+ })
1141
+
1142
+ it('should register SIGINT handler for stateful HTTP', async () => {
1143
+ await createServer({
1144
+ transport: 'http',
1145
+ dbPath: './test-server.db',
1146
+ statelessHttp: false,
1147
+ })
1148
+
1149
+ expect(mockSigintHandlers.length).toBe(1)
1150
+
1151
+ // Exercise the SIGINT handler
1152
+ const sigintHandler = mockSigintHandlers[0]
1153
+ if (sigintHandler) {
1154
+ sigintHandler()
1155
+ await new Promise((r) => setTimeout(r, 50))
1156
+ }
1157
+
1158
+ expect(mockDbClose).toHaveBeenCalled()
1159
+ })
674
1160
  })
675
1161
  })