@friggframework/admin-scripts 2.0.0--canary.545.b4ca16d.0 → 2.0.0--canary.517.8eaf5df.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +277 -0
  2. package/index.js +23 -6
  3. package/package.json +6 -6
  4. package/src/adapters/__tests__/aws-scheduler-adapter.test.js +106 -45
  5. package/src/adapters/__tests__/local-scheduler-adapter.test.js +24 -10
  6. package/src/adapters/__tests__/scheduler-adapter-factory.test.js +30 -9
  7. package/src/adapters/__tests__/scheduler-adapter.test.js +6 -2
  8. package/src/adapters/aws-scheduler-adapter.js +55 -28
  9. package/src/adapters/local-scheduler-adapter.js +2 -2
  10. package/src/adapters/scheduler-adapter-factory.js +3 -1
  11. package/src/adapters/scheduler-adapter.js +9 -3
  12. package/src/application/__tests__/admin-frigg-commands.test.js +73 -30
  13. package/src/application/__tests__/admin-script-base.test.js +23 -6
  14. package/src/application/__tests__/script-factory.test.js +30 -6
  15. package/src/application/__tests__/script-runner.test.js +113 -24
  16. package/src/application/__tests__/validate-script-input.test.js +54 -15
  17. package/src/application/admin-frigg-commands.js +21 -9
  18. package/src/application/admin-script-base.js +3 -2
  19. package/src/application/script-factory.js +3 -1
  20. package/src/application/script-runner.js +90 -48
  21. package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +16 -7
  22. package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +9 -4
  23. package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +21 -10
  24. package/src/application/use-cases/delete-schedule-use-case.js +6 -4
  25. package/src/application/use-cases/get-effective-schedule-use-case.js +3 -1
  26. package/src/application/use-cases/index.js +3 -1
  27. package/src/application/use-cases/upsert-schedule-use-case.js +30 -11
  28. package/src/application/validate-script-input.js +10 -3
  29. package/src/builtins/__tests__/integration-health-check.test.js +232 -127
  30. package/src/builtins/__tests__/oauth-token-refresh.test.js +128 -75
  31. package/src/builtins/index.js +1 -4
  32. package/src/builtins/integration-health-check.js +63 -30
  33. package/src/builtins/oauth-token-refresh.js +64 -29
  34. package/src/infrastructure/__tests__/admin-auth-middleware.test.js +9 -3
  35. package/src/infrastructure/__tests__/admin-script-router.test.js +125 -52
  36. package/src/infrastructure/admin-auth-middleware.js +3 -1
  37. package/src/infrastructure/admin-script-router.js +129 -14
  38. package/src/infrastructure/bootstrap.js +87 -0
  39. package/src/infrastructure/script-executor-handler.js +119 -52
  40. package/src/application/__tests__/dry-run-http-interceptor.test.js +0 -313
  41. package/src/application/__tests__/dry-run-repository-wrapper.test.js +0 -257
  42. package/src/application/__tests__/schedule-management-use-case.test.js +0 -276
  43. package/src/application/dry-run-http-interceptor.js +0 -296
  44. package/src/application/dry-run-repository-wrapper.js +0 -261
  45. package/src/application/schedule-management-use-case.js +0 -230
@@ -19,19 +19,21 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
19
19
  integrationIds: {
20
20
  type: 'array',
21
21
  items: { type: 'string' },
22
- description: 'Specific integration IDs to refresh (optional, defaults to all)'
22
+ description:
23
+ 'Specific integration IDs to refresh (optional, defaults to all)',
23
24
  },
24
25
  expiryThresholdHours: {
25
26
  type: 'number',
26
27
  default: 24,
27
- description: 'Refresh tokens expiring within this many hours'
28
+ description:
29
+ 'Refresh tokens expiring within this many hours',
28
30
  },
29
31
  dryRun: {
30
32
  type: 'boolean',
31
33
  default: false,
32
- description: 'Preview without making changes'
33
- }
34
- }
34
+ description: 'Preview without making changes',
35
+ },
36
+ },
35
37
  },
36
38
 
37
39
  outputSchema: {
@@ -40,13 +42,12 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
40
42
  refreshed: { type: 'number' },
41
43
  failed: { type: 'number' },
42
44
  skipped: { type: 'number' },
43
- details: { type: 'array' }
44
- }
45
+ details: { type: 'array' },
46
+ },
45
47
  },
46
48
 
