@friggframework/core 2.0.0-next.45 → 2.0.0-next.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/application/commands/integration-commands.js +19 -0
- package/core/Worker.js +8 -21
- package/credential/repositories/credential-repository-mongo.js +14 -8
- package/credential/repositories/credential-repository-postgres.js +14 -8
- package/credential/repositories/credential-repository.js +3 -8
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +11 -2
- package/database/models/WebsocketConnection.js +11 -10
- package/database/prisma.js +63 -3
- package/database/repositories/health-check-repository-mongodb.js +3 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +3 -2
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/run-database-migration-use-case.js +137 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +91 -0
- package/database/utils/mongodb-schema-init.js +106 -0
- package/database/utils/prisma-runner.js +400 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/encrypt/Cryptor.js +14 -16
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +334 -0
- package/generated/prisma-mongodb/index-browser.js +316 -0
- package/generated/prisma-mongodb/index.d.ts +22897 -0
- package/generated/prisma-mongodb/index.js +359 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
- package/generated/prisma-mongodb/runtime/binary.js +289 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +362 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +341 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +356 -0
- package/generated/prisma-postgresql/index-browser.js +338 -0
- package/generated/prisma-postgresql/index.d.ts +25071 -0
- package/generated/prisma-postgresql/index.js +381 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
- package/generated/prisma-postgresql/runtime/binary.js +289 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +345 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +363 -0
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/routers/auth.js +1 -1
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +256 -0
- package/handlers/routers/health.js +41 -6
- package/handlers/routers/integration-webhook-routers.js +2 -2
- package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
- package/handlers/workers/db-migration.js +352 -0
- package/index.js +12 -0
- package/integrations/integration-router.js +60 -70
- package/integrations/repositories/integration-repository-interface.js +12 -0
- package/integrations/repositories/integration-repository-mongo.js +32 -0
- package/integrations/repositories/integration-repository-postgres.js +33 -0
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/test-integration-repository.js +2 -2
- package/logs/logger.js +0 -4
- package/modules/entity.js +0 -1
- package/modules/repositories/module-repository-mongo.js +3 -12
- package/modules/repositories/module-repository-postgres.js +0 -11
- package/modules/repositories/module-repository.js +1 -12
- package/modules/use-cases/get-entity-options-by-id.js +1 -1
- package/modules/use-cases/get-module.js +1 -2
- package/modules/use-cases/refresh-entity-options.js +1 -1
- package/modules/use-cases/test-module-auth.js +1 -1
- package/package.json +82 -66
- package/prisma-mongodb/schema.prisma +21 -21
- package/prisma-postgresql/schema.prisma +15 -15
- package/queues/queuer-util.js +24 -21
- package/types/core/index.d.ts +2 -2
- package/types/module-plugin/index.d.ts +0 -2
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
- package/user/user.js +16 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
- package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
- package/websocket/repositories/websocket-connection-repository.js +11 -10
- package/application/commands/integration-commands.test.js +0 -123
- package/database/encryption/encryption-integration.test.js +0 -553
- package/database/encryption/encryption-schema-registry.test.js +0 -392
- package/database/encryption/field-encryption-service.test.js +0 -525
- package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
- package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
- package/database/encryption/postgres-relation-decryption.test.js +0 -245
- package/database/encryption/prisma-encryption-extension.test.js +0 -439
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/handlers/auth-flow.integration.test.js +0 -147
- package/handlers/integration-event-dispatcher.test.js +0 -209
- package/handlers/routers/health.test.js +0 -210
- package/handlers/routers/integration-webhook-routers.test.js +0 -126
- package/handlers/webhook-flow.integration.test.js +0 -356
- package/handlers/workers/integration-defined-workers.test.js +0 -184
- package/integrations/tests/use-cases/create-integration.test.js +0 -131
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
- package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
- package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
- package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
- package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
- package/integrations/tests/use-cases/update-integration.test.js +0 -141
- package/integrations/use-cases/create-process.test.js +0 -178
- package/integrations/use-cases/get-process.test.js +0 -190
- package/integrations/use-cases/load-integration-context-full.test.js +0 -329
- package/integrations/use-cases/load-integration-context.test.js +0 -114
- package/integrations/use-cases/update-process-metrics.test.js +0 -308
- package/integrations/use-cases/update-process-state.test.js +0 -256
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/modules/module-hydration.test.js +0 -205
- package/modules/requester/requester.test.js +0 -28
- package/user/tests/use-cases/create-individual-user.test.js +0 -24
- package/user/tests/use-cases/create-organization-user.test.js +0 -28
- package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
- package/user/tests/use-cases/login-user.test.js +0 -220
- package/user/tests/user-password-encryption-isolation.test.js +0 -237
- 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
|
-
});
|