@friggframework/core 2.0.0--canary.461.61382d8.0 → 2.0.0--canary.461.3d6d8ad.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/database/use-cases/check-migration-status-use-case.js +81 -0
- 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 +336 -0
- package/generated/prisma-mongodb/index-browser.js +318 -0
- package/generated/prisma-mongodb/index.d.ts +22993 -0
- package/generated/prisma-mongodb/index.js +361 -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 +364 -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 +343 -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 +358 -0
- package/generated/prisma-postgresql/index-browser.js +340 -0
- package/generated/prisma-postgresql/index.d.ts +25171 -0
- package/generated/prisma-postgresql/index.js +383 -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 +347 -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 +365 -0
- package/handlers/routers/db-migration.js +52 -0
- package/package.json +5 -5
- package/application/commands/integration-commands.test.js +0 -123
- package/core/Worker.test.js +0 -159
- 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/database/repositories/migration-status-repository-s3.test.js +0 -158
- package/database/use-cases/check-encryption-health-use-case.test.js +0 -192
- package/database/use-cases/get-migration-status-use-case.test.js +0 -171
- package/database/use-cases/run-database-migration-use-case.test.js +0 -310
- package/database/use-cases/trigger-database-migration-use-case.test.js +0 -250
- package/database/utils/prisma-runner.test.js +0 -486
- package/encrypt/Cryptor.test.js +0 -144
- 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/db-migration.test.js +0 -51
- package/handlers/routers/health.test.js +0 -210
- package/handlers/routers/integration-webhook-routers.test.js +0 -126
- package/handlers/use-cases/check-integrations-health-use-case.test.js +0 -125
- package/handlers/webhook-flow.integration.test.js +0 -356
- package/handlers/workers/db-migration.test.js +0 -50
- package/handlers/workers/integration-defined-workers.test.js +0 -184
- package/integrations/tests/integration-router-multi-auth.test.js +0 -369
- 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/queues/queuer-util.test.js +0 -132
- 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-adopter-jwt.test.js +0 -113
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
- package/user/tests/use-cases/get-user-from-x-frigg-headers.test.js +0 -346
- 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
- package/websocket/repositories/websocket-connection-repository.test.js +0 -227
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const { LoadIntegrationContextUseCase } = require('./load-integration-context');
|
|
2
|
-
|
|
3
|
-
class FakeIntegration {}
|
|
4
|
-
FakeIntegration.Definition = {
|
|
5
|
-
name: 'fake',
|
|
6
|
-
modules: {},
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
describe('LoadIntegrationContextUseCase', () => {
|
|
10
|
-
it('throws when neither integrationId nor integrationRecord resolve to a record', async () => {
|
|
11
|
-
const integrationRepository = {
|
|
12
|
-
findIntegrationById: jest.fn().mockResolvedValue(null),
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const useCase = new LoadIntegrationContextUseCase({
|
|
16
|
-
integrationClass: FakeIntegration,
|
|
17
|
-
integrationRepository,
|
|
18
|
-
moduleRepository: { findEntitiesByIds: jest.fn() },
|
|
19
|
-
moduleFactory: { getModuleInstance: jest.fn() },
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
await expect(
|
|
23
|
-
useCase.execute({ integrationId: 'missing-id' })
|
|
24
|
-
).rejects.toMatchObject({
|
|
25
|
-
message: 'Integration record not found',
|
|
26
|
-
code: 'INTEGRATION_RECORD_NOT_FOUND',
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('returns record with empty entities/modules when no entity ids provided', async () => {
|
|
31
|
-
const integrationRecord = {
|
|
32
|
-
id: 'integration-1',
|
|
33
|
-
userId: 'user-1',
|
|
34
|
-
entitiesIds: [],
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const moduleRepository = { findEntitiesByIds: jest.fn() };
|
|
38
|
-
const moduleFactory = { getModuleInstance: jest.fn() };
|
|
39
|
-
|
|
40
|
-
const useCase = new LoadIntegrationContextUseCase({
|
|
41
|
-
integrationClass: FakeIntegration,
|
|
42
|
-
integrationRepository: {},
|
|
43
|
-
moduleRepository,
|
|
44
|
-
moduleFactory,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const result = await useCase.execute({ integrationRecord });
|
|
48
|
-
|
|
49
|
-
expect(result).toEqual({
|
|
50
|
-
record: {
|
|
51
|
-
...integrationRecord,
|
|
52
|
-
entities: [],
|
|
53
|
-
},
|
|
54
|
-
modules: [],
|
|
55
|
-
});
|
|
56
|
-
expect(moduleRepository.findEntitiesByIds).not.toHaveBeenCalled();
|
|
57
|
-
expect(moduleFactory.getModuleInstance).not.toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('hydrates modules and entities when entity ids are provided', async () => {
|
|
61
|
-
const integrationRecord = {
|
|
62
|
-
id: 'integration-2',
|
|
63
|
-
userId: 'user-2',
|
|
64
|
-
entitiesIds: ['entity-1', 'entity-2'],
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const entities = [
|
|
68
|
-
{ id: 'entity-1', name: 'First Entity' },
|
|
69
|
-
{ id: 'entity-2', name: 'Second Entity' },
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
const modules = [{ name: 'module-1' }, { name: 'module-2' }];
|
|
73
|
-
|
|
74
|
-
const moduleRepository = {
|
|
75
|
-
findEntitiesByIds: jest.fn().mockResolvedValue(entities),
|
|
76
|
-
};
|
|
77
|
-
const moduleFactory = {
|
|
78
|
-
getModuleInstance: jest
|
|
79
|
-
.fn()
|
|
80
|
-
.mockResolvedValueOnce(modules[0])
|
|
81
|
-
.mockResolvedValueOnce(modules[1]),
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const useCase = new LoadIntegrationContextUseCase({
|
|
85
|
-
integrationClass: FakeIntegration,
|
|
86
|
-
integrationRepository: {},
|
|
87
|
-
moduleRepository,
|
|
88
|
-
moduleFactory,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const result = await useCase.execute({ integrationRecord });
|
|
92
|
-
|
|
93
|
-
expect(moduleRepository.findEntitiesByIds).toHaveBeenCalledWith(
|
|
94
|
-
integrationRecord.entitiesIds
|
|
95
|
-
);
|
|
96
|
-
expect(moduleFactory.getModuleInstance).toHaveBeenNthCalledWith(
|
|
97
|
-
1,
|
|
98
|
-
'entity-1',
|
|
99
|
-
integrationRecord.userId
|
|
100
|
-
);
|
|
101
|
-
expect(moduleFactory.getModuleInstance).toHaveBeenNthCalledWith(
|
|
102
|
-
2,
|
|
103
|
-
'entity-2',
|
|
104
|
-
integrationRecord.userId
|
|
105
|
-
);
|
|
106
|
-
expect(result).toEqual({
|
|
107
|
-
record: {
|
|
108
|
-
...integrationRecord,
|
|
109
|
-
entities,
|
|
110
|
-
},
|
|
111
|
-
modules,
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UpdateProcessMetrics Use Case Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests metrics updates, aggregate calculations, and ETA computation.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { UpdateProcessMetrics } = require('./update-process-metrics');
|
|
8
|
-
|
|
9
|
-
describe('UpdateProcessMetrics', () => {
|
|
10
|
-
let updateProcessMetricsUseCase;
|
|
11
|
-
let mockProcessRepository;
|
|
12
|
-
let mockWebsocketService;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
mockProcessRepository = {
|
|
16
|
-
findById: jest.fn(),
|
|
17
|
-
update: jest.fn(),
|
|
18
|
-
};
|
|
19
|
-
mockWebsocketService = {
|
|
20
|
-
broadcast: jest.fn(),
|
|
21
|
-
};
|
|
22
|
-
updateProcessMetricsUseCase = new UpdateProcessMetrics({
|
|
23
|
-
processRepository: mockProcessRepository,
|
|
24
|
-
websocketService: mockWebsocketService,
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('constructor', () => {
|
|
29
|
-
it('should require processRepository', () => {
|
|
30
|
-
expect(() => new UpdateProcessMetrics({})).toThrow('processRepository is required');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should initialize with processRepository and optional websocketService', () => {
|
|
34
|
-
expect(updateProcessMetricsUseCase.processRepository).toBe(mockProcessRepository);
|
|
35
|
-
expect(updateProcessMetricsUseCase.websocketService).toBe(mockWebsocketService);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should work without websocketService', () => {
|
|
39
|
-
const useCase = new UpdateProcessMetrics({
|
|
40
|
-
processRepository: mockProcessRepository,
|
|
41
|
-
});
|
|
42
|
-
expect(useCase.websocketService).toBeUndefined();
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('execute', () => {
|
|
47
|
-
const processId = 'process-123';
|
|
48
|
-
const baseTime = new Date('2024-01-01T10:00:00Z');
|
|
49
|
-
|
|
50
|
-
const mockProcess = {
|
|
51
|
-
id: processId,
|
|
52
|
-
userId: 'user-456',
|
|
53
|
-
integrationId: 'integration-789',
|
|
54
|
-
name: 'test-sync',
|
|
55
|
-
type: 'CRM_SYNC',
|
|
56
|
-
state: 'PROCESSING_BATCHES',
|
|
57
|
-
context: {
|
|
58
|
-
syncType: 'INITIAL',
|
|
59
|
-
totalRecords: 1000,
|
|
60
|
-
processedRecords: 100,
|
|
61
|
-
startTime: baseTime.toISOString(),
|
|
62
|
-
},
|
|
63
|
-
results: {
|
|
64
|
-
aggregateData: {
|
|
65
|
-
totalSynced: 95,
|
|
66
|
-
totalFailed: 5,
|
|
67
|
-
duration: 30000, // 30 seconds
|
|
68
|
-
recordsPerSecond: 3.33,
|
|
69
|
-
errors: [
|
|
70
|
-
{ contactId: 'contact-1', error: 'Missing email', timestamp: '2024-01-01T10:00:30Z' }
|
|
71
|
-
],
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
createdAt: baseTime,
|
|
75
|
-
updatedAt: baseTime,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
beforeEach(() => {
|
|
79
|
-
// Mock current time to be 45 seconds after start
|
|
80
|
-
jest.useFakeTimers();
|
|
81
|
-
jest.setSystemTime(new Date(baseTime.getTime() + 45000));
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
afterEach(() => {
|
|
85
|
-
jest.useRealTimers();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should update metrics with new batch data', async () => {
|
|
89
|
-
const metricsUpdate = {
|
|
90
|
-
processed: 50,
|
|
91
|
-
success: 48,
|
|
92
|
-
errors: 2,
|
|
93
|
-
errorDetails: [
|
|
94
|
-
{ contactId: 'contact-2', error: 'Invalid phone', timestamp: '2024-01-01T10:00:45Z' }
|
|
95
|
-
],
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const expectedContext = {
|
|
99
|
-
...mockProcess.context,
|
|
100
|
-
processedRecords: 150, // 100 + 50
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const expectedResults = {
|
|
104
|
-
aggregateData: {
|
|
105
|
-
totalSynced: 143, // 95 + 48
|
|
106
|
-
totalFailed: 7, // 5 + 2
|
|
107
|
-
duration: 45000, // Current elapsed time
|
|
108
|
-
recordsPerSecond: 3.33, // 150 / 45
|
|
109
|
-
errors: [
|
|
110
|
-
{ contactId: 'contact-1', error: 'Missing email', timestamp: '2024-01-01T10:00:30Z' },
|
|
111
|
-
{ contactId: 'contact-2', error: 'Invalid phone', timestamp: '2024-01-01T10:00:45Z' }
|
|
112
|
-
],
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const updatedProcess = {
|
|
117
|
-
...mockProcess,
|
|
118
|
-
context: expectedContext,
|
|
119
|
-
results: expectedResults,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
123
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
124
|
-
|
|
125
|
-
const result = await updateProcessMetricsUseCase.execute(processId, metricsUpdate);
|
|
126
|
-
|
|
127
|
-
expect(mockProcessRepository.findById).toHaveBeenCalledWith(processId);
|
|
128
|
-
expect(mockProcessRepository.update).toHaveBeenCalledWith(processId, {
|
|
129
|
-
context: expectedContext,
|
|
130
|
-
results: expectedResults,
|
|
131
|
-
});
|
|
132
|
-
expect(result).toEqual(updatedProcess);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should calculate ETA when total records known', async () => {
|
|
136
|
-
const metricsUpdate = { processed: 100, success: 100, errors: 0 };
|
|
137
|
-
|
|
138
|
-
// With 850 remaining records and 3.33 records/sec, ETA should be ~255 seconds
|
|
139
|
-
const expectedETA = new Date(Date.now() + (850 / 3.33 * 1000));
|
|
140
|
-
|
|
141
|
-
const updatedProcess = {
|
|
142
|
-
...mockProcess,
|
|
143
|
-
context: {
|
|
144
|
-
...mockProcess.context,
|
|
145
|
-
processedRecords: 200,
|
|
146
|
-
estimatedCompletion: expectedETA.toISOString(),
|
|
147
|
-
},
|
|
148
|
-
results: {
|
|
149
|
-
aggregateData: {
|
|
150
|
-
totalSynced: 195,
|
|
151
|
-
totalFailed: 5,
|
|
152
|
-
duration: 45000,
|
|
153
|
-
recordsPerSecond: 4.44, // 200 / 45
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
159
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
160
|
-
|
|
161
|
-
const result = await updateProcessMetricsUseCase.execute(processId, metricsUpdate);
|
|
162
|
-
|
|
163
|
-
const updateCall = mockProcessRepository.update.mock.calls[0][1];
|
|
164
|
-
expect(updateCall.context.estimatedCompletion).toBeDefined();
|
|
165
|
-
expect(new Date(updateCall.context.estimatedCompletion)).toBeInstanceOf(Date);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('should limit error details to last 100', async () => {
|
|
169
|
-
// Create a process with 98 existing errors
|
|
170
|
-
const existingErrors = Array.from({ length: 98 }, (_, i) => ({
|
|
171
|
-
contactId: `contact-${i}`,
|
|
172
|
-
error: `Error ${i}`,
|
|
173
|
-
timestamp: new Date().toISOString(),
|
|
174
|
-
}));
|
|
175
|
-
|
|
176
|
-
const processWithManyErrors = {
|
|
177
|
-
...mockProcess,
|
|
178
|
-
results: {
|
|
179
|
-
aggregateData: {
|
|
180
|
-
totalSynced: 95,
|
|
181
|
-
totalFailed: 5,
|
|
182
|
-
duration: 30000,
|
|
183
|
-
recordsPerSecond: 3.33,
|
|
184
|
-
errors: existingErrors,
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const newErrors = Array.from({ length: 5 }, (_, i) => ({
|
|
190
|
-
contactId: `new-contact-${i}`,
|
|
191
|
-
error: `New error ${i}`,
|
|
192
|
-
timestamp: new Date().toISOString(),
|
|
193
|
-
}));
|
|
194
|
-
|
|
195
|
-
const metricsUpdate = {
|
|
196
|
-
processed: 5,
|
|
197
|
-
success: 0,
|
|
198
|
-
errors: 5,
|
|
199
|
-
errorDetails: newErrors,
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
mockProcessRepository.findById.mockResolvedValue(processWithManyErrors);
|
|
203
|
-
mockProcessRepository.update.mockResolvedValue({});
|
|
204
|
-
|
|
205
|
-
await updateProcessMetricsUseCase.execute(processId, metricsUpdate);
|
|
206
|
-
|
|
207
|
-
const updateCall = mockProcessRepository.update.mock.calls[0][1];
|
|
208
|
-
const errorCount = updateCall.results.aggregateData.errors.length;
|
|
209
|
-
expect(errorCount).toBe(100); // Should be limited to 100
|
|
210
|
-
expect(updateCall.results.aggregateData.errors[0]).toEqual(existingErrors[3]); // First 3 old errors dropped
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should handle process with no existing context', async () => {
|
|
214
|
-
const processWithNoContext = {
|
|
215
|
-
...mockProcess,
|
|
216
|
-
context: null,
|
|
217
|
-
results: null,
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const metricsUpdate = { processed: 10, success: 8, errors: 2 };
|
|
221
|
-
const updatedProcess = { ...processWithNoContext };
|
|
222
|
-
|
|
223
|
-
mockProcessRepository.findById.mockResolvedValue(processWithNoContext);
|
|
224
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
225
|
-
|
|
226
|
-
const result = await updateProcessMetricsUseCase.execute(processId, metricsUpdate);
|
|
227
|
-
|
|
228
|
-
const updateCall = mockProcessRepository.update.mock.calls[0][1];
|
|
229
|
-
expect(updateCall.context.processedRecords).toBe(10);
|
|
230
|
-
expect(updateCall.results.aggregateData.totalSynced).toBe(8);
|
|
231
|
-
expect(updateCall.results.aggregateData.totalFailed).toBe(2);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('should broadcast progress via WebSocket', async () => {
|
|
235
|
-
const metricsUpdate = { processed: 50, success: 48, errors: 2 };
|
|
236
|
-
const updatedProcess = { ...mockProcess };
|
|
237
|
-
|
|
238
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
239
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
240
|
-
|
|
241
|
-
await updateProcessMetricsUseCase.execute(processId, metricsUpdate);
|
|
242
|
-
|
|
243
|
-
expect(mockWebsocketService.broadcast).toHaveBeenCalledWith({
|
|
244
|
-
type: 'PROCESS_PROGRESS',
|
|
245
|
-
data: {
|
|
246
|
-
processId,
|
|
247
|
-
processName: mockProcess.name,
|
|
248
|
-
processType: mockProcess.type,
|
|
249
|
-
state: mockProcess.state,
|
|
250
|
-
processed: 150, // 100 + 50
|
|
251
|
-
total: 1000,
|
|
252
|
-
successCount: 143, // 95 + 48
|
|
253
|
-
errorCount: 7, // 5 + 2
|
|
254
|
-
recordsPerSecond: expect.any(Number),
|
|
255
|
-
estimatedCompletion: expect.any(String),
|
|
256
|
-
timestamp: expect.any(String),
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('should handle WebSocket broadcast errors gracefully', async () => {
|
|
262
|
-
const websocketError = new Error('WebSocket connection failed');
|
|
263
|
-
mockWebsocketService.broadcast.mockRejectedValue(websocketError);
|
|
264
|
-
|
|
265
|
-
const metricsUpdate = { processed: 10, success: 10, errors: 0 };
|
|
266
|
-
const updatedProcess = { ...mockProcess };
|
|
267
|
-
|
|
268
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
269
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
270
|
-
|
|
271
|
-
// Should not throw error even if WebSocket fails
|
|
272
|
-
const result = await updateProcessMetricsUseCase.execute(processId, metricsUpdate);
|
|
273
|
-
|
|
274
|
-
expect(result).toEqual(updatedProcess);
|
|
275
|
-
expect(mockWebsocketService.broadcast).toHaveBeenCalled();
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('should throw error if processId is missing', async () => {
|
|
279
|
-
await expect(updateProcessMetricsUseCase.execute('', {}))
|
|
280
|
-
.rejects.toThrow('processId must be a non-empty string');
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('should throw error if processId is not a string', async () => {
|
|
284
|
-
await expect(updateProcessMetricsUseCase.execute(123, {}))
|
|
285
|
-
.rejects.toThrow('processId must be a non-empty string');
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('should throw error if metricsUpdate is missing', async () => {
|
|
289
|
-
await expect(updateProcessMetricsUseCase.execute(processId, null))
|
|
290
|
-
.rejects.toThrow('metricsUpdate must be an object');
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it('should throw error if process not found', async () => {
|
|
294
|
-
mockProcessRepository.findById.mockResolvedValue(null);
|
|
295
|
-
|
|
296
|
-
await expect(updateProcessMetricsUseCase.execute(processId, {}))
|
|
297
|
-
.rejects.toThrow('Process not found: process-123');
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('should handle repository errors', async () => {
|
|
301
|
-
const repositoryError = new Error('Database connection failed');
|
|
302
|
-
mockProcessRepository.findById.mockRejectedValue(repositoryError);
|
|
303
|
-
|
|
304
|
-
await expect(updateProcessMetricsUseCase.execute(processId, {}))
|
|
305
|
-
.rejects.toThrow('Failed to update process metrics: Database connection failed');
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
});
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UpdateProcessState Use Case Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests state transitions and context updates.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { UpdateProcessState } = require('./update-process-state');
|
|
8
|
-
|
|
9
|
-
describe('UpdateProcessState', () => {
|
|
10
|
-
let updateProcessStateUseCase;
|
|
11
|
-
let mockProcessRepository;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
mockProcessRepository = {
|
|
15
|
-
findById: jest.fn(),
|
|
16
|
-
update: jest.fn(),
|
|
17
|
-
};
|
|
18
|
-
updateProcessStateUseCase = new UpdateProcessState({
|
|
19
|
-
processRepository: mockProcessRepository,
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('constructor', () => {
|
|
24
|
-
it('should require processRepository', () => {
|
|
25
|
-
expect(() => new UpdateProcessState({})).toThrow('processRepository is required');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should initialize with processRepository', () => {
|
|
29
|
-
expect(updateProcessStateUseCase.processRepository).toBe(mockProcessRepository);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('execute', () => {
|
|
34
|
-
const processId = 'process-123';
|
|
35
|
-
const mockProcess = {
|
|
36
|
-
id: processId,
|
|
37
|
-
userId: 'user-456',
|
|
38
|
-
integrationId: 'integration-789',
|
|
39
|
-
name: 'test-sync',
|
|
40
|
-
type: 'CRM_SYNC',
|
|
41
|
-
state: 'INITIALIZING',
|
|
42
|
-
context: {
|
|
43
|
-
syncType: 'INITIAL',
|
|
44
|
-
totalRecords: 100,
|
|
45
|
-
processedRecords: 0,
|
|
46
|
-
},
|
|
47
|
-
results: {
|
|
48
|
-
aggregateData: {
|
|
49
|
-
totalSynced: 0,
|
|
50
|
-
totalFailed: 0,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
createdAt: new Date(),
|
|
54
|
-
updatedAt: new Date(),
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
it('should update process state only', async () => {
|
|
58
|
-
const updatedProcess = { ...mockProcess, state: 'FETCHING_TOTAL' };
|
|
59
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
60
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
61
|
-
|
|
62
|
-
const result = await updateProcessStateUseCase.execute(processId, 'FETCHING_TOTAL');
|
|
63
|
-
|
|
64
|
-
expect(mockProcessRepository.findById).toHaveBeenCalledWith(processId);
|
|
65
|
-
expect(mockProcessRepository.update).toHaveBeenCalledWith(processId, {
|
|
66
|
-
state: 'FETCHING_TOTAL',
|
|
67
|
-
});
|
|
68
|
-
expect(result).toEqual(updatedProcess);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should update process state with context updates', async () => {
|
|
72
|
-
const contextUpdates = {
|
|
73
|
-
currentPage: 5,
|
|
74
|
-
pagination: { pageSize: 100, hasMore: true },
|
|
75
|
-
};
|
|
76
|
-
const expectedContext = {
|
|
77
|
-
...mockProcess.context,
|
|
78
|
-
...contextUpdates,
|
|
79
|
-
};
|
|
80
|
-
const updatedProcess = {
|
|
81
|
-
...mockProcess,
|
|
82
|
-
state: 'PROCESSING_BATCHES',
|
|
83
|
-
context: expectedContext,
|
|
84
|
-
};
|
|
85
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
86
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
87
|
-
|
|
88
|
-
const result = await updateProcessStateUseCase.execute(
|
|
89
|
-
processId,
|
|
90
|
-
'PROCESSING_BATCHES',
|
|
91
|
-
contextUpdates
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
expect(mockProcessRepository.update).toHaveBeenCalledWith(processId, {
|
|
95
|
-
state: 'PROCESSING_BATCHES',
|
|
96
|
-
context: expectedContext,
|
|
97
|
-
});
|
|
98
|
-
expect(result).toEqual(updatedProcess);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should merge context updates with existing context', async () => {
|
|
102
|
-
const contextUpdates = {
|
|
103
|
-
currentPage: 3,
|
|
104
|
-
// Should preserve existing context fields
|
|
105
|
-
};
|
|
106
|
-
const expectedContext = {
|
|
107
|
-
syncType: 'INITIAL',
|
|
108
|
-
totalRecords: 100,
|
|
109
|
-
processedRecords: 0,
|
|
110
|
-
currentPage: 3,
|
|
111
|
-
};
|
|
112
|
-
const updatedProcess = {
|
|
113
|
-
...mockProcess,
|
|
114
|
-
state: 'QUEUING_PAGES',
|
|
115
|
-
context: expectedContext,
|
|
116
|
-
};
|
|
117
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
118
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
119
|
-
|
|
120
|
-
const result = await updateProcessStateUseCase.execute(
|
|
121
|
-
processId,
|
|
122
|
-
'QUEUING_PAGES',
|
|
123
|
-
contextUpdates
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
expect(mockProcessRepository.update).toHaveBeenCalledWith(processId, {
|
|
127
|
-
state: 'QUEUING_PAGES',
|
|
128
|
-
context: expectedContext,
|
|
129
|
-
});
|
|
130
|
-
expect(result).toEqual(updatedProcess);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should handle process with empty context', async () => {
|
|
134
|
-
const processWithEmptyContext = { ...mockProcess, context: {} };
|
|
135
|
-
const contextUpdates = { newField: 'value' };
|
|
136
|
-
const expectedContext = { newField: 'value' };
|
|
137
|
-
const updatedProcess = {
|
|
138
|
-
...processWithEmptyContext,
|
|
139
|
-
state: 'COMPLETED',
|
|
140
|
-
context: expectedContext,
|
|
141
|
-
};
|
|
142
|
-
mockProcessRepository.findById.mockResolvedValue(processWithEmptyContext);
|
|
143
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
144
|
-
|
|
145
|
-
const result = await updateProcessStateUseCase.execute(
|
|
146
|
-
processId,
|
|
147
|
-
'COMPLETED',
|
|
148
|
-
contextUpdates
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
expect(mockProcessRepository.update).toHaveBeenCalledWith(processId, {
|
|
152
|
-
state: 'COMPLETED',
|
|
153
|
-
context: expectedContext,
|
|
154
|
-
});
|
|
155
|
-
expect(result).toEqual(updatedProcess);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should throw error if processId is missing', async () => {
|
|
159
|
-
await expect(updateProcessStateUseCase.execute('', 'NEW_STATE'))
|
|
160
|
-
.rejects.toThrow('processId must be a non-empty string');
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('should throw error if processId is not a string', async () => {
|
|
164
|
-
await expect(updateProcessStateUseCase.execute(123, 'NEW_STATE'))
|
|
165
|
-
.rejects.toThrow('processId must be a non-empty string');
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('should throw error if newState is missing', async () => {
|
|
169
|
-
await expect(updateProcessStateUseCase.execute(processId, ''))
|
|
170
|
-
.rejects.toThrow('newState must be a non-empty string');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should throw error if newState is not a string', async () => {
|
|
174
|
-
await expect(updateProcessStateUseCase.execute(processId, 123))
|
|
175
|
-
.rejects.toThrow('newState must be a non-empty string');
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should throw error if contextUpdates is not an object', async () => {
|
|
179
|
-
await expect(updateProcessStateUseCase.execute(processId, 'NEW_STATE', 'invalid'))
|
|
180
|
-
.rejects.toThrow('contextUpdates must be an object');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should throw error if process not found', async () => {
|
|
184
|
-
mockProcessRepository.findById.mockResolvedValue(null);
|
|
185
|
-
|
|
186
|
-
await expect(updateProcessStateUseCase.execute(processId, 'NEW_STATE'))
|
|
187
|
-
.rejects.toThrow('Process not found: process-123');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should handle repository errors during findById', async () => {
|
|
191
|
-
const findError = new Error('Database connection failed');
|
|
192
|
-
mockProcessRepository.findById.mockRejectedValue(findError);
|
|
193
|
-
|
|
194
|
-
await expect(updateProcessStateUseCase.execute(processId, 'NEW_STATE'))
|
|
195
|
-
.rejects.toThrow('Failed to update process state: Database connection failed');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('should handle repository errors during update', async () => {
|
|
199
|
-
const updateError = new Error('Update failed');
|
|
200
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
201
|
-
mockProcessRepository.update.mockRejectedValue(updateError);
|
|
202
|
-
|
|
203
|
-
await expect(updateProcessStateUseCase.execute(processId, 'NEW_STATE'))
|
|
204
|
-
.rejects.toThrow('Failed to update process state: Update failed');
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
describe('updateStateOnly', () => {
|
|
209
|
-
it('should call execute with empty context updates', async () => {
|
|
210
|
-
const processId = 'process-123';
|
|
211
|
-
const newState = 'COMPLETED';
|
|
212
|
-
const updatedProcess = { id: processId, state: newState };
|
|
213
|
-
|
|
214
|
-
jest.spyOn(updateProcessStateUseCase, 'execute').mockResolvedValue(updatedProcess);
|
|
215
|
-
|
|
216
|
-
const result = await updateProcessStateUseCase.updateStateOnly(processId, newState);
|
|
217
|
-
|
|
218
|
-
expect(updateProcessStateUseCase.execute).toHaveBeenCalledWith(processId, newState, {});
|
|
219
|
-
expect(result).toEqual(updatedProcess);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe('updateContextOnly', () => {
|
|
224
|
-
const processId = 'process-123';
|
|
225
|
-
const mockProcess = {
|
|
226
|
-
id: processId,
|
|
227
|
-
state: 'PROCESSING_BATCHES',
|
|
228
|
-
context: { existingField: 'value' },
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
it('should update context without changing state', async () => {
|
|
232
|
-
const contextUpdates = { newField: 'newValue' };
|
|
233
|
-
const expectedContext = { existingField: 'value', newField: 'newValue' };
|
|
234
|
-
const updatedProcess = {
|
|
235
|
-
...mockProcess,
|
|
236
|
-
context: expectedContext,
|
|
237
|
-
};
|
|
238
|
-
mockProcessRepository.findById.mockResolvedValue(mockProcess);
|
|
239
|
-
mockProcessRepository.update.mockResolvedValue(updatedProcess);
|
|
240
|
-
|
|
241
|
-
const result = await updateProcessStateUseCase.updateContextOnly(processId, contextUpdates);
|
|
242
|
-
|
|
243
|
-
expect(mockProcessRepository.update).toHaveBeenCalledWith(processId, {
|
|
244
|
-
context: expectedContext,
|
|
245
|
-
});
|
|
246
|
-
expect(result).toEqual(updatedProcess);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('should throw error if process not found', async () => {
|
|
250
|
-
mockProcessRepository.findById.mockResolvedValue(null);
|
|
251
|
-
|
|
252
|
-
await expect(updateProcessStateUseCase.updateContextOnly(processId, {}))
|
|
253
|
-
.rejects.toThrow('Process not found: process-123');
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
});
|