@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,356 +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 { IntegrationBase } = require('../integrations/integration-base');
9
- const { IntegrationEventDispatcher } = require('./integration-event-dispatcher');
10
- const { QueuerUtil } = require('../queues');
11
-
12
- // Mock AWS SQS
13
- jest.mock('aws-sdk', () => {
14
- const mockSQS = {
15
- sendMessage: jest.fn((params, callback) => {
16
- callback(null, { MessageId: 'mock-message-id-123' });
17
- }),
18
- };
19
- return {
20
- SQS: jest.fn(() => mockSQS),
21
- config: { update: jest.fn() },
22
- };
23
- });
24
-
25
- class WebhookTestIntegration extends IntegrationBase {
26
- static Definition = {
27
- name: 'webhook-test',
28
- version: '1.0.0',
29
- modules: {},
30
- webhooks: true,
31
- };
32
-
33
- constructor(params) {
34
- super(params);
35
- this.webhookData = null;
36
- }
37
-
38
- // Override for custom signature verification
39
- async onWebhookReceived({ req, res }) {
40
- const signature = req.headers['x-custom-signature'];
41
-
42
- if (signature && signature !== 'valid-signature-123') {
43
- return res.status(401).json({ error: 'Invalid signature' });
44
- }
45
-
46
- await this.queueWebhook({
47
- integrationId: req.params.integrationId,
48
- body: req.body,
49
- headers: req.headers,
50
- query: req.query,
51
- });
52
-
53
- res.status(200).json({ received: true, verified: !!signature });
54
- }
55
-
56
- // Override for webhook processing
57
- async onWebhook({ data }) {
58
- this.webhookData = data;
59
- return { processed: true, webhookData: data };
60
- }
61
- }
62
-
63
- describe('Webhook Flow Integration Test', () => {
64
- describe('End-to-End Webhook Flow', () => {
65
- beforeEach(() => {
66
- jest.clearAllMocks();
67
- process.env.WEBHOOK_TEST_QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue';
68
- });
69
-
70
- it('should complete full webhook flow: HTTP → Queue → Worker', async () => {
71
- // Step 1: Simulate HTTP webhook received
72
- const integration = new WebhookTestIntegration();
73
- const dispatcher = new IntegrationEventDispatcher(integration);
74
-
75
- const req = {
76
- body: { event: 'item.created', itemId: '12345' },
77
- params: { integrationId: 'int-789' },
78
- headers: { 'content-type': 'application/json' },
79
- query: {},
80
- };
81
- const res = {
82
- status: jest.fn().mockReturnThis(),
83
- json: jest.fn(),
84
- };
85
-
86
- // Execute WEBHOOK_RECEIVED
87
- await dispatcher.dispatchHttp({
88
- event: 'WEBHOOK_RECEIVED',
89
- req,
90
- res,
91
- next: jest.fn(),
92
- });
93
-
94
- // Verify HTTP response
95
- expect(res.status).toHaveBeenCalledWith(200);
96
- expect(res.json).toHaveBeenCalledWith({ received: true, verified: false });
97
-
98
- // Verify message was queued
99
- const AWS = require('aws-sdk');
100
- const mockSQS = new AWS.SQS();
101
- expect(mockSQS.sendMessage).toHaveBeenCalled();
102
-
103
- const queueCall = mockSQS.sendMessage.mock.calls[0][0];
104
- expect(queueCall.QueueUrl).toBe(process.env.WEBHOOK_TEST_QUEUE_URL);
105
-
106
- const queuedMessage = JSON.parse(queueCall.MessageBody);
107
- expect(queuedMessage.event).toBe('ON_WEBHOOK');
108
- expect(queuedMessage.data.integrationId).toBe('int-789');
109
- expect(queuedMessage.data.body).toEqual({ event: 'item.created', itemId: '12345' });
110
-
111
- // Step 2: Simulate worker processing from queue
112
- const workerIntegration = new WebhookTestIntegration();
113
- const workerDispatcher = new IntegrationEventDispatcher(workerIntegration);
114
-
115
- const result = await workerDispatcher.dispatchJob({
116
- event: 'ON_WEBHOOK',
117
- data: queuedMessage.data,
118
- context: {},
119
- });
120
-
121
- // Verify processing result
122
- expect(result.processed).toBe(true);
123
- expect(result.webhookData).toEqual(queuedMessage.data);
124
- expect(workerIntegration.webhookData).not.toBeNull();
125
- });
126
-
127
- it('should support custom signature verification', async () => {
128
- const integration = new WebhookTestIntegration();
129
- const dispatcher = new IntegrationEventDispatcher(integration);
130
-
131
- const reqInvalid = {
132
- body: { event: 'test' },
133
- params: {},
134
- headers: { 'x-custom-signature': 'invalid-sig' },
135
- query: {},
136
- };
137
- const resInvalid = {
138
- status: jest.fn().mockReturnThis(),
139
- json: jest.fn(),
140
- };
141
-
142
- // Test invalid signature
143
- await dispatcher.dispatchHttp({
144
- event: 'WEBHOOK_RECEIVED',
145
- req: reqInvalid,
146
- res: resInvalid,
147
- next: jest.fn(),
148
- });
149
-
150
- expect(resInvalid.status).toHaveBeenCalledWith(401);
151
- expect(resInvalid.json).toHaveBeenCalledWith({ error: 'Invalid signature' });
152
-
153
- // Test valid signature
154
- const reqValid = {
155
- body: { event: 'test' },
156
- params: {},
157
- headers: { 'x-custom-signature': 'valid-signature-123' },
158
- query: {},
159
- };
160
- const resValid = {
161
- status: jest.fn().mockReturnThis(),
162
- json: jest.fn(),
163
- };
164
-
165
- await dispatcher.dispatchHttp({
166
- event: 'WEBHOOK_RECEIVED',
167
- req: reqValid,
168
- res: resValid,
169
- next: jest.fn(),
170
- });
171
-
172
- expect(resValid.status).toHaveBeenCalledWith(200);
173
- expect(resValid.json).toHaveBeenCalledWith({ received: true, verified: true });
174
- });
175
-
176
- it('should handle webhooks without integration ID', async () => {
177
- const integration = new WebhookTestIntegration();
178
- const dispatcher = new IntegrationEventDispatcher(integration);
179
-
180
- const req = {
181
- body: { event: 'system.event' },
182
- params: {}, // No integrationId
183
- headers: {},
184
- query: {},
185
- };
186
- const res = {
187
- status: jest.fn().mockReturnThis(),
188
- json: jest.fn(),
189
- };
190
-
191
- await dispatcher.dispatchHttp({
192
- event: 'WEBHOOK_RECEIVED',
193
- req,
194
- res,
195
- next: jest.fn(),
196
- });
197
-
198
- // Should queue with integrationId: null
199
- const AWS = require('aws-sdk');
200
- const mockSQS = new AWS.SQS();
201
- const queuedMessage = JSON.parse(mockSQS.sendMessage.mock.calls[0][0].MessageBody);
202
-
203
- expect(queuedMessage.data.integrationId).toBeNull();
204
- });
205
-
206
- it('should preserve webhook headers and query params', async () => {
207
- const integration = new WebhookTestIntegration();
208
- const dispatcher = new IntegrationEventDispatcher(integration);
209
-
210
- const req = {
211
- body: { event: 'test' },
212
- params: { integrationId: 'int-456' },
213
- headers: {
214
- 'x-webhook-id': 'webhook-123',
215
- 'x-custom-header': 'value',
216
- },
217
- query: { timestamp: '2025-10-15', version: '2' },
218
- };
219
- const res = {
220
- status: jest.fn().mockReturnThis(),
221
- json: jest.fn(),
222
- };
223
-
224
- await dispatcher.dispatchHttp({
225
- event: 'WEBHOOK_RECEIVED',
226
- req,
227
- res,
228
- next: jest.fn(),
229
- });
230
-
231
- const AWS = require('aws-sdk');
232
- const mockSQS = new AWS.SQS();
233
- const queuedMessage = JSON.parse(mockSQS.sendMessage.mock.calls[0][0].MessageBody);
234
-
235
- expect(queuedMessage.data.headers).toEqual(req.headers);
236
- expect(queuedMessage.data.query).toEqual(req.query);
237
- });
238
- });
239
-
240
- describe('Default Webhook Handlers', () => {
241
- it('should use default WEBHOOK_RECEIVED handler if not overridden', async () => {
242
- // Integration without custom handler
243
- class DefaultWebhookIntegration extends IntegrationBase {
244
- static Definition = {
245
- name: 'default-webhook',
246
- version: '1.0.0',
247
- modules: {},
248
- webhooks: true,
249
- };
250
- }
251
-
252
- process.env.DEFAULT_WEBHOOK_QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/123456789/default-queue';
253
-
254
- const integration = new DefaultWebhookIntegration();
255
- const dispatcher = new IntegrationEventDispatcher(integration);
256
-
257
- const req = {
258
- body: { test: 'data' },
259
- params: {},
260
- headers: {},
261
- query: {},
262
- };
263
- const res = {
264
- status: jest.fn().mockReturnThis(),
265
- json: jest.fn(),
266
- };
267
-
268
- await dispatcher.dispatchHttp({
269
- event: 'WEBHOOK_RECEIVED',
270
- req,
271
- res,
272
- next: jest.fn(),
273
- });
274
-
275
- // Should use default handler
276
- expect(res.status).toHaveBeenCalledWith(200);
277
- expect(res.json).toHaveBeenCalledWith({ received: true });
278
- });
279
-
280
- it('should use default ON_WEBHOOK handler if not overridden', async () => {
281
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
282
-
283
- class DefaultWebhookIntegration extends IntegrationBase {
284
- static Definition = {
285
- name: 'default-webhook-worker',
286
- version: '1.0.0',
287
- modules: {},
288
- webhooks: true,
289
- };
290
- }
291
-
292
- const integration = new DefaultWebhookIntegration();
293
- const dispatcher = new IntegrationEventDispatcher(integration);
294
-
295
- const webhookData = { body: { test: 'data' } };
296
-
297
- await dispatcher.dispatchJob({
298
- event: 'ON_WEBHOOK',
299
- data: webhookData,
300
- context: {},
301
- });
302
-
303
- // Default handler logs the data
304
- expect(consoleSpy).toHaveBeenCalledWith('Webhook received:', webhookData);
305
-
306
- consoleSpy.mockRestore();
307
- });
308
- });
309
-
310
- describe('Error Handling', () => {
311
- it('should handle queueing errors gracefully', async () => {
312
- const AWS = require('aws-sdk');
313
- const mockSQS = new AWS.SQS();
314
- mockSQS.sendMessage.mockImplementation((params, callback) => {
315
- callback(new Error('Queue is full'), null);
316
- });
317
-
318
- process.env.WEBHOOK_TEST_QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue';
319
-
320
- const integration = new WebhookTestIntegration();
321
- const dispatcher = new IntegrationEventDispatcher(integration);
322
-
323
- const req = {
324
- body: { event: 'test' },
325
- params: {},
326
- headers: {},
327
- query: {},
328
- };
329
- const res = {
330
- status: jest.fn().mockReturnThis(),
331
- json: jest.fn(),
332
- };
333
-
334
- // Should throw error when queueing fails
335
- await expect(
336
- dispatcher.dispatchHttp({
337
- event: 'WEBHOOK_RECEIVED',
338
- req,
339
- res,
340
- next: jest.fn(),
341
- })
342
- ).rejects.toThrow('Queue is full');
343
- });
344
-
345
- it('should throw error if queue URL not configured', async () => {
346
- delete process.env.WEBHOOK_TEST_QUEUE_URL;
347
-
348
- const integration = new WebhookTestIntegration();
349
-
350
- await expect(
351
- integration.queueWebhook({ test: 'data' })
352
- ).rejects.toThrow('Queue URL not found for WEBHOOK_TEST_QUEUE_URL');
353
- });
354
- });
355
- });
356
-
@@ -1,184 +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 { createQueueWorker } = require('../backend-utils');
9
- const { IntegrationBase } = require('../../integrations/integration-base');
10
- const { IntegrationEventDispatcher } = require('../integration-event-dispatcher');
11
-
12
- class TestWebhookIntegration extends IntegrationBase {
13
- static Definition = {
14
- name: 'test-webhook',
15
- version: '1.0.0',
16
- modules: {},
17
- webhooks: true,
18
- };
19
-
20
- constructor(params) {
21
- super(params);
22
- this.onWebhookCalled = false;
23
- this.receivedData = null;
24
- }
25
-
26
- async onWebhook({ data }) {
27
- this.onWebhookCalled = true;
28
- this.receivedData = data;
29
- return { processed: true, data };
30
- }
31
- }
32
-
33
- describe('Webhook Queue Worker', () => {
34
- describe('ON_WEBHOOK event processing', () => {
35
- it('should process ON_WEBHOOK event without integration ID (unhydrated)', async () => {
36
- const QueueWorker = createQueueWorker(TestWebhookIntegration);
37
- const worker = new QueueWorker();
38
-
39
- const params = {
40
- event: 'ON_WEBHOOK',
41
- data: {
42
- body: { webhookEvent: 'created', entityId: '123' },
43
- headers: { 'content-type': 'application/json' },
44
- },
45
- };
46
-
47
- const sqsEvent = {
48
- Records: [{ body: JSON.stringify(params) }],
49
- };
50
-
51
- // Should work with unhydrated instance without throwing
52
- await expect(worker.run(sqsEvent, {})).resolves.not.toThrow();
53
- });
54
-
55
- it('should call ON_WEBHOOK handler with webhook data', async () => {
56
- const QueueWorker = createQueueWorker(TestWebhookIntegration);
57
- const worker = new QueueWorker();
58
-
59
- const webhookData = {
60
- body: { webhookEvent: 'created', entityId: '123' },
61
- headers: { 'content-type': 'application/json' },
62
- query: {},
63
- };
64
-
65
- const params = {
66
- event: 'ON_WEBHOOK',
67
- data: webhookData,
68
- };
69
-
70
- const sqsEvent = {
71
- Records: [{ body: JSON.stringify(params) }],
72
- };
73
-
74
- await worker.run(sqsEvent, {});
75
-
76
- // The handler should have been called
77
- });
78
-
79
- it('should handle multiple webhook messages in batch', async () => {
80
- const QueueWorker = createQueueWorker(TestWebhookIntegration);
81
- const worker = new QueueWorker();
82
-
83
- const message1 = {
84
- event: 'ON_WEBHOOK',
85
- data: { body: { event: '1' } },
86
- };
87
- const message2 = {
88
- event: 'ON_WEBHOOK',
89
- data: { body: { event: '2' } },
90
- };
91
-
92
- const sqsEvent = {
93
- Records: [
94
- { body: JSON.stringify(message1) },
95
- { body: JSON.stringify(message2) },
96
- ],
97
- };
98
-
99
- await worker.run(sqsEvent, {});
100
-
101
- // Should process both messages without error
102
- });
103
- });
104
-
105
- describe('Error Handling', () => {
106
- it('should throw error if ON_WEBHOOK handler fails', async () => {
107
- const FailingIntegration = class extends TestWebhookIntegration {
108
- async onWebhook({ data }) {
109
- throw new Error('Processing failed');
110
- }
111
- };
112
-
113
- const FailingWorker = createQueueWorker(FailingIntegration);
114
- const failingWorker = new FailingWorker();
115
-
116
- const params = {
117
- event: 'ON_WEBHOOK',
118
- data: { body: { invalid: 'data' } },
119
- };
120
-
121
- const sqsEvent = {
122
- Records: [{ body: JSON.stringify(params) }],
123
- };
124
-
125
- await expect(failingWorker.run(sqsEvent, {})).rejects.toThrow('Processing failed');
126
- });
127
-
128
- it('should log errors with integration context', async () => {
129
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
130
-
131
- const FailingIntegration = class extends TestWebhookIntegration {
132
- async onWebhook({ data }) {
133
- throw new Error('Test error');
134
- }
135
- };
136
-
137
- const FailingWorker = createQueueWorker(FailingIntegration);
138
- const failingWorker = new FailingWorker();
139
-
140
- const params = {
141
- event: 'ON_WEBHOOK',
142
- data: { body: {} },
143
- };
144
-
145
- const sqsEvent = {
146
- Records: [{ body: JSON.stringify(params) }],
147
- };
148
-
149
- await expect(failingWorker.run(sqsEvent, {})).rejects.toThrow();
150
- expect(consoleSpy).toHaveBeenCalledWith(
151
- expect.stringContaining('Error in ON_WEBHOOK for test-webhook'),
152
- expect.any(Error)
153
- );
154
-
155
- consoleSpy.mockRestore();
156
- });
157
- });
158
-
159
- describe('Integration Hydration for webhooks with integrationId', () => {
160
- it('should attempt to load integration when integrationId present', async () => {
161
- // This test verifies the logic path - full integration test
162
- // will verify actual DB loading with mocked repositories
163
- const QueueWorker = createQueueWorker(TestWebhookIntegration);
164
- const worker = new QueueWorker();
165
-
166
- const params = {
167
- event: 'ON_WEBHOOK',
168
- data: {
169
- integrationId: 'integration-456',
170
- body: { webhookEvent: 'updated' },
171
- },
172
- };
173
-
174
- const sqsEvent = {
175
- Records: [{ body: JSON.stringify(params) }],
176
- };
177
-
178
- // This will fail trying to load the integration from DB
179
- // but it proves the code path is attempted
180
- await expect(worker.run(sqsEvent, {})).rejects.toThrow();
181
- });
182
- });
183
- });
184
-
@@ -1,131 +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 { CreateIntegration } = require('../../use-cases/create-integration');
9
- const { TestIntegrationRepository } = require('../doubles/test-integration-repository');
10
- const { TestModuleFactory } = require('../../../modules/tests/doubles/test-module-factory');
11
- const { DummyIntegration } = require('../doubles/dummy-integration-class');
12
-
13
- describe('CreateIntegration Use-Case', () => {
14
- let integrationRepository;
15
- let moduleFactory;
16
- let useCase;
17
-
18
- beforeEach(() => {
19
- integrationRepository = new TestIntegrationRepository();
20
- moduleFactory = new TestModuleFactory();
21
- useCase = new CreateIntegration({
22
- integrationRepository,
23
- integrationClasses: [DummyIntegration],
24
- moduleFactory,
25
- });
26
- });
27
-
28
- describe('happy path', () => {
29
- it('creates an integration and returns DTO', async () => {
30
- const entities = ['entity-1'];
31
- const userId = 'user-1';
32
- const config = { type: 'dummy', foo: 'bar' };
33
-
34
- const dto = await useCase.execute(entities, userId, config);
35
-
36
- expect(dto.id).toBeDefined();
37
- expect(dto.config).toEqual(config);
38
- expect(dto.userId).toBe(userId);
39
- expect(dto.entities).toEqual(entities);
40
- expect(dto.status).toBe('NEW');
41
- });
42
-
43
- it('triggers ON_CREATE event with correct payload', async () => {
44
- const entities = ['entity-1'];
45
- const userId = 'user-1';
46
- const config = { type: 'dummy', foo: 'bar' };
47
-
48
- const dto = await useCase.execute(entities, userId, config);
49
-
50
- const record = await integrationRepository.findIntegrationById(dto.id);
51
- expect(record).toBeTruthy();
52
-
53
- const history = integrationRepository.getOperationHistory();
54
- const createOperation = history.find(op => op.operation === 'create');
55
- expect(createOperation).toEqual({
56
- operation: 'create',
57
- id: dto.id,
58
- userId,
59
- config
60
- });
61
- });
62
-
63
- it('loads modules for each entity', async () => {
64
- const entities = ['entity-1', 'entity-2'];
65
- const userId = 'user-1';
66
- const config = { type: 'dummy' };
67
-
68
- const dto = await useCase.execute(entities, userId, config);
69
-
70
- expect(dto.entities).toEqual(entities);
71
- });
72
- });
73
-
74
- describe('error cases', () => {
75
- it('throws error when integration class is not found', async () => {
76
- const entities = ['entity-1'];
77
- const userId = 'user-1';
78
- const config = { type: 'unknown-type' };
79
-
80
- await expect(useCase.execute(entities, userId, config))
81
- .rejects
82
- .toThrow('No integration class found for type: unknown-type');
83
- });
84
-
85
- it('throws error when no integration classes provided', async () => {
86
- const useCaseWithoutClasses = new CreateIntegration({
87
- integrationRepository,
88
- integrationClasses: [],
89
- moduleFactory,
90
- });
91
-
92
- const entities = ['entity-1'];
93
- const userId = 'user-1';
94
- const config = { type: 'dummy' };
95
-
96
- await expect(useCaseWithoutClasses.execute(entities, userId, config))
97
- .rejects
98
- .toThrow('No integration class found for type: dummy');
99
- });
100
- });
101
-
102
- describe('edge cases', () => {
103
- it('handles empty entities array', async () => {
104
- const entities = [];
105
- const userId = 'user-1';
106
- const config = { type: 'dummy' };
107
-
108
- const dto = await useCase.execute(entities, userId, config);
109
-
110
- expect(dto.entities).toEqual([]);
111
- expect(dto.id).toBeDefined();
112
- });
113
-
114
- it('handles complex config objects', async () => {
115
- const entities = ['entity-1'];
116
- const userId = 'user-1';
117
- const config = {
118
- type: 'dummy',
119
- nested: {
120
- value: 123,
121
- array: [1, 2, 3],
122
- bool: true
123
- }
124
- };
125
-
126
- const dto = await useCase.execute(entities, userId, config);
127
-
128
- expect(dto.config).toEqual(config);
129
- });
130
- });
131
- });