@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.
@@ -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
- });
@@ -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
- };