@friggframework/admin-scripts 2.0.0--canary.517.41839c5.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/LICENSE.md +9 -0
- package/index.js +66 -0
- package/package.json +53 -0
- package/src/adapters/__tests__/aws-scheduler-adapter.test.js +322 -0
- package/src/adapters/__tests__/local-scheduler-adapter.test.js +325 -0
- package/src/adapters/__tests__/scheduler-adapter-factory.test.js +257 -0
- package/src/adapters/__tests__/scheduler-adapter.test.js +103 -0
- package/src/adapters/aws-scheduler-adapter.js +138 -0
- package/src/adapters/local-scheduler-adapter.js +103 -0
- package/src/adapters/scheduler-adapter-factory.js +69 -0
- package/src/adapters/scheduler-adapter.js +64 -0
- package/src/application/__tests__/admin-frigg-commands.test.js +643 -0
- package/src/application/__tests__/admin-script-base.test.js +273 -0
- package/src/application/__tests__/dry-run-http-interceptor.test.js +313 -0
- package/src/application/__tests__/dry-run-repository-wrapper.test.js +257 -0
- package/src/application/__tests__/schedule-management-use-case.test.js +276 -0
- package/src/application/__tests__/script-factory.test.js +381 -0
- package/src/application/__tests__/script-runner.test.js +202 -0
- package/src/application/admin-frigg-commands.js +242 -0
- package/src/application/admin-script-base.js +138 -0
- package/src/application/dry-run-http-interceptor.js +296 -0
- package/src/application/dry-run-repository-wrapper.js +261 -0
- package/src/application/schedule-management-use-case.js +230 -0
- package/src/application/script-factory.js +161 -0
- package/src/application/script-runner.js +254 -0
- package/src/builtins/__tests__/integration-health-check.test.js +598 -0
- package/src/builtins/__tests__/oauth-token-refresh.test.js +344 -0
- package/src/builtins/index.js +28 -0
- package/src/builtins/integration-health-check.js +279 -0
- package/src/builtins/oauth-token-refresh.js +221 -0
- package/src/infrastructure/__tests__/admin-auth-middleware.test.js +148 -0
- package/src/infrastructure/__tests__/admin-script-router.test.js +701 -0
- package/src/infrastructure/admin-auth-middleware.js +49 -0
- package/src/infrastructure/admin-script-router.js +311 -0
- package/src/infrastructure/script-executor-handler.js +75 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
const { AdminFriggCommands, createAdminFriggCommands } = require('../admin-frigg-commands');
|
|
2
|
+
|
|
3
|
+
// Mock all repository factories
|
|
4
|
+
jest.mock('@friggframework/core/integrations/repositories/integration-repository-factory');
|
|
5
|
+
jest.mock('@friggframework/core/user/repositories/user-repository-factory');
|
|
6
|
+
jest.mock('@friggframework/core/modules/repositories/module-repository-factory');
|
|
7
|
+
jest.mock('@friggframework/core/credential/repositories/credential-repository-factory');
|
|
8
|
+
jest.mock('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
|
|
9
|
+
jest.mock('@friggframework/core/queues');
|
|
10
|
+
|
|
11
|
+
describe('AdminFriggCommands', () => {
|
|
12
|
+
let mockIntegrationRepo;
|
|
13
|
+
let mockUserRepo;
|
|
14
|
+
let mockModuleRepo;
|
|
15
|
+
let mockCredentialRepo;
|
|
16
|
+
let mockScriptExecutionRepo;
|
|
17
|
+
let mockQueuerUtil;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Reset all mocks
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
|
|
23
|
+
// Create mock repositories
|
|
24
|
+
mockIntegrationRepo = {
|
|
25
|
+
findIntegrations: jest.fn(),
|
|
26
|
+
findIntegrationById: jest.fn(),
|
|
27
|
+
findIntegrationsByUserId: jest.fn(),
|
|
28
|
+
updateIntegrationConfig: jest.fn(),
|
|
29
|
+
updateIntegrationStatus: jest.fn(),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
mockUserRepo = {
|
|
33
|
+
findIndividualUserById: jest.fn(),
|
|
34
|
+
findIndividualUserByAppUserId: jest.fn(),
|
|
35
|
+
findIndividualUserByUsername: jest.fn(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
mockModuleRepo = {
|
|
39
|
+
findEntity: jest.fn(),
|
|
40
|
+
findEntityById: jest.fn(),
|
|
41
|
+
findEntitiesByUserId: jest.fn(),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
mockCredentialRepo = {
|
|
45
|
+
findCredential: jest.fn(),
|
|
46
|
+
updateCredential: jest.fn(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
mockScriptExecutionRepo = {
|
|
50
|
+
appendExecutionLog: jest.fn().mockResolvedValue(undefined),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
mockQueuerUtil = {
|
|
54
|
+
send: jest.fn().mockResolvedValue(undefined),
|
|
55
|
+
batchSend: jest.fn().mockResolvedValue(undefined),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Mock factory functions
|
|
59
|
+
const { createIntegrationRepository } = require('@friggframework/core/integrations/repositories/integration-repository-factory');
|
|
60
|
+
const { createUserRepository } = require('@friggframework/core/user/repositories/user-repository-factory');
|
|
61
|
+
const { createModuleRepository } = require('@friggframework/core/modules/repositories/module-repository-factory');
|
|
62
|
+
const { createCredentialRepository } = require('@friggframework/core/credential/repositories/credential-repository-factory');
|
|
63
|
+
const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
|
|
64
|
+
const { QueuerUtil } = require('@friggframework/core/queues');
|
|
65
|
+
|
|
66
|
+
createIntegrationRepository.mockReturnValue(mockIntegrationRepo);
|
|
67
|
+
createUserRepository.mockReturnValue(mockUserRepo);
|
|
68
|
+
createModuleRepository.mockReturnValue(mockModuleRepo);
|
|
69
|
+
createCredentialRepository.mockReturnValue(mockCredentialRepo);
|
|
70
|
+
createScriptExecutionRepository.mockReturnValue(mockScriptExecutionRepo);
|
|
71
|
+
|
|
72
|
+
// Mock QueuerUtil methods
|
|
73
|
+
QueuerUtil.send = mockQueuerUtil.send;
|
|
74
|
+
QueuerUtil.batchSend = mockQueuerUtil.batchSend;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('Constructor', () => {
|
|
78
|
+
it('creates with executionId', () => {
|
|
79
|
+
const commands = new AdminFriggCommands({ executionId: 'exec_123' });
|
|
80
|
+
|
|
81
|
+
expect(commands.executionId).toBe('exec_123');
|
|
82
|
+
expect(commands.logs).toEqual([]);
|
|
83
|
+
expect(commands.integrationFactory).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('creates with integrationFactory', () => {
|
|
87
|
+
const mockFactory = { getInstanceFromIntegrationId: jest.fn() };
|
|
88
|
+
const commands = new AdminFriggCommands({ integrationFactory: mockFactory });
|
|
89
|
+
|
|
90
|
+
expect(commands.integrationFactory).toBe(mockFactory);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('creates without params (defaults)', () => {
|
|
94
|
+
const commands = new AdminFriggCommands();
|
|
95
|
+
|
|
96
|
+
expect(commands.executionId).toBeNull();
|
|
97
|
+
expect(commands.logs).toEqual([]);
|
|
98
|
+
expect(commands.integrationFactory).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('Lazy Repository Loading', () => {
|
|
103
|
+
it('creates integrationRepository on first access', () => {
|
|
104
|
+
const commands = new AdminFriggCommands();
|
|
105
|
+
const { createIntegrationRepository } = require('@friggframework/core/integrations/repositories/integration-repository-factory');
|
|
106
|
+
|
|
107
|
+
expect(createIntegrationRepository).not.toHaveBeenCalled();
|
|
108
|
+
|
|
109
|
+
const repo = commands.integrationRepository;
|
|
110
|
+
|
|
111
|
+
expect(createIntegrationRepository).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(repo).toBe(mockIntegrationRepo);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('returns same instance on subsequent access', () => {
|
|
116
|
+
const commands = new AdminFriggCommands();
|
|
117
|
+
|
|
118
|
+
const repo1 = commands.integrationRepository;
|
|
119
|
+
const repo2 = commands.integrationRepository;
|
|
120
|
+
|
|
121
|
+
expect(repo1).toBe(repo2);
|
|
122
|
+
expect(repo1).toBe(mockIntegrationRepo);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('creates userRepository on first access', () => {
|
|
126
|
+
const commands = new AdminFriggCommands();
|
|
127
|
+
const { createUserRepository } = require('@friggframework/core/user/repositories/user-repository-factory');
|
|
128
|
+
|
|
129
|
+
expect(createUserRepository).not.toHaveBeenCalled();
|
|
130
|
+
|
|
131
|
+
const repo = commands.userRepository;
|
|
132
|
+
|
|
133
|
+
expect(createUserRepository).toHaveBeenCalledTimes(1);
|
|
134
|
+
expect(repo).toBe(mockUserRepo);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('creates moduleRepository on first access', () => {
|
|
138
|
+
const commands = new AdminFriggCommands();
|
|
139
|
+
const { createModuleRepository } = require('@friggframework/core/modules/repositories/module-repository-factory');
|
|
140
|
+
|
|
141
|
+
expect(createModuleRepository).not.toHaveBeenCalled();
|
|
142
|
+
|
|
143
|
+
const repo = commands.moduleRepository;
|
|
144
|
+
|
|
145
|
+
expect(createModuleRepository).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(repo).toBe(mockModuleRepo);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('creates credentialRepository on first access', () => {
|
|
150
|
+
const commands = new AdminFriggCommands();
|
|
151
|
+
const { createCredentialRepository } = require('@friggframework/core/credential/repositories/credential-repository-factory');
|
|
152
|
+
|
|
153
|
+
expect(createCredentialRepository).not.toHaveBeenCalled();
|
|
154
|
+
|
|
155
|
+
const repo = commands.credentialRepository;
|
|
156
|
+
|
|
157
|
+
expect(createCredentialRepository).toHaveBeenCalledTimes(1);
|
|
158
|
+
expect(repo).toBe(mockCredentialRepo);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('creates scriptExecutionRepository on first access', () => {
|
|
162
|
+
const commands = new AdminFriggCommands();
|
|
163
|
+
const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
|
|
164
|
+
|
|
165
|
+
expect(createScriptExecutionRepository).not.toHaveBeenCalled();
|
|
166
|
+
|
|
167
|
+
const repo = commands.scriptExecutionRepository;
|
|
168
|
+
|
|
169
|
+
expect(createScriptExecutionRepository).toHaveBeenCalledTimes(1);
|
|
170
|
+
expect(repo).toBe(mockScriptExecutionRepo);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('Integration Queries', () => {
|
|
175
|
+
it('listIntegrations with userId filter calls findIntegrationsByUserId', async () => {
|
|
176
|
+
const commands = new AdminFriggCommands();
|
|
177
|
+
const mockIntegrations = [{ id: '1' }, { id: '2' }];
|
|
178
|
+
mockIntegrationRepo.findIntegrationsByUserId.mockResolvedValue(mockIntegrations);
|
|
179
|
+
|
|
180
|
+
const result = await commands.listIntegrations({ userId: 'user_123' });
|
|
181
|
+
|
|
182
|
+
expect(result).toEqual(mockIntegrations);
|
|
183
|
+
expect(mockIntegrationRepo.findIntegrationsByUserId).toHaveBeenCalledWith('user_123');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('listIntegrations without userId calls findIntegrations', async () => {
|
|
187
|
+
const commands = new AdminFriggCommands();
|
|
188
|
+
const mockIntegrations = [{ id: '1' }];
|
|
189
|
+
mockIntegrationRepo.findIntegrations.mockResolvedValue(mockIntegrations);
|
|
190
|
+
|
|
191
|
+
const result = await commands.listIntegrations({ status: 'active' });
|
|
192
|
+
|
|
193
|
+
expect(result).toEqual(mockIntegrations);
|
|
194
|
+
expect(mockIntegrationRepo.findIntegrations).toHaveBeenCalledWith({ status: 'active' });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('findIntegrationById calls repository', async () => {
|
|
198
|
+
const commands = new AdminFriggCommands();
|
|
199
|
+
const mockIntegration = { id: 'int_123', name: 'Test' };
|
|
200
|
+
mockIntegrationRepo.findIntegrationById.mockResolvedValue(mockIntegration);
|
|
201
|
+
|
|
202
|
+
const result = await commands.findIntegrationById('int_123');
|
|
203
|
+
|
|
204
|
+
expect(result).toEqual(mockIntegration);
|
|
205
|
+
expect(mockIntegrationRepo.findIntegrationById).toHaveBeenCalledWith('int_123');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('findIntegrationsByUserId calls repository', async () => {
|
|
209
|
+
const commands = new AdminFriggCommands();
|
|
210
|
+
const mockIntegrations = [{ id: '1' }, { id: '2' }];
|
|
211
|
+
mockIntegrationRepo.findIntegrationsByUserId.mockResolvedValue(mockIntegrations);
|
|
212
|
+
|
|
213
|
+
const result = await commands.findIntegrationsByUserId('user_123');
|
|
214
|
+
|
|
215
|
+
expect(result).toEqual(mockIntegrations);
|
|
216
|
+
expect(mockIntegrationRepo.findIntegrationsByUserId).toHaveBeenCalledWith('user_123');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('updateIntegrationConfig calls repository', async () => {
|
|
220
|
+
const commands = new AdminFriggCommands();
|
|
221
|
+
const newConfig = { setting: 'value' };
|
|
222
|
+
const updatedIntegration = { id: 'int_123', config: newConfig };
|
|
223
|
+
mockIntegrationRepo.updateIntegrationConfig.mockResolvedValue(updatedIntegration);
|
|
224
|
+
|
|
225
|
+
const result = await commands.updateIntegrationConfig('int_123', newConfig);
|
|
226
|
+
|
|
227
|
+
expect(result).toEqual(updatedIntegration);
|
|
228
|
+
expect(mockIntegrationRepo.updateIntegrationConfig).toHaveBeenCalledWith('int_123', newConfig);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('updateIntegrationStatus calls repository', async () => {
|
|
232
|
+
const commands = new AdminFriggCommands();
|
|
233
|
+
const updatedIntegration = { id: 'int_123', status: 'active' };
|
|
234
|
+
mockIntegrationRepo.updateIntegrationStatus.mockResolvedValue(updatedIntegration);
|
|
235
|
+
|
|
236
|
+
const result = await commands.updateIntegrationStatus('int_123', 'active');
|
|
237
|
+
|
|
238
|
+
expect(result).toEqual(updatedIntegration);
|
|
239
|
+
expect(mockIntegrationRepo.updateIntegrationStatus).toHaveBeenCalledWith('int_123', 'active');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('User Queries', () => {
|
|
244
|
+
it('findUserById calls repository', async () => {
|
|
245
|
+
const commands = new AdminFriggCommands();
|
|
246
|
+
const mockUser = { id: 'user_123', email: 'test@example.com' };
|
|
247
|
+
mockUserRepo.findIndividualUserById.mockResolvedValue(mockUser);
|
|
248
|
+
|
|
249
|
+
const result = await commands.findUserById('user_123');
|
|
250
|
+
|
|
251
|
+
expect(result).toEqual(mockUser);
|
|
252
|
+
expect(mockUserRepo.findIndividualUserById).toHaveBeenCalledWith('user_123');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('findUserByAppUserId calls repository', async () => {
|
|
256
|
+
const commands = new AdminFriggCommands();
|
|
257
|
+
const mockUser = { id: 'user_123', appUserId: 'app_456' };
|
|
258
|
+
mockUserRepo.findIndividualUserByAppUserId.mockResolvedValue(mockUser);
|
|
259
|
+
|
|
260
|
+
const result = await commands.findUserByAppUserId('app_456');
|
|
261
|
+
|
|
262
|
+
expect(result).toEqual(mockUser);
|
|
263
|
+
expect(mockUserRepo.findIndividualUserByAppUserId).toHaveBeenCalledWith('app_456');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('findUserByUsername calls repository', async () => {
|
|
267
|
+
const commands = new AdminFriggCommands();
|
|
268
|
+
const mockUser = { id: 'user_123', username: 'testuser' };
|
|
269
|
+
mockUserRepo.findIndividualUserByUsername.mockResolvedValue(mockUser);
|
|
270
|
+
|
|
271
|
+
const result = await commands.findUserByUsername('testuser');
|
|
272
|
+
|
|
273
|
+
expect(result).toEqual(mockUser);
|
|
274
|
+
expect(mockUserRepo.findIndividualUserByUsername).toHaveBeenCalledWith('testuser');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('Entity Queries', () => {
|
|
279
|
+
it('listEntities with userId filter calls findEntitiesByUserId', async () => {
|
|
280
|
+
const commands = new AdminFriggCommands();
|
|
281
|
+
const mockEntities = [{ id: 'ent_1' }, { id: 'ent_2' }];
|
|
282
|
+
mockModuleRepo.findEntitiesByUserId.mockResolvedValue(mockEntities);
|
|
283
|
+
|
|
284
|
+
const result = await commands.listEntities({ userId: 'user_123' });
|
|
285
|
+
|
|
286
|
+
expect(result).toEqual(mockEntities);
|
|
287
|
+
expect(mockModuleRepo.findEntitiesByUserId).toHaveBeenCalledWith('user_123');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('listEntities without userId calls findEntity', async () => {
|
|
291
|
+
const commands = new AdminFriggCommands();
|
|
292
|
+
const mockEntities = [{ id: 'ent_1' }];
|
|
293
|
+
mockModuleRepo.findEntity.mockResolvedValue(mockEntities);
|
|
294
|
+
|
|
295
|
+
const result = await commands.listEntities({ type: 'account' });
|
|
296
|
+
|
|
297
|
+
expect(result).toEqual(mockEntities);
|
|
298
|
+
expect(mockModuleRepo.findEntity).toHaveBeenCalledWith({ type: 'account' });
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('findEntityById calls repository', async () => {
|
|
302
|
+
const commands = new AdminFriggCommands();
|
|
303
|
+
const mockEntity = { id: 'ent_123', name: 'Test Entity' };
|
|
304
|
+
mockModuleRepo.findEntityById.mockResolvedValue(mockEntity);
|
|
305
|
+
|
|
306
|
+
const result = await commands.findEntityById('ent_123');
|
|
307
|
+
|
|
308
|
+
expect(result).toEqual(mockEntity);
|
|
309
|
+
expect(mockModuleRepo.findEntityById).toHaveBeenCalledWith('ent_123');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('Credential Queries', () => {
|
|
314
|
+
it('findCredential calls repository', async () => {
|
|
315
|
+
const commands = new AdminFriggCommands();
|
|
316
|
+
const mockCredential = { id: 'cred_123', userId: 'user_123' };
|
|
317
|
+
mockCredentialRepo.findCredential.mockResolvedValue(mockCredential);
|
|
318
|
+
|
|
319
|
+
const result = await commands.findCredential({ userId: 'user_123' });
|
|
320
|
+
|
|
321
|
+
expect(result).toEqual(mockCredential);
|
|
322
|
+
expect(mockCredentialRepo.findCredential).toHaveBeenCalledWith({ userId: 'user_123' });
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('updateCredential calls repository', async () => {
|
|
326
|
+
const commands = new AdminFriggCommands();
|
|
327
|
+
const updates = { data: { newToken: 'xyz' } };
|
|
328
|
+
const updatedCredential = { id: 'cred_123', ...updates };
|
|
329
|
+
mockCredentialRepo.updateCredential.mockResolvedValue(updatedCredential);
|
|
330
|
+
|
|
331
|
+
const result = await commands.updateCredential('cred_123', updates);
|
|
332
|
+
|
|
333
|
+
expect(result).toEqual(updatedCredential);
|
|
334
|
+
expect(mockCredentialRepo.updateCredential).toHaveBeenCalledWith('cred_123', updates);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('instantiate()', () => {
|
|
339
|
+
it('throws if no integrationFactory', async () => {
|
|
340
|
+
const commands = new AdminFriggCommands();
|
|
341
|
+
|
|
342
|
+
await expect(commands.instantiate('int_123')).rejects.toThrow(
|
|
343
|
+
'instantiate() requires integrationFactory. ' +
|
|
344
|
+
'Set Definition.config.requiresIntegrationFactory = true'
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('calls integrationFactory.getInstanceFromIntegrationId', async () => {
|
|
349
|
+
const mockInstance = { primary: { api: {} } };
|
|
350
|
+
const mockFactory = {
|
|
351
|
+
getInstanceFromIntegrationId: jest.fn().mockResolvedValue(mockInstance),
|
|
352
|
+
};
|
|
353
|
+
const commands = new AdminFriggCommands({ integrationFactory: mockFactory });
|
|
354
|
+
|
|
355
|
+
const result = await commands.instantiate('int_123');
|
|
356
|
+
|
|
357
|
+
expect(result).toEqual(mockInstance);
|
|
358
|
+
expect(mockFactory.getInstanceFromIntegrationId).toHaveBeenCalledWith({
|
|
359
|
+
integrationId: 'int_123',
|
|
360
|
+
_isAdminContext: true,
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('passes _isAdminContext: true', async () => {
|
|
365
|
+
const mockInstance = { primary: { api: {} } };
|
|
366
|
+
const mockFactory = {
|
|
367
|
+
getInstanceFromIntegrationId: jest.fn().mockResolvedValue(mockInstance),
|
|
368
|
+
};
|
|
369
|
+
const commands = new AdminFriggCommands({ integrationFactory: mockFactory });
|
|
370
|
+
|
|
371
|
+
await commands.instantiate('int_123');
|
|
372
|
+
|
|
373
|
+
const callArgs = mockFactory.getInstanceFromIntegrationId.mock.calls[0][0];
|
|
374
|
+
expect(callArgs._isAdminContext).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe('queueScript()', () => {
|
|
379
|
+
const originalEnv = process.env;
|
|
380
|
+
|
|
381
|
+
beforeEach(() => {
|
|
382
|
+
process.env = { ...originalEnv };
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
afterEach(() => {
|
|
386
|
+
process.env = originalEnv;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('throws if ADMIN_SCRIPT_QUEUE_URL not set', async () => {
|
|
390
|
+
delete process.env.ADMIN_SCRIPT_QUEUE_URL;
|
|
391
|
+
const commands = new AdminFriggCommands();
|
|
392
|
+
|
|
393
|
+
await expect(commands.queueScript('test-script', {})).rejects.toThrow(
|
|
394
|
+
'ADMIN_SCRIPT_QUEUE_URL environment variable not set'
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('calls QueuerUtil.send with correct params', async () => {
|
|
399
|
+
process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/123456789012/admin-scripts';
|
|
400
|
+
const commands = new AdminFriggCommands({ executionId: 'exec_123' });
|
|
401
|
+
const params = { integrationId: 'int_456' };
|
|
402
|
+
|
|
403
|
+
await commands.queueScript('test-script', params);
|
|
404
|
+
|
|
405
|
+
expect(mockQueuerUtil.send).toHaveBeenCalledWith(
|
|
406
|
+
{
|
|
407
|
+
scriptName: 'test-script',
|
|
408
|
+
trigger: 'QUEUE',
|
|
409
|
+
params: { integrationId: 'int_456' },
|
|
410
|
+
parentExecutionId: 'exec_123',
|
|
411
|
+
},
|
|
412
|
+
'https://sqs.us-east-1.amazonaws.com/123456789012/admin-scripts'
|
|
413
|
+
);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('includes parentExecutionId from constructor', async () => {
|
|
417
|
+
process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.example.com/queue';
|
|
418
|
+
const commands = new AdminFriggCommands({ executionId: 'exec_parent' });
|
|
419
|
+
|
|
420
|
+
await commands.queueScript('my-script', {});
|
|
421
|
+
|
|
422
|
+
const callArgs = mockQueuerUtil.send.mock.calls[0][0];
|
|
423
|
+
expect(callArgs.parentExecutionId).toBe('exec_parent');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('logs queuing operation', async () => {
|
|
427
|
+
process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.example.com/queue';
|
|
428
|
+
const commands = new AdminFriggCommands();
|
|
429
|
+
const params = { batchId: 'batch_1' };
|
|
430
|
+
|
|
431
|
+
await commands.queueScript('test-script', params);
|
|
432
|
+
|
|
433
|
+
const logs = commands.getLogs();
|
|
434
|
+
expect(logs).toHaveLength(1);
|
|
435
|
+
expect(logs[0].level).toBe('info');
|
|
436
|
+
expect(logs[0].message).toBe('Queued continuation for test-script');
|
|
437
|
+
expect(logs[0].data).toEqual({ params });
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
describe('queueScriptBatch()', () => {
|
|
442
|
+
const originalEnv = process.env;
|
|
443
|
+
|
|
444
|
+
beforeEach(() => {
|
|
445
|
+
process.env = { ...originalEnv };
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
afterEach(() => {
|
|
449
|
+
process.env = originalEnv;
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('throws if ADMIN_SCRIPT_QUEUE_URL not set', async () => {
|
|
453
|
+
delete process.env.ADMIN_SCRIPT_QUEUE_URL;
|
|
454
|
+
const commands = new AdminFriggCommands();
|
|
455
|
+
|
|
456
|
+
await expect(commands.queueScriptBatch([])).rejects.toThrow(
|
|
457
|
+
'ADMIN_SCRIPT_QUEUE_URL environment variable not set'
|
|
458
|
+
);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('calls QueuerUtil.batchSend', async () => {
|
|
462
|
+
process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.example.com/queue';
|
|
463
|
+
const commands = new AdminFriggCommands({ executionId: 'exec_123' });
|
|
464
|
+
const entries = [
|
|
465
|
+
{ scriptName: 'script-1', params: { id: '1' } },
|
|
466
|
+
{ scriptName: 'script-2', params: { id: '2' } },
|
|
467
|
+
];
|
|
468
|
+
|
|
469
|
+
await commands.queueScriptBatch(entries);
|
|
470
|
+
|
|
471
|
+
expect(mockQueuerUtil.batchSend).toHaveBeenCalledWith(
|
|
472
|
+
[
|
|
473
|
+
{
|
|
474
|
+
scriptName: 'script-1',
|
|
475
|
+
trigger: 'QUEUE',
|
|
476
|
+
params: { id: '1' },
|
|
477
|
+
parentExecutionId: 'exec_123',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
scriptName: 'script-2',
|
|
481
|
+
trigger: 'QUEUE',
|
|
482
|
+
params: { id: '2' },
|
|
483
|
+
parentExecutionId: 'exec_123',
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
'https://sqs.example.com/queue'
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('maps entries correctly', async () => {
|
|
491
|
+
process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.example.com/queue';
|
|
492
|
+
const commands = new AdminFriggCommands();
|
|
493
|
+
const entries = [
|
|
494
|
+
{ scriptName: 'test-script', params: { value: 'abc' } },
|
|
495
|
+
];
|
|
496
|
+
|
|
497
|
+
await commands.queueScriptBatch(entries);
|
|
498
|
+
|
|
499
|
+
const callArgs = mockQueuerUtil.batchSend.mock.calls[0][0];
|
|
500
|
+
expect(callArgs).toHaveLength(1);
|
|
501
|
+
expect(callArgs[0].scriptName).toBe('test-script');
|
|
502
|
+
expect(callArgs[0].params).toEqual({ value: 'abc' });
|
|
503
|
+
expect(callArgs[0].trigger).toBe('QUEUE');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('handles entries without params', async () => {
|
|
507
|
+
process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.example.com/queue';
|
|
508
|
+
const commands = new AdminFriggCommands();
|
|
509
|
+
const entries = [
|
|
510
|
+
{ scriptName: 'no-params-script' },
|
|
511
|
+
];
|
|
512
|
+
|
|
513
|
+
await commands.queueScriptBatch(entries);
|
|
514
|
+
|
|
515
|
+
const callArgs = mockQueuerUtil.batchSend.mock.calls[0][0];
|
|
516
|
+
expect(callArgs[0].params).toEqual({});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('logs batch queuing operation', async () => {
|
|
520
|
+
process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.example.com/queue';
|
|
521
|
+
const commands = new AdminFriggCommands();
|
|
522
|
+
const entries = [
|
|
523
|
+
{ scriptName: 'script-1', params: {} },
|
|
524
|
+
{ scriptName: 'script-2', params: {} },
|
|
525
|
+
{ scriptName: 'script-3', params: {} },
|
|
526
|
+
];
|
|
527
|
+
|
|
528
|
+
await commands.queueScriptBatch(entries);
|
|
529
|
+
|
|
530
|
+
const logs = commands.getLogs();
|
|
531
|
+
expect(logs).toHaveLength(1);
|
|
532
|
+
expect(logs[0].level).toBe('info');
|
|
533
|
+
expect(logs[0].message).toBe('Queued 3 script continuations');
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('Logging', () => {
|
|
538
|
+
it('log() adds entry to logs array', () => {
|
|
539
|
+
const commands = new AdminFriggCommands();
|
|
540
|
+
|
|
541
|
+
const entry = commands.log('info', 'Test message', { key: 'value' });
|
|
542
|
+
|
|
543
|
+
expect(entry.level).toBe('info');
|
|
544
|
+
expect(entry.message).toBe('Test message');
|
|
545
|
+
expect(entry.data).toEqual({ key: 'value' });
|
|
546
|
+
expect(entry.timestamp).toBeDefined();
|
|
547
|
+
expect(commands.logs).toHaveLength(1);
|
|
548
|
+
expect(commands.logs[0]).toBe(entry);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('log() persists if executionId set', async () => {
|
|
552
|
+
const commands = new AdminFriggCommands({ executionId: 'exec_123' });
|
|
553
|
+
// Force repository creation
|
|
554
|
+
commands.scriptExecutionRepository;
|
|
555
|
+
|
|
556
|
+
commands.log('warn', 'Warning message', { detail: 'xyz' });
|
|
557
|
+
|
|
558
|
+
// Give async operation a chance to execute
|
|
559
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
560
|
+
|
|
561
|
+
expect(mockScriptExecutionRepo.appendExecutionLog).toHaveBeenCalled();
|
|
562
|
+
const callArgs = mockScriptExecutionRepo.appendExecutionLog.mock.calls[0];
|
|
563
|
+
expect(callArgs[0]).toBe('exec_123');
|
|
564
|
+
expect(callArgs[1].level).toBe('warn');
|
|
565
|
+
expect(callArgs[1].message).toBe('Warning message');
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('log() does not persist if no executionId', async () => {
|
|
569
|
+
const commands = new AdminFriggCommands();
|
|
570
|
+
|
|
571
|
+
commands.log('info', 'Test');
|
|
572
|
+
|
|
573
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
574
|
+
|
|
575
|
+
expect(mockScriptExecutionRepo.appendExecutionLog).not.toHaveBeenCalled();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('log() handles persistence failure gracefully', async () => {
|
|
579
|
+
const commands = new AdminFriggCommands({ executionId: 'exec_123' });
|
|
580
|
+
// Force repository creation
|
|
581
|
+
commands.scriptExecutionRepository;
|
|
582
|
+
mockScriptExecutionRepo.appendExecutionLog.mockRejectedValue(new Error('DB Error'));
|
|
583
|
+
|
|
584
|
+
// Should not throw
|
|
585
|
+
expect(() => commands.log('error', 'Test error')).not.toThrow();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('getLogs() returns all logs', () => {
|
|
589
|
+
const commands = new AdminFriggCommands();
|
|
590
|
+
|
|
591
|
+
commands.log('info', 'First');
|
|
592
|
+
commands.log('warn', 'Second');
|
|
593
|
+
commands.log('error', 'Third');
|
|
594
|
+
|
|
595
|
+
const logs = commands.getLogs();
|
|
596
|
+
|
|
597
|
+
expect(logs).toHaveLength(3);
|
|
598
|
+
expect(logs[0].message).toBe('First');
|
|
599
|
+
expect(logs[1].message).toBe('Second');
|
|
600
|
+
expect(logs[2].message).toBe('Third');
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('clearLogs() clears logs array', () => {
|
|
604
|
+
const commands = new AdminFriggCommands();
|
|
605
|
+
|
|
606
|
+
commands.log('info', 'First');
|
|
607
|
+
commands.log('info', 'Second');
|
|
608
|
+
expect(commands.logs).toHaveLength(2);
|
|
609
|
+
|
|
610
|
+
commands.clearLogs();
|
|
611
|
+
|
|
612
|
+
expect(commands.logs).toHaveLength(0);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it('getExecutionId() returns executionId', () => {
|
|
616
|
+
const commands = new AdminFriggCommands({ executionId: 'exec_789' });
|
|
617
|
+
|
|
618
|
+
expect(commands.getExecutionId()).toBe('exec_789');
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('getExecutionId() returns null if not set', () => {
|
|
622
|
+
const commands = new AdminFriggCommands();
|
|
623
|
+
|
|
624
|
+
expect(commands.getExecutionId()).toBeNull();
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
describe('createAdminFriggCommands factory', () => {
|
|
629
|
+
it('creates AdminFriggCommands instance', () => {
|
|
630
|
+
const commands = createAdminFriggCommands({ executionId: 'exec_123' });
|
|
631
|
+
|
|
632
|
+
expect(commands).toBeInstanceOf(AdminFriggCommands);
|
|
633
|
+
expect(commands.executionId).toBe('exec_123');
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it('creates with default params', () => {
|
|
637
|
+
const commands = createAdminFriggCommands();
|
|
638
|
+
|
|
639
|
+
expect(commands).toBeInstanceOf(AdminFriggCommands);
|
|
640
|
+
expect(commands.executionId).toBeNull();
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
});
|