47
49
  config: {
48
50
  timeout: 600000, // 10 minutes
49
- maxRetries: 1,
50
51
  requireIntegrationInstance: true, // Needs to call external APIs
51
52
  },
52
53
 
@@ -60,27 +61,31 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
60
61
  const {
61
62
  integrationIds = null,
62
63
  expiryThresholdHours = 24,
63
- dryRun = false
64
+ dryRun = false,
64
65
  } = params;
65
66
 
66
67
  const results = {
67
68
  refreshed: 0,
68
69
  failed: 0,
69
70
  skipped: 0,
70
- details: []
71
+ details: [],
71
72
  };
72
73
 
73
74
  this.context.log('info', 'Starting OAuth token refresh', {
74
75
  expiryThresholdHours,
75
76
  dryRun,
76
- specificIds: integrationIds?.length || 'all'
77
+ specificIds: integrationIds?.length || 'all',
77
78
  });
78
79
 
79
80
  // Get integrations to check
80
81
  let integrations;
81
82
  if (integrationIds && integrationIds.length > 0) {
82
83
  integrations = await Promise.all(
83
- integrationIds.map(id => this.context.integrationRepository.findIntegrationById(id).catch(() => null))
84
+ integrationIds.map((id) =>
85
+ this.context.integrationRepository
86
+ .findIntegrationById(id)
87
+ .catch(() => null)
88
+ )
84
89
  );
85
90
  integrations = integrations.filter(Boolean);
86
91
  } else {
@@ -88,13 +93,16 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
88
93
  integrations = await this.getAllIntegrations();
89
94
  }
90
95
 
91
- this.context.log('info', `Found ${integrations.length} integrations to check`);
96
+ this.context.log(
97
+ 'info',
98
+ `Found ${integrations.length} integrations to check`
99
+ );
92
100
 
93
101
  for (const integration of integrations) {
94
102
  try {
95
103
  const detail = await this.processIntegration(integration, {
96
104
  expiryThresholdHours,
97
- dryRun
105
+ dryRun,
98
106
  });
99
107
 
100
108
  results.details.push(detail);
@@ -107,14 +115,18 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
107
115
  results.failed++;
108
116
  }
109
117
  } catch (error) {
110
- this.context.log('error', `Error processing integration ${integration.id}`, {
111
- error: error.message
112
- });
118
+ this.context.log(
119
+ 'error',
120
+ `Error processing integration ${integration.id}`,
121
+ {
122
+ error: error.message,
123
+ }
124
+ );
113
125
  results.failed++;
114
126
  results.details.push({
115
127
  integrationId: integration.id,
116
128
  action: 'failed',
117
- reason: error.message
129
+ reason: error.message,
118
130
  });
119
131
  }
120
132
  }
@@ -122,7 +134,7 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
122
134
  this.context.log('info', 'OAuth token refresh completed', {
123
135
  refreshed: results.refreshed,
124
136
  failed: results.failed,
125
- skipped: results.skipped
137
+ skipped: results.skipped,
126
138
  });
127
139
 
128
140
  return results;
@@ -138,15 +150,25 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
138
150
  const { expiryThresholdHours, dryRun } = options;
139
151
 
140
152
  // Check prerequisites
141
- const skipReason = this._checkRefreshPrerequisites(integration, expiryThresholdHours);
153
+ const skipReason = this._checkRefreshPrerequisites(
154
+ integration,
155
+ expiryThresholdHours
156
+ );
142
157
  if (skipReason) {
143
158
  return this._createResult(integration.id, 'skipped', skipReason);
144
159
  }
145
160
 
146
161
  // Handle dry run
147
162
  if (dryRun) {
148
- this.context.log('info', `[DRY RUN] Would refresh token for ${integration.id}`);
149
- return this._createResult(integration.id, 'skipped', 'Dry run - would have refreshed');
163
+ this.context.log(
164
+ 'info',
165
+ `[DRY RUN] Would refresh token for ${integration.id}`
166
+ );
167
+ return this._createResult(
168
+ integration.id,
169
+ 'skipped',
170
+ 'Dry run - would have refreshed'
171
+ );
150
172
  }
151
173
 
152
174
  // Perform refresh
@@ -169,7 +191,9 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
169
191
  }
170
192
 
171
193
  const expiryTime = new Date(expiresAt);
