@friggframework/core 2.0.0--canary.396.6862738.0 → 2.0.0--canary.397.c07f148.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +931 -50
- package/core/create-handler.js +1 -0
- package/credential/credential-repository.js +48 -1
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/handlers/backend-utils.js +34 -31
- package/handlers/routers/auth.js +1 -15
- package/index.js +1 -5
- package/integrations/integration-base.js +133 -40
- package/integrations/integration-repository.js +39 -3
- package/integrations/integration-router.js +109 -85
- package/integrations/tests/doubles/dummy-integration-class.js +90 -0
- package/integrations/tests/doubles/test-integration-repository.js +89 -0
- package/integrations/tests/use-cases/create-integration.test.js +124 -0
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +143 -0
- package/integrations/tests/use-cases/get-integration-for-user.test.js +143 -0
- package/integrations/tests/use-cases/get-integration-instance.test.js +169 -0
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +169 -0
- package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
- package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
- package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
- package/integrations/tests/use-cases/update-integration.test.js +134 -0
- package/integrations/use-cases/create-integration.js +25 -12
- package/integrations/use-cases/delete-integration-for-user.js +21 -2
- package/integrations/use-cases/get-integration-for-user.js +28 -13
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +20 -11
- package/integrations/use-cases/get-integrations-for-user.js +22 -10
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/update-integration-messages.js +31 -0
- package/integrations/use-cases/update-integration-status.js +28 -0
- package/integrations/use-cases/update-integration.js +23 -13
- package/integrations/utils/map-integration-dto.js +0 -1
- package/{module-plugin → modules}/entity.js +1 -0
- package/{module-plugin → modules}/index.js +0 -4
- package/{module-plugin/module-service.js → modules/module-factory.js} +9 -5
- package/modules/module-repository.js +107 -0
- package/modules/module.js +218 -0
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +19 -0
- package/{module-plugin → modules}/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +58 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +31 -0
- package/modules/use-cases/get-module.js +56 -0
- package/modules/use-cases/process-authorization-callback.js +108 -0
- package/modules/use-cases/refresh-entity-options.js +58 -0
- package/modules/use-cases/test-module-auth.js +54 -0
- package/{module-plugin → modules}/utils/map-module-dto.js +1 -1
- package/package.json +5 -5
- package/syncs/sync.js +0 -1
- package/types/module-plugin/index.d.ts +0 -35
- package/types/syncs/index.d.ts +0 -2
- package/integrations/integration.js +0 -233
- package/integrations/test/integration-base.test.js +0 -144
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -42
- package/module-plugin/module-repository.js +0 -70
- package/module-plugin/module.js +0 -329
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/credential.js +0 -0
- /package/{module-plugin → modules}/requester/api-key.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/requester/requester.js +0 -0
- /package/{module-plugin → modules}/requester/requester.test.js +0 -0
- /package/{module-plugin → modules}/test/auther.test.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/api.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/definition.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -8,28 +8,37 @@ const { GetIntegrationsForUser } = require('./use-cases/get-integrations-for-use
|
|
|
8
8
|
const { CredentialRepository } = require('../credential/credential-repository');
|
|
9
9
|
const { GetCredentialForUser } = require('../credential/use-cases/get-credential-for-user');
|
|
10
10
|
const { CreateIntegration } = require('./use-cases/create-integration');
|
|
11
|
-
const {
|
|
12
|
-
const { ModuleRepository } = require('../
|
|
13
|
-
const { GetEntitiesForUser } = require('../
|
|
11
|
+
const { ModuleFactory } = require('../modules/module-factory');
|
|
12
|
+
const { ModuleRepository } = require('../modules/module-repository');
|
|
13
|
+
const { GetEntitiesForUser } = require('../modules/use-cases/get-entities-for-user');
|
|
14
14
|
const { loadAppDefinition } = require('../handlers/app-definition-loader');
|
|
15
15
|
const { GetIntegrationInstance } = require('./use-cases/get-integration-instance');
|
|
16
16
|
const { UpdateIntegration } = require('./use-cases/update-integration');
|
|
17
17
|
const { getModulesDefinitionFromIntegrationClasses } = require('./utils/map-integration-dto');
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
const { GetModuleInstanceFromType } = require('../modules/use-cases/get-module-instance-from-type');
|
|
19
|
+
const { GetEntityOptionsByType } = require('../modules/use-cases/get-entity-options-by-type');
|
|
20
|
+
const { TestModuleAuth } = require('../modules/use-cases/test-module-auth');
|
|
21
|
+
const { GetModule } = require('../modules/use-cases/get-module');
|
|
22
|
+
const { GetEntityOptionsById } = require('../modules/use-cases/get-entity-options-by-id');
|
|
23
|
+
const { RefreshEntityOptions } = require('../modules/use-cases/refresh-entity-options');
|
|
24
|
+
const { GetPossibleIntegrations } = require('./use-cases/get-possible-integrations');
|
|
25
|
+
const { UserRepository } = require('../user/user-repository');
|
|
26
|
+
const { GetUserFromBearerToken } = require('../user/use-cases/get-user-from-bearer-token');
|
|
27
|
+
const { ProcessAuthorizationCallback } = require('../modules/use-cases/process-authorization-callback');
|
|
28
|
+
|
|
29
|
+
function createIntegrationRouter() {
|
|
30
|
+
const { integrations: integrationClasses, userConfig } = loadAppDefinition();
|
|
28
31
|
const moduleRepository = new ModuleRepository();
|
|
29
32
|
const integrationRepository = new IntegrationRepository();
|
|
30
33
|
const credentialRepository = new CredentialRepository();
|
|
34
|
+
const userRepository = new UserRepository({ userConfig });
|
|
35
|
+
|
|
36
|
+
const getUserFromBearerToken = new GetUserFromBearerToken({
|
|
37
|
+
userRepository,
|
|
38
|
+
userConfig,
|
|
39
|
+
});
|
|
31
40
|
|
|
32
|
-
const
|
|
41
|
+
const moduleFactory = new ModuleFactory({
|
|
33
42
|
moduleRepository,
|
|
34
43
|
moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
35
44
|
});
|
|
@@ -41,16 +50,18 @@ function createIntegrationRouter(params) {
|
|
|
41
50
|
const getIntegrationsForUser = new GetIntegrationsForUser({
|
|
42
51
|
integrationRepository,
|
|
43
52
|
integrationClasses,
|
|
44
|
-
|
|
53
|
+
moduleFactory,
|
|
45
54
|
moduleRepository,
|
|
46
55
|
});
|
|
56
|
+
|
|
47
57
|
const getCredentialForUser = new GetCredentialForUser({
|
|
48
58
|
credentialRepository,
|
|
49
59
|
});
|
|
60
|
+
|
|
50
61
|
const createIntegration = new CreateIntegration({
|
|
51
62
|
integrationRepository,
|
|
52
63
|
integrationClasses,
|
|
53
|
-
|
|
64
|
+
moduleFactory,
|
|
54
65
|
});
|
|
55
66
|
|
|
56
67
|
const getEntitiesForUser = new GetEntitiesForUser({
|
|
@@ -61,18 +72,54 @@ function createIntegrationRouter(params) {
|
|
|
61
72
|
const getIntegrationInstance = new GetIntegrationInstance({
|
|
62
73
|
integrationRepository,
|
|
63
74
|
integrationClasses,
|
|
64
|
-
|
|
75
|
+
moduleFactory,
|
|
65
76
|
});
|
|
66
77
|
|
|
67
78
|
const updateIntegration = new UpdateIntegration({
|
|
68
79
|
integrationRepository,
|
|
69
80
|
integrationClasses,
|
|
70
|
-
|
|
81
|
+
moduleFactory,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const getModuleInstanceFromType = new GetModuleInstanceFromType({
|
|
85
|
+
moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const getEntityOptionsByType = new GetEntityOptionsByType({
|
|
89
|
+
moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const testModuleAuth = new TestModuleAuth({
|
|
93
|
+
moduleRepository,
|
|
94
|
+
moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const getModule = new GetModule({
|
|
98
|
+
moduleRepository,
|
|
99
|
+
moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const getEntityOptionsById = new GetEntityOptionsById({
|
|
103
|
+
moduleRepository,
|
|
104
|
+
moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const refreshEntityOptions = new RefreshEntityOptions({
|
|
108
|
+
moduleRepository,
|
|
109
|
+
moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
71
110
|
});
|
|
72
111
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
const getPossibleIntegrations = new GetPossibleIntegrations({
|
|
113
|
+
integrationClasses,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const processAuthorizationCallback = new ProcessAuthorizationCallback({
|
|
117
|
+
moduleRepository,
|
|
118
|
+
credentialRepository,
|
|
119
|
+
moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const router = express();
|
|
76
123
|
|
|
77
124
|
setIntegrationRoutes(router, getUserFromBearerToken, {
|
|
78
125
|
createIntegration,
|
|
@@ -81,9 +128,17 @@ function createIntegrationRouter(params) {
|
|
|
81
128
|
getEntitiesForUser,
|
|
82
129
|
getIntegrationInstance,
|
|
83
130
|
updateIntegration,
|
|
131
|
+
getPossibleIntegrations,
|
|
84
132
|
});
|
|
85
|
-
setEntityRoutes(router,
|
|
133
|
+
setEntityRoutes(router, getUserFromBearerToken, {
|
|
86
134
|
getCredentialForUser,
|
|
135
|
+
getModuleInstanceFromType,
|
|
136
|
+
getEntityOptionsByType,
|
|
137
|
+
testModuleAuth,
|
|
138
|
+
getModule,
|
|
139
|
+
getEntityOptionsById,
|
|
140
|
+
refreshEntityOptions,
|
|
141
|
+
processAuthorizationCallback,
|
|
87
142
|
});
|
|
88
143
|
return router;
|
|
89
144
|
}
|
|
@@ -124,6 +179,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
124
179
|
getEntitiesForUser,
|
|
125
180
|
getIntegrationInstance,
|
|
126
181
|
updateIntegration,
|
|
182
|
+
getPossibleIntegrations,
|
|
127
183
|
} = useCases;
|
|
128
184
|
router.route('/api/integrations').get(
|
|
129
185
|
catchAsyncError(async (req, res) => {
|
|
@@ -134,9 +190,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
134
190
|
const integrations = await getIntegrationsForUser.execute(userId);
|
|
135
191
|
const results = {
|
|
136
192
|
entities: {
|
|
137
|
-
options:
|
|
138
|
-
integration.options
|
|
139
|
-
),
|
|
193
|
+
options: await getPossibleIntegrations.execute(),
|
|
140
194
|
authorized: await getEntitiesForUser.execute(userId),
|
|
141
195
|
},
|
|
142
196
|
integrations: integrations,
|
|
@@ -303,7 +357,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
303
357
|
}
|
|
304
358
|
|
|
305
359
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
306
|
-
const integration = await
|
|
360
|
+
const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
|
|
307
361
|
|
|
308
362
|
// We could perhaps augment router with dynamic options? Haven't decided yet, but here may be the place
|
|
309
363
|
|
|
@@ -348,25 +402,19 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
348
402
|
/**
|
|
349
403
|
* Sets up entity-related routes for the integration router
|
|
350
404
|
* @param {Object} router - Express router instance
|
|
351
|
-
* @param {Object} factory - Factory object containing moduleFactory
|
|
352
405
|
* @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} getUserFromBearerToken - Use case for retrieving a user from a bearer token
|
|
353
406
|
*/
|
|
354
|
-
function setEntityRoutes(router,
|
|
355
|
-
const {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return await moduleFactory.getInstanceFromTypeName(
|
|
366
|
-
entityType,
|
|
367
|
-
userId
|
|
368
|
-
);
|
|
369
|
-
};
|
|
407
|
+
function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
408
|
+
const {
|
|
409
|
+
getCredentialForUser,
|
|
410
|
+
getModuleInstanceFromType,
|
|
411
|
+
getEntityOptionsByType,
|
|
412
|
+
testModuleAuth,
|
|
413
|
+
getModule,
|
|
414
|
+
getEntityOptionsById,
|
|
415
|
+
refreshEntityOptions,
|
|
416
|
+
processAuthorizationCallback,
|
|
417
|
+
} = useCases;
|
|
370
418
|
|
|
371
419
|
router.route('/api/authorize').get(
|
|
372
420
|
catchAsyncError(async (req, res) => {
|
|
@@ -375,16 +423,15 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
|
|
|
375
423
|
);
|
|
376
424
|
const userId = user.getId();
|
|
377
425
|
const params = checkRequiredParams(req.query, ['entityType']);
|
|
378
|
-
const module = await
|
|
379
|
-
const areRequirementsValid =
|
|
380
|
-
module.validateAuthorizationRequirements();
|
|
426
|
+
const module = await getModuleInstanceFromType.execute(userId, params.entityType);
|
|
427
|
+
const areRequirementsValid = module.validateAuthorizationRequirements();
|
|
381
428
|
if (!areRequirementsValid) {
|
|
382
429
|
throw new Error(
|
|
383
430
|
`Error: Entity of type ${params.entityType} requires a valid url`
|
|
384
431
|
);
|
|
385
432
|
}
|
|
386
433
|
|
|
387
|
-
res.json(
|
|
434
|
+
res.json(module.getAuthorizationRequirements());
|
|
388
435
|
})
|
|
389
436
|
);
|
|
390
437
|
|
|
@@ -398,14 +445,10 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
|
|
|
398
445
|
'entityType',
|
|
399
446
|
'data',
|
|
400
447
|
]);
|
|
401
|
-
const module = await getModuleInstance(userId, params.entityType);
|
|
402
448
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
data: params.data,
|
|
407
|
-
})
|
|
408
|
-
);
|
|
449
|
+
const entityDetails = await processAuthorizationCallback.execute(userId, params.entityType, params.data);
|
|
450
|
+
|
|
451
|
+
res.json(entityDetails);
|
|
409
452
|
})
|
|
410
453
|
);
|
|
411
454
|
|
|
@@ -431,7 +474,7 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
|
|
|
431
474
|
throw Boom.badRequest('Invalid credential ID');
|
|
432
475
|
}
|
|
433
476
|
|
|
434
|
-
const module = await
|
|
477
|
+
const module = await getModuleInstanceFromType.execute(userId, params.entityType);
|
|
435
478
|
const entityDetails = await module.getEntityDetails(
|
|
436
479
|
module.api,
|
|
437
480
|
null,
|
|
@@ -460,9 +503,9 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
|
|
|
460
503
|
}
|
|
461
504
|
|
|
462
505
|
const params = checkRequiredParams(req.query, ['entityType']);
|
|
463
|
-
const
|
|
506
|
+
const entityOptions = await getEntityOptionsByType.execute(userId, params.entityType);
|
|
464
507
|
|
|
465
|
-
res.json(
|
|
508
|
+
res.json(entityOptions);
|
|
466
509
|
})
|
|
467
510
|
);
|
|
468
511
|
|
|
@@ -473,17 +516,11 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
|
|
|
473
516
|
);
|
|
474
517
|
const userId = user.getId();
|
|
475
518
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
476
|
-
const
|
|
519
|
+
const testAuthResponse = await testModuleAuth.execute(
|
|
477
520
|
params.entityId,
|
|
478
521
|
userId
|
|
479
522
|
);
|
|
480
523
|
|
|
481
|
-
if (!module) {
|
|
482
|
-
throw Boom.notFound();
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const testAuthResponse = await module.testAuth();
|
|
486
|
-
|
|
487
524
|
if (!testAuthResponse) {
|
|
488
525
|
res.status(400);
|
|
489
526
|
res.json({
|
|
@@ -508,16 +545,12 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
|
|
|
508
545
|
);
|
|
509
546
|
const userId = user.getId();
|
|
510
547
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
511
|
-
const module = await
|
|
548
|
+
const module = await getModule.execute(
|
|
512
549
|
params.entityId,
|
|
513
550
|
userId
|
|
514
551
|
);
|
|
515
552
|
|
|
516
|
-
|
|
517
|
-
throw Boom.notFound();
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
res.json(module.entity);
|
|
553
|
+
res.json(module);
|
|
521
554
|
})
|
|
522
555
|
);
|
|
523
556
|
|
|
@@ -530,16 +563,10 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
|
|
|
530
563
|
const params = checkRequiredParams(req.params, [
|
|
531
564
|
'entityId',
|
|
532
565
|
]);
|
|
533
|
-
const module = await moduleFactory.getModuleInstanceFromEntityId(
|
|
534
|
-
params.entityId,
|
|
535
|
-
userId
|
|
536
|
-
);
|
|
537
566
|
|
|
538
|
-
|
|
539
|
-
throw Boom.notFound();
|
|
540
|
-
}
|
|
567
|
+
const entityOptions = await getEntityOptionsById.execute(params.entityId, userId);
|
|
541
568
|
|
|
542
|
-
res.json(
|
|
569
|
+
res.json(entityOptions);
|
|
543
570
|
})
|
|
544
571
|
);
|
|
545
572
|
|
|
@@ -552,16 +579,13 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
|
|
|
552
579
|
const params = checkRequiredParams(req.params, [
|
|
553
580
|
'entityId',
|
|
554
581
|
]);
|
|
555
|
-
const
|
|
582
|
+
const updatedOptions = await refreshEntityOptions.execute(
|
|
556
583
|
params.entityId,
|
|
557
|
-
userId
|
|
584
|
+
userId,
|
|
585
|
+
req.body
|
|
558
586
|
);
|
|
559
587
|
|
|
560
|
-
|
|
561
|
-
throw Boom.notFound();
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
res.json(await module.refreshEntityOptions(req.body));
|
|
588
|
+
res.json(updatedOptions);
|
|
565
589
|
})
|
|
566
590
|
);
|
|
567
591
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { IntegrationBase } = require('../../integration-base');
|
|
2
|
+
|
|
3
|
+
class DummyModule {
|
|
4
|
+
static definition = {
|
|
5
|
+
getName: () => 'dummy'
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class DummyIntegration extends IntegrationBase {
|
|
10
|
+
static Definition = {
|
|
11
|
+
name: 'dummy',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
modules: {
|
|
14
|
+
dummy: DummyModule
|
|
15
|
+
},
|
|
16
|
+
display: {
|
|
17
|
+
label: 'Dummy Integration',
|
|
18
|
+
description: 'A dummy integration for testing',
|
|
19
|
+
detailsUrl: 'https://example.com',
|
|
20
|
+
icon: 'dummy-icon'
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
static getOptionDetails() {
|
|
25
|
+
return {
|
|
26
|
+
name: this.Definition.name,
|
|
27
|
+
version: this.Definition.version,
|
|
28
|
+
display: this.Definition.display
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor(params) {
|
|
33
|
+
super(params);
|
|
34
|
+
this.sendSpy = jest.fn();
|
|
35
|
+
this.eventCallHistory = [];
|
|
36
|
+
this.events = {};
|
|
37
|
+
|
|
38
|
+
this.integrationRepository = {
|
|
39
|
+
updateIntegrationById: jest.fn().mockResolvedValue({}),
|
|
40
|
+
findIntegrationById: jest.fn().mockResolvedValue({}),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.updateIntegrationStatus = {
|
|
44
|
+
execute: jest.fn().mockResolvedValue({})
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.updateIntegrationMessages = {
|
|
48
|
+
execute: jest.fn().mockResolvedValue({})
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this.registerEventHandlers();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async loadDynamicUserActions() {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async registerEventHandlers() {
|
|
59
|
+
super.registerEventHandlers();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async send(event, data) {
|
|
64
|
+
this.sendSpy(event, data);
|
|
65
|
+
this.eventCallHistory.push({ event, data, timestamp: Date.now() });
|
|
66
|
+
return super.send(event, data);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async initialize() {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async onCreate({ integrationId }) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async onUpdate(params) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async onDelete(params) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getConfig() {
|
|
86
|
+
return this.config || {};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { DummyIntegration };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const { v4: uuid } = require('uuid');
|
|
2
|
+
|
|
3
|
+
class TestIntegrationRepository {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.store = new Map();
|
|
6
|
+
this.operationHistory = [];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async createIntegration(entities, userId, config) {
|
|
10
|
+
const id = uuid();
|
|
11
|
+
const record = {
|
|
12
|
+
id,
|
|
13
|
+
_id: id,
|
|
14
|
+
entitiesIds: entities,
|
|
15
|
+
userId: userId,
|
|
16
|
+
config,
|
|
17
|
+
version: '0.0.0',
|
|
18
|
+
status: 'NEW',
|
|
19
|
+
messages: {},
|
|
20
|
+
};
|
|
21
|
+
this.store.set(id, record);
|
|
22
|
+
this.operationHistory.push({ operation: 'create', id, userId, config });
|
|
23
|
+
return record;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async findIntegrationById(id) {
|
|
27
|
+
const rec = this.store.get(id);
|
|
28
|
+
this.operationHistory.push({ operation: 'findById', id, found: !!rec });
|
|
29
|
+
if (!rec) return null;
|
|
30
|
+
return rec;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async findIntegrationsByUserId(userId) {
|
|
34
|
+
const results = Array.from(this.store.values()).filter(r => r.userId === userId);
|
|
35
|
+
this.operationHistory.push({ operation: 'findByUserId', userId, count: results.length });
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async updateIntegrationMessages(id, type, title, body, timestamp) {
|
|
40
|
+
const rec = this.store.get(id);
|
|
41
|
+
if (!rec) {
|
|
42
|
+
this.operationHistory.push({ operation: 'updateMessages', id, success: false });
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (!rec.messages[type]) rec.messages[type] = [];
|
|
46
|
+
rec.messages[type].push({ title, message: body, timestamp });
|
|
47
|
+
this.operationHistory.push({ operation: 'updateMessages', id, type, success: true });
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async updateIntegrationConfig(id, config) {
|
|
52
|
+
const rec = this.store.get(id);
|
|
53
|
+
if (!rec) {
|
|
54
|
+
this.operationHistory.push({ operation: 'updateConfig', id, success: false });
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
rec.config = config;
|
|
58
|
+
this.operationHistory.push({ operation: 'updateConfig', id, success: true });
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async deleteIntegrationById(id) {
|
|
63
|
+
const existed = this.store.has(id);
|
|
64
|
+
const result = this.store.delete(id);
|
|
65
|
+
this.operationHistory.push({ operation: 'delete', id, existed, success: result });
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async updateIntegrationStatus(id, status) {
|
|
70
|
+
const rec = this.store.get(id);
|
|
71
|
+
if (rec) {
|
|
72
|
+
rec.status = status;
|
|
73
|
+
this.operationHistory.push({ operation: 'updateStatus', id, status, success: true });
|
|
74
|
+
} else {
|
|
75
|
+
this.operationHistory.push({ operation: 'updateStatus', id, status, success: false });
|
|
76
|
+
}
|
|
77
|
+
return !!rec;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getOperationHistory() {
|
|
81
|
+
return [...this.operationHistory];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
clearHistory() {
|
|
85
|
+
this.operationHistory = [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { TestIntegrationRepository };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const { CreateIntegration } = require('../../use-cases/create-integration');
|
|
2
|
+
const { TestIntegrationRepository } = require('../doubles/test-integration-repository');
|
|
3
|
+
const { TestModuleFactory } = require('../../../modules/tests/doubles/test-module-factory');
|
|
4
|
+
const { DummyIntegration } = require('../doubles/dummy-integration-class');
|
|
5
|
+
|
|
6
|
+
describe('CreateIntegration Use-Case', () => {
|
|
7
|
+
let integrationRepository;
|
|
8
|
+
let moduleFactory;
|
|
9
|
+
let useCase;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
integrationRepository = new TestIntegrationRepository();
|
|
13
|
+
moduleFactory = new TestModuleFactory();
|
|
14
|
+
useCase = new CreateIntegration({
|
|
15
|
+
integrationRepository,
|
|
16
|
+
integrationClasses: [DummyIntegration],
|
|
17
|
+
moduleFactory,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('happy path', () => {
|
|
22
|
+
it('creates an integration and returns DTO', async () => {
|
|
23
|
+
const entities = ['entity-1'];
|
|
24
|
+
const userId = 'user-1';
|
|
25
|
+
const config = { type: 'dummy', foo: 'bar' };
|
|
26
|
+
|
|
27
|
+
const dto = await useCase.execute(entities, userId, config);
|
|
28
|
+
|
|
29
|
+
expect(dto.id).toBeDefined();
|
|
30
|
+
expect(dto.config).toEqual(config);
|
|
31
|
+
expect(dto.userId).toBe(userId);
|
|
32
|
+
expect(dto.entities).toEqual(entities);
|
|
33
|
+
expect(dto.status).toBe('NEW');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('triggers ON_CREATE event with correct payload', async () => {
|
|
37
|
+
const entities = ['entity-1'];
|
|
38
|
+
const userId = 'user-1';
|
|
39
|
+
const config = { type: 'dummy', foo: 'bar' };
|
|
40
|
+
|
|
41
|
+
const dto = await useCase.execute(entities, userId, config);
|
|
42
|
+
|
|
43
|
+
const record = await integrationRepository.findIntegrationById(dto.id);
|
|
44
|
+
expect(record).toBeTruthy();
|
|
45
|
+
|
|
46
|
+
const history = integrationRepository.getOperationHistory();
|
|
47
|
+
const createOperation = history.find(op => op.operation === 'create');
|
|
48
|
+
expect(createOperation).toEqual({
|
|
49
|
+
operation: 'create',
|
|
50
|
+
id: dto.id,
|
|
51
|
+
userId,
|
|
52
|
+
config
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('loads modules for each entity', async () => {
|
|
57
|
+
const entities = ['entity-1', 'entity-2'];
|
|
58
|
+
const userId = 'user-1';
|
|
59
|
+
const config = { type: 'dummy' };
|
|
60
|
+
|
|
61
|
+
const dto = await useCase.execute(entities, userId, config);
|
|
62
|
+
|
|
63
|
+
expect(dto.entities).toEqual(entities);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('error cases', () => {
|
|
68
|
+
it('throws error when integration class is not found', async () => {
|
|
69
|
+
const entities = ['entity-1'];
|
|
70
|
+
const userId = 'user-1';
|
|
71
|
+
const config = { type: 'unknown-type' };
|
|
72
|
+
|
|
73
|
+
await expect(useCase.execute(entities, userId, config))
|
|
74
|
+
.rejects
|
|
75
|
+
.toThrow('No integration class found for type: unknown-type');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('throws error when no integration classes provided', async () => {
|
|
79
|
+
const useCaseWithoutClasses = new CreateIntegration({
|
|
80
|
+
integrationRepository,
|
|
81
|
+
integrationClasses: [],
|
|
82
|
+
moduleFactory,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const entities = ['entity-1'];
|
|
86
|
+
const userId = 'user-1';
|
|
87
|
+
const config = { type: 'dummy' };
|
|
88
|
+
|
|
89
|
+
await expect(useCaseWithoutClasses.execute(entities, userId, config))
|
|
90
|
+
.rejects
|
|
91
|
+
.toThrow('No integration class found for type: dummy');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('edge cases', () => {
|
|
96
|
+
it('handles empty entities array', async () => {
|
|
97
|
+
const entities = [];
|
|
98
|
+
const userId = 'user-1';
|
|
99
|
+
const config = { type: 'dummy' };
|
|
100
|
+
|
|
101
|
+
const dto = await useCase.execute(entities, userId, config);
|
|
102
|
+
|
|
103
|
+
expect(dto.entities).toEqual([]);
|
|
104
|
+
expect(dto.id).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('handles complex config objects', async () => {
|
|
108
|
+
const entities = ['entity-1'];
|
|
109
|
+
const userId = 'user-1';
|
|
110
|
+
const config = {
|
|
111
|
+
type: 'dummy',
|
|
112
|
+
nested: {
|
|
113
|
+
value: 123,
|
|
114
|
+
array: [1, 2, 3],
|
|
115
|
+
bool: true
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const dto = await useCase.execute(entities, userId, config);
|
|
120
|
+
|
|
121
|
+
expect(dto.config).toEqual(config);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|