@friggframework/core 2.0.0-next.44 → 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 (166) 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/WEBHOOKS.md +653 -0
  82. package/handlers/backend-utils.js +118 -3
  83. package/handlers/database-migration-handler.js +227 -0
  84. package/handlers/routers/auth.js +1 -1
  85. package/handlers/routers/db-migration.handler.js +29 -0
  86. package/handlers/routers/db-migration.js +256 -0
  87. package/handlers/routers/health.js +41 -6
  88. package/handlers/routers/integration-webhook-routers.js +67 -0
  89. package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
  90. package/handlers/workers/db-migration.js +352 -0
  91. package/index.js +28 -0
  92. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  93. package/integrations/integration-base.js +74 -3
  94. package/integrations/integration-router.js +60 -70
  95. package/integrations/repositories/integration-repository-interface.js +12 -0
  96. package/integrations/repositories/integration-repository-mongo.js +32 -0
  97. package/integrations/repositories/integration-repository-postgres.js +33 -0
  98. package/integrations/repositories/process-repository-postgres.js +43 -20
  99. package/integrations/tests/doubles/dummy-integration-class.js +1 -8
  100. package/integrations/tests/doubles/test-integration-repository.js +2 -2
  101. package/logs/logger.js +0 -4
  102. package/modules/entity.js +0 -1
  103. package/modules/repositories/module-repository-mongo.js +3 -12
  104. package/modules/repositories/module-repository-postgres.js +0 -11
  105. package/modules/repositories/module-repository.js +1 -12
  106. package/modules/use-cases/get-entity-options-by-id.js +1 -1
  107. package/modules/use-cases/get-module.js +1 -2
  108. package/modules/use-cases/refresh-entity-options.js +1 -1
  109. package/modules/use-cases/test-module-auth.js +1 -1
  110. package/package.json +82 -66
  111. package/prisma-mongodb/schema.prisma +21 -21
  112. package/prisma-postgresql/schema.prisma +15 -15
  113. package/queues/queuer-util.js +28 -15
  114. package/types/core/index.d.ts +2 -2
  115. package/types/module-plugin/index.d.ts +0 -2
  116. package/user/repositories/user-repository-mongo.js +53 -12
  117. package/user/repositories/user-repository-postgres.js +53 -14
  118. package/user/use-cases/authenticate-user.js +127 -0
  119. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  120. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  121. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  122. package/user/use-cases/login-user.js +1 -1
  123. package/user/user.js +18 -2
  124. package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
  125. package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
  126. package/websocket/repositories/websocket-connection-repository.js +11 -10
  127. package/application/commands/integration-commands.test.js +0 -123
  128. package/database/encryption/encryption-integration.test.js +0 -553
  129. package/database/encryption/encryption-schema-registry.test.js +0 -392
  130. package/database/encryption/field-encryption-service.test.js +0 -525
  131. package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
  132. package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
  133. package/database/encryption/postgres-relation-decryption.test.js +0 -245
  134. package/database/encryption/prisma-encryption-extension.test.js +0 -439
  135. package/errors/base-error.test.js +0 -32
  136. package/errors/fetch-error.test.js +0 -79
  137. package/errors/halt-error.test.js +0 -11
  138. package/errors/validation-errors.test.js +0 -120
  139. package/handlers/auth-flow.integration.test.js +0 -147
  140. package/handlers/integration-event-dispatcher.test.js +0 -141
  141. package/handlers/routers/health.test.js +0 -210
  142. package/integrations/tests/use-cases/create-integration.test.js +0 -131
  143. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
  144. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
  145. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
  146. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
  147. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
  148. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
  149. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
  150. package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
  151. package/integrations/tests/use-cases/update-integration.test.js +0 -141
  152. package/integrations/use-cases/create-process.test.js +0 -178
  153. package/integrations/use-cases/get-process.test.js +0 -190
  154. package/integrations/use-cases/load-integration-context-full.test.js +0 -329
  155. package/integrations/use-cases/load-integration-context.test.js +0 -114
  156. package/integrations/use-cases/update-process-metrics.test.js +0 -308
  157. package/integrations/use-cases/update-process-state.test.js +0 -256
  158. package/lambda/TimeoutCatcher.test.js +0 -68
  159. package/logs/logger.test.js +0 -76
  160. package/modules/module-hydration.test.js +0 -205
  161. package/modules/requester/requester.test.js +0 -28
  162. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  163. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  164. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  165. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  166. package/user/tests/use-cases/login-user.test.js +0 -140
