@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.
- package/README.md +277 -0
- package/index.js +23 -6
- package/package.json +6 -6
- package/src/adapters/__tests__/aws-scheduler-adapter.test.js +106 -45
- package/src/adapters/__tests__/local-scheduler-adapter.test.js +24 -10
- package/src/adapters/__tests__/scheduler-adapter-factory.test.js +30 -9
- package/src/adapters/__tests__/scheduler-adapter.test.js +6 -2
- package/src/adapters/aws-scheduler-adapter.js +55 -28
- package/src/adapters/local-scheduler-adapter.js +2 -2
- package/src/adapters/scheduler-adapter-factory.js +3 -1
- package/src/adapters/scheduler-adapter.js +9 -3
- package/src/application/__tests__/admin-frigg-commands.test.js +73 -30
- package/src/application/__tests__/admin-script-base.test.js +23 -6
- package/src/application/__tests__/script-factory.test.js +30 -6
- package/src/application/__tests__/script-runner.test.js +113 -24
- package/src/application/__tests__/validate-script-input.test.js +54 -15
- package/src/application/admin-frigg-commands.js +21 -9
- package/src/application/admin-script-base.js +3 -2
- package/src/application/script-factory.js +3 -1
- package/src/application/script-runner.js +90 -48
- package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +16 -7
- package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +9 -4
- package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +21 -10
- package/src/application/use-cases/delete-schedule-use-case.js +6 -4
- package/src/application/use-cases/get-effective-schedule-use-case.js +3 -1
- package/src/application/use-cases/index.js +3 -1
- package/src/application/use-cases/upsert-schedule-use-case.js +30 -11
- package/src/application/validate-script-input.js +10 -3
- package/src/builtins/__tests__/integration-health-check.test.js +232 -127
- package/src/builtins/__tests__/oauth-token-refresh.test.js +128 -75
- package/src/builtins/index.js +1 -4
- package/src/builtins/integration-health-check.js +63 -30
- package/src/builtins/oauth-token-refresh.js +64 -29
- package/src/infrastructure/__tests__/admin-auth-middleware.test.js +9 -3
- package/src/infrastructure/__tests__/admin-script-router.test.js +125 -52
- package/src/infrastructure/admin-auth-middleware.js +3 -1
- package/src/infrastructure/admin-script-router.js +129 -14
- package/src/infrastructure/bootstrap.js +87 -0
- package/src/infrastructure/script-executor-handler.js +119 -52
- package/src/application/__tests__/dry-run-http-interceptor.test.js +0 -313
- package/src/application/__tests__/dry-run-repository-wrapper.test.js +0 -257
- package/src/application/__tests__/schedule-management-use-case.test.js +0 -276
- package/src/application/dry-run-http-interceptor.js +0 -296
- package/src/application/dry-run-repository-wrapper.js +0 -261
- package/src/application/schedule-management-use-case.js +0 -230
|
@@ -3,10 +3,15 @@ const { OAuthTokenRefreshScript } = require('../oauth-token-refresh');
|
|
|
3
3
|
describe('OAuthTokenRefreshScript', () => {
|
|
4
4
|
describe('Definition', () => {
|
|
5
5
|
it('should have correct name and metadata', () => {
|
|
6
|
-
expect(OAuthTokenRefreshScript.Definition.name).toBe(
|
|
6
|
+
expect(OAuthTokenRefreshScript.Definition.name).toBe(
|
|
7
|
+
'oauth-token-refresh'
|
|
8
|
+
);
|
|
7
9
|
expect(OAuthTokenRefreshScript.Definition.version).toBe('1.0.0');
|
|
8
10
|
expect(OAuthTokenRefreshScript.Definition.source).toBe('BUILTIN');
|
|
9
|
-
expect(
|
|
11
|
+
expect(
|
|
12
|
+
OAuthTokenRefreshScript.Definition.config
|
|
13
|
+
.requireIntegrationInstance
|
|
14
|
+
).toBe(true);
|
|
10
15
|
});
|
|
11
16
|
|
|
12
17
|
it('should have valid input schema', () => {
|
|
@@ -27,15 +32,23 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
27
32
|
});
|
|
28
33
|
|
|
29
34
|
it('should have appropriate timeout configuration', () => {
|
|
30
|
-
expect(OAuthTokenRefreshScript.Definition.config.timeout).toBe(
|
|
35
|
+
expect(OAuthTokenRefreshScript.Definition.config.timeout).toBe(
|
|
36
|
+
600000
|
|
37
|
+
); // 10 minutes
|
|
31
38
|
});
|
|
32
39
|
|
|
33
40
|
it('should have clean display object without redundant fields', () => {
|
|
34
41
|
expect(OAuthTokenRefreshScript.Definition.display).toBeDefined();
|
|
35
|
-
expect(OAuthTokenRefreshScript.Definition.display.category).toBe(
|
|
42
|
+
expect(OAuthTokenRefreshScript.Definition.display.category).toBe(
|
|
43
|
+
'maintenance'
|
|
44
|
+
);
|
|
36
45
|
// Should NOT have redundant label/description
|
|
37
|
-
expect(
|
|
38
|
-
|
|
46
|
+
expect(
|
|
47
|
+
OAuthTokenRefreshScript.Definition.display.label
|
|
48
|
+
).toBeUndefined();
|
|
49
|
+
expect(
|
|
50
|
+
OAuthTokenRefreshScript.Definition.display.description
|
|
51
|
+
).toBeUndefined();
|
|
39
52
|
});
|
|
40
53
|
});
|
|
41
54
|
|
|
@@ -56,7 +69,9 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
56
69
|
});
|
|
57
70
|
|
|
58
71
|
it('should return empty results when no integrations found', async () => {
|
|
59
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
72
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
73
|
+
[]
|
|
74
|
+
);
|
|
60
75
|
|
|
61
76
|
const result = await script.execute({});
|
|
62
77
|
|
|
@@ -64,15 +79,21 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
64
79
|
expect(result.failed).toBe(0);
|
|
65
80
|
expect(result.skipped).toBe(0);
|
|
66
81
|
expect(result.details).toEqual([]);
|
|
67
|
-
expect(mockContext.log).toHaveBeenCalledWith(
|
|
82
|
+
expect(mockContext.log).toHaveBeenCalledWith(
|
|
83
|
+
'info',
|
|
84
|
+
expect.any(String),
|
|
85
|
+
expect.any(Object)
|
|
86
|
+
);
|
|
68
87
|
});
|
|
69
88
|
|
|
70
89
|
it('should skip integrations without OAuth credentials', async () => {
|
|
71
90
|
const integration = {
|
|
72
91
|
id: 'int-1',
|
|
73
|
-
config: {} // No credentials
|
|
92
|
+
config: {}, // No credentials
|
|
74
93
|
};
|
|
75
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
94
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
95
|
+
[integration]
|
|
96
|
+
);
|
|
76
97
|
|
|
77
98
|
const result = await script.execute({});
|
|
78
99
|
|
|
@@ -81,7 +102,7 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
81
102
|
expect(result.details[0]).toMatchObject({
|
|
82
103
|
integrationId: 'int-1',
|
|
83
104
|
action: 'skipped',
|
|
84
|
-
reason: 'No OAuth credentials found'
|
|
105
|
+
reason: 'No OAuth credentials found',
|
|
85
106
|
});
|
|
86
107
|
});
|
|
87
108
|
|
|
@@ -90,12 +111,14 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
90
111
|
id: 'int-1',
|
|
91
112
|
config: {
|
|
92
113
|
credentials: {
|
|
93
|
-
access_token: 'token123'
|
|
114
|
+
access_token: 'token123',
|
|
94
115
|
// No expires_at
|
|
95
|
-
}
|
|
96
|
-
}
|
|
116
|
+
},
|
|
117
|
+
},
|
|
97
118
|
};
|
|
98
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
119
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
120
|
+
[integration]
|
|
121
|
+
);
|
|
99
122
|
|
|
100
123
|
const result = await script.execute({});
|
|
101
124
|
|
|
@@ -103,7 +126,7 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
103
126
|
expect(result.details[0]).toMatchObject({
|
|
104
127
|
integrationId: 'int-1',
|
|
105
128
|
action: 'skipped',
|
|
106
|
-
reason: 'No expiry time found'
|
|
129
|
+
reason: 'No expiry time found',
|
|
107
130
|
});
|
|
108
131
|
});
|
|
109
132
|
|
|
@@ -114,21 +137,23 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
114
137
|
config: {
|
|
115
138
|
credentials: {
|
|
116
139
|
access_token: 'token123',
|
|
117
|
-
expires_at: farFutureExpiry.toISOString()
|
|
118
|
-
}
|
|
119
|
-
}
|
|
140
|
+
expires_at: farFutureExpiry.toISOString(),
|
|
141
|
+
},
|
|
142
|
+
},
|
|
120
143
|
};
|
|
121
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
144
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
145
|
+
[integration]
|
|
146
|
+
);
|
|
122
147
|
|
|
123
148
|
const result = await script.execute({
|
|
124
|
-
expiryThresholdHours: 24
|
|
149
|
+
expiryThresholdHours: 24,
|
|
125
150
|
});
|
|
126
151
|
|
|
127
152
|
expect(result.skipped).toBe(1);
|
|
128
153
|
expect(result.details[0]).toMatchObject({
|
|
129
154
|
integrationId: 'int-1',
|
|
130
155
|
action: 'skipped',
|
|
131
|
-
reason: 'Token not near expiry'
|
|
156
|
+
reason: 'Token not near expiry',
|
|
132
157
|
});
|
|
133
158
|
});
|
|
134
159
|
|
|
@@ -139,32 +164,38 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
139
164
|
config: {
|
|
140
165
|
credentials: {
|
|
141
166
|
access_token: 'token123',
|
|
142
|
-
expires_at: soonExpiry.toISOString()
|
|
143
|
-
}
|
|
144
|
-
}
|
|
167
|
+
expires_at: soonExpiry.toISOString(),
|
|
168
|
+
},
|
|
169
|
+
},
|
|
145
170
|
};
|
|
146
171
|
|
|
147
172
|
const mockInstance = {
|
|
148
173
|
primary: {
|
|
149
174
|
api: {
|
|
150
|
-
refreshAccessToken: jest
|
|
151
|
-
|
|
152
|
-
|
|
175
|
+
refreshAccessToken: jest
|
|
176
|
+
.fn()
|
|
177
|
+
.mockResolvedValue(undefined),
|
|
178
|
+
},
|
|
179
|
+
},
|
|
153
180
|
};
|
|
154
181
|
|
|
155
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
182
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
183
|
+
[integration]
|
|
184
|
+
);
|
|
156
185
|
mockContext.instantiate.mockResolvedValue(mockInstance);
|
|
157
186
|
|
|
158
187
|
const result = await script.execute({
|
|
159
|
-
expiryThresholdHours: 24
|
|
188
|
+
expiryThresholdHours: 24,
|
|
160
189
|
});
|
|
161
190
|
|
|
162
191
|
expect(result.refreshed).toBe(1);
|
|
163
192
|
expect(result.skipped).toBe(0);
|
|
164
|
-
expect(
|
|
193
|
+
expect(
|
|
194
|
+
mockInstance.primary.api.refreshAccessToken
|
|
195
|
+
).toHaveBeenCalled();
|
|
165
196
|
expect(result.details[0]).toMatchObject({
|
|
166
197
|
integrationId: 'int-1',
|
|
167
|
-
action: 'refreshed'
|
|
198
|
+
action: 'refreshed',
|
|
168
199
|
});
|
|
169
200
|
});
|
|
170
201
|
|
|
@@ -175,16 +206,18 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
175
206
|
config: {
|
|
176
207
|
credentials: {
|
|
177
208
|
access_token: 'token123',
|
|
178
|
-
expires_at: soonExpiry.toISOString()
|
|
179
|
-
}
|
|
180
|
-
}
|
|
209
|
+
expires_at: soonExpiry.toISOString(),
|
|
210
|
+
},
|
|
211
|
+
},
|
|
181
212
|
};
|
|
182
213
|
|
|
183
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
214
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
215
|
+
[integration]
|
|
216
|
+
);
|
|
184
217
|
|
|
185
218
|
const result = await script.execute({
|
|
186
219
|
expiryThresholdHours: 24,
|
|
187
|
-
dryRun: true
|
|
220
|
+
dryRun: true,
|
|
188
221
|
});
|
|
189
222
|
|
|
190
223
|
expect(result.refreshed).toBe(0);
|
|
@@ -193,7 +226,7 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
193
226
|
expect(result.details[0]).toMatchObject({
|
|
194
227
|
integrationId: 'int-1',
|
|
195
228
|
action: 'skipped',
|
|
196
|
-
reason: 'Dry run - would have refreshed'
|
|
229
|
+
reason: 'Dry run - would have refreshed',
|
|
197
230
|
});
|
|
198
231
|
});
|
|
199
232
|
|
|
@@ -204,24 +237,28 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
204
237
|
config: {
|
|
205
238
|
credentials: {
|
|
206
239
|
access_token: 'token123',
|
|
207
|
-
expires_at: soonExpiry.toISOString()
|
|
208
|
-
}
|
|
209
|
-
}
|
|
240
|
+
expires_at: soonExpiry.toISOString(),
|
|
241
|
+
},
|
|
242
|
+
},
|
|
210
243
|
};
|
|
211
244
|
|
|
212
245
|
const mockInstance = {
|
|
213
246
|
primary: {
|
|
214
247
|
api: {
|
|
215
|
-
refreshAccessToken: jest
|
|
216
|
-
|
|
217
|
-
|
|
248
|
+
refreshAccessToken: jest
|
|
249
|
+
.fn()
|
|
250
|
+
.mockRejectedValue(new Error('API Error')),
|
|
251
|
+
},
|
|
252
|
+
},
|
|
218
253
|
};
|
|
219
254
|
|
|
220
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
255
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
256
|
+
[integration]
|
|
257
|
+
);
|
|
221
258
|
mockContext.instantiate.mockResolvedValue(mockInstance);
|
|
222
259
|
|
|
223
260
|
const result = await script.execute({
|
|
224
|
-
expiryThresholdHours: 24
|
|
261
|
+
expiryThresholdHours: 24,
|
|
225
262
|
});
|
|
226
263
|
|
|
227
264
|
expect(result.failed).toBe(1);
|
|
@@ -229,7 +266,7 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
229
266
|
expect(result.details[0]).toMatchObject({
|
|
230
267
|
integrationId: 'int-1',
|
|
231
268
|
action: 'failed',
|
|
232
|
-
reason: 'API Error'
|
|
269
|
+
reason: 'API Error',
|
|
233
270
|
});
|
|
234
271
|
});
|
|
235
272
|
|
|
@@ -240,57 +277,67 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
240
277
|
config: {
|
|
241
278
|
credentials: {
|
|
242
279
|
access_token: 'token123',
|
|
243
|
-
expires_at: soonExpiry.toISOString()
|
|
244
|
-
}
|
|
245
|
-
}
|
|
280
|
+
expires_at: soonExpiry.toISOString(),
|
|
281
|
+
},
|
|
282
|
+
},
|
|
246
283
|
};
|
|
247
284
|
|
|
248
285
|
const mockInstance = {
|
|
249
286
|
primary: {
|
|
250
287
|
api: {
|
|
251
288
|
// No refreshAccessToken method
|
|
252
|
-
}
|
|
253
|
-
}
|
|
289
|
+
},
|
|
290
|
+
},
|
|
254
291
|
};
|
|
255
292
|
|
|
256
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
293
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
294
|
+
[integration]
|
|
295
|
+
);
|
|
257
296
|
mockContext.instantiate.mockResolvedValue(mockInstance);
|
|
258
297
|
|
|
259
298
|
const result = await script.execute({
|
|
260
|
-
expiryThresholdHours: 24
|
|
299
|
+
expiryThresholdHours: 24,
|
|
261
300
|
});
|
|
262
301
|
|
|
263
302
|
expect(result.skipped).toBe(1);
|
|
264
303
|
expect(result.details[0]).toMatchObject({
|
|
265
304
|
integrationId: 'int-1',
|
|
266
305
|
action: 'skipped',
|
|
267
|
-
reason: 'API does not support token refresh'
|
|
306
|
+
reason: 'API does not support token refresh',
|
|
268
307
|
});
|
|
269
308
|
});
|
|
270
309
|
|
|
271
310
|
it('should filter by specific integration IDs', async () => {
|
|
272
311
|
const integration1 = {
|
|
273
312
|
id: 'int-1',
|
|
274
|
-
config: { credentials: { access_token: 'token1' } }
|
|
313
|
+
config: { credentials: { access_token: 'token1' } },
|
|
275
314
|
};
|
|
276
315
|
const integration2 = {
|
|
277
316
|
id: 'int-2',
|
|
278
|
-
config: { credentials: { access_token: 'token2' } }
|
|
317
|
+
config: { credentials: { access_token: 'token2' } },
|
|
279
318
|
};
|
|
280
319
|
|
|
281
|
-
mockContext.integrationRepository.findIntegrationById.mockImplementation(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
+
);
|
|
286
327
|
|
|
287
328
|
const result = await script.execute({
|
|
288
|
-
integrationIds: ['int-1', 'int-2']
|
|
329
|
+
integrationIds: ['int-1', 'int-2'],
|
|
289
330
|
});
|
|
290
331
|
|
|
291
|
-
expect(
|
|
292
|
-
|
|
293
|
-
|
|
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();
|
|
294
341
|
expect(result.details).toHaveLength(2);
|
|
295
342
|
});
|
|
296
343
|
|
|
@@ -300,23 +347,29 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
300
347
|
config: {
|
|
301
348
|
credentials: {
|
|
302
349
|
access_token: 'token123',
|
|
303
|
-
expires_at: new Date(
|
|
304
|
-
|
|
305
|
-
|
|
350
|
+
expires_at: new Date(
|
|
351
|
+
Date.now() + 12 * 60 * 60 * 1000
|
|
352
|
+
).toISOString(),
|
|
353
|
+
},
|
|
354
|
+
},
|
|
306
355
|
};
|
|
307
356
|
|
|
308
|
-
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
309
|
-
|
|
357
|
+
mockContext.integrationRepository.findIntegrations.mockResolvedValue(
|
|
358
|
+
[integration]
|
|
359
|
+
);
|
|
360
|
+
mockContext.instantiate.mockRejectedValue(
|
|
361
|
+
new Error('Instantiation failed')
|
|
362
|
+
);
|
|
310
363
|
|
|
311
364
|
const result = await script.execute({
|
|
312
|
-
expiryThresholdHours: 24
|
|
365
|
+
expiryThresholdHours: 24,
|
|
313
366
|
});
|
|
314
367
|
|
|
315
368
|
expect(result.failed).toBe(1);
|
|
316
369
|
expect(result.details[0]).toMatchObject({
|
|
317
370
|
integrationId: 'int-1',
|
|
318
371
|
action: 'failed',
|
|
319
|
-
reason: 'Instantiation failed'
|
|
372
|
+
reason: 'Instantiation failed',
|
|
320
373
|
});
|
|
321
374
|
});
|
|
322
375
|
});
|
|
@@ -338,12 +391,12 @@ describe('OAuthTokenRefreshScript', () => {
|
|
|
338
391
|
// This test validates the method can be called directly
|
|
339
392
|
const integration = {
|
|
340
393
|
id: 'int-1',
|
|
341
|
-
config: {}
|
|
394
|
+
config: {},
|
|
342
395
|
};
|
|
343
396
|
|
|
344
397
|
const result = await script.processIntegration(integration, {
|
|
345
398
|
expiryThresholdHours: 24,
|
|
346
|
-
dryRun: false
|
|
399
|
+
dryRun: false,
|
|
347
400
|
});
|
|
348
401
|
|
|
349
402
|
expect(result).toHaveProperty('integrationId');
|
package/src/builtins/index.js
CHANGED
|
@@ -7,10 +7,7 @@ const { IntegrationHealthCheckScript } = require('./integration-health-check');
|
|
|
7
7
|
* These scripts ship with @friggframework/admin-scripts and provide
|
|
8
8
|
* common maintenance and monitoring functionality.
|
|
9
9
|
*/
|
|
10
|
-
const builtinScripts = [
|
|
11
|
-
OAuthTokenRefreshScript,
|
|
12
|
-
IntegrationHealthCheckScript,
|
|
13
|
-
];
|
|
10
|
+
const builtinScripts = [OAuthTokenRefreshScript, IntegrationHealthCheckScript];
|
|
14
11
|
|
|
15
12
|
/**
|
|
16
13
|
* Register all built-in scripts with a factory
|
|
@@ -21,24 +21,25 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
21
21
|
integrationIds: {
|
|
22
22
|
type: 'array',
|
|
23
23
|
items: { type: 'string' },
|
|
24
|
-
description:
|
|
24
|
+
description:
|
|
25
|
+
'Specific integration IDs to check (optional, defaults to all)',
|
|
25
26
|
},
|
|
26
27
|
checkCredentials: {
|
|
27
28
|
type: 'boolean',
|
|
28
29
|
default: true,
|
|
29
|
-
description: 'Verify credential validity'
|
|
30
|
+
description: 'Verify credential validity',
|
|
30
31
|
},
|
|
31
32
|
checkConnectivity: {
|
|
32
33
|
type: 'boolean',
|
|
33
34
|
default: true,
|
|
34
|
-
description: 'Test API connectivity'
|
|
35
|
+
description: 'Test API connectivity',
|
|
35
36
|
},
|
|
36
37
|
updateStatus: {
|
|
37
38
|
type: 'boolean',
|
|
38
39
|
default: false,
|
|
39
|
-
description: 'Update integration status based on health'
|
|
40
|
-
}
|
|
41
|
-
}
|
|
40
|
+
description: 'Update integration status based on health',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
42
43
|
},
|
|
43
44
|
|
|
44
45
|
outputSchema: {
|
|
@@ -47,18 +48,17 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
47
48
|
healthy: { type: 'number' },
|
|
48
49
|
unhealthy: { type: 'number' },
|
|
49
50
|
unknown: { type: 'number' },
|
|
50
|
-
results: { type: 'array' }
|
|
51
|
-
}
|
|
51
|
+
results: { type: 'array' },
|
|
52
|
+
},
|
|
52
53
|
},
|
|
53
54
|
|
|
54
55
|
config: {
|
|
55
56
|
timeout: 900000, // 15 minutes
|
|
56
|
-
maxRetries: 0,
|
|
57
57
|
requireIntegrationInstance: true,
|
|
58
58
|
},
|
|
59
59
|
|
|
60
60
|
schedule: {
|
|
61
|
-
enabled: false,
|
|
61
|
+
enabled: false, // Can be enabled via API
|
|
62
62
|
cronExpression: 'cron(0 6 * * ? *)', // Daily at 6 AM UTC
|
|
63
63
|
},
|
|
64
64
|
|
|
@@ -73,40 +73,47 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
73
73
|
integrationIds = null,
|
|
74
74
|
checkCredentials = true,
|
|
75
75
|
checkConnectivity = true,
|
|
76
|
-
updateStatus = false
|
|
76
|
+
updateStatus = false,
|
|
77
77
|
} = params;
|
|
78
78
|
|
|
79
79
|
const summary = {
|
|
80
80
|
healthy: 0,
|
|
81
81
|
unhealthy: 0,
|
|
82
82
|
unknown: 0,
|
|
83
|
-
results: []
|
|
83
|
+
results: [],
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
this.context.log('info', 'Starting integration health check', {
|
|
87
87
|
checkCredentials,
|
|
88
88
|
checkConnectivity,
|
|
89
89
|
updateStatus,
|
|
90
|
-
specificIds: integrationIds?.length || 'all'
|
|
90
|
+
specificIds: integrationIds?.length || 'all',
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
// Get integrations to check
|
|
94
94
|
let integrations;
|
|
95
95
|
if (integrationIds && integrationIds.length > 0) {
|
|
96
96
|
integrations = await Promise.all(
|
|
97
|
-
integrationIds.map(
|
|
97
|
+
integrationIds.map((id) =>
|
|
98
|
+
this.context.integrationRepository
|
|
99
|
+
.findIntegrationById(id)
|
|
100
|
+
.catch(() => null)
|
|
101
|
+
)
|
|
98
102
|
);
|
|
99
103
|
integrations = integrations.filter(Boolean);
|
|
100
104
|
} else {
|
|
101
105
|
integrations = await this.getAllIntegrations();
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
this.context.log(
|
|
108
|
+
this.context.log(
|
|
109
|
+
'info',
|
|
110
|
+
`Checking ${integrations.length} integrations`
|
|
111
|
+
);
|
|
105
112
|
|
|
106
113
|
for (const integration of integrations) {
|
|
107
114
|
const result = await this.checkIntegration(integration, {
|
|
108
115
|
checkCredentials,
|
|
109
|
-
checkConnectivity
|
|
116
|
+
checkConnectivity,
|
|
110
117
|
});
|
|
111
118
|
|
|
112
119
|
summary.results.push(result);
|
|
@@ -122,13 +129,24 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
122
129
|
// Optionally update integration status
|
|
123
130
|
if (updateStatus && result.status !== 'unknown') {
|
|
124
131
|
try {
|
|
125
|
-
const newStatus =
|
|
126
|
-
|
|
127
|
-
this.context.
|
|
132
|
+
const newStatus =
|
|
133
|
+
result.status === 'healthy' ? 'ACTIVE' : 'ERROR';
|
|
134
|
+
await this.context.integrationRepository.updateIntegrationStatus(
|
|
135
|
+
integration.id,
|
|
136
|
+
newStatus
|
|
137
|
+
);
|
|
138
|
+
this.context.log(
|
|
139
|
+
'info',
|
|
140
|
+
`Updated status for ${integration.id} to ${newStatus}`
|
|
141
|
+
);
|
|
128
142
|
} catch (error) {
|
|
129
|
-
this.context.log(
|
|
130
|
-
|
|
131
|
-
|
|
143
|
+
this.context.log(
|
|
144
|
+
'warn',
|
|
145
|
+
`Failed to update status for ${integration.id}`,
|
|
146
|
+
{
|
|
147
|
+
error: error.message,
|
|
148
|
+
}
|
|
149
|
+
);
|
|
132
150
|
}
|
|
133
151
|
}
|
|
134
152
|
}
|
|
@@ -136,7 +154,7 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
136
154
|
this.context.log('info', 'Health check completed', {
|
|
137
155
|
healthy: summary.healthy,
|
|
138
156
|
unhealthy: summary.unhealthy,
|
|
139
|
-
unknown: summary.unknown
|
|
157
|
+
unknown: summary.unknown,
|
|
140
158
|
});
|
|
141
159
|
|
|
142
160
|
return summary;
|
|
@@ -151,7 +169,10 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
151
169
|
const result = this._createCheckResult(integration);
|
|
152
170
|
|
|
153
171
|
try {
|
|
154
|
-
await this._runChecks(integration, result, {
|
|
172
|
+
await this._runChecks(integration, result, {
|
|
173
|
+
checkCredentials,
|
|
174
|
+
checkConnectivity,
|
|
175
|
+
});
|
|
155
176
|
this._determineOverallStatus(result);
|
|
156
177
|
} catch (error) {
|
|
157
178
|
this._handleCheckError(integration, result, error);
|
|
@@ -170,7 +191,7 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
170
191
|
integrationType: integration.config?.type || 'unknown',
|
|
171
192
|
status: 'unknown',
|
|
172
193
|
checks: {},
|
|
173
|
-
issues: []
|
|
194
|
+
issues: [],
|
|
174
195
|
};
|
|
175
196
|
}
|
|
176
197
|
|
|
@@ -182,11 +203,19 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
182
203
|
const { checkCredentials, checkConnectivity } = options;
|
|
183
204
|
|
|
184
205
|
if (checkCredentials) {
|
|
185
|
-
this._addCheckResult(
|
|
206
|
+
this._addCheckResult(
|
|
207
|
+
result,
|
|
208
|
+
'credentials',
|
|
209
|
+
this.checkCredentialValidity(integration)
|
|
210
|
+
);
|
|
186
211
|
}
|
|
187
212
|
|
|
188
213
|
if (checkConnectivity) {
|
|
189
|
-
this._addCheckResult(
|
|
214
|
+
this._addCheckResult(
|
|
215
|
+
result,
|
|
216
|
+
'connectivity',
|
|
217
|
+
await this.checkApiConnectivity(integration)
|
|
218
|
+
);
|
|
190
219
|
}
|
|
191
220
|
}
|
|
192
221
|
|
|
@@ -214,9 +243,13 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
214
243
|
* @private
|
|
215
244
|
*/
|
|
216
245
|
_handleCheckError(integration, result, error) {
|
|
217
|
-
this.context.log(
|
|
218
|
-
error
|
|
219
|
-
|
|
246
|
+
this.context.log(
|
|
247
|
+
'error',
|
|
248
|
+
`Error checking integration ${integration.id}`,
|
|
249
|
+
{
|
|
250
|
+
error: error.message,
|
|
251
|
+
}
|
|
252
|
+
);
|
|
220
253
|
result.status = 'unknown';
|
|
221
254
|
result.issues.push(`Check failed: ${error.message}`);
|
|
222
255
|
}
|