@friggframework/core 2.0.0--canary.395.65f5f64.0 → 2.0.0--canary.398.24926ac.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +50 -931
  2. package/core/create-handler.js +0 -1
  3. package/database/models/WebsocketConnection.js +5 -0
  4. package/handlers/app-handler-helpers.js +3 -0
  5. package/handlers/backend-utils.js +44 -42
  6. package/handlers/routers/auth.js +14 -3
  7. package/handlers/routers/integration-defined-routers.js +5 -8
  8. package/handlers/routers/middleware/loadUser.js +15 -0
  9. package/handlers/routers/middleware/requireLoggedInUser.js +12 -0
  10. package/handlers/routers/user.js +5 -25
  11. package/handlers/workers/integration-defined-workers.js +3 -6
  12. package/index.js +16 -1
  13. package/integrations/create-frigg-backend.js +31 -0
  14. package/integrations/index.js +5 -0
  15. package/integrations/integration-base.js +46 -142
  16. package/integrations/integration-factory.js +251 -0
  17. package/integrations/integration-router.js +181 -303
  18. package/integrations/integration-user.js +144 -0
  19. package/integrations/options.js +1 -1
  20. package/integrations/test/integration-base.test.js +144 -0
  21. package/module-plugin/auther.js +393 -0
  22. package/module-plugin/entity-manager.js +70 -0
  23. package/{modules → module-plugin}/entity.js +0 -1
  24. package/{modules → module-plugin}/index.js +8 -0
  25. package/module-plugin/manager.js +169 -0
  26. package/module-plugin/module-factory.js +61 -0
  27. package/{modules → module-plugin}/test/mock-api/api.js +3 -8
  28. package/{modules → module-plugin}/test/mock-api/definition.js +8 -12
  29. package/package.json +5 -5
  30. package/syncs/sync.js +1 -0
  31. package/types/integrations/index.d.ts +6 -2
  32. package/types/module-plugin/index.d.ts +56 -4
  33. package/types/syncs/index.d.ts +2 -0
  34. package/credential/credential-repository.js +0 -56
  35. package/credential/use-cases/get-credential-for-user.js +0 -21
  36. package/credential/use-cases/update-authentication-status.js +0 -15
  37. package/handlers/app-definition-loader.js +0 -38
  38. package/integrations/integration-repository.js +0 -80
  39. package/integrations/tests/doubles/dummy-integration-class.js +0 -90
  40. package/integrations/tests/doubles/test-integration-repository.js +0 -89
  41. package/integrations/tests/use-cases/create-integration.test.js +0 -124
  42. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -143
  43. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -143
  44. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -169
  45. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -169
  46. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
  47. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
  48. package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
  49. package/integrations/tests/use-cases/update-integration.test.js +0 -134
  50. package/integrations/use-cases/create-integration.js +0 -71
  51. package/integrations/use-cases/delete-integration-for-user.js +0 -72
  52. package/integrations/use-cases/get-integration-for-user.js +0 -78
  53. package/integrations/use-cases/get-integration-instance-by-definition.js +0 -67
  54. package/integrations/use-cases/get-integration-instance.js +0 -82
  55. package/integrations/use-cases/get-integrations-for-user.js +0 -76
  56. package/integrations/use-cases/get-possible-integrations.js +0 -27
  57. package/integrations/use-cases/index.js +0 -11
  58. package/integrations/use-cases/update-integration-messages.js +0 -31
  59. package/integrations/use-cases/update-integration-status.js +0 -28
  60. package/integrations/use-cases/update-integration.js +0 -91
  61. package/integrations/utils/map-integration-dto.js +0 -36
  62. package/modules/module-factory.js +0 -54
  63. package/modules/module-repository.js +0 -107
  64. package/modules/module.js +0 -218
  65. package/modules/tests/doubles/test-module-factory.js +0 -16
  66. package/modules/tests/doubles/test-module-repository.js +0 -19
  67. package/modules/use-cases/get-entities-for-user.js +0 -32
  68. package/modules/use-cases/get-entity-options-by-id.js +0 -58
  69. package/modules/use-cases/get-entity-options-by-type.js +0 -34
  70. package/modules/use-cases/get-module-instance-from-type.js +0 -31
  71. package/modules/use-cases/get-module.js +0 -56
  72. package/modules/use-cases/process-authorization-callback.js +0 -108
  73. package/modules/use-cases/refresh-entity-options.js +0 -58
  74. package/modules/use-cases/test-module-auth.js +0 -54
  75. package/modules/utils/map-module-dto.js +0 -18
  76. package/user/tests/doubles/test-user-repository.js +0 -72
  77. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  78. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  79. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  80. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  81. package/user/tests/use-cases/login-user.test.js +0 -140
  82. package/user/use-cases/create-individual-user.js +0 -61
  83. package/user/use-cases/create-organization-user.js +0 -47
  84. package/user/use-cases/create-token-for-user-id.js +0 -30
  85. package/user/use-cases/get-user-from-bearer-token.js +0 -77
  86. package/user/use-cases/login-user.js +0 -122
  87. package/user/user-repository.js +0 -62
  88. package/user/user.js +0 -77
  89. /package/{modules → module-plugin}/ModuleConstants.js +0 -0
  90. /package/{modules → module-plugin}/credential.js +0 -0
  91. /package/{modules → module-plugin}/requester/api-key.js +0 -0
  92. /package/{modules → module-plugin}/requester/basic.js +0 -0
  93. /package/{modules → module-plugin}/requester/oauth-2.js +0 -0
  94. /package/{modules → module-plugin}/requester/requester.js +0 -0
  95. /package/{modules → module-plugin}/requester/requester.test.js +0 -0
  96. /package/{modules → module-plugin}/test/auther.test.js +0 -0
  97. /package/{modules → module-plugin}/test/mock-api/mocks/hubspot.js +0 -0
