@friggframework/core 2.0.0-next.45 → 2.0.0-next.46

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 (163) hide show
  1. package/README.md +28 -0
  2. package/application/commands/integration-commands.js +19 -0
  3. package/core/Worker.js +8 -21
  4. package/credential/repositories/credential-repository-mongo.js +14 -8
  5. package/credential/repositories/credential-repository-postgres.js +14 -8
  6. package/credential/repositories/credential-repository.js +3 -8
  7. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  8. package/database/adapters/lambda-invoker.js +97 -0
  9. package/database/config.js +11 -2
  10. package/database/models/WebsocketConnection.js +11 -10
  11. package/database/prisma.js +63 -3
  12. package/database/repositories/health-check-repository-mongodb.js +3 -0
  13. package/database/repositories/migration-status-repository-s3.js +137 -0
  14. package/database/use-cases/check-database-state-use-case.js +81 -0
  15. package/database/use-cases/check-encryption-health-use-case.js +3 -2
  16. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  17. package/database/use-cases/get-migration-status-use-case.js +93 -0
  18. package/database/use-cases/run-database-migration-use-case.js +137 -0
  19. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  20. package/database/utils/mongodb-collection-utils.js +91 -0
  21. package/database/utils/mongodb-schema-init.js +106 -0
  22. package/database/utils/prisma-runner.js +400 -0
  23. package/database/utils/prisma-schema-parser.js +182 -0
  24. package/encrypt/Cryptor.js +14 -16
  25. package/generated/prisma-mongodb/client.d.ts +1 -0
  26. package/generated/prisma-mongodb/client.js +4 -0
  27. package/generated/prisma-mongodb/default.d.ts +1 -0
  28. package/generated/prisma-mongodb/default.js +4 -0
  29. package/generated/prisma-mongodb/edge.d.ts +1 -0
  30. package/generated/prisma-mongodb/edge.js +334 -0
  31. package/generated/prisma-mongodb/index-browser.js +316 -0
  32. package/generated/prisma-mongodb/index.d.ts +22897 -0
  33. package/generated/prisma-mongodb/index.js +359 -0
  34. package/generated/prisma-mongodb/package.json +183 -0
  35. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  36. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  37. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  38. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  39. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  40. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  41. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  42. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  43. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  44. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  45. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  46. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  47. package/generated/prisma-mongodb/schema.prisma +362 -0
  48. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  49. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  50. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  51. package/generated/prisma-mongodb/wasm.js +341 -0
  52. package/generated/prisma-postgresql/client.d.ts +1 -0
  53. package/generated/prisma-postgresql/client.js +4 -0
  54. package/generated/prisma-postgresql/default.d.ts +1 -0
  55. package/generated/prisma-postgresql/default.js +4 -0
  56. package/generated/prisma-postgresql/edge.d.ts +1 -0
  57. package/generated/prisma-postgresql/edge.js +356 -0
  58. package/generated/prisma-postgresql/index-browser.js +338 -0
  59. package/generated/prisma-postgresql/index.d.ts +25071 -0
  60. package/generated/prisma-postgresql/index.js +381 -0
  61. package/generated/prisma-postgresql/package.json +183 -0
  62. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  63. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  64. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  65. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  66. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  67. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  68. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  69. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  70. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  71. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  72. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  73. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  74. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  75. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  76. package/generated/prisma-postgresql/schema.prisma +345 -0
  77. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  78. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  79. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  80. package/generated/prisma-postgresql/wasm.js +363 -0
  81. package/handlers/database-migration-handler.js +227 -0
  82. package/handlers/routers/auth.js +1 -1
  83. package/handlers/routers/db-migration.handler.js +29 -0
  84. package/handlers/routers/db-migration.js +256 -0
  85. package/handlers/routers/health.js +41 -6
  86. package/handlers/routers/integration-webhook-routers.js +2 -2
  87. package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
  88. package/handlers/workers/db-migration.js +352 -0
  89. package/index.js +12 -0
  90. package/integrations/integration-router.js +60 -70
  91. package/integrations/repositories/integration-repository-interface.js +12 -0
  92. package/integrations/repositories/integration-repository-mongo.js +32 -0
  93. package/integrations/repositories/integration-repository-postgres.js +33 -0
  94. package/integrations/repositories/process-repository-postgres.js +2 -2
  95. package/integrations/tests/doubles/test-integration-repository.js +2 -2
  96. package/logs/logger.js +0 -4
  97. package/modules/entity.js +0 -1
  98. package/modules/repositories/module-repository-mongo.js +3 -12
  99. package/modules/repositories/module-repository-postgres.js +0 -11
  100. package/modules/repositories/module-repository.js +1 -12
  101. package/modules/use-cases/get-entity-options-by-id.js +1 -1
  102. package/modules/use-cases/get-module.js +1 -2
  103. package/modules/use-cases/refresh-entity-options.js +1 -1
  104. package/modules/use-cases/test-module-auth.js +1 -1
  105. package/package.json +82 -66
  106. package/prisma-mongodb/schema.prisma +21 -21
  107. package/prisma-postgresql/schema.prisma +15 -15
  108. package/queues/queuer-util.js +24 -21
  109. package/types/core/index.d.ts +2 -2
  110. package/types/module-plugin/index.d.ts +0 -2
  111. package/user/use-cases/authenticate-user.js +127 -0
  112. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  113. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  114. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  115. package/user/user.js +16 -0
  116. package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
  117. package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
  118. package/websocket/repositories/websocket-connection-repository.js +11 -10
  119. package/application/commands/integration-commands.test.js +0 -123
  120. package/database/encryption/encryption-integration.test.js +0 -553
  121. package/database/encryption/encryption-schema-registry.test.js +0 -392
  122. package/database/encryption/field-encryption-service.test.js +0 -525
  123. package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
  124. package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
  125. package/database/encryption/postgres-relation-decryption.test.js +0 -245
  126. package/database/encryption/prisma-encryption-extension.test.js +0 -439
  127. package/errors/base-error.test.js +0 -32
  128. package/errors/fetch-error.test.js +0 -79
  129. package/errors/halt-error.test.js +0 -11
  130. package/errors/validation-errors.test.js +0 -120
  131. package/handlers/auth-flow.integration.test.js +0 -147
  132. package/handlers/integration-event-dispatcher.test.js +0 -209
  133. package/handlers/routers/health.test.js +0 -210
  134. package/handlers/routers/integration-webhook-routers.test.js +0 -126
  135. package/handlers/webhook-flow.integration.test.js +0 -356
  136. package/handlers/workers/integration-defined-workers.test.js +0 -184
  137. package/integrations/tests/use-cases/create-integration.test.js +0 -131
  138. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
  139. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
  140. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
  141. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
  142. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
  143. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
  144. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
  145. package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
  146. package/integrations/tests/use-cases/update-integration.test.js +0 -141
  147. package/integrations/use-cases/create-process.test.js +0 -178
  148. package/integrations/use-cases/get-process.test.js +0 -190
  149. package/integrations/use-cases/load-integration-context-full.test.js +0 -329
  150. package/integrations/use-cases/load-integration-context.test.js +0 -114
  151. package/integrations/use-cases/update-process-metrics.test.js +0 -308
  152. package/integrations/use-cases/update-process-state.test.js +0 -256
  153. package/lambda/TimeoutCatcher.test.js +0 -68
  154. package/logs/logger.test.js +0 -76
  155. package/modules/module-hydration.test.js +0 -205
  156. package/modules/requester/requester.test.js +0 -28
  157. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  158. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  159. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  160. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  161. package/user/tests/use-cases/login-user.test.js +0 -220
  162. package/user/tests/user-password-encryption-isolation.test.js +0 -237
  163. package/user/tests/user-password-hashing.test.js +0 -235
