@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.
|
@@ -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.1a6e465.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.1a6e465.0",
|
|
13
|
+
"@friggframework/test": "2.0.0--canary.428.1a6e465.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.1a6e465.0",
|
|
36
|
+
"@friggframework/prettier-config": "2.0.0--canary.428.1a6e465.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": "1a6e465d8c4c0d552ca460371a7739b8e06e001e"
|
|
72
72
|
}
|