@friggframework/devtools 2.0.0--canary.428.d004aeb.0 → 2.0.0--canary.428.1a6e465.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.
@@ -9,7 +9,8 @@ const {
9
9
  DescribeSecurityGroupsCommand,
10
10
  DescribeRouteTablesCommand,
11
11
  DescribeNatGatewaysCommand,
12
- DescribeAddressesCommand
12
+ DescribeAddressesCommand,
13
+ DescribeInternetGatewaysCommand,
13
14
  } = require('@aws-sdk/client-ec2');
14
15
  const {
15
16
  KMSClient,
@@ -77,6 +78,18 @@ describe('AWSDiscovery', () => {
77
78
  expect(vpc).toEqual(mockVpc);
78
79
  });
79
80
 
81
+ it('should retry without default filter when default VPC query returns empty', async () => {
82
+ const mockVpc = { VpcId: 'vpc-23456789', IsDefault: false };
83
+
84
+ ec2Mock
85
+ .on(DescribeVpcsCommand)
86
+ .resolvesOnce({ Vpcs: [] })
87
+ .resolves({ Vpcs: [mockVpc] });
88
+
89
+ const vpc = await discovery.findDefaultVpc();
90
+ expect(vpc).toEqual(mockVpc);
91
+ });
92
+
80
93
  it('should throw error when no VPCs found', async () => {
81
94
  ec2Mock.on(DescribeVpcsCommand).resolves({
82
95
  Vpcs: []
@@ -127,6 +140,39 @@ describe('AWSDiscovery', () => {
127
140
  const isPrivate = await discovery.isSubnetPrivate(mockSubnetId, mockVpcId);
128
141
  expect(isPrivate).toBe(false);
129
142
  });
143
+
144
+ it('should default to private when no route table found', async () => {
145
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
146
+ Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
147
+ });
148
+
149
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({ RouteTables: [] });
150
+
151
+ const isPrivate = await discovery.isSubnetPrivate(mockSubnetId, mockVpcId);
152
+ expect(isPrivate).toBe(true);
153
+ });
154
+
155
+ it('should default to private when AWS throws describing subnet', async () => {
156
+ ec2Mock.on(DescribeSubnetsCommand).rejects(new Error('boom'));
157
+
158
+ const isPrivate = await discovery.isSubnetPrivate(mockSubnetId, mockVpcId);
159
+ expect(isPrivate).toBe(true);
160
+ });
161
+
162
+ it('should log warning when subnet cannot be found', async () => {
163
+ ec2Mock.on(DescribeSubnetsCommand).resolves({ Subnets: [] });
164
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
165
+
166
+ const isPrivate = await discovery.isSubnetPrivate(mockSubnetId);
167
+
168
+ expect(isPrivate).toBe(true);
169
+ expect(warnSpy).toHaveBeenCalledWith(
170
+ `Could not determine if subnet ${mockSubnetId} is private:`,
171
+ expect.any(Error)
172
+ );
173
+
174
+ warnSpy.mockRestore();
175
+ });
130
176
  });
131
177
 
132
178
  describe('findPrivateSubnets', () => {
@@ -159,6 +205,26 @@ describe('AWSDiscovery', () => {
159
205
  expect(subnets).toEqual(mockSubnets);
160
206
  });
161
207
 
208
+ it('should duplicate single private subnet when only one available', async () => {
209
+ const mockSubnets = [
210
+ { SubnetId: 'subnet-private-1', AvailabilityZone: 'us-east-1a' },
211
+ { SubnetId: 'subnet-public-1', AvailabilityZone: 'us-east-1b' }
212
+ ];
213
+
214
+ ec2Mock.on(DescribeSubnetsCommand).resolves({ Subnets: mockSubnets });
215
+
216
+ jest
217
+ .spyOn(discovery, 'isSubnetPrivate')
218
+ .mockResolvedValueOnce(true)
219
+ .mockResolvedValueOnce(false);
220
+
221
+ const subnets = await discovery.findPrivateSubnets(mockVpcId);
222
+ expect(subnets.map((s) => s.SubnetId)).toEqual([
223
+ 'subnet-private-1',
224
+ 'subnet-public-1'
225
+ ]);
226
+ });
227
+
162
228
  it('should throw error when no private subnets found and autoConvert is false', async () => {
163
229
  const mockSubnets = [
164
230
  { SubnetId: 'subnet-public-1', AvailabilityZone: 'us-east-1a' },
@@ -215,6 +281,31 @@ describe('AWSDiscovery', () => {
215
281
  expect(subnets).toHaveLength(2);
216
282
  expect(subnets[0].SubnetId).toBe('subnet-public-1');
217
283
  });
284
+
285
+ it('should select converted subnets when autoConvert handles three public subnets', async () => {
286
+ const mockSubnets = [
287
+ { SubnetId: 'subnet-public-1' },
288
+ { SubnetId: 'subnet-public-2' },
289
+ { SubnetId: 'subnet-public-3' }
290
+ ];
291
+
292
+ ec2Mock.on(DescribeSubnetsCommand).resolves({ Subnets: mockSubnets });
293
+ jest.spyOn(discovery, 'isSubnetPrivate').mockResolvedValue(false);
294
+
295
+ const subnets = await discovery.findPrivateSubnets(mockVpcId, true);
296
+ expect(subnets.map((s) => s.SubnetId)).toEqual([
297
+ 'subnet-public-2',
298
+ 'subnet-public-3'
299
+ ]);
300
+ });
301
+
302
+ it('should throw when AWS returns no subnets', async () => {
303
+ ec2Mock.on(DescribeSubnetsCommand).resolves({ Subnets: [] });
304
+
305
+ await expect(discovery.findPrivateSubnets(mockVpcId)).rejects.toThrow(
306
+ `No subnets found in VPC ${mockVpcId}`
307
+ );
308
+ });
218
309
  });
219
310
 
220
311
  describe('findPublicSubnets', () => {
@@ -246,6 +337,21 @@ describe('AWSDiscovery', () => {
246
337
  await expect(discovery.findPublicSubnets(mockVpcId))
247
338
  .rejects.toThrow('No subnets found in VPC');
248
339
  });
340
+
341
+ it('should return null when no public subnets identified', async () => {
342
+ const mockSubnet = { SubnetId: 'subnet-private-1' };
343
+ ec2Mock.on(DescribeSubnetsCommand).resolves({ Subnets: [mockSubnet] });
344
+
345
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
346
+ RouteTables: [{
347
+ Associations: [{ SubnetId: 'subnet-private-1' }],
348
+ Routes: [{ GatewayId: 'local' }]
349
+ }]
350
+ });
351
+
352
+ const subnet = await discovery.findPublicSubnets(mockVpcId);
353
+ expect(subnet).toBeNull();
354
+ });
249
355
  });
250
356
 
251
357
  describe('findDefaultSecurityGroup', () => {
@@ -257,14 +363,29 @@ describe('AWSDiscovery', () => {
257
363
  GroupName: 'default'
258
364
  };
259
365
 
260
- ec2Mock.on(DescribeSecurityGroupsCommand).resolves({
261
- SecurityGroups: [mockSecurityGroup]
262
- });
366
+ ec2Mock
367
+ .on(DescribeSecurityGroupsCommand)
368
+ .resolvesOnce({ SecurityGroups: [] })
369
+ .resolves({ SecurityGroups: [mockSecurityGroup] });
263
370
 
264
371
  const sg = await discovery.findDefaultSecurityGroup(mockVpcId);
265
372
  expect(sg).toEqual(mockSecurityGroup);
266
373
  });
267
374
 
375
+ it('should prefer Frigg-managed security group when present', async () => {
376
+ const friggSg = {
377
+ GroupId: 'sg-frigg',
378
+ GroupName: 'frigg-lambda-sg',
379
+ };
380
+
381
+ ec2Mock
382
+ .on(DescribeSecurityGroupsCommand)
383
+ .resolves({ SecurityGroups: [friggSg] });
384
+
385
+ const sg = await discovery.findDefaultSecurityGroup(mockVpcId);
386
+ expect(sg).toEqual(friggSg);
387
+ });
388
+
268
389
  it('should throw error when no default security group found', async () => {
269
390
  ec2Mock.on(DescribeSecurityGroupsCommand).resolves({
270
391
  SecurityGroups: []
@@ -309,6 +430,14 @@ describe('AWSDiscovery', () => {
309
430
  const rt = await discovery.findPrivateRouteTable(mockVpcId);
310
431
  expect(rt).toEqual(mockRouteTable);
311
432
  });
433
+
434
+ it('should throw error when no route tables found', async () => {
435
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({ RouteTables: [] });
436
+
437
+ await expect(
438
+ discovery.findPrivateRouteTable(mockVpcId)
439
+ ).rejects.toThrow(`No route tables found for VPC ${mockVpcId}`);
440
+ });
312
441
  });
313
442
 
314
443
  describe('findDefaultKmsKey', () => {
@@ -340,6 +469,69 @@ describe('AWSDiscovery', () => {
340
469
  const keyArn = await discovery.findDefaultKmsKey();
341
470
  expect(keyArn).toBeNull();
342
471
  });
472
+
473
+ it('should return null when only AWS-managed keys exist', async () => {
474
+ kmsMock.on(ListKeysCommand).resolves({
475
+ Keys: [{ KeyId: 'aws-key' }]
476
+ });
477
+
478
+ kmsMock.on(DescribeKeyCommand).resolves({
479
+ KeyMetadata: {
480
+ Arn: 'arn:aws:kms:us-east-1:123456789012:key/aws-key',
481
+ KeyManager: 'AWS',
482
+ KeyState: 'Enabled'
483
+ }
484
+ });
485
+
486
+ const keyArn = await discovery.findDefaultKmsKey();
487
+ expect(keyArn).toBeNull();
488
+ });
489
+
490
+ it('should skip keys that fail to describe', async () => {
491
+ kmsMock.on(ListKeysCommand).resolves({ Keys: [{ KeyId: 'bad-key' }] });
492
+ kmsMock.on(DescribeKeyCommand).rejects(new Error('describe failure'));
493
+
494
+ const keyArn = await discovery.findDefaultKmsKey();
495
+ expect(keyArn).toBeNull();
496
+ });
497
+
498
+ it('should return null when ListKeys fails', async () => {
499
+ kmsMock.on(ListKeysCommand).rejects(new Error('list failure'));
500
+
501
+ const keyArn = await discovery.findDefaultKmsKey();
502
+ expect(keyArn).toBeNull();
503
+ });
504
+
505
+ it('should skip customer keys pending deletion', async () => {
506
+ const mockKeyId = 'pending-key';
507
+ kmsMock.on(ListKeysCommand).resolves({ Keys: [{ KeyId: mockKeyId }] });
508
+ kmsMock.on(DescribeKeyCommand).resolves({
509
+ KeyMetadata: {
510
+ Arn: `arn:aws:kms:us-east-1:123456789012:key/${mockKeyId}`,
511
+ KeyManager: 'CUSTOMER',
512
+ KeyState: 'PendingDeletion',
513
+ DeletionDate: '2024-01-01T00:00:00Z',
514
+ },
515
+ });
516
+
517
+ const keyArn = await discovery.findDefaultKmsKey();
518
+ expect(keyArn).toBeNull();
519
+ });
520
+
521
+ it('should return null when all customer keys are disabled', async () => {
522
+ const mockKeyId = 'disabled-key';
523
+ kmsMock.on(ListKeysCommand).resolves({ Keys: [{ KeyId: mockKeyId }] });
524
+ kmsMock.on(DescribeKeyCommand).resolves({
525
+ KeyMetadata: {
526
+ Arn: `arn:aws:kms:us-east-1:123456789012:key/${mockKeyId}`,
527
+ KeyManager: 'CUSTOMER',
528
+ KeyState: 'Disabled',
529
+ },
530
+ });
531
+
532
+ const keyArn = await discovery.findDefaultKmsKey();
533
+ expect(keyArn).toBeNull();
534
+ });
343
535
  });
344
536
 
345
537
  describe('findAvailableElasticIP', () => {
@@ -365,6 +557,32 @@ describe('AWSDiscovery', () => {
365
557
  const eip = await discovery.findAvailableElasticIP();
366
558
  expect(eip).toBeNull();
367
559
  });
560
+
561
+ it('should return Frigg-tagged Elastic IP when present', async () => {
562
+ const friggAddress = {
563
+ AllocationId: 'eipalloc-frigg',
564
+ PublicIp: '52.0.0.1',
565
+ NetworkInterfaceId: 'eni-12345',
566
+ Tags: [{ Key: 'Name', Value: 'frigg-shared-ip' }]
567
+ };
568
+
569
+ ec2Mock.on(DescribeAddressesCommand).resolves({
570
+ Addresses: [
571
+ { AllocationId: 'eipalloc-associated', AssociationId: 'assoc-1' },
572
+ friggAddress,
573
+ ]
574
+ });
575
+
576
+ const eip = await discovery.findAvailableElasticIP();
577
+ expect(eip).toEqual(friggAddress);
578
+ });
579
+
580
+ it('should return null when DescribeAddresses fails', async () => {
581
+ ec2Mock.on(DescribeAddressesCommand).rejects(new Error('addr failure'));
582
+
583
+ const eip = await discovery.findAvailableElasticIP();
584
+ expect(eip).toBeNull();
585
+ });
368
586
  });
369
587
 
370
588
  describe('findExistingNatGateway', () => {
@@ -541,6 +759,197 @@ describe('AWSDiscovery', () => {
541
759
  const result = await discovery.findExistingNatGateway(mockVpcId);
542
760
  expect(result).toBeNull();
543
761
  });
762
+
763
+ it('should skip NAT Gateways that are not available', async () => {
764
+ const mockNatGateways = [
765
+ {
766
+ NatGatewayId: 'nat-pending',
767
+ SubnetId: 'subnet-public-1',
768
+ State: 'pending',
769
+ Tags: [],
770
+ },
771
+ ];
772
+
773
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({
774
+ NatGateways: mockNatGateways,
775
+ });
776
+
777
+ const result = await discovery.findExistingNatGateway(mockVpcId);
778
+ expect(result).toBeNull();
779
+ });
780
+
781
+ it('should return null when only non-Frigg NAT Gateways are in private subnets', async () => {
782
+ const mockNatGateways = [
783
+ {
784
+ NatGatewayId: 'nat-private-only',
785
+ SubnetId: 'subnet-private-1',
786
+ State: 'available',
787
+ Tags: [],
788
+ },
789
+ ];
790
+
791
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({
792
+ NatGateways: mockNatGateways,
793
+ });
794
+
795
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
796
+ Subnets: [{ SubnetId: 'subnet-private-1', VpcId: mockVpcId }],
797
+ });
798
+
799
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
800
+ RouteTables: [
801
+ {
802
+ Associations: [{ SubnetId: 'subnet-private-1' }],
803
+ Routes: [{ GatewayId: 'local' }],
804
+ },
805
+ ],
806
+ });
807
+
808
+ const result = await discovery.findExistingNatGateway(mockVpcId);
809
+ expect(result).toBeNull();
810
+ });
811
+
812
+ it('should return null when DescribeNatGateways fails', async () => {
813
+ ec2Mock.on(DescribeNatGatewaysCommand).rejects(new Error('nat failure'));
814
+
815
+ const result = await discovery.findExistingNatGateway(mockVpcId);
816
+ expect(result).toBeNull();
817
+ });
818
+ });
819
+
820
+ describe('findInternetGateway', () => {
821
+ const mockVpcId = 'vpc-12345678';
822
+
823
+ it('should return existing Internet Gateway', async () => {
824
+ const mockIgw = { InternetGatewayId: 'igw-12345' };
825
+
826
+ ec2Mock.on(DescribeInternetGatewaysCommand).resolves({
827
+ InternetGateways: [mockIgw]
828
+ });
829
+
830
+ const igw = await discovery.findInternetGateway(mockVpcId);
831
+ expect(igw).toEqual(mockIgw);
832
+ });
833
+
834
+ it('should return null when no Internet Gateway found', async () => {
835
+ ec2Mock.on(DescribeInternetGatewaysCommand).resolves({ InternetGateways: [] });
836
+
837
+ const igw = await discovery.findInternetGateway(mockVpcId);
838
+ expect(igw).toBeNull();
839
+ });
840
+
841
+ it('should return null when DescribeInternetGateways fails', async () => {
842
+ ec2Mock.on(DescribeInternetGatewaysCommand).rejects(new Error('igw failure'));
843
+
844
+ const igw = await discovery.findInternetGateway(mockVpcId);
845
+ expect(igw).toBeNull();
846
+ });
847
+ });
848
+
849
+ describe('findFriggManagedResources', () => {
850
+ it('should return tagged resources', async () => {
851
+ const mockNat = { NatGatewayId: 'nat-frigg', Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }] };
852
+ const mockEip = { AllocationId: 'eip-frigg', Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }] };
853
+ const mockRouteTable = { RouteTableId: 'rtb-frigg', Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }] };
854
+ const mockSubnet = { SubnetId: 'subnet-frigg', Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }] };
855
+ const mockSg = { GroupId: 'sg-frigg', Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }] };
856
+
857
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({ NatGateways: [mockNat] });
858
+ ec2Mock.on(DescribeAddressesCommand).resolves({ Addresses: [mockEip] });
859
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({ RouteTables: [mockRouteTable] });
860
+ ec2Mock.on(DescribeSubnetsCommand).resolves({ Subnets: [mockSubnet] });
861
+ ec2Mock.on(DescribeSecurityGroupsCommand).resolves({ SecurityGroups: [mockSg] });
862
+
863
+ const result = await discovery.findFriggManagedResources('service', 'stage');
864
+ expect(result).toMatchObject({
865
+ natGateways: [mockNat],
866
+ elasticIps: [mockEip],
867
+ routeTables: [mockRouteTable],
868
+ subnets: [mockSubnet],
869
+ securityGroups: [mockSg],
870
+ });
871
+ });
872
+
873
+ it('should return empty arrays when calls fail', async () => {
874
+ ec2Mock.on(DescribeNatGatewaysCommand).rejects(new Error('error'));
875
+ ec2Mock.on(DescribeAddressesCommand).rejects(new Error('error'));
876
+ ec2Mock.on(DescribeRouteTablesCommand).rejects(new Error('error'));
877
+ ec2Mock.on(DescribeSubnetsCommand).rejects(new Error('error'));
878
+ ec2Mock.on(DescribeSecurityGroupsCommand).rejects(new Error('error'));
879
+
880
+ const result = await discovery.findFriggManagedResources('service', 'stage');
881
+ expect(result).toEqual({
882
+ natGateways: [],
883
+ elasticIps: [],
884
+ routeTables: [],
885
+ subnets: [],
886
+ securityGroups: [],
887
+ });
888
+ });
889
+ });
890
+
891
+ describe('detectMisconfiguredResources', () => {
892
+ const mockVpcId = 'vpc-12345678';
893
+
894
+ it('should capture misconfigured resources', async () => {
895
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({
896
+ NatGateways: [
897
+ { NatGatewayId: 'nat-private', SubnetId: 'subnet-private', State: 'available' },
898
+ ],
899
+ });
900
+
901
+ ec2Mock.on(DescribeAddressesCommand).resolves({
902
+ Addresses: [
903
+ {
904
+ AllocationId: 'eip-123',
905
+ PublicIp: '52.0.0.1',
906
+ Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }],
907
+ },
908
+ ],
909
+ });
910
+
911
+ discovery.findPrivateSubnets = jest
912
+ .fn()
913
+ .mockResolvedValue([{ SubnetId: 'subnet-private', AvailabilityZone: 'us-east-1a' }]);
914
+ discovery.findRouteTables = jest.fn().mockResolvedValue([
915
+ {
916
+ Associations: [],
917
+ Routes: [],
918
+ },
919
+ ]);
920
+
921
+ jest.spyOn(discovery, 'isSubnetPrivate').mockResolvedValue(true);
922
+
923
+ const misconfigs = await discovery.detectMisconfiguredResources(mockVpcId);
924
+ expect(misconfigs.natGatewaysInPrivateSubnets).toHaveLength(1);
925
+ expect(misconfigs.orphanedElasticIps).toHaveLength(1);
926
+ expect(misconfigs.privateSubnetsWithoutNatRoute).toHaveLength(1);
927
+ });
928
+ });
929
+
930
+ describe('getHealingRecommendations', () => {
931
+ it('should produce ordered recommendations', () => {
932
+ const recs = discovery.getHealingRecommendations({
933
+ natGatewaysInPrivateSubnets: [{ natGatewayId: 'nat-1' }],
934
+ orphanedElasticIps: [{ allocationId: 'eip-1' }],
935
+ privateSubnetsWithoutNatRoute: [{ subnetId: 'subnet-1' }],
936
+ });
937
+
938
+ expect(recs[0].severity).toBe('critical');
939
+ expect(recs[0].issue).toContain('NAT Gateway');
940
+ expect(recs[1].severity).toBe('critical');
941
+ expect(recs[2].severity).toBe('warning');
942
+ });
943
+
944
+ it('should return empty array for no issues', () => {
945
+ const recs = discovery.getHealingRecommendations({
946
+ natGatewaysInPrivateSubnets: [],
947
+ orphanedElasticIps: [],
948
+ privateSubnetsWithoutNatRoute: [],
949
+ });
950
+
951
+ expect(recs).toEqual([]);
952
+ });
544
953
  });
