@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,712 +0,0 @@
1
- const { IntegrationHealthCheckScript } = require('../integration-health-check');
2
-
3
- describe('IntegrationHealthCheckScript', () => {
4
- describe('Definition', () => {
5
- it('should have correct name and metadata', () => {
6
- expect(IntegrationHealthCheckScript.Definition.name).toBe(
7
- 'integration-health-check'
8
- );
9
- expect(IntegrationHealthCheckScript.Definition.version).toBe(
10
- '1.0.0'
11
- );
12
- expect(IntegrationHealthCheckScript.Definition.source).toBe(
13
- 'BUILTIN'
14
- );
15
- expect(
16
- IntegrationHealthCheckScript.Definition.config
17
- .requireIntegrationInstance
18
- ).toBe(true);
19
- });
20
-
21
- it('should have valid input schema', () => {
22
- const schema = IntegrationHealthCheckScript.Definition.inputSchema;
23
- expect(schema.type).toBe('object');
24
- expect(schema.properties.integrationIds).toBeDefined();
25
- expect(schema.properties.checkCredentials).toBeDefined();
26
- expect(schema.properties.checkConnectivity).toBeDefined();
27
- expect(schema.properties.updateStatus).toBeDefined();
28
- });
29
-
30
- it('should have valid output schema', () => {
31
- const schema = IntegrationHealthCheckScript.Definition.outputSchema;
32
- expect(schema.type).toBe('object');
33
- expect(schema.properties.healthy).toBeDefined();
34
- expect(schema.properties.unhealthy).toBeDefined();
35
- expect(schema.properties.unknown).toBeDefined();
36
- expect(schema.properties.results).toBeDefined();
37
- });
38
-
39
- it('should have schedule configuration', () => {
40
- const schedule = IntegrationHealthCheckScript.Definition.schedule;
41
- expect(schedule).toBeDefined();
42
- expect(schedule.enabled).toBe(false);
43
- expect(schedule.cronExpression).toBe('cron(0 6 * * ? *)');
44
- });
45
-
46
- it('should have appropriate timeout configuration', () => {
47
- expect(IntegrationHealthCheckScript.Definition.config.timeout).toBe(
48
- 900000
49
- ); // 15 minutes
50
- });
51
-
52
- it('should have clean display object', () => {
53
- // Display should only have UI-specific fields
54
- expect(
55
- IntegrationHealthCheckScript.Definition.display.category
56
- ).toBe('maintenance');
57
- // Should NOT have redundant label/description - they're derived from top-level
58
- });
59
- });
60
-
61
- describe('execute()', () => {
62
- let script;
63
- let mockContext;
64
-
65
- beforeEach(() => {
66
- mockContext = {
67
- log: jest.fn(),
68
- integrationRepository: {
69
- findIntegrations: jest.fn(),
70
- findIntegrationById: jest.fn(),
71
- updateIntegrationStatus: jest.fn(),
72
- },
73
- instantiate: jest.fn(),
74
- };
75
- script = new IntegrationHealthCheckScript({ context: mockContext });
76
- });
77
-
78
- it('should return empty results when no integrations found', async () => {
79
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
80
- []
81
- );
82
-
83
- const result = await script.execute({});
84
-
85
- expect(result.healthy).toBe(0);
86
- expect(result.unhealthy).toBe(0);
87
- expect(result.unknown).toBe(0);
88
- expect(result.results).toEqual([]);
89
- });
90
-
91
- it('should return healthy for valid integrations', async () => {
92
- const integration = {
93
- id: 'int-1',
94
- config: {
95
- type: 'hubspot',
96
- credentials: {
97
- access_token: 'token123',
98
- expires_at: new Date(
99
- Date.now() + 24 * 60 * 60 * 1000
100
- ).toISOString(),
101
- },
102
- },
103
- };
104
-
105
- const mockInstance = {
106
- primary: {
107
- api: {
108
- getAuthenticationInfo: jest
109
- .fn()
110
- .mockResolvedValue({ user: 'test' }),
111
- },
112
- },
113
- };
114
-
115
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
116
- [integration]
117
- );
118
- mockContext.instantiate.mockResolvedValue(mockInstance);
119
-
120
- const result = await script.execute({
121
- checkCredentials: true,
122
- checkConnectivity: true,
123
- });
124
-
125
- expect(result.healthy).toBe(1);
126
- expect(result.unhealthy).toBe(0);
127
- expect(result.results[0]).toMatchObject({
128
- integrationId: 'int-1',
129
- status: 'healthy',
130
- issues: [],
131
- });
132
- expect(
133
- mockInstance.primary.api.getAuthenticationInfo
134
- ).toHaveBeenCalled();
135
- });
136
-
137
- it('should return unhealthy for missing access token', async () => {
138
- const integration = {
139
- id: 'int-1',
140
- config: {
141
- type: 'hubspot',
142
- credentials: {}, // No access_token
143
- },
144
- };
145
-
146
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
147
- [integration]
148
- );
149
-
150
- const result = await script.execute({
151
- checkCredentials: true,
152
- checkConnectivity: false,
153
- });
154
-
155
- expect(result.healthy).toBe(0);
156
- expect(result.unhealthy).toBe(1);
157
- expect(result.results[0]).toMatchObject({
158
- integrationId: 'int-1',
159
- status: 'unhealthy',
160
- issues: ['Missing access token'],
161
- });
162
- });
163
-
164
- it('should return unhealthy for expired credentials', async () => {
165
- const pastDate = new Date(Date.now() - 24 * 60 * 60 * 1000); // 24 hours ago
166
- const integration = {
167
- id: 'int-1',
168
- config: {
169
- type: 'hubspot',
170
- credentials: {
171
- access_token: 'token123',
172
- expires_at: pastDate.toISOString(),
173
- },
174
- },
175
- };
176
-
177
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
178
- [integration]
179
- );
180
-
181
- const result = await script.execute({
182
- checkCredentials: true,
183
- checkConnectivity: false,
184
- });
185
-
186
- expect(result.unhealthy).toBe(1);
187
- expect(result.results[0]).toMatchObject({
188
- integrationId: 'int-1',
189
- status: 'unhealthy',
190
- issues: ['Access token expired'],
191
- });
192
- });
193
-
194
- it('should return unhealthy for connectivity failures', async () => {
195
- const integration = {
196
- id: 'int-1',
197
- config: {
198
- type: 'hubspot',
199
- credentials: {
200
- access_token: 'token123',
201
- expires_at: new Date(
202
- Date.now() + 24 * 60 * 60 * 1000
203
- ).toISOString(),
204
- },
205
- },
206
- };
207
-
208
- const mockInstance = {
209
- primary: {
210
- api: {
211
- getAuthenticationInfo: jest
212
- .fn()
213
- .mockRejectedValue(new Error('Network error')),
214
- },
215
- },
216
- };
217
-
218
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
219
- [integration]
220
- );
221
- mockContext.instantiate.mockResolvedValue(mockInstance);
222
-
223
- const result = await script.execute({
224
- checkCredentials: true,
225
- checkConnectivity: true,
226
- });
227
-
228
- expect(result.unhealthy).toBe(1);
229
- expect(result.results[0].status).toBe('unhealthy');
230
- expect(result.results[0].issues).toContainEqual(
231
- expect.stringContaining('API connectivity failed')
232
- );
233
- });
234
-
235
- it('should update integration status when updateStatus is true', async () => {
236
- const integration = {
237
- id: 'int-1',
238
- config: {
239
- type: 'hubspot',
240
- credentials: {
241
- access_token: 'token123',
242
- expires_at: new Date(
243
- Date.now() + 24 * 60 * 60 * 1000
244
- ).toISOString(),
245
- },
246
- },
247
- };
248
-
249
- const mockInstance = {
250
- primary: {
251
- api: {
252
- getAuthenticationInfo: jest
253
- .fn()
254
- .mockResolvedValue({ user: 'test' }),
255
- },
256
- },
257
- };
258
-
259
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
260
- [integration]
261
- );
262
- mockContext.instantiate.mockResolvedValue(mockInstance);
263
- mockContext.integrationRepository.updateIntegrationStatus.mockResolvedValue(
264
- undefined
265
- );
266
-
267
- const result = await script.execute({
268
- checkCredentials: true,
269
- checkConnectivity: true,
270
- updateStatus: true,
271
- });
272
-
273
- expect(result.healthy).toBe(1);
274
- expect(
275
- mockContext.integrationRepository.updateIntegrationStatus
276
- ).toHaveBeenCalledWith('int-1', 'ACTIVE');
277
- });
278
-
279
- it('should update integration status to ERROR for unhealthy integrations', async () => {
280
- const integration = {
281
- id: 'int-1',
282
- config: {
283
- type: 'hubspot',
284
- credentials: {}, // Missing credentials
285
- },
286
- };
287
-
288
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
289
- [integration]
290
- );
291
- mockContext.integrationRepository.updateIntegrationStatus.mockResolvedValue(
292
- undefined
293
- );
294
-
295
- const result = await script.execute({
296
- checkCredentials: true,
297
- checkConnectivity: false,
298
- updateStatus: true,
299
- });
300
-
301
- expect(result.unhealthy).toBe(1);
302
- expect(
303
- mockContext.integrationRepository.updateIntegrationStatus
304
- ).toHaveBeenCalledWith('int-1', 'ERROR');
305
- });
306
-
307
- it('should not update status when updateStatus is false', async () => {
308
- const integration = {
309
- id: 'int-1',
310
- config: {
311
- type: 'hubspot',
312
- credentials: {
313
- access_token: 'token123',
314
- expires_at: new Date(
315
- Date.now() + 24 * 60 * 60 * 1000
316
- ).toISOString(),
317
- },
318
- },
319
- };
320
-
321
- const mockInstance = {
322
- primary: {
323
- api: {
324
- getAuthenticationInfo: jest
325
- .fn()
326
- .mockResolvedValue({ user: 'test' }),
327
- },
328
- },
329
- };
330
-
331
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
332
- [integration]
333
- );
334
- mockContext.instantiate.mockResolvedValue(mockInstance);
335
-
336
- await script.execute({
337
- checkCredentials: true,
338
- checkConnectivity: true,
339
- updateStatus: false,
340
- });
341
-
342
- expect(
343
- mockContext.integrationRepository.updateIntegrationStatus
344
- ).not.toHaveBeenCalled();
345
- });
346
-
347
- it('should handle status update failures gracefully', async () => {
348
- const integration = {
349
- id: 'int-1',
350
- config: {
351
- type: 'hubspot',
352
- credentials: {
353
- access_token: 'token123',
354
- expires_at: new Date(
355
- Date.now() + 24 * 60 * 60 * 1000
356
- ).toISOString(),
357
- },
358
- },
359
- };
360
-
361
- const mockInstance = {
362
- primary: {
363
- api: {
364
- getAuthenticationInfo: jest
365
- .fn()
366
- .mockResolvedValue({ user: 'test' }),
367
- },
368
- },
369
- };
370
-
371
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
372
- [integration]
373
- );
374
- mockContext.instantiate.mockResolvedValue(mockInstance);
375
- mockContext.integrationRepository.updateIntegrationStatus.mockRejectedValue(
376
- new Error('Update failed')
377
- );
378
-
379
- const result = await script.execute({
380
- checkCredentials: true,
381
- checkConnectivity: true,
382
- updateStatus: true,
383
- });
384
-
385
- expect(result.healthy).toBe(1); // Should still report healthy
386
- expect(mockContext.log).toHaveBeenCalledWith(
387
- 'warn',
388
- expect.stringContaining('Failed to update status'),
389
- expect.any(Object)
390
- );
391
- });
392
-
393
- it('should filter by specific integration IDs', async () => {
394
- const integration1 = {
395
- id: 'int-1',
396
- config: {
397
- type: 'hubspot',
398
- credentials: { access_token: 'token1' },
399
- },
400
- };
401
- const integration2 = {
402
- id: 'int-2',
403
- config: {
404
- type: 'salesforce',
405
- credentials: { access_token: 'token2' },
406
- },
407
- };
408
-
409
- mockContext.integrationRepository.findIntegrationById.mockImplementation(
410
- (id) => {
411
- if (id === 'int-1') return Promise.resolve(integration1);
412
- if (id === 'int-2') return Promise.resolve(integration2);
413
- return Promise.reject(new Error('Not found'));
414
- }
415
- );
416
-
417
- const result = await script.execute({
418
- integrationIds: ['int-1', 'int-2'],
419
- checkCredentials: true,
420
- checkConnectivity: false,
421
- });
422
-
423
- expect(
424
- mockContext.integrationRepository.findIntegrationById
425
- ).toHaveBeenCalledWith('int-1');
426
- expect(
427
- mockContext.integrationRepository.findIntegrationById
428
- ).toHaveBeenCalledWith('int-2');
429
- expect(
430
- mockContext.integrationRepository.findIntegrations
431
- ).not.toHaveBeenCalled();
432
- expect(result.results).toHaveLength(2);
433
- });
434
-
435
- it('should handle errors when checking integrations', async () => {
436
- const integration = {
437
- id: 'int-1',
438
- config: {
439
- type: 'hubspot',
440
- credentials: {
441
- access_token: 'token123',
442
- expires_at: new Date(
443
- Date.now() + 24 * 60 * 60 * 1000
444
- ).toISOString(),
445
- },
446
- },
447
- };
448
-
449
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
450
- [integration]
451
- );
452
- mockContext.instantiate.mockRejectedValue(
453
- new Error('Instantiation failed')
454
- );
455
-
456
- const result = await script.execute({
457
- checkCredentials: true,
458
- checkConnectivity: true,
459
- });
460
-
461
- // Should still complete but mark as unknown or unhealthy
462
- expect(result.results).toHaveLength(1);
463
- expect(result.results[0].integrationId).toBe('int-1');
464
- });
465
-
466
- it('should skip credential check when checkCredentials is false', async () => {
467
- const integration = {
468
- id: 'int-1',
469
- config: {
470
- type: 'hubspot',
471
- credentials: {}, // Missing credentials, but check is disabled
472
- },
473
- };
474
-
475
- const mockInstance = {
476
- primary: {
477
- api: {
478
- getAuthenticationInfo: jest
479
- .fn()
480
- .mockResolvedValue({ user: 'test' }),
481
- },
482
- },
483
- };
484
-
485
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
486
- [integration]
487
- );
488
- mockContext.instantiate.mockResolvedValue(mockInstance);
489
-
490
- const result = await script.execute({
491
- checkCredentials: false,
492
- checkConnectivity: true,
493
- });
494
-
495
- expect(result.results[0].checks.credentials).toBeUndefined();
496
- expect(result.results[0].checks.connectivity).toBeDefined();
497
- });
498
-
499
- it('should skip connectivity check when checkConnectivity is false', async () => {
500
- const integration = {
501
- id: 'int-1',
502
- config: {
503
- type: 'hubspot',
504
- credentials: {
505
- access_token: 'token123',
506
- expires_at: new Date(
507
- Date.now() + 24 * 60 * 60 * 1000
508
- ).toISOString(),
509
- },
510
- },
511
- };
512
-
513
- mockContext.integrationRepository.findIntegrations.mockResolvedValue(
514
- [integration]
515
- );
516
-
517
- const result = await script.execute({
518
- checkCredentials: true,
519
- checkConnectivity: false,
520
- });
521
-
522
- expect(result.results[0].checks.credentials).toBeDefined();
523
- expect(result.results[0].checks.connectivity).toBeUndefined();
524
- expect(mockContext.instantiate).not.toHaveBeenCalled();
525
- });
526
- });
527
-
528
- describe('checkCredentialValidity()', () => {
529
- let script;
530
-
531
- beforeEach(() => {
532
- script = new IntegrationHealthCheckScript({
533
- context: { log: jest.fn() },
534
- });
535
- });
536
-
537
- it('should return valid for integrations with valid credentials', () => {
538
- const integration = {
539
- id: 'int-1',
540
- config: {
541
- credentials: {
542
- access_token: 'token123',
543
- expires_at: new Date(
544
- Date.now() + 24 * 60 * 60 * 1000
545
- ).toISOString(),
546
- },
547
- },
548
- };
549
-
550
- const result = script.checkCredentialValidity(integration);
551
-
552
- expect(result.valid).toBe(true);
553
- expect(result.issue).toBeNull();
554
- });
555
-
556
- it('should return invalid for missing access token', () => {
557
- const integration = {
558
- id: 'int-1',
559
- config: {
560
- credentials: {},
561
- },
562
- };
563
-
564
- const result = script.checkCredentialValidity(integration);
565
-
566
- expect(result.valid).toBe(false);
567
- expect(result.issue).toBe('Missing access token');
568
- });
569
-
570
- it('should return invalid for expired tokens', () => {
571
- const integration = {
572
- id: 'int-1',
573
- config: {
574
- credentials: {
575
- access_token: 'token123',
576
- expires_at: new Date(Date.now() - 1000).toISOString(), // Expired
577
- },
578
- },
579
- };
580
-
581
- const result = script.checkCredentialValidity(integration);
582
-
583
- expect(result.valid).toBe(false);
584
- expect(result.issue).toBe('Access token expired');
585
- });
586
-
587
- it('should return valid for credentials without expiry', () => {
588
- const integration = {
589
- id: 'int-1',
590
- config: {
591
- credentials: {
592
- access_token: 'token123',
593
- // No expires_at
594
- },
595
- },
596
- };
597
-
598
- const result = script.checkCredentialValidity(integration);
599
-
600
- expect(result.valid).toBe(true);
601
- expect(result.issue).toBeNull();
602
- });
603
- });
604
-
605
- describe('checkApiConnectivity()', () => {
606
- let script;
607
- let mockContext;
608
-
609
- beforeEach(() => {
610
- mockContext = {
611
- log: jest.fn(),
612
- instantiate: jest.fn(),
613
- };
614
- script = new IntegrationHealthCheckScript({ context: mockContext });
615
- });
616
-
617
- it('should return valid for successful API calls', async () => {
618
- const integration = {
619
- id: 'int-1',
620
- config: { type: 'hubspot' },
621
- };
622
-
623
- const mockInstance = {
624
- primary: {
625
- api: {
626
- getAuthenticationInfo: jest
627
- .fn()
628
- .mockResolvedValue({ user: 'test' }),
629
- },
630
- },
631
- };
632
-
633
- mockContext.instantiate.mockResolvedValue(mockInstance);
634
-
635
- const result = await script.checkApiConnectivity(integration);
636
-
637
- expect(result.valid).toBe(true);
638
- expect(result.issue).toBeNull();
639
- expect(result.responseTime).toBeGreaterThanOrEqual(0);
640
- });
641
-
642
- it('should try getCurrentUser if getAuthenticationInfo is not available', async () => {
643
- const integration = {
644
- id: 'int-1',
645
- config: { type: 'hubspot' },
646
- };
647
-
648
- const mockInstance = {
649
- primary: {
650
- api: {
651
- getCurrentUser: jest
652
- .fn()
653
- .mockResolvedValue({ user: 'test' }),
654
- },
655
- },
656
- };
657
-
658
- mockContext.instantiate.mockResolvedValue(mockInstance);
659
-
660
- const result = await script.checkApiConnectivity(integration);
661
-
662
- expect(result.valid).toBe(true);
663
- expect(mockInstance.primary.api.getCurrentUser).toHaveBeenCalled();
664
- });
665
-
666
- it('should return note when no health check endpoint is available', async () => {
667
- const integration = {
668
- id: 'int-1',
669
- config: { type: 'hubspot' },
670
- };
671
-
672
- const mockInstance = {
673
- primary: {
674
- api: {}, // No health check methods
675
- },
676
- };
677
-
678
- mockContext.instantiate.mockResolvedValue(mockInstance);
679
-
680
- const result = await script.checkApiConnectivity(integration);
681
-
682
- expect(result.valid).toBe(true);
683
- expect(result.issue).toBeNull();
684
- expect(result.note).toBe('No health check endpoint available');
685
- });
686
-
687
- it('should return invalid for API failures', async () => {
688
- const integration = {
689
- id: 'int-1',
690
- config: { type: 'hubspot' },
691
- };
692
-
693
- const mockInstance = {
694
- primary: {
695
- api: {
696
- getAuthenticationInfo: jest
697
- .fn()
698
- .mockRejectedValue(new Error('Network error')),
699
- },
700
- },
701
- };
702
-
703
- mockContext.instantiate.mockResolvedValue(mockInstance);
704
-
705
- const result = await script.checkApiConnectivity(integration);
706
-
707
- expect(result.valid).toBe(false);
708
- expect(result.issue).toContain('API connectivity failed');
709
- expect(result.issue).toContain('Network error');
710
- });
711
- });
712
- });