@friggframework/admin-scripts 2.0.0--canary.517.8eaf5df.0 → 2.0.0--canary.517.c197eb5.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/README.md +13 -18
- package/index.js +0 -14
- package/package.json +7 -6
- package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +1 -1
- package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +1 -1
- package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +3 -3
- package/src/application/use-cases/delete-schedule-use-case.js +3 -3
- package/src/application/use-cases/get-effective-schedule-use-case.js +3 -3
- package/src/application/use-cases/upsert-schedule-use-case.js +5 -9
- package/src/infrastructure/__tests__/admin-script-router.test.js +3 -5
- package/src/infrastructure/admin-script-router.js +20 -49
- package/src/infrastructure/bootstrap.js +1 -6
- package/src/builtins/__tests__/integration-health-check.test.js +0 -712
- package/src/builtins/__tests__/oauth-token-refresh.test.js +0 -407
- package/src/builtins/index.js +0 -25
- package/src/builtins/integration-health-check.js +0 -311
- package/src/builtins/oauth-token-refresh.js +0 -255
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
const { OAuthTokenRefreshScript } = require('../oauth-token-refresh');
|
|
2
|
-
|
|
3
|
-
describe('OAuthTokenRefreshScript', () => {
|
|
4
|
-
describe('Definition', () => {
|
|
5
|
-
it('should have correct name and metadata', () => {
|
|
6
|
-
expect(OAuthTokenRefreshScript.Definition.name).toBe(
|
|
7
|
-
'oauth-token-refresh'
|
|
8
|
-
);
|
|
9
|
-
expect(OAuthTokenRefreshScript.Definition.version).toBe('1.0.0');
|
|
10
|
-
expect(OAuthTokenRefreshScript.Definition.source).toBe('BUILTIN');
|
|
11
|
-
expect(
|
|
12
|
-
OAuthTokenRefreshScript.Definition.config
|
|
13
|
-
.requireIntegrationInstance
|
|
14
|
-
).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should have valid input schema', () => {
|
|
18
|
-
const schema = OAuthTokenRefreshScript.Definition.inputSchema;
|
|
19
|
-
expect(schema.type).toBe('object');
|
|
20
|
-
expect(schema.properties.integrationIds).toBeDefined();
|
|
21
|
-
expect(schema.properties.expiryThresholdHours).toBeDefined();
|
|
22
|
-
expect(schema.properties.dryRun).toBeDefined();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should have valid output schema', () => {
|
|
26
|
-
const schema = OAuthTokenRefreshScript.Definition.outputSchema;
|
|
27
|
-
expect(schema.type).toBe('object');
|
|
28
|
-
expect(schema.properties.refreshed).toBeDefined();
|
|
29
|
-
expect(schema.properties.failed).toBeDefined();
|
|
30
|
-
expect(schema.properties.skipped).toBeDefined();
|
|
31
|
-
expect(schema.properties.details).toBeDefined();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should have appropriate timeout configuration', () => {
|
|
35
|
-
expect(OAuthTokenRefreshScript.Definition.config.timeout).toBe(
|
|
36
|
-
600000
|
|
37
|
-
); // 10 minutes
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should have clean display object without redundant fields', () => {
|
|
41
|
-
expect(OAuthTokenRefreshScript.Definition.display).toBeDefined();
|
|
42
|
-
expect(OAuthTokenRefreshScript.Definition.display.category).toBe(
|
|
43
|
-
'maintenance'
|
|
44
|
-
);
|
|
45
|
-
// Should NOT have redundant label/description
|
|
46
|
-
expect(
|
|
47
|
-
OAuthTokenRefreshScript.Definition.display.label
|
|
48
|
-
).toBeUndefined();
|
|
49
|
-
expect(
|
|
50
|
-
OAuthTokenRefreshScript.Definition.display.description
|
|
51
|
-
).toBeUndefined();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('execute()', () => {
|
|
56
|
-
let script;
|
|
57
|
-
let mockContext;
|
|
58
|
-
|
|
59
|
-
beforeEach(() => {
|
|
60
|
-
mockContext = {
|
|
61
|
-
log: jest.fn(),
|
|
62
|
-
integrationRepository: {
|
|
63
|
-
findIntegrations: jest.fn(),
|
|
64
|
-
findIntegrationById: jest.fn(),
|
|
65
|
-
},
|
|
66
|
-
instantiate: jest.fn(),
|
|
67
|
-
};
|
|
68
|
-
script = new OAuthTokenRefreshScript({ context: mockContext });
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should return empty results when no integrations found', async () => {
|
|
72
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
73
|
-
[]
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
const result = await script.execute({});
|
|
77
|
-
|
|
78
|
-
expect(result.refreshed).toBe(0);
|
|
79
|
-
expect(result.failed).toBe(0);
|
|
80
|
-
expect(result.skipped).toBe(0);
|
|
81
|
-
expect(result.details).toEqual([]);
|
|
82
|
-
expect(mockContext.log).toHaveBeenCalledWith(
|
|
83
|
-
'info',
|
|
84
|
-
expect.any(String),
|
|
85
|
-
expect.any(Object)
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should skip integrations without OAuth credentials', async () => {
|
|
90
|
-
const integration = {
|
|
91
|
-
id: 'int-1',
|
|
92
|
-
config: {}, // No credentials
|
|
93
|
-
};
|
|
94
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
95
|
-
[integration]
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const result = await script.execute({});
|
|
99
|
-
|
|
100
|
-
expect(result.skipped).toBe(1);
|
|
101
|
-
expect(result.refreshed).toBe(0);
|
|
102
|
-
expect(result.details[0]).toMatchObject({
|
|
103
|
-
integrationId: 'int-1',
|
|
104
|
-
action: 'skipped',
|
|
105
|
-
reason: 'No OAuth credentials found',
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should skip integrations without expiry time', async () => {
|
|
110
|
-
const integration = {
|
|
111
|
-
id: 'int-1',
|
|
112
|
-
config: {
|
|
113
|
-
credentials: {
|
|
114
|
-
access_token: 'token123',
|
|
115
|
-
// No expires_at
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
120
|
-
[integration]
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const result = await script.execute({});
|
|
124
|
-
|
|
125
|
-
expect(result.skipped).toBe(1);
|
|
126
|
-
expect(result.details[0]).toMatchObject({
|
|
127
|
-
integrationId: 'int-1',
|
|
128
|
-
action: 'skipped',
|
|
129
|
-
reason: 'No expiry time found',
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should skip tokens not near expiry', async () => {
|
|
134
|
-
const farFutureExpiry = new Date(Date.now() + 48 * 60 * 60 * 1000); // 48 hours from now
|
|
135
|
-
const integration = {
|
|
136
|
-
id: 'int-1',
|
|
137
|
-
config: {
|
|
138
|
-
credentials: {
|
|
139
|
-
access_token: 'token123',
|
|
140
|
-
expires_at: farFutureExpiry.toISOString(),
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
145
|
-
[integration]
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const result = await script.execute({
|
|
149
|
-
expiryThresholdHours: 24,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
expect(result.skipped).toBe(1);
|
|
153
|
-
expect(result.details[0]).toMatchObject({
|
|
154
|
-
integrationId: 'int-1',
|
|
155
|
-
action: 'skipped',
|
|
156
|
-
reason: 'Token not near expiry',
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should refresh tokens that are near expiry', async () => {
|
|
161
|
-
const soonExpiry = new Date(Date.now() + 12 * 60 * 60 * 1000); // 12 hours from now
|
|
162
|
-
const integration = {
|
|
163
|
-
id: 'int-1',
|
|
164
|
-
config: {
|
|
165
|
-
credentials: {
|
|
166
|
-
access_token: 'token123',
|
|
167
|
-
expires_at: soonExpiry.toISOString(),
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const mockInstance = {
|
|
173
|
-
primary: {
|
|
174
|
-
api: {
|
|
175
|
-
refreshAccessToken: jest
|
|
176
|
-
.fn()
|
|
177
|
-
.mockResolvedValue(undefined),
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
183
|
-
[integration]
|
|
184
|
-
);
|
|
185
|
-
mockContext.instantiate.mockResolvedValue(mockInstance);
|
|
186
|
-
|
|
187
|
-
const result = await script.execute({
|
|
188
|
-
expiryThresholdHours: 24,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
expect(result.refreshed).toBe(1);
|
|
192
|
-
expect(result.skipped).toBe(0);
|
|
193
|
-
expect(
|
|
194
|
-
mockInstance.primary.api.refreshAccessToken
|
|
195
|
-
).toHaveBeenCalled();
|
|
196
|
-
expect(result.details[0]).toMatchObject({
|
|
197
|
-
integrationId: 'int-1',
|
|
198
|
-
action: 'refreshed',
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should handle dryRun mode correctly', async () => {
|
|
203
|
-
const soonExpiry = new Date(Date.now() + 12 * 60 * 60 * 1000);
|
|
204
|
-
const integration = {
|
|
205
|
-
id: 'int-1',
|
|
206
|
-
config: {
|
|
207
|
-
credentials: {
|
|
208
|
-
access_token: 'token123',
|
|
209
|
-
expires_at: soonExpiry.toISOString(),
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
215
|
-
[integration]
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
const result = await script.execute({
|
|
219
|
-
expiryThresholdHours: 24,
|
|
220
|
-
dryRun: true,
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
expect(result.refreshed).toBe(0);
|
|
224
|
-
expect(result.skipped).toBe(1);
|
|
225
|
-
expect(mockContext.instantiate).not.toHaveBeenCalled();
|
|
226
|
-
expect(result.details[0]).toMatchObject({
|
|
227
|
-
integrationId: 'int-1',
|
|
228
|
-
action: 'skipped',
|
|
229
|
-
reason: 'Dry run - would have refreshed',
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should handle refresh failures gracefully', async () => {
|
|
234
|
-
const soonExpiry = new Date(Date.now() + 12 * 60 * 60 * 1000);
|
|
235
|
-
const integration = {
|
|
236
|
-
id: 'int-1',
|
|
237
|
-
config: {
|
|
238
|
-
credentials: {
|
|
239
|
-
access_token: 'token123',
|
|
240
|
-
expires_at: soonExpiry.toISOString(),
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const mockInstance = {
|
|
246
|
-
primary: {
|
|
247
|
-
api: {
|
|
248
|
-
refreshAccessToken: jest
|
|
249
|
-
.fn()
|
|
250
|
-
.mockRejectedValue(new Error('API Error')),
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
256
|
-
[integration]
|
|
257
|
-
);
|
|
258
|
-
mockContext.instantiate.mockResolvedValue(mockInstance);
|
|
259
|
-
|
|
260
|
-
const result = await script.execute({
|
|
261
|
-
expiryThresholdHours: 24,
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
expect(result.failed).toBe(1);
|
|
265
|
-
expect(result.refreshed).toBe(0);
|
|
266
|
-
expect(result.details[0]).toMatchObject({
|
|
267
|
-
integrationId: 'int-1',
|
|
268
|
-
action: 'failed',
|
|
269
|
-
reason: 'API Error',
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should skip integrations without refresh support', async () => {
|
|
274
|
-
const soonExpiry = new Date(Date.now() + 12 * 60 * 60 * 1000);
|
|
275
|
-
const integration = {
|
|
276
|
-
id: 'int-1',
|
|
277
|
-
config: {
|
|
278
|
-
credentials: {
|
|
279
|
-
access_token: 'token123',
|
|
280
|
-
expires_at: soonExpiry.toISOString(),
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const mockInstance = {
|
|
286
|
-
primary: {
|
|
287
|
-
api: {
|
|
288
|
-
// No refreshAccessToken method
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
294
|
-
[integration]
|
|
295
|
-
);
|
|
296
|
-
mockContext.instantiate.mockResolvedValue(mockInstance);
|
|
297
|
-
|
|
298
|
-
const result = await script.execute({
|
|
299
|
-
expiryThresholdHours: 24,
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
expect(result.skipped).toBe(1);
|
|
303
|
-
expect(result.details[0]).toMatchObject({
|
|
304
|
-
integrationId: 'int-1',
|
|
305
|
-
action: 'skipped',
|
|
306
|
-
reason: 'API does not support token refresh',
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('should filter by specific integration IDs', async () => {
|
|
311
|
-
const integration1 = {
|
|
312
|
-
id: 'int-1',
|
|
313
|
-
config: { credentials: { access_token: 'token1' } },
|
|
314
|
-
};
|
|
315
|
-
const integration2 = {
|
|
316
|
-
id: 'int-2',
|
|
317
|
-
config: { credentials: { access_token: 'token2' } },
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
mockContext.integrationRepository.findIntegrationById.mockImplementation(
|
|
321
|
-
(id) => {
|
|
322
|
-
if (id === 'int-1') return Promise.resolve(integration1);
|
|
323
|
-
if (id === 'int-2') return Promise.resolve(integration2);
|
|
324
|
-
return Promise.reject(new Error('Not found'));
|
|
325
|
-
}
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
const result = await script.execute({
|
|
329
|
-
integrationIds: ['int-1', 'int-2'],
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
expect(
|
|
333
|
-
mockContext.integrationRepository.findIntegrationById
|
|
334
|
-
).toHaveBeenCalledWith('int-1');
|
|
335
|
-
expect(
|
|
336
|
-
mockContext.integrationRepository.findIntegrationById
|
|
337
|
-
).toHaveBeenCalledWith('int-2');
|
|
338
|
-
expect(
|
|
339
|
-
mockContext.integrationRepository.findIntegrations
|
|
340
|
-
).not.toHaveBeenCalled();
|
|
341
|
-
expect(result.details).toHaveLength(2);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('should handle errors when processing integrations', async () => {
|
|
345
|
-
const integration = {
|
|
346
|
-
id: 'int-1',
|
|
347
|
-
config: {
|
|
348
|
-
credentials: {
|
|
349
|
-
access_token: 'token123',
|
|
350
|
-
expires_at: new Date(
|
|
351
|
-
Date.now() + 12 * 60 * 60 * 1000
|
|
352
|
-
).toISOString(),
|
|
353
|
-
},
|
|
354
|
-
},
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
358
|
-
[integration]
|
|
359
|
-
);
|
|
360
|
-
mockContext.instantiate.mockRejectedValue(
|
|
361
|
-
new Error('Instantiation failed')
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
const result = await script.execute({
|
|
365
|
-
expiryThresholdHours: 24,
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
expect(result.failed).toBe(1);
|
|
369
|
-
expect(result.details[0]).toMatchObject({
|
|
370
|
-
integrationId: 'int-1',
|
|
371
|
-
action: 'failed',
|
|
372
|
-
reason: 'Instantiation failed',
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
describe('processIntegration()', () => {
|
|
378
|
-
let script;
|
|
379
|
-
let mockContext;
|
|
380
|
-
|
|
381
|
-
beforeEach(() => {
|
|
382
|
-
mockContext = {
|
|
383
|
-
log: jest.fn(),
|
|
384
|
-
instantiate: jest.fn(),
|
|
385
|
-
};
|
|
386
|
-
script = new OAuthTokenRefreshScript({ context: mockContext });
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('should return correct detail object for each scenario', async () => {
|
|
390
|
-
// Test various scenarios are covered in execute() tests above
|
|
391
|
-
// This test validates the method can be called directly
|
|
392
|
-
const integration = {
|
|
393
|
-
id: 'int-1',
|
|
394
|
-
config: {},
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
const result = await script.processIntegration(integration, {
|
|
398
|
-
expiryThresholdHours: 24,
|
|
399
|
-
dryRun: false,
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
expect(result).toHaveProperty('integrationId');
|
|
403
|
-
expect(result).toHaveProperty('action');
|
|
404
|
-
expect(result).toHaveProperty('reason');
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
});
|
package/src/builtins/index.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
const { OAuthTokenRefreshScript } = require('./oauth-token-refresh');
|
|
2
|
-
const { IntegrationHealthCheckScript } = require('./integration-health-check');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Built-in Admin Scripts
|
|
6
|
-
*
|
|
7
|
-
* These scripts ship with @friggframework/admin-scripts and provide
|
|
8
|
-
* common maintenance and monitoring functionality.
|
|
9
|
-
*/
|
|
10
|
-
const builtinScripts = [OAuthTokenRefreshScript, IntegrationHealthCheckScript];
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Register all built-in scripts with a factory
|
|
14
|
-
* @param {ScriptFactory} factory - Script factory to register with
|
|
15
|
-
*/
|
|
16
|
-
function registerBuiltinScripts(factory) {
|
|
17
|
-
factory.registerAll(builtinScripts);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
module.exports = {
|
|
21
|
-
OAuthTokenRefreshScript,
|
|
22
|
-
IntegrationHealthCheckScript,
|
|
23
|
-
builtinScripts,
|
|
24
|
-
registerBuiltinScripts,
|
|
25
|
-
};
|