@friggframework/devtools 2.0.0--canary.428.d004aeb.0 → 2.0.0--canary.428.d54dca5.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
|
|
261
|
-
|
|
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
|
+
});
|
|
@@ -55,6 +55,37 @@ describe('composeServerlessDefinition', () => {
|
|
|
55
55
|
jest.restoreAllMocks();
|
|
56
56
|
// Restore env
|
|
57
57
|
delete process.env.AWS_REGION;
|
|
58
|
+
process.argv = ['node', 'test'];
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('AWS discovery gating', () => {
|
|
62
|
+
it('should skip AWS discovery when no features require it', async () => {
|
|
63
|
+
AWSDiscovery.mockClear();
|
|
64
|
+
|
|
65
|
+
const appDefinition = {
|
|
66
|
+
integrations: [],
|
|
67
|
+
vpc: { enable: false },
|
|
68
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
69
|
+
ssm: { enable: false },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
await composeServerlessDefinition(appDefinition);
|
|
73
|
+
|
|
74
|
+
expect(AWSDiscovery).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should run AWS discovery when VPC features are enabled', async () => {
|
|
78
|
+
AWSDiscovery.mockClear();
|
|
79
|
+
|
|
80
|
+
const appDefinition = {
|
|
81
|
+
integrations: [],
|
|
82
|
+
vpc: { enable: true },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
await composeServerlessDefinition(appDefinition);
|
|
86
|
+
|
|
87
|
+
expect(AWSDiscovery).toHaveBeenCalledTimes(1);
|
|
88
|
+
});
|
|
58
89
|
});
|
|
59
90
|
|
|
60
91
|
describe('Basic Configuration', () => {
|
|
@@ -124,6 +155,38 @@ describe('composeServerlessDefinition', () => {
|
|
|
124
155
|
});
|
|
125
156
|
});
|
|
126
157
|
|
|
158
|
+
describe('Environment variables', () => {
|
|
159
|
+
it('should include only non-reserved environment flags', async () => {
|
|
160
|
+
const appDefinition = {
|
|
161
|
+
integrations: [],
|
|
162
|
+
environment: {
|
|
163
|
+
CUSTOM_FLAG: true,
|
|
164
|
+
AWS_REGION: true,
|
|
165
|
+
OPTIONAL_DISABLED: false,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
170
|
+
|
|
171
|
+
expect(result.provider.environment.CUSTOM_FLAG).toBe("${env:CUSTOM_FLAG, ''}");
|
|
172
|
+
expect(result.provider.environment).not.toHaveProperty('AWS_REGION');
|
|
173
|
+
expect(result.provider.environment).not.toHaveProperty('OPTIONAL_DISABLED');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should ignore string-valued environment entries', async () => {
|
|
177
|
+
const appDefinition = {
|
|
178
|
+
integrations: [],
|
|
179
|
+
environment: {
|
|
180
|
+
CUSTOM_FLAG: 'enabled',
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
185
|
+
|
|
186
|
+
expect(result.provider.environment).not.toHaveProperty('CUSTOM_FLAG');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
127
190
|
describe('VPC Configuration', () => {
|
|
128
191
|
it('should add VPC configuration when vpc.enable is true with discover mode', async () => {
|
|
129
192
|
const appDefinition = {
|
|
@@ -261,6 +324,49 @@ describe('composeServerlessDefinition', () => {
|
|
|
261
324
|
expect(result.provider.vpc.subnetIds).toEqual(['subnet-explicit1', 'subnet-explicit2']);
|
|
262
325
|
});
|
|
263
326
|
|
|
327
|
+
it('should respect provided security group IDs when supplied', async () => {
|
|
328
|
+
const appDefinition = {
|
|
329
|
+
vpc: {
|
|
330
|
+
enable: true,
|
|
331
|
+
management: 'discover',
|
|
332
|
+
securityGroupIds: ['sg-custom-1', 'sg-custom-2'],
|
|
333
|
+
},
|
|
334
|
+
integrations: [],
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
338
|
+
|
|
339
|
+
expect(result.provider.vpc.securityGroupIds).toEqual([
|
|
340
|
+
'sg-custom-1',
|
|
341
|
+
'sg-custom-2',
|
|
342
|
+
]);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should throw when discover mode finds no subnets and self-heal is disabled', async () => {
|
|
346
|
+
const discoveryInstance = {
|
|
347
|
+
discoverResources: jest.fn().mockResolvedValue(
|
|
348
|
+
createDiscoveryResponse({
|
|
349
|
+
privateSubnetId1: null,
|
|
350
|
+
privateSubnetId2: null,
|
|
351
|
+
})
|
|
352
|
+
),
|
|
353
|
+
};
|
|
354
|
+
AWSDiscovery.mockImplementation(() => discoveryInstance);
|
|
355
|
+
|
|
356
|
+
const appDefinition = {
|
|
357
|
+
vpc: {
|
|
358
|
+
enable: true,
|
|
359
|
+
management: 'discover',
|
|
360
|
+
subnets: { management: 'discover' },
|
|
361
|
+
},
|
|
362
|
+
integrations: [],
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
366
|
+
'No subnets discovered and subnets.management is "discover". Either enable vpc.selfHeal, set subnets.management to "create", or provide subnet IDs.'
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
264
370
|
it('should use Fn::Cidr for subnet CIDR blocks in new VPC to avoid conflicts', async () => {
|
|
265
371
|
const appDefinition = {
|
|
266
372
|
vpc: {
|
|
@@ -357,6 +463,39 @@ describe('composeServerlessDefinition', () => {
|
|
|
357
463
|
expect(result.resources.Resources.VPCEndpointS3.Properties.VpcId).toBe('vpc-123456');
|
|
358
464
|
});
|
|
359
465
|
|
|
466
|
+
it('should skip creating VPC endpoints when disabled explicitly', async () => {
|
|
467
|
+
const appDefinition = {
|
|
468
|
+
vpc: {
|
|
469
|
+
enable: true,
|
|
470
|
+
management: 'discover',
|
|
471
|
+
enableVPCEndpoints: false,
|
|
472
|
+
},
|
|
473
|
+
integrations: [],
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
477
|
+
|
|
478
|
+
expect(result.resources.Resources.VPCEndpointS3).toBeUndefined();
|
|
479
|
+
expect(result.resources.Resources.VPCEndpointKMS).toBeUndefined();
|
|
480
|
+
expect(result.resources.Resources.VPCEndpointSecretsManager).toBeUndefined();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should add Secrets Manager endpoint only when enabled', async () => {
|
|
484
|
+
const appDefinition = {
|
|
485
|
+
vpc: {
|
|
486
|
+
enable: true,
|
|
487
|
+
management: 'discover',
|
|
488
|
+
},
|
|
489
|
+
secretsManager: { enable: true },
|
|
490
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
491
|
+
integrations: [],
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
495
|
+
|
|
496
|
+
expect(result.resources.Resources.VPCEndpointSecretsManager).toBeDefined();
|
|
497
|
+
});
|
|
498
|
+
|
|
360
499
|
it('should allow Lambda security group access for VPC endpoints when security group is discovered', async () => {
|
|
361
500
|
const appDefinition = {
|
|
362
501
|
vpc: {
|
|
@@ -511,6 +650,114 @@ describe('composeServerlessDefinition', () => {
|
|
|
511
650
|
});
|
|
512
651
|
});
|
|
513
652
|
|
|
653
|
+
describe('NAT Gateway behaviour', () => {
|
|
654
|
+
it('should reuse discovered NAT gateway in discover mode without creating new resources', async () => {
|
|
655
|
+
const discoveryInstance = {
|
|
656
|
+
discoverResources: jest.fn().mockResolvedValue(
|
|
657
|
+
createDiscoveryResponse({
|
|
658
|
+
existingNatGatewayId: 'nat-existing123',
|
|
659
|
+
natGatewayInPrivateSubnet: false,
|
|
660
|
+
})
|
|
661
|
+
),
|
|
662
|
+
};
|
|
663
|
+
AWSDiscovery.mockImplementation(() => discoveryInstance);
|
|
664
|
+
|
|
665
|
+
const appDefinition = {
|
|
666
|
+
vpc: {
|
|
667
|
+
enable: true,
|
|
668
|
+
management: 'discover',
|
|
669
|
+
natGateway: { management: 'discover' },
|
|
670
|
+
},
|
|
671
|
+
integrations: [],
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
675
|
+
|
|
676
|
+
expect(result.resources.Resources.FriggNATGateway).toBeUndefined();
|
|
677
|
+
expect(result.resources.Resources.FriggNATRoute).toBeDefined();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it('should reference provided NAT gateway when management set to useExisting', async () => {
|
|
681
|
+
const discoveryInstance = {
|
|
682
|
+
discoverResources: jest.fn().mockResolvedValue(
|
|
683
|
+
createDiscoveryResponse({ existingNatGatewayId: null })
|
|
684
|
+
),
|
|
685
|
+
};
|
|
686
|
+
AWSDiscovery.mockImplementation(() => discoveryInstance);
|
|
687
|
+
|
|
688
|
+
const appDefinition = {
|
|
689
|
+
vpc: {
|
|
690
|
+
enable: true,
|
|
691
|
+
management: 'discover',
|
|
692
|
+
natGateway: { management: 'useExisting', id: 'nat-custom-001' },
|
|
693
|
+
},
|
|
694
|
+
integrations: [],
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
698
|
+
|
|
699
|
+
expect(result.resources.Resources.FriggNATGateway).toBeUndefined();
|
|
700
|
+
expect(result.resources.Resources.FriggNATRoute.Properties.NatGatewayId).toBe('nat-custom-001');
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('should reuse existing elastic IP allocation when creating managed NAT', async () => {
|
|
704
|
+
const discoveryInstance = {
|
|
705
|
+
discoverResources: jest.fn().mockResolvedValue(
|
|
706
|
+
createDiscoveryResponse({
|
|
707
|
+
existingNatGatewayId: null,
|
|
708
|
+
existingElasticIpAllocationId: 'eip-alloc-123',
|
|
709
|
+
})
|
|
710
|
+
),
|
|
711
|
+
};
|
|
712
|
+
AWSDiscovery.mockImplementation(() => discoveryInstance);
|
|
713
|
+
|
|
714
|
+
const appDefinition = {
|
|
715
|
+
vpc: {
|
|
716
|
+
enable: true,
|
|
717
|
+
management: 'discover',
|
|
718
|
+
natGateway: { management: 'createAndManage' },
|
|
719
|
+
selfHeal: true,
|
|
720
|
+
},
|
|
721
|
+
integrations: [],
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
725
|
+
|
|
726
|
+
expect(result.resources.Resources.FriggNATGatewayEIP).toBeUndefined();
|
|
727
|
+
expect(result.resources.Resources.FriggNATGateway.Properties.AllocationId).toBe(
|
|
728
|
+
'eip-alloc-123'
|
|
729
|
+
);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('should create a public subnet when discovery provides none', async () => {
|
|
733
|
+
const discoveryInstance = {
|
|
734
|
+
discoverResources: jest.fn().mockResolvedValue(
|
|
735
|
+
createDiscoveryResponse({
|
|
736
|
+
publicSubnetId: null,
|
|
737
|
+
internetGatewayId: null,
|
|
738
|
+
existingNatGatewayId: null,
|
|
739
|
+
})
|
|
740
|
+
),
|
|
741
|
+
};
|
|
742
|
+
AWSDiscovery.mockImplementation(() => discoveryInstance);
|
|
743
|
+
|
|
744
|
+
const appDefinition = {
|
|
745
|
+
vpc: {
|
|
746
|
+
enable: true,
|
|
747
|
+
management: 'discover',
|
|
748
|
+
natGateway: { management: 'createAndManage' },
|
|
749
|
+
selfHeal: true,
|
|
750
|
+
},
|
|
751
|
+
integrations: [],
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
755
|
+
|
|
756
|
+
expect(result.resources.Resources.FriggPublicSubnet).toBeDefined();
|
|
757
|
+
expect(result.resources.Resources.FriggPublicRouteTable).toBeDefined();
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
|
|
514
761
|
describe('KMS Configuration', () => {
|
|
515
762
|
it('should add KMS configuration when encryption is enabled and key is found', async () => {
|
|
516
763
|
const appDefinition = {
|
|
@@ -913,6 +1160,27 @@ describe('composeServerlessDefinition', () => {
|
|
|
913
1160
|
});
|
|
914
1161
|
});
|
|
915
1162
|
|
|
1163
|
+
describe('Handler path adjustments', () => {
|
|
1164
|
+
const fs = require('fs');
|
|
1165
|
+
const path = require('path');
|
|
1166
|
+
|
|
1167
|
+
it('should rewrite handler paths in offline mode', async () => {
|
|
1168
|
+
process.argv = ['node', 'test', 'offline'];
|
|
1169
|
+
const existsSpy = jest.spyOn(fs, 'existsSync');
|
|
1170
|
+
const fallbackNodeModules = path.resolve(process.cwd(), '..', 'node_modules');
|
|
1171
|
+
existsSpy.mockImplementation((p) => p === fallbackNodeModules);
|
|
1172
|
+
|
|
1173
|
+
const appDefinition = { integrations: [] };
|
|
1174
|
+
|
|
1175
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
1176
|
+
|
|
1177
|
+
expect(existsSpy).toHaveBeenCalled();
|
|
1178
|
+
expect(result.functions.auth.handler.startsWith('../node_modules/')).toBe(true);
|
|
1179
|
+
|
|
1180
|
+
existsSpy.mockRestore();
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1183
|
+
|
|
916
1184
|
describe('NAT Gateway Management', () => {
|
|
917
1185
|
it('should handle NAT Gateway with createAndManage mode', async () => {
|
|
918
1186
|
const appDefinition = {
|
|
@@ -932,6 +1200,24 @@ describe('composeServerlessDefinition', () => {
|
|
|
932
1200
|
expect(result.resources.Resources.FriggNATRoute).toBeDefined();
|
|
933
1201
|
});
|
|
934
1202
|
|
|
1203
|
+
it('should mark managed NAT Gateway resources for retention', async () => {
|
|
1204
|
+
const appDefinition = {
|
|
1205
|
+
vpc: {
|
|
1206
|
+
enable: true,
|
|
1207
|
+
management: 'discover',
|
|
1208
|
+
natGateway: { management: 'createAndManage' },
|
|
1209
|
+
selfHeal: true,
|
|
1210
|
+
},
|
|
1211
|
+
integrations: [],
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
1215
|
+
|
|
1216
|
+
expect(result.resources.Resources.FriggNATGateway).toBeDefined();
|
|
1217
|
+
expect(result.resources.Resources.FriggNATGateway.DeletionPolicy).toBe('Retain');
|
|
1218
|
+
expect(result.resources.Resources.FriggNATGateway.UpdateReplacePolicy).toBe('Retain');
|
|
1219
|
+
});
|
|
1220
|
+
|
|
935
1221
|
it('should handle NAT Gateway with discover mode', async () => {
|
|
936
1222
|
// Mock discovery to return existing NAT Gateway
|
|
937
1223
|
const { AWSDiscovery } = require('./aws-discovery');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.428.
|
|
4
|
+
"version": "2.0.0--canary.428.d54dca5.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"@babel/eslint-parser": "^7.18.9",
|
|
10
10
|
"@babel/parser": "^7.25.3",
|
|
11
11
|
"@babel/traverse": "^7.25.3",
|
|
12
|
-
"@friggframework/schemas": "2.0.0--canary.428.
|
|
13
|
-
"@friggframework/test": "2.0.0--canary.428.
|
|
12
|
+
"@friggframework/schemas": "2.0.0--canary.428.d54dca5.0",
|
|
13
|
+
"@friggframework/test": "2.0.0--canary.428.d54dca5.0",
|
|
14
14
|
"@hapi/boom": "^10.0.1",
|
|
15
15
|
"@inquirer/prompts": "^5.3.8",
|
|
16
16
|
"axios": "^1.7.2",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"serverless-http": "^2.7.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@friggframework/eslint-config": "2.0.0--canary.428.
|
|
36
|
-
"@friggframework/prettier-config": "2.0.0--canary.428.
|
|
35
|
+
"@friggframework/eslint-config": "2.0.0--canary.428.d54dca5.0",
|
|
36
|
+
"@friggframework/prettier-config": "2.0.0--canary.428.d54dca5.0",
|
|
37
37
|
"aws-sdk-client-mock": "^4.1.0",
|
|
38
38
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
39
39
|
"jest": "^30.1.3",
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
"publishConfig": {
|
|
69
69
|
"access": "public"
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "d54dca5ba151e72370d6f82b4f841e2a8e5934c1"
|
|
72
72
|
}
|