@@ -1,147 +0,0 @@
1
- jest.mock('../database/config', () => ({
2
- DB_TYPE: 'mongodb',
3
- getDatabaseType: jest.fn(() => 'mongodb'),
4
- PRISMA_LOG_LEVEL: 'error,warn',
5
- PRISMA_QUERY_LOGGING: false,
6
- }));
7
-
8
- const { IntegrationEventDispatcher } = require('./integration-event-dispatcher');
9
- const { IntegrationBase } = require('../integrations/integration-base');
10
-
11
- class SimulatedAsanaIntegration extends IntegrationBase {
12
- static Definition = {
13
- name: 'asana',
14
- version: '1.0.0',
15
- modules: {},
16
- routes: [
17
- { path: '/auth', method: 'GET', event: 'AUTH_REQUEST' },
18
- { path: '/auth/redirect/:provider', method: 'GET', event: 'AUTH_REDIRECT' },
19
- { path: '/form', method: 'GET', event: 'LOAD_FORM' },
20
- ],
21
- };
22
-
23
- constructor(params = {}) {
24
- super(params);
25
- this.events = {
26
- AUTH_REQUEST: { handler: this.authRequest.bind(this) },
27
- AUTH_REDIRECT: { handler: this.authRedirect.bind(this) },
28
- LOAD_FORM: { handler: this.loadForm.bind(this) },
29
- };
30
- }
31
-
32
- async authRequest() {
33
- return {
34
- success: true,
35
- action: 'redirect',
36
- hydrated: this.isHydrated,
37
- };
38
- }
39
-
40
- async authRedirect({ req }) {
41
- const { code } = req.query || {};
42
- return {
43
- success: true,
44
- action: 'tokens_received',
45
- receivedCode: code,
46
- hydrated: this.isHydrated,
47
- };
48
- }
49
-
50
- async loadForm() {
51
- if (!this.isHydrated && SimulatedAsanaIntegration.testRecord) {
52
- this.setIntegrationRecord({
53
- record: SimulatedAsanaIntegration.testRecord.record,
54
- modules: SimulatedAsanaIntegration.testRecord.modules,
55
- });
56
- }
57
-
58
- this.assertHydrated('Integration not found - must authenticate first');
59
-
60
- return {
61
- success: true,
62
- form: {
63
- fields: ['field1', 'field2'],
64
- },
65
- integrationId: this.id,
66
- };
67
- }
68
- }
69
-
70
- describe('IntegrationEventDispatcher auth flow', () => {
71
- const createDispatcher = () =>
72
- new IntegrationEventDispatcher(new SimulatedAsanaIntegration());
73
-
74
- beforeEach(() => {
75
- SimulatedAsanaIntegration.testRecord = null;
76
- });
77
-
78
- it('handles auth request without hydration', async () => {
79
- const dispatcher = createDispatcher();
80
- const result = await dispatcher.dispatchHttp({
81
- event: 'AUTH_REQUEST',
82
- req: { params: { provider: 'asana' }, query: {} },
83
- res: {},
84
- next: jest.fn(),
85
- });
86
-
87
- expect(result).toEqual({ success: true, action: 'redirect', hydrated: false });
88
- });
89
-
90
- it('handles auth redirect without hydration', async () => {
91
- const dispatcher = createDispatcher();
92
- const result = await dispatcher.dispatchHttp({
93
- event: 'AUTH_REDIRECT',
94
- req: { params: { provider: 'asana' }, query: { code: 'abc123' } },
95
- res: {},
96
- next: jest.fn(),
97
- });
98
-
99
- expect(result).toEqual({
100
- success: true,
101
- action: 'tokens_received',
102
- receivedCode: 'abc123',
103
- hydrated: false,
104
- });
105
- });
106
-
107
- it('throws for protected routes when no record is loaded', async () => {
108
- const dispatcher = createDispatcher();
109
- await expect(
110
- dispatcher.dispatchHttp({
111
- event: 'LOAD_FORM',
112
- req: { query: {} },
113
- res: {},
114
- next: jest.fn(),
115
- })
116
- ).rejects.toThrow('Integration not found - must authenticate first');
117
- });
118
-
119
- it('allows handlers to hydrate explicitly before continuing', async () => {
120
- SimulatedAsanaIntegration.testRecord = {
121
- record: {
122
- id: 'integration-123',
123
- userId: 'user-456',
124
- config: { type: 'asana' },
125
- status: 'ENABLED',
126
- version: '1.0.0',
127
- messages: { errors: [], warnings: [] },
128
- entities: [],
129
- },
130
- modules: [],
131
- };
132
-
133
- const dispatcher = createDispatcher();
134
- const result = await dispatcher.dispatchHttp({
135
- event: 'LOAD_FORM',
136
- req: { query: {} },
137
- res: {},
138
- next: jest.fn(),
139
- });
140
-
141
- expect(result).toEqual({
142
- success: true,
143
- form: { fields: ['field1', 'field2'] },
144
- integrationId: 'integration-123',
145
- });
146
- });
147
- });
@@ -1,209 +0,0 @@
1
- jest.mock('../database/config', () => ({
2
- DB_TYPE: 'mongodb',
3
- getDatabaseType: jest.fn(() => 'mongodb'),
4
- PRISMA_LOG_LEVEL: 'error,warn',
5
- PRISMA_QUERY_LOGGING: false,
6
- }));
7
-
8
- const { IntegrationEventDispatcher } = require('./integration-event-dispatcher');
9
- const { IntegrationBase } = require('../integrations/integration-base');
10
-
11
- class TestIntegration extends IntegrationBase {
12
- static Definition = {
13
- name: 'test-integration',
14
- version: '1.0.0',
15
- modules: {},
16
- routes: [
17
- { path: '/auth', method: 'GET', event: 'AUTH_REQUEST' },
18
- { path: '/data', method: 'GET', event: 'LOAD_DATA' },
19
- { path: '/job', method: 'POST', event: 'TEST_EVENT' },
20
- { path: '/dynamic', method: 'GET', event: 'DYNAMIC_EVENT' },
21
- ],
22
- };
23
-
24
- constructor(params) {
25
- super(params);
26
- this.events = {
27
- AUTH_REQUEST: { handler: this.authRequest.bind(this) },
28
- LOAD_DATA: { handler: this.loadData.bind(this) },
29
- TEST_EVENT: { handler: this.testHandler.bind(this) },
30
- };
31
- }
32
-
33
- async authRequest() {
34
- TestIntegration.latestInstance = this;
35
- return {
36
- success: true,
37
- hydrated: this.isHydrated,
38
- };
39
- }
40
-
41
- async loadData() {
42
- this.assertHydrated('loadData requires hydration');
43
- return { success: true };
44
- }
45
-
46
- async testHandler({ data }) {
47
- TestIntegration.latestInstance = this;
48
- return { received: data };
49
- }
50
-
51
- async initialize() {
52
- this.events = {
53
- ...this.events,
54
- DYNAMIC_EVENT: { handler: this.dynamicHandler.bind(this) },
55
- };
56
- }
57
-
58
- async dynamicHandler() {
59
- TestIntegration.latestInstance = this;
60
- return { dynamic: true };
61
- }
62
- }
63
-
64
- describe('IntegrationEventDispatcher', () => {
65
- const createDispatcher = () =>
66
- new IntegrationEventDispatcher(new TestIntegration());
67
-
68
- beforeEach(() => {
69
- TestIntegration.latestInstance = null;
70
- });
71
-
72
- describe('dispatchHttp', () => {
73
- it('creates a stateless integration instance for HTTP events', async () => {
74
- const dispatcher = createDispatcher();
75
- const result = await dispatcher.dispatchHttp({
76
- event: 'AUTH_REQUEST',
77
- req: {},
78
- res: {},
79
- next: jest.fn(),
80
- });
81
-
82
- expect(result).toEqual({ success: true, hydrated: false });
83
- expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
84
- expect(TestIntegration.latestInstance.isHydrated).toBe(false);
85
- });
86
-
87
- it('calls initialize to register dynamic events', async () => {
88
- const dispatcher = createDispatcher();
89
- await dispatcher.integrationInstance.initialize();
90
- const result = await dispatcher.dispatchHttp({
91
- event: 'DYNAMIC_EVENT',
92
- req: {},
93
- res: {},
94
- next: jest.fn(),
95
- });
96
-
97
- expect(result).toEqual({ dynamic: true });
98
- expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
99
- });
100
-
101
- it('throws when requesting an unknown event', async () => {
102
- const dispatcher = createDispatcher();
103
- await expect(
104
- dispatcher.dispatchHttp({
105
- event: 'UNKNOWN',
106
- req: {},
107
- res: {},
108
- next: jest.fn(),
109
- })
110
- ).rejects.toThrow('Event UNKNOWN not registered for test-integration');
111
- });
112
-
113
- it('does not hydrate automatically for handlers that require data', async () => {
114
- const dispatcher = createDispatcher();
115
- await expect(
116
- dispatcher.dispatchHttp({
117
- event: 'LOAD_DATA',
118
- req: {},
119
- res: {},
120
- next: jest.fn(),
121
- })
122
- ).rejects.toThrow('loadData requires hydration');
123
- });
124
- });
125
-
126
- describe('dispatchJob', () => {
127
- it('creates a stateless integration instance for job events', async () => {
128
- const payload = { foo: 'bar' };
129
- const dispatcher = createDispatcher();
130
- const result = await dispatcher.dispatchJob({
131
- event: 'TEST_EVENT',
132
- data: payload,
133
- context: {},
134
- });
135
-
136
- expect(result).toEqual({ received: payload });
137
- expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
138
- expect(TestIntegration.latestInstance.isHydrated).toBe(false);
139
- });
140
- });
141
-
142
- describe('Webhook Events', () => {
143
- it('should dispatch WEBHOOK_RECEIVED without hydration', async () => {
144
- const integration = new TestIntegration();
145
- integration.events.WEBHOOK_RECEIVED = {
146
- handler: jest.fn().mockResolvedValue({ received: true })
147
- };
148
-
149
- const dispatcher = new IntegrationEventDispatcher(integration);
150
- const req = { body: { test: 'data' }, params: {} };
151
- const res = {};
152
-
153
- await dispatcher.dispatchHttp({
154
- event: 'WEBHOOK_RECEIVED',
155
- req,
156
- res,
157
- next: jest.fn()
158
- });
159
-
160
- expect(integration.events.WEBHOOK_RECEIVED.handler).toHaveBeenCalledWith({
161
- req,
162
- res,
163
- next: expect.any(Function)
164
- });
165
- });
166
-
167
- it('should dispatch ON_WEBHOOK with job context', async () => {
168
- const integration = new TestIntegration({ id: '123', userId: 'user1' });
169
- integration.events.ON_WEBHOOK = {
170
- handler: jest.fn().mockResolvedValue({ processed: true })
171
- };
172
-
173
- const dispatcher = new IntegrationEventDispatcher(integration);
174
- const data = { integrationId: '123', body: { event: 'test' } };
175
-
176
- await dispatcher.dispatchJob({
177
- event: 'ON_WEBHOOK',
178
- data,
179
- context: {}
180
- });
181
-
182
- expect(integration.events.ON_WEBHOOK.handler).toHaveBeenCalledWith({
183
- data,
184
- context: {}
185
- });
186
- expect(integration.isHydrated).toBe(true);
187
- });
188
-
189
- it('should use default WEBHOOK_RECEIVED handler if not overridden', async () => {
190
- const integration = new TestIntegration();
191
- const dispatcher = new IntegrationEventDispatcher(integration);
192
-
193
- const req = { body: { test: 'data' }, params: {}, headers: {}, query: {} };
194
- const res = { status: jest.fn().mockReturnThis(), json: jest.fn() };
195
-
196
- // Mock queueWebhook
197
- integration.queueWebhook = jest.fn().mockResolvedValue('message-id');
198
-
199
- const handler = dispatcher.findEventHandler(integration, 'WEBHOOK_RECEIVED');
200
- expect(handler).toBeDefined();
201
-
202
- await handler.call(integration, { req, res });
203
-
204
- expect(integration.queueWebhook).toHaveBeenCalled();
205
- expect(res.status).toHaveBeenCalledWith(200);
206
- expect(res.json).toHaveBeenCalledWith({ received: true });
207
- });
208
- });
209
- });
@@ -1,210 +0,0 @@
1
- process.env.HEALTH_API_KEY = 'test-api-key';
2
-
3
- jest.mock('../../database/config', () => ({
4
- DB_TYPE: 'mongodb',
5
- getDatabaseType: jest.fn(() => 'mongodb'),
6
- PRISMA_LOG_LEVEL: 'error,warn',
7
- PRISMA_QUERY_LOGGING: false,
8
- }));
9
-
10
- jest.mock('mongoose', () => ({
11
- set: jest.fn(),
12
- connection: {
13
- readyState: 1,
14
- db: {
15
- admin: () => ({
16
- ping: jest.fn().mockResolvedValue(true)
17
- })
18
- }
19
- }
20
- }));
21
-
22
- jest.mock('./../backend-utils', () => ({
23
- moduleFactory: {
24
- moduleTypes: ['test-module', 'another-module']
25
- },
26
- integrationFactory: {
27
- integrationTypes: ['test-integration', 'another-integration']
28
- }
29
- }));
30
-
31
- jest.mock('./../app-handler-helpers', () => ({
32
- createAppHandler: jest.fn((name, router) => ({ name, router }))
33
- }));
34
-
35
- const { router } = require('./health');
36
- const mongoose = require('mongoose');
37
-
38
- const mockRequest = (path, headers = {}) => ({
39
- path,
40
- headers
41
- });
42
-
43
- const mockResponse = () => {
44
- const res = {};
45
- res.status = jest.fn().mockReturnValue(res);
46
- res.json = jest.fn().mockReturnValue(res);
47
- return res;
48
- };
49
-
50
- describe('Health Check Endpoints', () => {
51
- beforeEach(() => {
52
- mongoose.connection.readyState = 1;
53
- });
54
-
55
- describe('Middleware - validateApiKey', () => {
56
- it('should allow access to /health without authentication', async () => {
57
- expect(true).toBe(true);
58
- });
59
- });
60
-
61
- describe('GET /health', () => {
62
- it('should return basic health status', async () => {
63
- const req = mockRequest('/health');
64
- const res = mockResponse();
65
-
66
- const routeHandler = router.stack.find(layer =>
67
- layer.route && layer.route.path === '/health'
68
- ).route.stack[0].handle;
69
-
70
- await routeHandler(req, res);
71
-
72
- expect(res.status).toHaveBeenCalledWith(200);
73
- expect(res.json).toHaveBeenCalledWith({
74
- status: 'ok',
75
- timestamp: expect.any(String),
76
- service: 'frigg-core-api'
77
- });
78
- });
79
- });
80
-
81
- describe('GET /health/detailed', () => {
82
- it('should return detailed health status when healthy', async () => {
83
- const req = mockRequest('/health/detailed', { 'x-api-key': 'test-api-key' });
84
- const res = mockResponse();
85
-
86
- const originalPromiseAll = Promise.all;
87
- Promise.all = jest.fn().mockResolvedValue([
88
- { name: 'github', status: 'healthy', reachable: true, statusCode: 200, responseTime: 100 },
89
- { name: 'npm', status: 'healthy', reachable: true, statusCode: 200, responseTime: 150 }
90
- ]);
91
-
92
- const routeHandler = router.stack.find(layer =>
93
- layer.route && layer.route.path === '/health/detailed'
94
- ).route.stack[0].handle;
95
-
96
- await routeHandler(req, res);
97
-
98
- Promise.all = originalPromiseAll;
99
-
100
- expect(res.status).toHaveBeenCalledWith(200);
101
- expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
102
- status: 'healthy',
103
- service: 'frigg-core-api',
104
- timestamp: expect.any(String),
105
- checks: expect.objectContaining({
106
- database: expect.objectContaining({
107
- status: 'healthy',
108
- state: 'connected'
109
- }),
110
- integrations: expect.objectContaining({
111
- status: 'healthy'
112
- })
113
- }),
114
- responseTime: expect.any(Number)
115
- }));
116
-
117
- const response = res.json.mock.calls[0][0];
118
- expect(response).not.toHaveProperty('version');
119
- expect(response).not.toHaveProperty('uptime');
120
- expect(response.checks).not.toHaveProperty('memory');
121
- expect(response.checks.database).not.toHaveProperty('type');
122
- });
123
-
124
- it('should return 503 when database is disconnected', async () => {
125
- mongoose.connection.readyState = 0;
126
-
127
- const req = mockRequest('/health/detailed', { 'x-api-key': 'test-api-key' });
128
- const res = mockResponse();
129
-
130
- const originalPromiseAll = Promise.all;
131
- Promise.all = jest.fn().mockResolvedValue([
132
- { name: 'github', status: 'healthy', reachable: true, statusCode: 200, responseTime: 100 },
133
- { name: 'npm', status: 'healthy', reachable: true, statusCode: 200, responseTime: 150 }
134
- ]);
135
-
136
- const routeHandler = router.stack.find(layer =>
137
- layer.route && layer.route.path === '/health/detailed'
138
- ).route.stack[0].handle;
139
-
140
- await routeHandler(req, res);
141
-
142
- Promise.all = originalPromiseAll;
143
-
144
- expect(res.status).toHaveBeenCalledWith(503);
145
- expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
146
- status: 'unhealthy'
147
- }));
148
- });
149
- });
150
-
151
- describe('GET /health/live', () => {
152
- it('should return alive status', async () => {
153
- const req = mockRequest('/health/live', { 'x-api-key': 'test-api-key' });
154
- const res = mockResponse();
155
-
156
- const routeHandler = router.stack.find(layer =>
157
- layer.route && layer.route.path === '/health/live'
158
- ).route.stack[0].handle;
159
-
160
- routeHandler(req, res);
161
-
162
- expect(res.status).toHaveBeenCalledWith(200);
163
- expect(res.json).toHaveBeenCalledWith({
164
- status: 'alive',
165
- timestamp: expect.any(String)
166
- });
167
- });
168
- });
169
-
170
- describe('GET /health/ready', () => {
171
- it('should return ready when all checks pass', async () => {
172
- const req = mockRequest('/health/ready', { 'x-api-key': 'test-api-key' });
173
- const res = mockResponse();
174
-
175
- const routeHandler = router.stack.find(layer =>
176
- layer.route && layer.route.path === '/health/ready'
177
- ).route.stack[0].handle;
178
-
179
- await routeHandler(req, res);
180
-
181
- expect(res.status).toHaveBeenCalledWith(200);
182
- expect(res.json).toHaveBeenCalledWith({
183
- ready: true,
184
- timestamp: expect.any(String),
185
- checks: {
186
- database: true,
187
- modules: true
188
- }
189
- });
190
- });
191
-
192
- it('should return 503 when database is not connected', async () => {
193
- mongoose.connection.readyState = 0;
194
-
195
- const req = mockRequest('/health/ready', { 'x-api-key': 'test-api-key' });
196
- const res = mockResponse();
197
-
198
- const routeHandler = router.stack.find(layer =>
199
- layer.route && layer.route.path === '/health/ready'
200
- ).route.stack[0].handle;
201
-
202
- await routeHandler(req, res);
203
-
204
- expect(res.status).toHaveBeenCalledWith(503);
205
- expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
206
- ready: false
207
- }));
208
- });
209
- });
210
- });
@@ -1,126 +0,0 @@
1
- jest.mock('../../database/config', () => ({
2
- DB_TYPE: 'mongodb',
3
- getDatabaseType: jest.fn(() => 'mongodb'),
4
- PRISMA_LOG_LEVEL: 'error,warn',
5
- PRISMA_QUERY_LOGGING: false,
6
- }));
7
-
8
- jest.mock('../app-definition-loader', () => {
9
- const { IntegrationBase } = require('../../integrations/integration-base');
10
-
11
- class WebhookEnabledIntegration extends IntegrationBase {
12
- static Definition = {
13
- name: 'webhook-enabled',
14
- version: '1.0.0',
15
- modules: {},
16
- webhooks: true,
17
- };
18
-
19
- constructor(params) {
20
- super(params);
21
- this.queueWebhook = jest.fn().mockResolvedValue('message-id');
22
- }
23
- }
24
-
25
- class AdvancedWebhookIntegration extends IntegrationBase {
26
- static Definition = {
27
- name: 'advanced-webhook',
28
- version: '1.0.0',
29
- modules: {},
30
- webhooks: {
31
- enabled: true,
32
- },
33
- };
34
-
35
- constructor(params) {
36
- super(params);
37
- this.events = {
38
- WEBHOOK_RECEIVED: {
39
- handler: async ({ req, res }) => {
40
- // Custom signature verification
41
- const signature = req.headers['x-webhook-signature'];
42
- if (signature !== 'valid-signature') {
43
- return res.status(401).json({ error: 'Invalid signature' });
44
- }
45
- await this.queueWebhook({ body: req.body });
46
- res.status(200).json({ verified: true });
47
- },
48
- },
49
- };
50
- this.queueWebhook = jest.fn().mockResolvedValue('message-id');
51
- }
52
- }
53
-
54
- class NoWebhookIntegration extends IntegrationBase {
55
- static Definition = {
56
- name: 'no-webhook',
57
- version: '1.0.0',
58
- modules: {},
59
- };
60
- }
61
-
62
- return {
63
- loadAppDefinition: () => ({
64
- integrations: [
65
- WebhookEnabledIntegration,
66
- AdvancedWebhookIntegration,
67
- NoWebhookIntegration,
68
- ],
69
- }),
70
- };
71
- });
72
-
73
- describe('Integration Webhook Routers', () => {
74
- let handlers;
75
-
76
- beforeEach(() => {
77
- // Clear module cache to get fresh handlers
78
- jest.resetModules();
79
- jest.clearAllMocks();
80
-
81
- // Re-require after mocking
82
- handlers = require('./integration-webhook-routers').handlers;
83
- });
84
-
85
- describe('Handler Creation', () => {
86
- it('should create webhook handlers for integrations with webhooks: true', () => {
87
- expect(handlers['webhook-enabledWebhook']).toBeDefined();
88
- expect(handlers['webhook-enabledWebhook'].handler).toBeDefined();
89
- });
90
-
91
- it('should create webhook handlers for integrations with webhooks.enabled: true', () => {
92
- expect(handlers['advanced-webhookWebhook']).toBeDefined();
93
- expect(handlers['advanced-webhookWebhook'].handler).toBeDefined();
94
- });
95
-
96
- it('should not create webhook handlers for integrations without webhooks', () => {
97
- expect(handlers['no-webhookWebhook']).toBeUndefined();
98
- });
99
-
100
- it('should configure handlers to not use database connection', () => {
101
- // Handlers are created with createAppHandler(..., false)
102
- // This means shouldUseDatabase = false
103
- // Actual behavior is tested in integration tests
104
- expect(handlers['webhook-enabledWebhook']).toBeDefined();
105
- expect(handlers['advanced-webhookWebhook']).toBeDefined();
106
- });
107
- });
108
-
109
- describe('Webhook Configuration', () => {
110
- it('should support boolean webhook configuration', () => {
111
- // webhooks: true should enable webhook handling
112
- expect(handlers['webhook-enabledWebhook']).toBeDefined();
113
- });
114
-
115
- it('should support object webhook configuration', () => {
116
- // webhooks: { enabled: true } should enable webhook handling
117
- expect(handlers['advanced-webhookWebhook']).toBeDefined();
118
- });
119
-
120
- it('should skip integrations with webhooks disabled', () => {
121
- // webhooks: false or missing should not create handlers
122
- expect(handlers['no-webhookWebhook']).toBeUndefined();
123
- });
124
- });
125
- });
126
-