@@ -1,147 +0,0 @@
1
- jest.mock('../database/config', () => ({
2
- DB_TYPE: 'mongodb',
3
- getDatabaseType: jest.fn(() => 'mongodb'),
4
- PRISMA_LOG_LEVEL: 'error,warn',
5
- PRISMA_QUERY_LOGGING: false,
6
- }));
7
-
8
- const { IntegrationEventDispatcher } = require('./integration-event-dispatcher');
9
- const { IntegrationBase } = require('../integrations/integration-base');
10
-
11
- class SimulatedAsanaIntegration extends IntegrationBase {
12
- static Definition = {
13
- name: 'asana',
14
- version: '1.0.0',
15
- modules: {},
16
- routes: [
17
- { path: '/auth', method: 'GET', event: 'AUTH_REQUEST' },
18
- { path: '/auth/redirect/:provider', method: 'GET', event: 'AUTH_REDIRECT' },
19
- { path: '/form', method: 'GET', event: 'LOAD_FORM' },
20
- ],
21
- };
22
-
23
- constructor(params = {}) {
24
- super(params);
25
- this.events = {
26
- AUTH_REQUEST: { handler: this.authRequest.bind(this) },
27
- AUTH_REDIRECT: { handler: this.authRedirect.bind(this) },
28
- LOAD_FORM: { handler: this.loadForm.bind(this) },
29
- };
30
- }
31
-
32
- async authRequest() {
33
- return {
34
- success: true,
35
- action: 'redirect',
36
- hydrated: this.isHydrated,
37
- };
38
- }
39
-
40
- async authRedirect({ req }) {
41
- const { code } = req.query || {};
42
- return {
43
- success: true,
44
- action: 'tokens_received',
45
- receivedCode: code,
46
- hydrated: this.isHydrated,
47
- };
48
- }
49
-
50
- async loadForm() {
51
- if (!this.isHydrated && SimulatedAsanaIntegration.testRecord) {
52
- this.setIntegrationRecord({
53
- record: SimulatedAsanaIntegration.testRecord.record,
54
- modules: SimulatedAsanaIntegration.testRecord.modules,
55
- });
56
- }
57
-
58
- this.assertHydrated('Integration not found - must authenticate first');
59
-
60
- return {
61
- success: true,
62
- form: {
63
- fields: ['field1', 'field2'],
64
- },
65
- integrationId: this.id,
66
- };
67
- }
68
- }
69
-
70
- describe('IntegrationEventDispatcher auth flow', () => {
71
- const createDispatcher = () =>
72
- new IntegrationEventDispatcher(new SimulatedAsanaIntegration());
73
-
74
- beforeEach(() => {
75
- SimulatedAsanaIntegration.testRecord = null;
76
- });
77
-
78
- it('handles auth request without hydration', async () => {
79
- const dispatcher = createDispatcher();
80
- const result = await dispatcher.dispatchHttp({
81
- event: 'AUTH_REQUEST',
82
- req: { params: { provider: 'asana' }, query: {} },
83
- res: {},
84
- next: jest.fn(),
85
- });
86
-
87
- expect(result).toEqual({ success: true, action: 'redirect', hydrated: false });
88
- });
89
-
90
- it('handles auth redirect without hydration', async () => {
91
- const dispatcher = createDispatcher();
92
- const result = await dispatcher.dispatchHttp({
93
- event: 'AUTH_REDIRECT',
94
- req: { params: { provider: 'asana' }, query: { code: 'abc123' } },
95
- res: {},
96
- next: jest.fn(),
97
- });
98
-
99
- expect(result).toEqual({
100
- success: true,
101
- action: 'tokens_received',
102
- receivedCode: 'abc123',
103
- hydrated: false,
104
- });
105
- });
106
-
107
- it('throws for protected routes when no record is loaded', async () => {
108
- const dispatcher = createDispatcher();
109
- await expect(
110
- dispatcher.dispatchHttp({
111
- event: 'LOAD_FORM',
112
- req: { query: {} },
113
- res: {},
114
- next: jest.fn(),
115
- })
116
- ).rejects.toThrow('Integration not found - must authenticate first');
117
- });
118
-
119
- it('allows handlers to hydrate explicitly before continuing', async () => {
120
- SimulatedAsanaIntegration.testRecord = {
121
- record: {
122
- id: 'integration-123',
123
- userId: 'user-456',
124
- config: { type: 'asana' },
125
- status: 'ENABLED',
126
- version: '1.0.0',
127
- messages: { errors: [], warnings: [] },
128
- entities: [],
129
- },
130
- modules: [],
131
- };
132
-
133
- const dispatcher = createDispatcher();
134
- const result = await dispatcher.dispatchHttp({
135
- event: 'LOAD_FORM',
136
- req: { query: {} },
137
- res: {},
138
- next: jest.fn(),
139
- });
140
-
141
- expect(result).toEqual({
142
- success: true,
143
- form: { fields: ['field1', 'field2'] },
144
- integrationId: 'integration-123',
145
- });
146
- });
147
- });
@@ -1,141 +0,0 @@
1
- jest.mock('../database/config', () => ({
2
- DB_TYPE: 'mongodb',
3
- getDatabaseType: jest.fn(() => 'mongodb'),
4
- PRISMA_LOG_LEVEL: 'error,warn',
5
- PRISMA_QUERY_LOGGING: false,
6
- }));
7
-
8
- const { IntegrationEventDispatcher } = require('./integration-event-dispatcher');
9
- const { IntegrationBase } = require('../integrations/integration-base');
10
-
11
- class TestIntegration extends IntegrationBase {
12
- static Definition = {
13
- name: 'test-integration',
14
- version: '1.0.0',
15
- modules: {},
16
- routes: [
17
- { path: '/auth', method: 'GET', event: 'AUTH_REQUEST' },
18
- { path: '/data', method: 'GET', event: 'LOAD_DATA' },
19
- { path: '/job', method: 'POST', event: 'TEST_EVENT' },
20
- { path: '/dynamic', method: 'GET', event: 'DYNAMIC_EVENT' },
21
- ],
22
- };
23
-
24
- constructor(params) {
25
- super(params);
26
- this.events = {
27
- AUTH_REQUEST: { handler: this.authRequest.bind(this) },
28
- LOAD_DATA: { handler: this.loadData.bind(this) },
29
- TEST_EVENT: { handler: this.testHandler.bind(this) },
30
- };
31
- }
32
-
33
- async authRequest() {
34
- TestIntegration.latestInstance = this;
35
- return {
36
- success: true,
37
- hydrated: this.isHydrated,
38
- };
39
- }
40
-
41
- async loadData() {
42
- this.assertHydrated('loadData requires hydration');
43
- return { success: true };
44
- }
45
-
46
- async testHandler({ data }) {
47
- TestIntegration.latestInstance = this;
48
- return { received: data };
49
- }
50
-
51
- async initialize() {
52
- this.events = {
53
- ...this.events,
54
- DYNAMIC_EVENT: { handler: this.dynamicHandler.bind(this) },
55
- };
56
- }
57
-
58
- async dynamicHandler() {
59
- TestIntegration.latestInstance = this;
60
- return { dynamic: true };
61
- }
62
- }
63
-
64
- describe('IntegrationEventDispatcher', () => {
65
- const createDispatcher = () =>
66
- new IntegrationEventDispatcher(new TestIntegration());
67
-
68
- beforeEach(() => {
69
- TestIntegration.latestInstance = null;
70
- });
71
-
72
- describe('dispatchHttp', () => {
73
- it('creates a stateless integration instance for HTTP events', async () => {
74
- const dispatcher = createDispatcher();
75
- const result = await dispatcher.dispatchHttp({
76
- event: 'AUTH_REQUEST',
77
- req: {},
78
- res: {},
79
- next: jest.fn(),
80
- });
81
-
82
- expect(result).toEqual({ success: true, hydrated: false });
83
- expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
84
- expect(TestIntegration.latestInstance.isHydrated).toBe(false);
85
- });
86
-
87
- it('calls initialize to register dynamic events', async () => {
88
- const dispatcher = createDispatcher();
89
- await dispatcher.integrationInstance.initialize();
90
- const result = await dispatcher.dispatchHttp({
91
- event: 'DYNAMIC_EVENT',
92
- req: {},
93
- res: {},
94
- next: jest.fn(),
95
- });
96
-
97
- expect(result).toEqual({ dynamic: true });
98
- expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
99
- });
100
-
101
- it('throws when requesting an unknown event', async () => {
102
- const dispatcher = createDispatcher();
103
- await expect(
104
- dispatcher.dispatchHttp({
105
- event: 'UNKNOWN',
106
- req: {},
107
- res: {},
108
- next: jest.fn(),
109
- })
110
- ).rejects.toThrow('Event UNKNOWN not registered for test-integration');
111
- });
112
-
113
- it('does not hydrate automatically for handlers that require data', async () => {
114
- const dispatcher = createDispatcher();
115
- await expect(
116
- dispatcher.dispatchHttp({
117
- event: 'LOAD_DATA',
118
- req: {},
119
- res: {},
120
- next: jest.fn(),
121
- })
122
- ).rejects.toThrow('loadData requires hydration');
123
- });
124
- });
125
-
126
- describe('dispatchJob', () => {
127
- it('creates a stateless integration instance for job events', async () => {
128
- const payload = { foo: 'bar' };
129
- const dispatcher = createDispatcher();
130
- const result = await dispatcher.dispatchJob({
131
- event: 'TEST_EVENT',
132
- data: payload,
133
- context: {},
134
- });
135
-
136
- expect(result).toEqual({ received: payload });
137
- expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
138
- expect(TestIntegration.latestInstance.isHydrated).toBe(false);
139
- });
140
- });
141
- });
@@ -1,210 +0,0 @@
1
- process.env.HEALTH_API_KEY = 'test-api-key';
2
-
3
- jest.mock('../../database/config', () => ({
4
- DB_TYPE: 'mongodb',
5
- getDatabaseType: jest.fn(() => 'mongodb'),
6
- PRISMA_LOG_LEVEL: 'error,warn',
7
- PRISMA_QUERY_LOGGING: false,
8
- }));
9
-
10
- jest.mock('mongoose', () => ({
11
- set: jest.fn(),
12
- connection: {
13
- readyState: 1,
14
- db: {
15
- admin: () => ({
16
- ping: jest.fn().mockResolvedValue(true)
17
- })
18
- }
19
- }
20
- }));
21
-
22
- jest.mock('./../backend-utils', () => ({
23
- moduleFactory: {
24
- moduleTypes: ['test-module', 'another-module']
25
- },
26
- integrationFactory: {
27
- integrationTypes: ['test-integration', 'another-integration']
28
- }
29
- }));
30
-
31
- jest.mock('./../app-handler-helpers', () => ({
32
- createAppHandler: jest.fn((name, router) => ({ name, router }))
33
- }));
34
-
35
- const { router } = require('./health');
36
- const mongoose = require('mongoose');
37
-
38
- const mockRequest = (path, headers = {}) => ({
39
- path,
40
- headers
41
- });
42
-
43
- const mockResponse = () => {
44
- const res = {};
45
- res.status = jest.fn().mockReturnValue(res);
46
- res.json = jest.fn().mockReturnValue(res);
47
- return res;
48
- };
49
-
50
- describe('Health Check Endpoints', () => {
51
- beforeEach(() => {
52
- mongoose.connection.readyState = 1;
53
- });
54
-
55
- describe('Middleware - validateApiKey', () => {
56
- it('should allow access to /health without authentication', async () => {
57
- expect(true).toBe(true);
58
- });
59
- });
60
-
61
- describe('GET /health', () => {
62
- it('should return basic health status', async () => {
63
- const req = mockRequest('/health');
64
- const res = mockResponse();
65
-
66
- const routeHandler = router.stack.find(layer =>
67
- layer.route && layer.route.path === '/health'
68
- ).route.stack[0].handle;
69
-
70
- await routeHandler(req, res);
71
-
72
- expect(res.status).toHaveBeenCalledWith(200);
73
- expect(res.json).toHaveBeenCalledWith({
74
- status: 'ok',
75
- timestamp: expect.any(String),
76
- service: 'frigg-core-api'
77
- });
78
- });
79
- });
80
-
81
- describe('GET /health/detailed', () => {
82
- it('should return detailed health status when healthy', async () => {
83
- const req = mockRequest('/health/detailed', { 'x-api-key': 'test-api-key' });
84
- const res = mockResponse();
85
-
86
- const originalPromiseAll = Promise.all;
87
- Promise.all = jest.fn().mockResolvedValue([
88
- { name: 'github', status: 'healthy', reachable: true, statusCode: 200, responseTime: 100 },
89
- { name: 'npm', status: 'healthy', reachable: true, statusCode: 200, responseTime: 150 }
90
- ]);
91
-
92
- const routeHandler = router.stack.find(layer =>
93
- layer.route && layer.route.path === '/health/detailed'
94
- ).route.stack[0].handle;
95
-
96
- await routeHandler(req, res);
97
-
98
- Promise.all = originalPromiseAll;
99
-
100
- expect(res.status).toHaveBeenCalledWith(200);
101
- expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
102
- status: 'healthy',
103
- service: 'frigg-core-api',
104
- timestamp: expect.any(String),
105
- checks: expect.objectContaining({
106
- database: expect.objectContaining({
107
- status: 'healthy',
108
- state: 'connected'
109
- }),
110
- integrations: expect.objectContaining({
111
- status: 'healthy'
112
- })
113
- }),
114
- responseTime: expect.any(Number)
115
- }));
116
-
117
- const response = res.json.mock.calls[0][0];
118
- expect(response).not.toHaveProperty('version');
119
- expect(response).not.toHaveProperty('uptime');
120
- expect(response.checks).not.toHaveProperty('memory');
121
- expect(response.checks.database).not.toHaveProperty('type');
122
- });
123
-
124
- it('should return 503 when database is disconnected', async () => {
125
- mongoose.connection.readyState = 0;
126
-
127
- const req = mockRequest('/health/detailed', { 'x-api-key': 'test-api-key' });
128
- const res = mockResponse();
129
-
130
- const originalPromiseAll = Promise.all;
131
- Promise.all = jest.fn().mockResolvedValue([
132
- { name: 'github', status: 'healthy', reachable: true, statusCode: 200, responseTime: 100 },
133
- { name: 'npm', status: 'healthy', reachable: true, statusCode: 200, responseTime: 150 }
134
- ]);
135
-
136
- const routeHandler = router.stack.find(layer =>
137
- layer.route && layer.route.path === '/health/detailed'
138
- ).route.stack[0].handle;
139
-
140
- await routeHandler(req, res);
141
-
142
- Promise.all = originalPromiseAll;
143
-
144
- expect(res.status).toHaveBeenCalledWith(503);
145
- expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
146
- status: 'unhealthy'
147
- }));
148
- });
149
- });
150
-
151
- describe('GET /health/live', () => {
152
- it('should return alive status', async () => {
153
- const req = mockRequest('/health/live', { 'x-api-key': 'test-api-key' });
154
- const res = mockResponse();
155
-
156
- const routeHandler = router.stack.find(layer =>
157
- layer.route && layer.route.path === '/health/live'
158
- ).route.stack[0].handle;
159
-
160
- routeHandler(req, res);
161
-
162
- expect(res.status).toHaveBeenCalledWith(200);
163
- expect(res.json).toHaveBeenCalledWith({
164
- status: 'alive',
165
- timestamp: expect.any(String)
166
- });
167
- });
168
- });
169
-
170
- describe('GET /health/ready', () => {
171
- it('should return ready when all checks pass', async () => {
172
- const req = mockRequest('/health/ready', { 'x-api-key': 'test-api-key' });
173
- const res = mockResponse();
174
-
175
- const routeHandler = router.stack.find(layer =>
176
- layer.route && layer.route.path === '/health/ready'
177
- ).route.stack[0].handle;
178
-
179
- await routeHandler(req, res);
180
-
181
- expect(res.status).toHaveBeenCalledWith(200);
182
- expect(res.json).toHaveBeenCalledWith({
183
- ready: true,
184
- timestamp: expect.any(String),
185
- checks: {
186
- database: true,
187
- modules: true
188
- }
189
- });
190
- });
191
-
192
- it('should return 503 when database is not connected', async () => {
193
- mongoose.connection.readyState = 0;
194
-
195
- const req = mockRequest('/health/ready', { 'x-api-key': 'test-api-key' });
196
- const res = mockResponse();
197
-
198
- const routeHandler = router.stack.find(layer =>
199
- layer.route && layer.route.path === '/health/ready'
200
- ).route.stack[0].handle;
201
-
202
- await routeHandler(req, res);
203
-
204
- expect(res.status).toHaveBeenCalledWith(503);
205
- expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
206
- ready: false
207
- }));
208
- });
209
- });
210
- });
@@ -1,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
- });