172
- const thresholdTime = new Date(Date.now() + (expiryThresholdHours * 60 * 60 * 1000));
194
+ const thresholdTime = new Date(
195
+ Date.now() + expiryThresholdHours * 60 * 60 * 1000
196
+ );
173
197
 
174
198
  if (expiryTime > thresholdTime) {
175
199
  return 'Token not near expiry';
@@ -189,21 +213,32 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
189
213
  const instance = await this.context.instantiate(integration.id);
190
214
 
191
215
  if (!instance.primary?.api?.refreshAccessToken) {
192
- return this._createResult(integration.id, 'skipped', 'API does not support token refresh');
216
+ return this._createResult(
217
+ integration.id,
218
+ 'skipped',
219
+ 'API does not support token refresh'
220
+ );
193
221
  }
194
222
 
195
223
  await instance.primary.api.refreshAccessToken();
196
- this.context.log('info', `Refreshed token for integration ${integration.id}`);
224
+ this.context.log(
225
+ 'info',
226
+ `Refreshed token for integration ${integration.id}`
227
+ );
197
228
 
198
229
  return {
199
230
  integrationId: integration.id,
200
231
  action: 'refreshed',
201
- previousExpiry: expiresAt
232
+ previousExpiry: expiresAt,
202
233
  };
203
234
  } catch (error) {
204
- this.context.log('error', `Failed to refresh token for ${integration.id}`, {
205
- error: error.message
206
- });
235
+ this.context.log(
236
+ 'error',
237
+ `Failed to refresh token for ${integration.id}`,
238
+ {
239
+ error: error.message,
240
+ }
241
+ );
207
242
  return this._createResult(integration.id, 'failed', error.message);
208
243
  }
209
244
  }
@@ -1,5 +1,9 @@
1
+ const crypto = require('node:crypto');
1
2
  const { validateAdminApiKey } = require('../admin-auth-middleware');
2
3
 