@@ -1,38 +0,0 @@
1
- const { findNearestBackendPackageJson } = require('@friggframework/core/utils');
2
- const path = require('node:path');
3
- const fs = require('fs-extra');
4
-
5
- /**
6
- * Loads the App definition from the nearest backend package
7
- * @function loadAppDefinition
8
- * @description Searches for the nearest backend package.json, loads the corresponding index.js file,
9
- * and extracts the application definition containing integrations and user configuration.
10
- * @returns {{integrations: Array<object>, userConfig: object | null}} An object containing the application definition.
11
- * @throws {Error} Throws error if backend package.json cannot be found.
12
- * @throws {Error} Throws error if index.js file cannot be found in the backend directory.
13
- * @example
14
- * const { integrations, userConfig } = loadAppDefinition();
15
- * console.log(`Found ${integrations.length} integrations`);
16
- */
17
- function loadAppDefinition() {
18
- const backendPath = findNearestBackendPackageJson();
19
- if (!backendPath) {
20
- throw new Error('Could not find backend package.json');
21
- }
22
-
23
- const backendDir = path.dirname(backendPath);
24
- const backendFilePath = path.join(backendDir, 'index.js');
25
- if (!fs.existsSync(backendFilePath)) {
26
- throw new Error('Could not find index.js');
27
- }
28
-
29
- const backendJsFile = require(backendFilePath);
30
- const appDefinition = backendJsFile.Definition;
31
-
32
- const { integrations = [], user: userConfig = null } = appDefinition;
33
- return { integrations, userConfig };
34
- }
35
-
36
- module.exports = {
37
- loadAppDefinition,
38
- };
@@ -1,80 +0,0 @@
1
- const { IntegrationModel } = require('./integration-model');
2
-
3
- class IntegrationRepository {
4
- async findIntegrationsByUserId(userId) {
5
- const integrationRecords = await IntegrationModel.find({ user: userId }, '', { lean: true }).populate('entities');
6
- return integrationRecords.map(integrationRecord => ({
7
- id: integrationRecord._id.toString(),
8
- entitiesIds: integrationRecord.entities.map(e => e._id),
9
- userId: integrationRecord.user.toString(),
10
- config: integrationRecord.config,
11
- version: integrationRecord.version,
12
- status: integrationRecord.status,
13
- messages: integrationRecord.messages,
14
- }));
15
- }
16
-
17
- async deleteIntegrationById(integrationId) {
18
- return IntegrationModel.deleteOne({ _id: integrationId });
19
- }
20
-
21
- async findIntegrationByName(name) {
22
- const integrationRecord = await IntegrationModel.findOne({ 'config.type': name }, '', { lean: true }).populate('entities');
23
- return {
24
- id: integrationRecord._id.toString(),
25
- entitiesIds: integrationRecord.entities.map(e => e._id),
26
- userId: integrationRecord.user.toString(),
27
- config: integrationRecord.config,
28
- version: integrationRecord.version,
29
- status: integrationRecord.status,
30
- messages: integrationRecord.messages,
31
- };
32
- }
33
-
34
- async findIntegrationById(id) {
35
- const integrationRecord = await IntegrationModel.findById(id, '', { lean: true }).populate('entities');
36
- return {
37
- id: integrationRecord._id.toString(),
38
- entitiesIds: integrationRecord.entities.map(e => e._id),
39
- userId: integrationRecord.user.toString(),
40
- config: integrationRecord.config,
41
- version: integrationRecord.version,
42
- status: integrationRecord.status,
43
- messages: integrationRecord.messages,
44
- }
45
- }
46
-
47
- async updateIntegrationStatus(integrationId, status) {
48
- const integrationRecord = await IntegrationModel.updateOne({ _id: integrationId }, { status });
49
- return integrationRecord.acknowledged;
50
- }
51
-
52
- async updateIntegrationMessages(integrationId, messageType, messageTitle, messageBody, messageTimestamp) {
53
- const integrationRecord = await IntegrationModel.updateOne(
54
- { _id: integrationId },
55
- { $push: { [`messages.${messageType}`]: { title: messageTitle, message: messageBody, timestamp: messageTimestamp } } }
56
- );
57
- return integrationRecord.acknowledged;
58
- }
59
-
60
- async createIntegration(entities, userId, config) {
61
- const integrationRecord = await IntegrationModel.create({
62
- entities: entities,
63
- user: userId,
64
- config,
65
- version: '0.0.0',
66
- });
67
-
68
- return {
69
- id: integrationRecord._id.toString(),
70
- entitiesIds: integrationRecord.entities.map(e => e._id),
71
- userId: integrationRecord.user.toString(),
72
- config: integrationRecord.config,
73
- version: integrationRecord.version,
74
- status: integrationRecord.status,
75
- messages: integrationRecord.messages,
76
- };
77
- }
78
- }
79
-
80
- module.exports = { IntegrationRepository };
@@ -1,90 +0,0 @@
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 };
@@ -1,89 +0,0 @@
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 };
@@ -1,124 +0,0 @@
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
- });
@@ -1,143 +0,0 @@
1
- const { DeleteIntegrationForUser } = require('../../use-cases/delete-integration-for-user');
2
- const { TestIntegrationRepository } = require('../doubles/test-integration-repository');
3
- const { DummyIntegration } = require('../doubles/dummy-integration-class');
4
-
5
- describe('DeleteIntegrationForUser Use-Case', () => {
6
- let integrationRepository;
7
- let useCase;
8
-
9
- beforeEach(() => {
10
- integrationRepository = new TestIntegrationRepository();
11
- useCase = new DeleteIntegrationForUser({
12
- integrationRepository,
13
- integrationClasses: [DummyIntegration],
14
- });
15
- });
16
-
17
- describe('happy path', () => {
18
- it('deletes integration successfully', async () => {
19
- const record = await integrationRepository.createIntegration(['e1'], 'user-1', { type: 'dummy' });
20
-
21
- await useCase.execute(record.id, 'user-1');
22
-
23
- const found = await integrationRepository.findIntegrationById(record.id);
24
- expect(found).toBeNull();
25
- });
26
-
27
- it('tracks delete operation', async () => {
28
- const record = await integrationRepository.createIntegration(['e1'], 'user-1', { type: 'dummy' });
29
- integrationRepository.clearHistory();
30
-
31
- await useCase.execute(record.id, 'user-1');
32
-
33
- const history = integrationRepository.getOperationHistory();
34
- const deleteOperation = history.find(op => op.operation === 'delete');
35
- expect(deleteOperation).toEqual({
36
- operation: 'delete',
37
- id: record.id,
38
- existed: true,
39
- success: true
40
- });
41
- });
42
-
43
- it('deletes integration with multiple entities', async () => {
44
- const record = await integrationRepository.createIntegration(['e1', 'e2', 'e3'], 'user-1', { type: 'dummy' });
45
-
46
- await useCase.execute(record.id, 'user-1');
47
-
48
- const found = await integrationRepository.findIntegrationById(record.id);
49
- expect(found).toBeNull();
50
- });
51
- });
52
-
53
- describe('error cases', () => {
54
- it('throws error when integration not found', async () => {
55
- const nonExistentId = 'non-existent-id';
56
-
57
- await expect(useCase.execute(nonExistentId, 'user-1'))
58
- .rejects
59
- .toThrow(`Integration with id of ${nonExistentId} does not exist`);
60
- });
61
-
62
- it('throws error when user does not own integration', async () => {
63
- const record = await integrationRepository.createIntegration(['e1'], 'user-1', { type: 'dummy' });
64
-
65
- await expect(useCase.execute(record.id, 'different-user'))
66
- .rejects
67
- .toThrow(`Integration ${record.id} does not belong to User different-user`);
68
- });
69
-
70
- it('throws error when integration class not found', async () => {
71
- const useCaseWithoutClasses = new DeleteIntegrationForUser({
72
- integrationRepository,
73
- integrationClasses: [],
74
- });
75
-
76
- const record = await integrationRepository.createIntegration(['e1'], 'user-1', { type: 'dummy' });
77
-
78
- await expect(useCaseWithoutClasses.execute(record.id, 'user-1'))
79
- .rejects
80
- .toThrow();
81
- });
82
-
83
- it('tracks failed delete operation for non-existent integration', async () => {
84
- const nonExistentId = 'non-existent-id';
85
- integrationRepository.clearHistory();
86
-
87
- try {
88
- await useCase.execute(nonExistentId, 'user-1');
89
- } catch (error) {
90
- const history = integrationRepository.getOperationHistory();
91
- const findOperation = history.find(op => op.operation === 'findById');
92
- expect(findOperation).toEqual({
93
- operation: 'findById',
94
- id: nonExistentId,
95
- found: false
96
- });
97
- }
98
- });
99
- });
100
-
101
- describe('edge cases', () => {
102
- it('handles deletion of already deleted integration', async () => {
103
- const record = await integrationRepository.createIntegration(['e1'], 'user-1', { type: 'dummy' });
104
-
105
- await useCase.execute(record.id, 'user-1');
106
-
107
- await expect(useCase.execute(record.id, 'user-1'))
108
- .rejects
109
- .toThrow(`Integration with id of ${record.id} does not exist`);
110
- });
111
-
112
- it('handles integration with complex config during deletion', async () => {
113
- const complexConfig = {
114
- type: 'dummy',
115
- settings: { nested: { deep: 'value' } },
116
- credentials: { encrypted: true }
117
- };
118
-
119
- const record = await integrationRepository.createIntegration(['e1'], 'user-1', complexConfig);
120
-
121
- await useCase.execute(record.id, 'user-1');
122
-
123
- const found = await integrationRepository.findIntegrationById(record.id);
124
- expect(found).toBeNull();
125
- });
126
-
127
- it('handles null userId gracefully', async () => {
128
- const record = await integrationRepository.createIntegration(['e1'], 'user-1', { type: 'dummy' });
129
-
130
- await expect(useCase.execute(record.id, null))
131
- .rejects
132
- .toThrow(`Integration ${record.id} does not belong to User null`);
133
- });
134
-
135
- it('handles undefined userId gracefully', async () => {
136
- const record = await integrationRepository.createIntegration(['e1'], 'user-1', { type: 'dummy' });
137
-
138
- await expect(useCase.execute(record.id, undefined))
139
- .rejects
140
- .toThrow(`Integration ${record.id} does not belong to User undefined`);
141
- });
142
- });
143
- });