545
954
 
546
955
  describe('discoverResources', () => {
@@ -664,6 +1073,36 @@ describe('AWSDiscovery', () => {
664
1073
  });
665
1074
  });
666
1075
 
1076
+ it('should reuse available Elastic IP when no NAT Gateway exists and no public subnet is found', async () => {
1077
+ const mockVpc = { VpcId: 'vpc-12345678' };
1078
+ const mockSubnets = [
1079
+ { SubnetId: 'subnet-a' },
1080
+ { SubnetId: 'subnet-b' }
1081
+ ];
1082
+ const mockSecurityGroup = { GroupId: 'sg-12345678' };
1083
+ const mockRouteTable = { RouteTableId: 'rtb-12345678' };
1084
+ const mockElasticIp = { AllocationId: 'eipalloc-available' };
1085
+
1086
+ jest.spyOn(discovery, 'findDefaultVpc').mockResolvedValue(mockVpc);
1087
+ jest.spyOn(discovery, 'findPrivateSubnets').mockResolvedValue(mockSubnets);
1088
+ jest.spyOn(discovery, 'findPublicSubnets').mockResolvedValue(null);
1089
+ jest.spyOn(discovery, 'findDefaultSecurityGroup').mockResolvedValue(mockSecurityGroup);
1090
+ jest.spyOn(discovery, 'findPrivateRouteTable').mockResolvedValue(mockRouteTable);
1091
+ jest.spyOn(discovery, 'findDefaultKmsKey').mockResolvedValue(null);
1092
+ jest.spyOn(discovery, 'findExistingNatGateway').mockResolvedValue(null);
1093
+ const findAvailableElasticIPSpy = jest
1094
+ .spyOn(discovery, 'findAvailableElasticIP')
1095
+ .mockResolvedValue(mockElasticIp);
1096
+ jest.spyOn(discovery, 'isSubnetPrivate').mockResolvedValue(true);
1097
+
1098
+ const result = await discovery.discoverResources();
1099
+
1100
+ expect(result.publicSubnetId).toBeNull();
1101
+ expect(result.existingNatGatewayId).toBeNull();
1102
+ expect(result.existingElasticIpAllocationId).toBe('eipalloc-available');
1103
+ expect(findAvailableElasticIPSpy).toHaveBeenCalled();
1104
+ });
1105
+
667
1106
  it('should detect NAT Gateway in private subnet in discoverResources', async () => {
668
1107
  const mockVpc = { VpcId: 'vpc-12345678' };
669
1108
  const mockSubnets = [
@@ -740,4 +1179,4 @@ describe('AWSDiscovery', () => {
740
1179
  expect(customDiscovery.region).toBe('us-west-2');
741
1180
  });
742
1181
  });
743
- });
1182
+ });