4
+ // Generated at runtime so no credential-like literal is committed
5
+ const TEST_ADMIN_KEY = crypto.randomBytes(16).toString('hex');
6
+
3
7
  describe('validateAdminApiKey', () => {
4
8
  let mockReq;
5
9
  let mockRes;
@@ -8,7 +12,7 @@ describe('validateAdminApiKey', () => {
8
12
 
9
13
  beforeEach(() => {
10
14
  originalEnv = process.env.ADMIN_API_KEY;
11
- process.env.ADMIN_API_KEY = 'test-admin-key-123';
15
+ process.env.ADMIN_API_KEY = TEST_ADMIN_KEY;
12
16
 
13
17
  mockReq = {
14
18
  headers: {},
@@ -61,7 +65,9 @@ describe('validateAdminApiKey', () => {
61
65
 
62
66
  describe('API key validation', () => {
63
67
  it('should reject request with invalid API key', () => {
64
- mockReq.headers['x-frigg-admin-api-key'] = 'invalid-key';
68
+ mockReq.headers[
69
+ 'x-frigg-admin-api-key'
70
+ ] = `${TEST_ADMIN_KEY}-wrong`;
65
71
 
66
72
  validateAdminApiKey(mockReq, mockRes, mockNext);
67
73
 
@@ -74,7 +80,7 @@ describe('validateAdminApiKey', () => {
74
80
  });
75
81
 
76
82
  it('should accept request with valid API key', () => {
77
- mockReq.headers['x-frigg-admin-api-key'] = 'test-admin-key-123';
83
+ mockReq.headers['x-frigg-admin-api-key'] = TEST_ADMIN_KEY;
78
84
 
79
85
  validateAdminApiKey(mockReq, mockRes, mockNext);
80
86
 
@@ -15,12 +15,19 @@ jest.mock('../../application/script-runner');
15
15
  jest.mock('@friggframework/core/application/commands/admin-script-commands');
16
16
  jest.mock('@friggframework/core/queues');
17
17
  jest.mock('../../adapters/scheduler-adapter-factory');
18
+ jest.mock('../bootstrap', () => ({
19
+ bootstrapAdminScripts: () => ({ integrationFactory: {} }),
20
+ }));
18
21
 
19
22
  const { getScriptFactory } = require('../../application/script-factory');
20
23
  const { createScriptRunner } = require('../../application/script-runner');
21
- const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
24
+ const {
25
+ createAdminScriptCommands,
26
+ } = require('@friggframework/core/application/commands/admin-script-commands');
22
27
  const { QueuerUtil } = require('@friggframework/core/queues');
23
- const { createSchedulerAdapter } = require('../../adapters/scheduler-adapter-factory');
28
+ const {
29
+ createSchedulerAdapter,
30
+ } = require('../../adapters/scheduler-adapter-factory');
24
31
 
25
32
  describe('Admin Script Router', () => {
26
33
  let mockFactory;
@@ -56,7 +63,7 @@ describe('Admin Script Router', () => {
56
63
  mockCommands = {
57
64
  createAdminProcess: jest.fn(),
58
65
  findAdminProcessById: jest.fn(),
59
- findRecentExecutions: jest.fn(),
66
+ findAdminProcessesByName: jest.fn(),
60
67
  };
61
68
 
62
69
  mockSchedulerAdapter = {
@@ -118,7 +125,9 @@ describe('Admin Script Router', () => {
118
125
 
119
126
  describe('GET /admin/scripts/:scriptName', () => {
120
127
  it('should return script details', async () => {
121
- const response = await request(app).get('/admin/scripts/test-script');
128
+ const response = await request(app).get(
129
+ '/admin/scripts/test-script'
130
+ );
122
131
 
123
132
  expect(response.status).toBe(200);
124
133
  expect(response.body.name).toBe('test-script');
@@ -169,7 +178,8 @@ describe('Admin Script Router', () => {
169
178
  });
170
179
 
171
180
  it('should queue script for async execution', async () => {
172
- process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/123/test-queue';
181
+ process.env.ADMIN_SCRIPT_QUEUE_URL =
182
+ 'https://sqs.us-east-1.amazonaws.com/123/test-queue';
173
183
  mockCommands.createAdminProcess.mockResolvedValue({
174
184
  id: 'exec-456',
175
185
  });
@@ -195,7 +205,8 @@ describe('Admin Script Router', () => {
195
205
  });
196
206
 
197
207
  it('should default to async mode', async () => {
198
- process.env.ADMIN_SCRIPT_QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/123/test-queue';
208
+ process.env.ADMIN_SCRIPT_QUEUE_URL =
209
+ 'https://sqs.us-east-1.amazonaws.com/123/test-queue';
199
210
  mockCommands.createAdminProcess.mockResolvedValue({
200
211
  id: 'exec-789',
201
212
  });
@@ -247,7 +258,9 @@ describe('Admin Script Router', () => {
247
258
  status: 'COMPLETED',
248
259
  });
249
260
 
250
- const response = await request(app).get('/admin/scripts/test-script/executions/exec-123');
261
+ const response = await request(app).get(
262
+ '/admin/scripts/test-script/executions/exec-123'
263
+ );
251
264
 
252
265
  expect(response.status).toBe(200);
253
266
  expect(response.body.id).toBe('exec-123');
@@ -272,33 +285,39 @@ describe('Admin Script Router', () => {
272
285
 
273
286
  describe('GET /admin/scripts/:scriptName/executions', () => {
274
287
  it('should list executions for specific script', async () => {
275
- mockCommands.findRecentExecutions.mockResolvedValue([
276
- { id: 'exec-1', scriptName: 'test-script', status: 'COMPLETED' },
277
- { id: 'exec-2', scriptName: 'test-script', status: 'RUNNING' },
288
+ mockCommands.findAdminProcessesByName.mockResolvedValue([
289
+ { id: 'exec-1', name: 'test-script', state: 'COMPLETED' },
290
+ { id: 'exec-2', name: 'test-script', state: 'RUNNING' },
278
291
  ]);
279
292
 
280
- const response = await request(app).get('/admin/scripts/test-script/executions');
293
+ const response = await request(app).get(
294
+ '/admin/scripts/test-script/executions'
295
+ );
281
296
 
282
297
  expect(response.status).toBe(200);
283
298
  expect(response.body.executions).toHaveLength(2);
284
- expect(mockCommands.findRecentExecutions).toHaveBeenCalledWith({
285
- scriptName: 'test-script',
286
- limit: 50,
287
- });
299
+ expect(mockCommands.findAdminProcessesByName).toHaveBeenCalledWith(
300
+ 'test-script',
301
+ {
302
+ limit: 50,
303
+ }
304
+ );
288
305
  });
289
306
 
290
- it('should accept query parameters', async () => {
291
- mockCommands.findRecentExecutions.mockResolvedValue([]);
307
+ it('should accept query parameters (status maps to state, limit is bounded)', async () => {
308
+ mockCommands.findAdminProcessesByName.mockResolvedValue([]);
292
309
 
293
310
  await request(app).get(
294
311
  '/admin/scripts/test-script/executions?status=COMPLETED&limit=10'
295
312
  );
296
313
 
297
- expect(mockCommands.findRecentExecutions).toHaveBeenCalledWith({
298
- scriptName: 'test-script',
299
- status: 'COMPLETED',
300
- limit: 10,
301
- });
314
+ expect(mockCommands.findAdminProcessesByName).toHaveBeenCalledWith(
315
+ 'test-script',
316
+ {
317
+ limit: 10,
318
+ state: 'COMPLETED',
319
+ }
320
+ );
302
321
  });
303
322
  });
304
323
 
@@ -311,15 +330,20 @@ describe('Admin Script Router', () => {
311
330
  timezone: 'America/New_York',
312
331
  lastTriggeredAt: new Date('2025-01-01T09:00:00Z'),
313
332
  nextTriggerAt: new Date('2025-01-02T09:00:00Z'),
314
- externalScheduleId: 'arn:aws:events:us-east-1:123456789012:rule/test',
333
+ externalScheduleId:
334
+ 'arn:aws:events:us-east-1:123456789012:rule/test',
315
335
  externalScheduleName: 'test-script-schedule',
316
336
  createdAt: new Date('2025-01-01T00:00:00Z'),
317
337
  updatedAt: new Date('2025-01-01T00:00:00Z'),
318
338
  };
319
339
 
320
- mockCommands.getScheduleByScriptName = jest.fn().mockResolvedValue(dbSchedule);
340
+ mockCommands.getScheduleByScriptName = jest
341
+ .fn()
342
+ .mockResolvedValue(dbSchedule);
321
343
 
322
- const response = await request(app).get('/admin/scripts/test-script/schedule');
344
+ const response = await request(app).get(
345
+ '/admin/scripts/test-script/schedule'
346
+ );
323
347
 
324
348
  expect(response.status).toBe(200);
325
349
  expect(response.body.source).toBe('database');
@@ -329,7 +353,9 @@ describe('Admin Script Router', () => {
329
353
  });
330
354
 
331
355
  it('should return definition schedule when no database override', async () => {
332
- mockCommands.getScheduleByScriptName = jest.fn().mockResolvedValue(null);
356
+ mockCommands.getScheduleByScriptName = jest
357
+ .fn()
358
+ .mockResolvedValue(null);
333
359
 
334
360
  // Update test script to include schedule
335
361
  class ScheduledTestScript extends TestScript {
@@ -345,7 +371,9 @@ describe('Admin Script Router', () => {
345
371
 
346
372
  mockFactory.get.mockReturnValue(ScheduledTestScript);
347
373
 
348
- const response = await request(app).get('/admin/scripts/test-script/schedule');
374
+ const response = await request(app).get(
375
+ '/admin/scripts/test-script/schedule'
376
+ );
349
377
 
350
378
  expect(response.status).toBe(200);
351
379
  expect(response.body.source).toBe('definition');
@@ -355,9 +383,13 @@ describe('Admin Script Router', () => {
355
383
  });
356
384
 
357
385
  it('should return none when no schedule configured', async () => {
358
- mockCommands.getScheduleByScriptName = jest.fn().mockResolvedValue(null);
386
+ mockCommands.getScheduleByScriptName = jest
387
+ .fn()
388
+ .mockResolvedValue(null);
359
389
 
360
- const response = await request(app).get('/admin/scripts/test-script/schedule');
390
+ const response = await request(app).get(
391
+ '/admin/scripts/test-script/schedule'
392
+ );
361
393
 
362
394
  expect(response.status).toBe(200);
363
395
  expect(response.body.source).toBe('none');
@@ -389,7 +421,9 @@ describe('Admin Script Router', () => {
389
421
  updatedAt: new Date(),
390
422
  };
391
423
 
392
- mockCommands.upsertSchedule = jest.fn().mockResolvedValue(newSchedule);
424
+ mockCommands.upsertSchedule = jest
425
+ .fn()
426
+ .mockResolvedValue(newSchedule);
393
427
 
394
428
  const response = await request(app)
395
429
  .put('/admin/scripts/test-script/schedule')
@@ -424,7 +458,9 @@ describe('Admin Script Router', () => {
424
458
  updatedAt: new Date(),
425
459
  };
426
460
 
427
- mockCommands.upsertSchedule = jest.fn().mockResolvedValue(updatedSchedule);
461
+ mockCommands.upsertSchedule = jest
462
+ .fn()
463
+ .mockResolvedValue(updatedSchedule);
428
464
 
429
465
  const response = await request(app)
430
466
  .put('/admin/scripts/test-script/schedule')
@@ -487,10 +523,15 @@ describe('Admin Script Router', () => {
487
523
  updatedAt: new Date(),
488
524
  };
489
525
 
490
- mockCommands.upsertSchedule = jest.fn().mockResolvedValue(newSchedule);
491
- mockCommands.updateScheduleExternalInfo = jest.fn().mockResolvedValue(newSchedule);
526
+ mockCommands.upsertSchedule = jest
527
+ .fn()
528
+ .mockResolvedValue(newSchedule);
529
+ mockCommands.updateScheduleExternalInfo = jest
530
+ .fn()
531
+ .mockResolvedValue(newSchedule);
492
532
  mockSchedulerAdapter.createSchedule.mockResolvedValue({
493
- scheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
533
+ scheduleArn:
534
+ 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
494
535
  scheduleName: 'frigg-script-test-script',
495
536
  });
496
537
 
@@ -508,11 +549,16 @@ describe('Admin Script Router', () => {
508
549
  cronExpression: '0 12 * * *',
509
550
  timezone: 'America/Los_Angeles',
510
551
  });
511
- expect(mockCommands.updateScheduleExternalInfo).toHaveBeenCalledWith('test-script', {
512
- externalScheduleId: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
552
+ expect(
553
+ mockCommands.updateScheduleExternalInfo
554
+ ).toHaveBeenCalledWith('test-script', {
555
+ externalScheduleId:
556
+ 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
513
557
  externalScheduleName: 'frigg-script-test-script',
514
558
  });
515
- expect(response.body.schedule.externalScheduleId).toBe('arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script');
559
+ expect(response.body.schedule.externalScheduleId).toBe(
560
+ 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script'
561
+ );
516
562
  });
517
563
 
518
564
  it('should delete EventBridge schedule when disabling existing schedule', async () => {
@@ -521,14 +567,19 @@ describe('Admin Script Router', () => {
521
567
  enabled: false,
522
568
  cronExpression: null,
523
569
  timezone: 'UTC',
524
- externalScheduleId: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
570
+ externalScheduleId:
571
+ 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
525
572
  externalScheduleName: 'frigg-script-test-script',
526
573
  createdAt: new Date(),
527
574
  updatedAt: new Date(),
528
575
  };
529
576
 
530
- mockCommands.upsertSchedule = jest.fn().mockResolvedValue(existingSchedule);
531
- mockCommands.updateScheduleExternalInfo = jest.fn().mockResolvedValue(existingSchedule);
577
+ mockCommands.upsertSchedule = jest
578
+ .fn()
579
+ .mockResolvedValue(existingSchedule);
580
+ mockCommands.updateScheduleExternalInfo = jest
581
+ .fn()
582
+ .mockResolvedValue(existingSchedule);
532
583
  mockSchedulerAdapter.deleteSchedule.mockResolvedValue();
533
584
 
534
585
  const response = await request(app)
@@ -538,8 +589,12 @@ describe('Admin Script Router', () => {
538
589
  });
539
590
 
540
591
  expect(response.status).toBe(200);
541
- expect(mockSchedulerAdapter.deleteSchedule).toHaveBeenCalledWith('test-script');
542
- expect(mockCommands.updateScheduleExternalInfo).toHaveBeenCalledWith('test-script', {
592
+ expect(mockSchedulerAdapter.deleteSchedule).toHaveBeenCalledWith(
593
+ 'test-script'
594
+ );
595
+ expect(
596
+ mockCommands.updateScheduleExternalInfo
597
+ ).toHaveBeenCalledWith('test-script', {
543
598
  externalScheduleId: null,
544
599
  externalScheduleName: null,
545
600
  });
@@ -555,8 +610,12 @@ describe('Admin Script Router', () => {
555
610
  updatedAt: new Date(),
556
611
  };
557
612
 
558
- mockCommands.upsertSchedule = jest.fn().mockResolvedValue(newSchedule);
559
- mockSchedulerAdapter.createSchedule.mockRejectedValue(new Error('AWS Scheduler API error'));
613
+ mockCommands.upsertSchedule = jest
614
+ .fn()
615
+ .mockResolvedValue(newSchedule);
616
+ mockSchedulerAdapter.createSchedule.mockRejectedValue(
617
+ new Error('AWS Scheduler API error')
618
+ );
560
619
 
561
620
  const response = await request(app)
562
621
  .put('/admin/scripts/test-script/schedule')
@@ -568,7 +627,9 @@ describe('Admin Script Router', () => {
568
627
  // Request should succeed despite scheduler error
569
628
  expect(response.status).toBe(200);
570
629
  expect(response.body.success).toBe(true);
571
- expect(response.body.schedulerWarning).toBe('AWS Scheduler API error');
630
+ expect(response.body.schedulerWarning).toBe(
631
+ 'AWS Scheduler API error'
632
+ );
572
633
  });
573
634
  });
574
635
 
@@ -592,7 +653,9 @@ describe('Admin Script Router', () => {
592
653
  expect(response.body.success).toBe(true);
593
654
  expect(response.body.deletedCount).toBe(1);
594
655
  expect(response.body.message).toContain('removed');
595
- expect(mockCommands.deleteSchedule).toHaveBeenCalledWith('test-script');
656
+ expect(mockCommands.deleteSchedule).toHaveBeenCalledWith(
657
+ 'test-script'
658
+ );
596
659
  });
597
660
 
598
661
  it('should return definition schedule after deleting override', async () => {
@@ -636,7 +699,9 @@ describe('Admin Script Router', () => {
636
699
 
637
700
  expect(response.status).toBe(200);
638
701
  expect(response.body.deletedCount).toBe(0);
639
- expect(response.body.message).toContain('No schedule override found');
702
+ expect(response.body.message).toContain(
703
+ 'No schedule override found'
704
+ );
640
705
  });
641
706
 
642
707
  it('should return 404 for non-existent script', async () => {
@@ -658,7 +723,8 @@ describe('Admin Script Router', () => {
658
723
  scriptName: 'test-script',
659
724
  enabled: true,
660
725
  cronExpression: '0 12 * * *',
661
- externalScheduleId: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
726
+ externalScheduleId:
727
+ 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
662
728
  externalScheduleName: 'frigg-script-test-script',
663
729
  },
664
730
  });
@@ -669,7 +735,9 @@ describe('Admin Script Router', () => {
669
735
  );
670
736
 
671
737
  expect(response.status).toBe(200);
672
- expect(mockSchedulerAdapter.deleteSchedule).toHaveBeenCalledWith('test-script');
738
+ expect(mockSchedulerAdapter.deleteSchedule).toHaveBeenCalledWith(
739
+ 'test-script'
740
+ );
673
741
  });
674
742
 
675
743
  it('should not call scheduler when no external rule exists', async () => {
@@ -700,10 +768,13 @@ describe('Admin Script Router', () => {
700
768
  scriptName: 'test-script',
701
769
  enabled: true,
702
770
  cronExpression: '0 12 * * *',
703
- externalScheduleId: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
771
+ externalScheduleId:
772
+ 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
704
773
  },
705
774
  });
706
- mockSchedulerAdapter.deleteSchedule.mockRejectedValue(new Error('Scheduler delete failed'));
775
+ mockSchedulerAdapter.deleteSchedule.mockRejectedValue(
776
+ new Error('Scheduler delete failed')
777
+ );
707
778
 
708
779
  const response = await request(app).delete(
709
780
  '/admin/scripts/test-script/schedule'
@@ -712,7 +783,9 @@ describe('Admin Script Router', () => {
712
783
  // Request should succeed despite scheduler error
713
784
  expect(response.status).toBe(200);
714
785
  expect(response.body.success).toBe(true);
715
- expect(response.body.schedulerWarning).toBe('Scheduler delete failed');
786
+ expect(response.body.schedulerWarning).toBe(
787
+ 'Scheduler delete failed'
788
+ );
716
789
  });
717
790
  });
718
791
  });
@@ -6,6 +6,8 @@
6
6
  * Expects: x-frigg-admin-api-key header
7
7
  */
8
8
 
9
- const { validateAdminApiKey } = require('@friggframework/core/handlers/middleware/admin-auth');
9
+ const {
10
+ validateAdminApiKey,
11
+ } = require('@friggframework/core/handlers/middleware/admin-auth');
10
12
 
11
13
  module.exports = { validateAdminApiKey };