@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,381 @@
|
|
|
1
|
+
const {
|
|
2
|
+
ScriptFactory,
|
|
3
|
+
createScriptFactory,
|
|
4
|
+
getScriptFactory,
|
|
5
|
+
} = require('../script-factory');
|
|
6
|
+
const { AdminScriptBase } = require('../admin-script-base');
|
|
7
|
+
|
|
8
|
+
describe('ScriptFactory', () => {
|
|
9
|
+
let factory;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
factory = new ScriptFactory();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('register()', () => {
|
|
16
|
+
it('should register a script class', () => {
|
|
17
|
+
class TestScript extends AdminScriptBase {
|
|
18
|
+
static Definition = {
|
|
19
|
+
name: 'test-script',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
description: 'A test script',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
factory.register(TestScript);
|
|
26
|
+
|
|
27
|
+
expect(factory.has('test-script')).toBe(true);
|
|
28
|
+
expect(factory.size).toBe(1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should throw error if script class has no Definition', () => {
|
|
32
|
+
class InvalidScript {}
|
|
33
|
+
|
|
34
|
+
expect(() => factory.register(InvalidScript)).toThrow(
|
|
35
|
+
'Script class must have a static Definition property'
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should throw error if Definition has no name', () => {
|
|
40
|
+
class InvalidScript extends AdminScriptBase {
|
|
41
|
+
static Definition = {
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
description: 'No name',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect(() => factory.register(InvalidScript)).toThrow(
|
|
48
|
+
'Script Definition must have a name'
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should throw error if script name is already registered', () => {
|
|
53
|
+
class Script1 extends AdminScriptBase {
|
|
54
|
+
static Definition = {
|
|
55
|
+
name: 'duplicate',
|
|
56
|
+
version: '1.0.0',
|
|
57
|
+
description: 'First',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class Script2 extends AdminScriptBase {
|
|
62
|
+
static Definition = {
|
|
63
|
+
name: 'duplicate',
|
|
64
|
+
version: '2.0.0',
|
|
65
|
+
description: 'Second',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
factory.register(Script1);
|
|
70
|
+
|
|
71
|
+
expect(() => factory.register(Script2)).toThrow(
|
|
72
|
+
'Script "duplicate" is already registered'
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('registerAll()', () => {
|
|
78
|
+
it('should register multiple scripts', () => {
|
|
79
|
+
class Script1 extends AdminScriptBase {
|
|
80
|
+
static Definition = {
|
|
81
|
+
name: 'script-1',
|
|
82
|
+
version: '1.0.0',
|
|
83
|
+
description: 'First',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class Script2 extends AdminScriptBase {
|
|
88
|
+
static Definition = {
|
|
89
|
+
name: 'script-2',
|
|
90
|
+
version: '1.0.0',
|
|
91
|
+
description: 'Second',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class Script3 extends AdminScriptBase {
|
|
96
|
+
static Definition = {
|
|
97
|
+
name: 'script-3',
|
|
98
|
+
version: '1.0.0',
|
|
99
|
+
description: 'Third',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
factory.registerAll([Script1, Script2, Script3]);
|
|
104
|
+
|
|
105
|
+
expect(factory.size).toBe(3);
|
|
106
|
+
expect(factory.has('script-1')).toBe(true);
|
|
107
|
+
expect(factory.has('script-2')).toBe(true);
|
|
108
|
+
expect(factory.has('script-3')).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle empty array', () => {
|
|
112
|
+
factory.registerAll([]);
|
|
113
|
+
|
|
114
|
+
expect(factory.size).toBe(0);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('get()', () => {
|
|
119
|
+
it('should return registered script class', () => {
|
|
120
|
+
class TestScript extends AdminScriptBase {
|
|
121
|
+
static Definition = {
|
|
122
|
+
name: 'test',
|
|
123
|
+
version: '1.0.0',
|
|
124
|
+
description: 'Test',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
factory.register(TestScript);
|
|
129
|
+
|
|
130
|
+
const retrieved = factory.get('test');
|
|
131
|
+
|
|
132
|
+
expect(retrieved).toBe(TestScript);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should throw error if script not found', () => {
|
|
136
|
+
expect(() => factory.get('non-existent')).toThrow(
|
|
137
|
+
'Script "non-existent" not found'
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('has()', () => {
|
|
143
|
+
it('should return true for registered script', () => {
|
|
144
|
+
class TestScript extends AdminScriptBase {
|
|
145
|
+
static Definition = {
|
|
146
|
+
name: 'test',
|
|
147
|
+
version: '1.0.0',
|
|
148
|
+
description: 'Test',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
factory.register(TestScript);
|
|
153
|
+
|
|
154
|
+
expect(factory.has('test')).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should return false for non-registered script', () => {
|
|
158
|
+
expect(factory.has('non-existent')).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('getNames()', () => {
|
|
163
|
+
it('should return array of all registered script names', () => {
|
|
164
|
+
class Script1 extends AdminScriptBase {
|
|
165
|
+
static Definition = { name: 'script-1', version: '1.0.0', description: 'One' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class Script2 extends AdminScriptBase {
|
|
169
|
+
static Definition = { name: 'script-2', version: '1.0.0', description: 'Two' };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
factory.registerAll([Script1, Script2]);
|
|
173
|
+
|
|
174
|
+
const names = factory.getNames();
|
|
175
|
+
|
|
176
|
+
expect(names).toHaveLength(2);
|
|
177
|
+
expect(names).toContain('script-1');
|
|
178
|
+
expect(names).toContain('script-2');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should return empty array when no scripts registered', () => {
|
|
182
|
+
const names = factory.getNames();
|
|
183
|
+
|
|
184
|
+
expect(names).toEqual([]);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('getAll()', () => {
|
|
189
|
+
it('should return all scripts with their definitions', () => {
|
|
190
|
+
class Script1 extends AdminScriptBase {
|
|
191
|
+
static Definition = {
|
|
192
|
+
name: 'script-1',
|
|
193
|
+
version: '1.0.0',
|
|
194
|
+
description: 'First script',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
class Script2 extends AdminScriptBase {
|
|
199
|
+
static Definition = {
|
|
200
|
+
name: 'script-2',
|
|
201
|
+
version: '2.0.0',
|
|
202
|
+
description: 'Second script',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
factory.registerAll([Script1, Script2]);
|
|
207
|
+
|
|
208
|
+
const all = factory.getAll();
|
|
209
|
+
|
|
210
|
+
expect(all).toHaveLength(2);
|
|
211
|
+
|
|
212
|
+
const script1Entry = all.find((s) => s.name === 'script-1');
|
|
213
|
+
const script2Entry = all.find((s) => s.name === 'script-2');
|
|
214
|
+
|
|
215
|
+
expect(script1Entry.definition).toEqual(Script1.Definition);
|
|
216
|
+
expect(script2Entry.definition).toEqual(Script2.Definition);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should return empty array when no scripts registered', () => {
|
|
220
|
+
const all = factory.getAll();
|
|
221
|
+
|
|
222
|
+
expect(all).toEqual([]);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('createInstance()', () => {
|
|
227
|
+
it('should create an instance of registered script', () => {
|
|
228
|
+
class TestScript extends AdminScriptBase {
|
|
229
|
+
static Definition = {
|
|
230
|
+
name: 'test',
|
|
231
|
+
version: '1.0.0',
|
|
232
|
+
description: 'Test',
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
factory.register(TestScript);
|
|
237
|
+
|
|
238
|
+
const instance = factory.createInstance('test');
|
|
239
|
+
|
|
240
|
+
expect(instance).toBeInstanceOf(TestScript);
|
|
241
|
+
expect(instance).toBeInstanceOf(AdminScriptBase);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should pass params to constructor', () => {
|
|
245
|
+
class TestScript extends AdminScriptBase {
|
|
246
|
+
static Definition = {
|
|
247
|
+
name: 'test',
|
|
248
|
+
version: '1.0.0',
|
|
249
|
+
description: 'Test',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
factory.register(TestScript);
|
|
254
|
+
|
|
255
|
+
const mockFactory = { mock: true };
|
|
256
|
+
const instance = factory.createInstance('test', {
|
|
257
|
+
executionId: 'exec_123',
|
|
258
|
+
integrationFactory: mockFactory,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(instance.executionId).toBe('exec_123');
|
|
262
|
+
expect(instance.integrationFactory).toBe(mockFactory);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should throw error if script not found', () => {
|
|
266
|
+
expect(() => factory.createInstance('non-existent')).toThrow(
|
|
267
|
+
'Script "non-existent" not found'
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('clear()', () => {
|
|
273
|
+
it('should remove all registered scripts', () => {
|
|
274
|
+
class Script1 extends AdminScriptBase {
|
|
275
|
+
static Definition = { name: 'script-1', version: '1.0.0', description: 'One' };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
class Script2 extends AdminScriptBase {
|
|
279
|
+
static Definition = { name: 'script-2', version: '1.0.0', description: 'Two' };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
factory.registerAll([Script1, Script2]);
|
|
283
|
+
expect(factory.size).toBe(2);
|
|
284
|
+
|
|
285
|
+
factory.clear();
|
|
286
|
+
|
|
287
|
+
expect(factory.size).toBe(0);
|
|
288
|
+
expect(factory.has('script-1')).toBe(false);
|
|
289
|
+
expect(factory.has('script-2')).toBe(false);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('size property', () => {
|
|
294
|
+
it('should return count of registered scripts', () => {
|
|
295
|
+
expect(factory.size).toBe(0);
|
|
296
|
+
|
|
297
|
+
class Script1 extends AdminScriptBase {
|
|
298
|
+
static Definition = { name: 'script-1', version: '1.0.0', description: 'One' };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
factory.register(Script1);
|
|
302
|
+
expect(factory.size).toBe(1);
|
|
303
|
+
|
|
304
|
+
class Script2 extends AdminScriptBase {
|
|
305
|
+
static Definition = { name: 'script-2', version: '1.0.0', description: 'Two' };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
factory.register(Script2);
|
|
309
|
+
expect(factory.size).toBe(2);
|
|
310
|
+
|
|
311
|
+
factory.clear();
|
|
312
|
+
expect(factory.size).toBe(0);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe('Global factory functions', () => {
|
|
317
|
+
it('getScriptFactory() should return singleton instance', () => {
|
|
318
|
+
const factory1 = getScriptFactory();
|
|
319
|
+
const factory2 = getScriptFactory();
|
|
320
|
+
|
|
321
|
+
expect(factory1).toBe(factory2);
|
|
322
|
+
expect(factory1).toBeInstanceOf(ScriptFactory);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('createScriptFactory() should create new instance', () => {
|
|
326
|
+
const factory1 = createScriptFactory();
|
|
327
|
+
const factory2 = createScriptFactory();
|
|
328
|
+
|
|
329
|
+
expect(factory1).not.toBe(factory2);
|
|
330
|
+
expect(factory1).toBeInstanceOf(ScriptFactory);
|
|
331
|
+
expect(factory2).toBeInstanceOf(ScriptFactory);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('global factory should be independent from created instances', () => {
|
|
335
|
+
class TestScript extends AdminScriptBase {
|
|
336
|
+
static Definition = {
|
|
337
|
+
name: 'test',
|
|
338
|
+
version: '1.0.0',
|
|
339
|
+
description: 'Test',
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const customFactory = createScriptFactory();
|
|
344
|
+
customFactory.register(TestScript);
|
|
345
|
+
|
|
346
|
+
const globalFactory = getScriptFactory();
|
|
347
|
+
|
|
348
|
+
// Custom factory has the script
|
|
349
|
+
expect(customFactory.has('test')).toBe(true);
|
|
350
|
+
|
|
351
|
+
// Global factory doesn't (assuming it's empty or has different scripts)
|
|
352
|
+
// We can't make assumptions about global factory state in tests
|
|
353
|
+
// so we just verify they're different instances
|
|
354
|
+
expect(customFactory).not.toBe(globalFactory);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe('Exported AdminScriptBase', () => {
|
|
359
|
+
it('should export AdminScriptBase class', () => {
|
|
360
|
+
expect(AdminScriptBase).toBeDefined();
|
|
361
|
+
expect(typeof AdminScriptBase).toBe('function');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should be usable to create scripts', () => {
|
|
365
|
+
class MyScript extends AdminScriptBase {
|
|
366
|
+
static Definition = {
|
|
367
|
+
name: 'my-script',
|
|
368
|
+
version: '1.0.0',
|
|
369
|
+
description: 'My script',
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
async execute(frigg, params) {
|
|
373
|
+
return { success: true };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const script = new MyScript();
|
|
378
|
+
expect(script).toBeInstanceOf(AdminScriptBase);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const { ScriptRunner, createScriptRunner } = require('../script-runner');
|
|
2
|
+
const { ScriptFactory } = require('../script-factory');
|
|
3
|
+
const { AdminScriptBase } = require('../admin-script-base');
|
|
4
|
+
|
|
5
|
+
// Mock dependencies
|
|
6
|
+
jest.mock('../admin-frigg-commands');
|
|
7
|
+
jest.mock('@friggframework/core/application/commands/admin-script-commands');
|
|
8
|
+
|
|
9
|
+
const { createAdminFriggCommands } = require('../admin-frigg-commands');
|
|
10
|
+
const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
|
|
11
|
+
|
|
12
|
+
describe('ScriptRunner', () => {
|
|
13
|
+
let scriptFactory;
|
|
14
|
+
let mockCommands;
|
|
15
|
+
let mockFrigg;
|
|
16
|
+
let testScript;
|
|
17
|
+
|
|
18
|
+
class TestScript extends AdminScriptBase {
|
|
19
|
+
static Definition = {
|
|
20
|
+
name: 'test-script',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
description: 'Test script',
|
|
23
|
+
config: {
|
|
24
|
+
timeout: 300000,
|
|
25
|
+
maxRetries: 0,
|
|
26
|
+
requiresIntegrationFactory: false,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
async execute(frigg, params) {
|
|
31
|
+
return { success: true, params };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
scriptFactory = new ScriptFactory([TestScript]);
|
|
37
|
+
|
|
38
|
+
mockCommands = {
|
|
39
|
+
createScriptExecution: jest.fn(),
|
|
40
|
+
updateScriptExecutionStatus: jest.fn(),
|
|
41
|
+
completeScriptExecution: jest.fn(),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
mockFrigg = {
|
|
45
|
+
log: jest.fn(),
|
|
46
|
+
getExecutionId: jest.fn(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
createAdminScriptCommands.mockReturnValue(mockCommands);
|
|
50
|
+
createAdminFriggCommands.mockReturnValue(mockFrigg);
|
|
51
|
+
|
|
52
|
+
mockCommands.createScriptExecution.mockResolvedValue({
|
|
53
|
+
id: 'exec-123',
|
|
54
|
+
});
|
|
55
|
+
mockCommands.updateScriptExecutionStatus.mockResolvedValue({});
|
|
56
|
+
mockCommands.completeScriptExecution.mockResolvedValue({ success: true });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
jest.clearAllMocks();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('execute()', () => {
|
|
64
|
+
it('should execute script successfully', async () => {
|
|
65
|
+
const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
|
|
66
|
+
|
|
67
|
+
const result = await runner.execute('test-script', { foo: 'bar' }, {
|
|
68
|
+
trigger: 'MANUAL',
|
|
69
|
+
mode: 'async',
|
|
70
|
+
audit: { apiKeyName: 'test-key' },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result.status).toBe('COMPLETED');
|
|
74
|
+
expect(result.scriptName).toBe('test-script');
|
|
75
|
+
expect(result.output).toEqual({ success: true, params: { foo: 'bar' } });
|
|
76
|
+
expect(result.executionId).toBe('exec-123');
|
|
77
|
+
expect(result.metrics.durationMs).toBeGreaterThanOrEqual(0);
|
|
78
|
+
|
|
79
|
+
expect(mockCommands.createScriptExecution).toHaveBeenCalledWith({
|
|
80
|
+
scriptName: 'test-script',
|
|
81
|
+
scriptVersion: '1.0.0',
|
|
82
|
+
trigger: 'MANUAL',
|
|
83
|
+
mode: 'async',
|
|
84
|
+
input: { foo: 'bar' },
|
|
85
|
+
audit: { apiKeyName: 'test-key' },
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(mockCommands.updateScriptExecutionStatus).toHaveBeenCalledWith(
|
|
89
|
+
'exec-123',
|
|
90
|
+
'RUNNING'
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(mockCommands.completeScriptExecution).toHaveBeenCalledWith(
|
|
94
|
+
'exec-123',
|
|
95
|
+
expect.objectContaining({
|
|
96
|
+
status: 'COMPLETED',
|
|
97
|
+
output: { success: true, params: { foo: 'bar' } },
|
|
98
|
+
metrics: expect.objectContaining({
|
|
99
|
+
durationMs: expect.any(Number),
|
|
100
|
+
}),
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should handle script execution failure', async () => {
|
|
106
|
+
class FailingScript extends AdminScriptBase {
|
|
107
|
+
static Definition = {
|
|
108
|
+
name: 'failing-script',
|
|
109
|
+
version: '1.0.0',
|
|
110
|
+
description: 'Failing script',
|
|
111
|
+
config: { timeout: 300000, maxRetries: 0 },
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
async execute() {
|
|
115
|
+
throw new Error('Script failed');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
scriptFactory.register(FailingScript);
|
|
120
|
+
const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
|
|
121
|
+
|
|
122
|
+
const result = await runner.execute('failing-script', {}, {
|
|
123
|
+
trigger: 'MANUAL',
|
|
124
|
+
mode: 'sync',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(result.status).toBe('FAILED');
|
|
128
|
+
expect(result.scriptName).toBe('failing-script');
|
|
129
|
+
expect(result.error.message).toBe('Script failed');
|
|
130
|
+
|
|
131
|
+
expect(mockCommands.completeScriptExecution).toHaveBeenCalledWith(
|
|
132
|
+
'exec-123',
|
|
133
|
+
expect.objectContaining({
|
|
134
|
+
status: 'FAILED',
|
|
135
|
+
error: expect.objectContaining({
|
|
136
|
+
message: 'Script failed',
|
|
137
|
+
}),
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should throw error if integrationFactory required but not provided', async () => {
|
|
143
|
+
class IntegrationScript extends AdminScriptBase {
|
|
144
|
+
static Definition = {
|
|
145
|
+
name: 'integration-script',
|
|
146
|
+
version: '1.0.0',
|
|
147
|
+
description: 'Integration script',
|
|
148
|
+
config: {
|
|
149
|
+
requiresIntegrationFactory: true,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
async execute() {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
scriptFactory.register(IntegrationScript);
|
|
159
|
+
const runner = new ScriptRunner({
|
|
160
|
+
scriptFactory,
|
|
161
|
+
commands: mockCommands,
|
|
162
|
+
integrationFactory: null,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await expect(
|
|
166
|
+
runner.execute('integration-script', {}, { trigger: 'MANUAL' })
|
|
167
|
+
).rejects.toThrow(
|
|
168
|
+
'Script "integration-script" requires integrationFactory but none was provided'
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should reuse existing execution ID when provided', async () => {
|
|
173
|
+
const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
|
|
174
|
+
|
|
175
|
+
const result = await runner.execute('test-script', { foo: 'bar' }, {
|
|
176
|
+
trigger: 'QUEUE',
|
|
177
|
+
executionId: 'existing-exec-456',
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(result.executionId).toBe('existing-exec-456');
|
|
181
|
+
expect(mockCommands.createScriptExecution).not.toHaveBeenCalled();
|
|
182
|
+
expect(mockCommands.updateScriptExecutionStatus).toHaveBeenCalledWith(
|
|
183
|
+
'existing-exec-456',
|
|
184
|
+
'RUNNING'
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('createScriptRunner()', () => {
|
|
190
|
+
it('should create runner with default factory', () => {
|
|
191
|
+
const runner = createScriptRunner();
|
|
192
|
+
expect(runner).toBeInstanceOf(ScriptRunner);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should create runner with custom params', () => {
|
|
196
|
+
const customFactory = new ScriptFactory();
|
|
197
|
+
const runner = createScriptRunner({ scriptFactory: customFactory });
|
|
198
|
+
expect(runner).toBeInstanceOf(ScriptRunner);
|
|
199
|
+
expect(runner.scriptFactory).toBe(customFactory);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|