@friggframework/devtools 2.0.0--canary.419.8343c27.0 → 2.0.0--canary.425.dd575ef.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +1 -1
- package/frigg-cli/generate-command/index.js +1 -1
- package/frigg-cli/init-command/backend-first-handler.js +1 -1
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +2 -2
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +1 -1
- package/infrastructure/GENERATE-IAM-DOCS.md +2 -2
- package/infrastructure/README.md +3 -2
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +2 -2
- package/infrastructure/__tests__/helpers/test-utils.js +1 -1
- package/infrastructure/aws-discovery.js +14 -12
- package/infrastructure/build-time-discovery.js +2 -2
- package/infrastructure/build-time-discovery.test.js +1 -1
- package/infrastructure/iam-generator.js +2 -3
- package/infrastructure/iam-generator.test.js +4 -4
- package/infrastructure/integration.test.js +4 -4
- package/infrastructure/run-discovery.js +4 -4
- package/infrastructure/serverless-template.js +72 -96
- package/infrastructure/serverless-template.test.js +238 -36
- package/package.json +7 -6
|
@@ -54,7 +54,7 @@ describe('Generate Command', () => {
|
|
|
54
54
|
// Mock app definition
|
|
55
55
|
jest.doMock(mockAppDefinitionPath, () => ({
|
|
56
56
|
vpc: { enable: true },
|
|
57
|
-
encryption: {
|
|
57
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
58
58
|
ssm: { enable: true },
|
|
59
59
|
websockets: { enable: false }
|
|
60
60
|
}), { virtual: true });
|
|
@@ -228,7 +228,7 @@ async function generateCommand(options = {}) {
|
|
|
228
228
|
function analyzeAppFeatures(appDefinition) {
|
|
229
229
|
const features = {
|
|
230
230
|
vpc: appDefinition.vpc?.enable === true,
|
|
231
|
-
kms: appDefinition.encryption?.
|
|
231
|
+
kms: appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms',
|
|
232
232
|
ssm: appDefinition.ssm?.enable === true,
|
|
233
233
|
websockets: appDefinition.websockets?.enable === true,
|
|
234
234
|
// Add more feature detection as needed
|
|
@@ -670,7 +670,7 @@ To integrate Frigg into your production application:
|
|
|
670
670
|
const appDefinition = {
|
|
671
671
|
integrations: [], // Will be populated based on selected integrations
|
|
672
672
|
user: { password: true },
|
|
673
|
-
encryption: {
|
|
673
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
674
674
|
vpc: { enable: true },
|
|
675
675
|
security: {
|
|
676
676
|
cors: {
|
|
@@ -9,7 +9,7 @@ AWS Discovery automatically finds your default AWS resources (VPC, subnets, secu
|
|
|
9
9
|
AWS Discovery runs automatically during `frigg build` and `frigg deploy` when your AppDefinition includes:
|
|
10
10
|
|
|
11
11
|
- `vpc.enable: true` - VPC support
|
|
12
|
-
- `encryption.
|
|
12
|
+
- `encryption.fieldLevelEncryptionMethod: 'kms'` - KMS encryption
|
|
13
13
|
- `ssm.enable: true` - SSM Parameter Store
|
|
14
14
|
|
|
15
15
|
## Fail-Fast Behavior
|
|
@@ -222,7 +222,7 @@ If you're stuck, try this recovery process:
|
|
|
222
222
|
// backend/index.js - temporarily disable problematic features
|
|
223
223
|
const appDefinition = {
|
|
224
224
|
vpc: { enable: false },
|
|
225
|
-
encryption: {
|
|
225
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
226
226
|
ssm: { enable: false }
|
|
227
227
|
};
|
|
228
228
|
```
|
|
@@ -354,7 +354,7 @@ Additional permissions needed when your app definition includes `vpc: { enable:
|
|
|
354
354
|
|
|
355
355
|
### KMS Support
|
|
356
356
|
|
|
357
|
-
Additional permissions needed when your app definition includes `encryption: {
|
|
357
|
+
Additional permissions needed when your app definition includes `encryption: { fieldLevelEncryptionMethod: 'kms' }`:
|
|
358
358
|
|
|
359
359
|
```json
|
|
360
360
|
{
|
|
@@ -58,7 +58,7 @@ The command analyzes your `backend/index.js` AppDefinition and generates IAM pol
|
|
|
58
58
|
- Route table and security group management
|
|
59
59
|
- Elastic IP allocation
|
|
60
60
|
|
|
61
|
-
#### KMS Encryption (`encryption.
|
|
61
|
+
#### KMS Encryption (`encryption.fieldLevelEncryptionMethod: 'kms'`)
|
|
62
62
|
|
|
63
63
|
- KMS key usage for Lambda and S3
|
|
64
64
|
- Data encryption and decryption permissions
|
|
@@ -85,7 +85,7 @@ const appDefinition = {
|
|
|
85
85
|
enable: true,
|
|
86
86
|
},
|
|
87
87
|
encryption: {
|
|
88
|
-
|
|
88
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
89
89
|
},
|
|
90
90
|
ssm: {
|
|
91
91
|
enable: false,
|
package/infrastructure/README.md
CHANGED
|
@@ -155,7 +155,8 @@ const appDefinition = {
|
|
|
155
155
|
|
|
156
156
|
// KMS encryption
|
|
157
157
|
encryption: {
|
|
158
|
-
|
|
158
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
159
|
+
createResourceIfNoneFound: true
|
|
159
160
|
},
|
|
160
161
|
|
|
161
162
|
// SSM Parameter Store
|
|
@@ -217,7 +218,7 @@ const serverlessConfig = await composeServerlessDefinition(appDefinition);
|
|
|
217
218
|
const appDefinition = {
|
|
218
219
|
name: 'secure-app',
|
|
219
220
|
vpc: { enable: true },
|
|
220
|
-
encryption: {
|
|
221
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
221
222
|
ssm: { enable: true },
|
|
222
223
|
integrations: [{ Definition: { name: 'salesforce' } }],
|
|
223
224
|
};
|
|
@@ -151,7 +151,7 @@ const mockAppDefinitions = {
|
|
|
151
151
|
|
|
152
152
|
kmsOnly: {
|
|
153
153
|
name: 'kms-test-app',
|
|
154
|
-
encryption: {
|
|
154
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
155
155
|
integrations: []
|
|
156
156
|
},
|
|
157
157
|
|
|
@@ -164,7 +164,7 @@ const mockAppDefinitions = {
|
|
|
164
164
|
allFeatures: {
|
|
165
165
|
name: 'full-feature-app',
|
|
166
166
|
vpc: { enable: true },
|
|
167
|
-
encryption: {
|
|
167
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
168
168
|
ssm: { enable: true },
|
|
169
169
|
integrations: [{
|
|
170
170
|
Definition: {
|
|
@@ -115,7 +115,7 @@ function createMockAppDefinition(features = {}, integrations = []) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (features.kms) {
|
|
118
|
-
appDefinition.encryption = {
|
|
118
|
+
appDefinition.encryption = { fieldLevelEncryptionMethod: 'kms' };
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
if (features.ssm) {
|
|
@@ -453,18 +453,16 @@ class AWSDiscovery {
|
|
|
453
453
|
|
|
454
454
|
/**
|
|
455
455
|
* Find the default KMS key for the account
|
|
456
|
-
* @returns {Promise<string>} KMS key ARN or
|
|
456
|
+
* @returns {Promise<string|null>} KMS key ARN or null if no key found
|
|
457
457
|
*/
|
|
458
458
|
async findDefaultKmsKey() {
|
|
459
459
|
try {
|
|
460
|
-
// First try to find a key with alias/aws/lambda
|
|
461
460
|
const command = new ListKeysCommand({});
|
|
462
461
|
const response = await this.kmsClient.send(command);
|
|
463
462
|
|
|
464
463
|
if (!response.Keys || response.Keys.length === 0) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return `arn:aws:kms:${this.region}:${accountId}:key/*`;
|
|
464
|
+
console.log('No KMS keys found in account');
|
|
465
|
+
return null;
|
|
468
466
|
}
|
|
469
467
|
|
|
470
468
|
// Look for customer managed keys first
|
|
@@ -476,21 +474,21 @@ class AWSDiscovery {
|
|
|
476
474
|
if (keyDetails.KeyMetadata &&
|
|
477
475
|
keyDetails.KeyMetadata.KeyManager === 'CUSTOMER' &&
|
|
478
476
|
keyDetails.KeyMetadata.KeyState === 'Enabled') {
|
|
477
|
+
console.log(`Found customer managed KMS key: ${keyDetails.KeyMetadata.Arn}`);
|
|
479
478
|
return keyDetails.KeyMetadata.Arn;
|
|
480
479
|
}
|
|
481
480
|
} catch (error) {
|
|
482
481
|
// Continue to next key if we can't describe this one
|
|
482
|
+
console.warn(`Could not describe key ${key.KeyId}:`, error.message);
|
|
483
483
|
continue;
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
return `arn:aws:kms:${this.region}:${accountId}:key/*`;
|
|
487
|
+
console.log('No customer managed KMS keys found');
|
|
488
|
+
return null;
|
|
490
489
|
} catch (error) {
|
|
491
490
|
console.error('Error finding default KMS key:', error);
|
|
492
|
-
|
|
493
|
-
return '*';
|
|
491
|
+
return null;
|
|
494
492
|
}
|
|
495
493
|
}
|
|
496
494
|
|
|
@@ -503,7 +501,7 @@ class AWSDiscovery {
|
|
|
503
501
|
* @returns {string} return.privateSubnetId2 - Second private subnet ID
|
|
504
502
|
* @returns {string} return.publicSubnetId - Public subnet ID for NAT Gateway
|
|
505
503
|
* @returns {string} return.privateRouteTableId - Private route table ID
|
|
506
|
-
* @returns {string} return.defaultKmsKeyId - Default KMS key ARN
|
|
504
|
+
* @returns {string|null} return.defaultKmsKeyId - Default KMS key ARN or null if not found
|
|
507
505
|
* @throws {Error} If resource discovery fails
|
|
508
506
|
*/
|
|
509
507
|
async discoverResources() {
|
|
@@ -526,7 +524,11 @@ class AWSDiscovery {
|
|
|
526
524
|
console.log(`Found route table: ${routeTable.RouteTableId}`);
|
|
527
525
|
|
|
528
526
|
const kmsKeyArn = await this.findDefaultKmsKey();
|
|
529
|
-
|
|
527
|
+
if (kmsKeyArn) {
|
|
528
|
+
console.log(`Found KMS key: ${kmsKeyArn}`);
|
|
529
|
+
} else {
|
|
530
|
+
console.log('No KMS key found');
|
|
531
|
+
}
|
|
530
532
|
|
|
531
533
|
// Try to find existing NAT Gateway
|
|
532
534
|
const existingNatGateway = await this.findExistingNatGateway(vpc.VpcId);
|
|
@@ -137,8 +137,8 @@ class BuildTimeDiscovery {
|
|
|
137
137
|
console.log('Running pre-build AWS discovery hook...');
|
|
138
138
|
|
|
139
139
|
// Only run discovery if VPC, KMS, or SSM features are enabled
|
|
140
|
-
const needsDiscovery = appDefinition.vpc?.enable ||
|
|
141
|
-
appDefinition.encryption?.
|
|
140
|
+
const needsDiscovery = appDefinition.vpc?.enable ||
|
|
141
|
+
appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
142
142
|
appDefinition.ssm?.enable;
|
|
143
143
|
|
|
144
144
|
if (!needsDiscovery) {
|
|
@@ -250,7 +250,7 @@ describe('BuildTimeDiscovery', () => {
|
|
|
250
250
|
|
|
251
251
|
it('should run discovery when KMS is enabled', async () => {
|
|
252
252
|
const appDefinition = {
|
|
253
|
-
encryption: {
|
|
253
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
254
254
|
integrations: []
|
|
255
255
|
};
|
|
256
256
|
|
|
@@ -35,7 +35,7 @@ function generateIAMCloudFormation(appDefinition, options = {}) {
|
|
|
35
35
|
vpc: appDefinition.vpc?.enable === true,
|
|
36
36
|
kms:
|
|
37
37
|
appDefinition.encryption
|
|
38
|
-
?.
|
|
38
|
+
?.fieldLevelEncryptionMethod === 'kms',
|
|
39
39
|
ssm: appDefinition.ssm?.enable === true,
|
|
40
40
|
websockets: appDefinition.websockets?.enable === true,
|
|
41
41
|
};
|
|
@@ -724,8 +724,7 @@ function getFeatureSummary(appDefinition) {
|
|
|
724
724
|
core: true, // Always enabled
|
|
725
725
|
vpc: appDefinition.vpc?.enable === true,
|
|
726
726
|
kms:
|
|
727
|
-
appDefinition.encryption?.
|
|
728
|
-
true,
|
|
727
|
+
appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms',
|
|
729
728
|
ssm: appDefinition.ssm?.enable === true,
|
|
730
729
|
websockets: appDefinition.websockets?.enable === true,
|
|
731
730
|
};
|
|
@@ -7,7 +7,7 @@ describe('IAM Generator', () => {
|
|
|
7
7
|
name: 'test-app',
|
|
8
8
|
integrations: ['Integration1', 'Integration2'],
|
|
9
9
|
vpc: { enable: true },
|
|
10
|
-
encryption: {
|
|
10
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
11
11
|
ssm: { enable: true },
|
|
12
12
|
websockets: { enable: true }
|
|
13
13
|
};
|
|
@@ -46,7 +46,7 @@ describe('IAM Generator', () => {
|
|
|
46
46
|
name: 'test-app',
|
|
47
47
|
integrations: [],
|
|
48
48
|
vpc: { enable: false },
|
|
49
|
-
encryption: {
|
|
49
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
50
50
|
ssm: { enable: false },
|
|
51
51
|
websockets: { enable: false }
|
|
52
52
|
};
|
|
@@ -77,7 +77,7 @@ describe('IAM Generator', () => {
|
|
|
77
77
|
const appDefinition = {
|
|
78
78
|
name: 'test-app',
|
|
79
79
|
integrations: [],
|
|
80
|
-
encryption: {
|
|
80
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' }
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const yaml = generateIAMCloudFormation(appDefinition);
|
|
@@ -106,7 +106,7 @@ describe('IAM Generator', () => {
|
|
|
106
106
|
name: 'test-app',
|
|
107
107
|
integrations: [],
|
|
108
108
|
vpc: { enable: true },
|
|
109
|
-
encryption: {
|
|
109
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
110
110
|
ssm: { enable: true }
|
|
111
111
|
};
|
|
112
112
|
|
|
@@ -49,7 +49,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
49
49
|
const appDefinition = {
|
|
50
50
|
name: 'test-frigg-app',
|
|
51
51
|
vpc: { enable: true },
|
|
52
|
-
encryption: {
|
|
52
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
53
53
|
ssm: { enable: true },
|
|
54
54
|
integrations: [{
|
|
55
55
|
Definition: {
|
|
@@ -173,7 +173,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
173
173
|
it('should generate config with only KMS enabled', async () => {
|
|
174
174
|
const appDefinition = {
|
|
175
175
|
name: 'kms-only-app',
|
|
176
|
-
encryption: {
|
|
176
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
177
177
|
integrations: []
|
|
178
178
|
};
|
|
179
179
|
|
|
@@ -231,7 +231,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
231
231
|
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
232
232
|
expect.objectContaining({
|
|
233
233
|
vpc: { enable: true },
|
|
234
|
-
encryption: {
|
|
234
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' }
|
|
235
235
|
}),
|
|
236
236
|
'us-east-1'
|
|
237
237
|
);
|
|
@@ -353,7 +353,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
353
353
|
|
|
354
354
|
const appDefinition = {
|
|
355
355
|
vpc: { enable: true },
|
|
356
|
-
encryption: {
|
|
356
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
357
357
|
integrations: []
|
|
358
358
|
};
|
|
359
359
|
|
|
@@ -41,7 +41,7 @@ async function runDiscovery() {
|
|
|
41
41
|
|
|
42
42
|
// Check if discovery is needed
|
|
43
43
|
const needsDiscovery = appDefinition.vpc?.enable ||
|
|
44
|
-
appDefinition.encryption?.
|
|
44
|
+
appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
45
45
|
appDefinition.ssm?.enable;
|
|
46
46
|
|
|
47
47
|
if (!needsDiscovery) {
|
|
@@ -51,7 +51,7 @@ async function runDiscovery() {
|
|
|
51
51
|
|
|
52
52
|
console.log('📋 App requires AWS discovery for:');
|
|
53
53
|
if (appDefinition.vpc?.enable) console.log(' ✅ VPC support');
|
|
54
|
-
if (appDefinition.encryption?.
|
|
54
|
+
if (appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') console.log(' ✅ KMS encryption');
|
|
55
55
|
if (appDefinition.ssm?.enable) console.log(' ✅ SSM parameters');
|
|
56
56
|
|
|
57
57
|
// Run discovery
|
|
@@ -82,7 +82,7 @@ async function runDiscovery() {
|
|
|
82
82
|
} else {
|
|
83
83
|
console.error('🚨 Discovery is required because your AppDefinition has these features enabled:');
|
|
84
84
|
if (appDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
|
|
85
|
-
if (appDefinition.encryption?.
|
|
85
|
+
if (appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') console.error(' ❌ KMS encryption (encryption.fieldLevelEncryptionMethod: \'kms\')');
|
|
86
86
|
if (appDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
|
|
87
87
|
console.error('');
|
|
88
88
|
console.error('💡 To fix this issue:');
|
|
@@ -95,7 +95,7 @@ async function runDiscovery() {
|
|
|
95
95
|
|
|
96
96
|
console.error('🔧 Or disable features in backend/index.js:');
|
|
97
97
|
console.error(' vpc: { enable: false }');
|
|
98
|
-
console.error(' encryption: {
|
|
98
|
+
console.error(' encryption: { fieldLevelEncryptionMethod: \'aes\' }');
|
|
99
99
|
console.error(' ssm: { enable: false }');
|
|
100
100
|
|
|
101
101
|
process.exit(1);
|
|
@@ -10,8 +10,7 @@ const { AWSDiscovery } = require('./aws-discovery');
|
|
|
10
10
|
const shouldRunDiscovery = (AppDefinition) => {
|
|
11
11
|
return (
|
|
12
12
|
AppDefinition.vpc?.enable === true ||
|
|
13
|
-
AppDefinition.encryption?.
|
|
14
|
-
true ||
|
|
13
|
+
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
15
14
|
AppDefinition.ssm?.enable === true
|
|
16
15
|
);
|
|
17
16
|
};
|
|
@@ -493,10 +492,7 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
493
492
|
};
|
|
494
493
|
|
|
495
494
|
// KMS Interface Endpoint (paid, but useful if using KMS)
|
|
496
|
-
if (
|
|
497
|
-
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ===
|
|
498
|
-
true
|
|
499
|
-
) {
|
|
495
|
+
if (AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
|
|
500
496
|
vpcResources.FriggKMSVPCEndpoint = {
|
|
501
497
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
502
498
|
Properties: {
|
|
@@ -891,9 +887,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
891
887
|
};
|
|
892
888
|
|
|
893
889
|
// KMS Configuration based on App Definition
|
|
894
|
-
if (
|
|
895
|
-
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true
|
|
896
|
-
) {
|
|
890
|
+
if (AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
|
|
897
891
|
// Check if a KMS key was discovered
|
|
898
892
|
if (discoveredResources.defaultKmsKeyId) {
|
|
899
893
|
// Use the existing discovered KMS key
|
|
@@ -910,42 +904,83 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
910
904
|
definition.provider.environment.KMS_KEY_ARN =
|
|
911
905
|
discoveredResources.defaultKmsKeyId;
|
|
912
906
|
} else {
|
|
913
|
-
// No existing key found
|
|
914
|
-
|
|
907
|
+
// No existing key found - check if we should create one or error
|
|
908
|
+
if (AppDefinition.encryption?.createResourceIfNoneFound === true) {
|
|
909
|
+
// Create a new KMS key
|
|
910
|
+
console.log('No existing KMS key found, creating a new one...');
|
|
915
911
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
'
|
|
912
|
+
definition.resources.Resources.FriggKMSKey = {
|
|
913
|
+
Type: 'AWS::KMS::Key',
|
|
914
|
+
Properties: {
|
|
915
|
+
EnableKeyRotation: true,
|
|
916
|
+
Description: 'Frigg KMS key for field-level encryption',
|
|
917
|
+
KeyPolicy: {
|
|
918
|
+
Version: '2012-10-17',
|
|
919
|
+
Statement: [
|
|
920
|
+
{
|
|
921
|
+
Sid: 'AllowRootAccountAdmin',
|
|
922
|
+
Effect: 'Allow',
|
|
923
|
+
Principal: {
|
|
924
|
+
AWS: {
|
|
925
|
+
'Fn::Sub':
|
|
926
|
+
'arn:aws:iam::${AWS::AccountId}:root',
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
Action: 'kms:*',
|
|
930
|
+
Resource: '*',
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
Sid: 'AllowLambdaService',
|
|
934
|
+
Effect: 'Allow',
|
|
935
|
+
Principal: {
|
|
936
|
+
Service: 'lambda.amazonaws.com',
|
|
937
|
+
},
|
|
938
|
+
Action: [
|
|
939
|
+
'kms:GenerateDataKey',
|
|
940
|
+
'kms:Decrypt',
|
|
941
|
+
'kms:DescribeKey',
|
|
942
|
+
],
|
|
943
|
+
Resource: '*',
|
|
944
|
+
Condition: {
|
|
945
|
+
StringEquals: {
|
|
946
|
+
'kms:ViaService': `lambda.${
|
|
947
|
+
process.env.AWS_REGION ||
|
|
948
|
+
'us-east-1'
|
|
949
|
+
}.amazonaws.com`,
|
|
950
|
+
},
|
|
930
951
|
},
|
|
931
952
|
},
|
|
932
|
-
|
|
933
|
-
|
|
953
|
+
],
|
|
954
|
+
},
|
|
955
|
+
Tags: [
|
|
956
|
+
{
|
|
957
|
+
Key: 'Name',
|
|
958
|
+
Value: '${self:service}-${self:provider.stage}-frigg-kms-key',
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
Key: 'Purpose',
|
|
962
|
+
Value: 'Field-level encryption for Frigg application',
|
|
934
963
|
},
|
|
935
964
|
],
|
|
936
965
|
},
|
|
937
|
-
}
|
|
938
|
-
};
|
|
966
|
+
};
|
|
939
967
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
968
|
+
definition.provider.iamRoleStatements.push({
|
|
969
|
+
Effect: 'Allow',
|
|
970
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
971
|
+
Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }],
|
|
972
|
+
});
|
|
945
973
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
974
|
+
definition.provider.environment.KMS_KEY_ARN = {
|
|
975
|
+
'Fn::GetAtt': ['FriggKMSKey', 'Arn'],
|
|
976
|
+
};
|
|
977
|
+
} else {
|
|
978
|
+
// No key found and createIfNoneFound is not enabled - error
|
|
979
|
+
throw new Error(
|
|
980
|
+
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
981
|
+
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
982
|
+
);
|
|
983
|
+
}
|
|
949
984
|
}
|
|
950
985
|
|
|
951
986
|
definition.plugins.push('serverless-kms-grants');
|
|
@@ -1252,62 +1287,6 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
1252
1287
|
definition.custom[queueReference] = queueName;
|
|
1253
1288
|
}
|
|
1254
1289
|
}
|
|
1255
|
-
|
|
1256
|
-
// Discovery has already run successfully at this point if needed
|
|
1257
|
-
// The discoveredResources object contains all the necessary AWS resources
|
|
1258
|
-
|
|
1259
|
-
// Add websocket function if enabled
|
|
1260
|
-
if (AppDefinition.websockets?.enable === true) {
|
|
1261
|
-
definition.functions.defaultWebsocket = {
|
|
1262
|
-
handler:
|
|
1263
|
-
'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1264
|
-
events: [
|
|
1265
|
-
{
|
|
1266
|
-
websocket: {
|
|
1267
|
-
route: '$connect',
|
|
1268
|
-
},
|
|
1269
|
-
},
|
|
1270
|
-
{
|
|
1271
|
-
websocket: {
|
|
1272
|
-
route: '$default',
|
|
1273
|
-
},
|
|
1274
|
-
},
|
|
1275
|
-
{
|
|
1276
|
-
websocket: {
|
|
1277
|
-
route: '$disconnect',
|
|
1278
|
-
},
|
|
1279
|
-
},
|
|
1280
|
-
],
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// Discovery has already run successfully at this point if needed
|
|
1285
|
-
// The discoveredResources object contains all the necessary AWS resources
|
|
1286
|
-
|
|
1287
|
-
// Add websocket function if enabled
|
|
1288
|
-
if (AppDefinition.websockets?.enable === true) {
|
|
1289
|
-
definition.functions.defaultWebsocket = {
|
|
1290
|
-
handler:
|
|
1291
|
-
'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1292
|
-
events: [
|
|
1293
|
-
{
|
|
1294
|
-
websocket: {
|
|
1295
|
-
route: '$connect',
|
|
1296
|
-
},
|
|
1297
|
-
},
|
|
1298
|
-
{
|
|
1299
|
-
websocket: {
|
|
1300
|
-
route: '$default',
|
|
1301
|
-
},
|
|
1302
|
-
},
|
|
1303
|
-
{
|
|
1304
|
-
websocket: {
|
|
1305
|
-
route: '$disconnect',
|
|
1306
|
-
},
|
|
1307
|
-
},
|
|
1308
|
-
],
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
1290
|
}
|
|
1312
1291
|
|
|
1313
1292
|
// Discovery has already run successfully at this point if needed
|
|
@@ -1338,9 +1317,6 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
1338
1317
|
};
|
|
1339
1318
|
}
|
|
1340
1319
|
|
|
1341
|
-
// Discovery has already run successfully at this point if needed
|
|
1342
|
-
// The discoveredResources object contains all the necessary AWS resources
|
|
1343
|
-
|
|
1344
1320
|
// Modify handler paths to point to the correct node_modules location
|
|
1345
1321
|
definition.functions = modifyHandlerPaths(definition.functions);
|
|
1346
1322
|
|
|
@@ -118,16 +118,9 @@ describe('composeServerlessDefinition', () => {
|
|
|
118
118
|
|
|
119
119
|
const result = await composeServerlessDefinition(appDefinition);
|
|
120
120
|
|
|
121
|
-
expect(result.provider.vpc).
|
|
122
|
-
expect(result.
|
|
123
|
-
|
|
124
|
-
securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
|
|
125
|
-
subnetIds: [
|
|
126
|
-
'${env:AWS_DISCOVERY_SUBNET_ID_1}',
|
|
127
|
-
'${env:AWS_DISCOVERY_SUBNET_ID_2}'
|
|
128
|
-
]
|
|
129
|
-
}
|
|
130
|
-
});
|
|
121
|
+
expect(result.provider.vpc).toBeDefined();
|
|
122
|
+
expect(result.provider.vpc.securityGroupIds).toEqual(['sg-123456']);
|
|
123
|
+
expect(result.provider.vpc.subnetIds).toEqual(['subnet-123456', 'subnet-789012']);
|
|
131
124
|
});
|
|
132
125
|
|
|
133
126
|
it('should add VPC endpoint for S3 when VPC is enabled', async () => {
|
|
@@ -138,15 +131,9 @@ describe('composeServerlessDefinition', () => {
|
|
|
138
131
|
|
|
139
132
|
const result = await composeServerlessDefinition(appDefinition);
|
|
140
133
|
|
|
141
|
-
expect(result.resources.Resources.VPCEndpointS3).
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
|
|
145
|
-
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
146
|
-
VpcEndpointType: 'Gateway',
|
|
147
|
-
RouteTableIds: ['${env:AWS_DISCOVERY_ROUTE_TABLE_ID}']
|
|
148
|
-
}
|
|
149
|
-
});
|
|
134
|
+
expect(result.resources.Resources.VPCEndpointS3).toBeDefined();
|
|
135
|
+
expect(result.resources.Resources.VPCEndpointS3.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
136
|
+
expect(result.resources.Resources.VPCEndpointS3.Properties.VpcId).toBe('vpc-123456');
|
|
150
137
|
});
|
|
151
138
|
|
|
152
139
|
it('should not add VPC configuration when vpc.enable is false', async () => {
|
|
@@ -158,7 +145,6 @@ describe('composeServerlessDefinition', () => {
|
|
|
158
145
|
const result = await composeServerlessDefinition(appDefinition);
|
|
159
146
|
|
|
160
147
|
expect(result.provider.vpc).toBeUndefined();
|
|
161
|
-
expect(result.custom.vpc).toBeUndefined();
|
|
162
148
|
expect(result.resources.Resources.VPCEndpointS3).toBeUndefined();
|
|
163
149
|
});
|
|
164
150
|
|
|
@@ -170,14 +156,13 @@ describe('composeServerlessDefinition', () => {
|
|
|
170
156
|
const result = await composeServerlessDefinition(appDefinition);
|
|
171
157
|
|
|
172
158
|
expect(result.provider.vpc).toBeUndefined();
|
|
173
|
-
expect(result.custom.vpc).toBeUndefined();
|
|
174
159
|
});
|
|
175
160
|
});
|
|
176
161
|
|
|
177
162
|
describe('KMS Configuration', () => {
|
|
178
|
-
it('should add KMS configuration when encryption is enabled', async () => {
|
|
163
|
+
it('should add KMS configuration when encryption is enabled and key is found', async () => {
|
|
179
164
|
const appDefinition = {
|
|
180
|
-
encryption: {
|
|
165
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
181
166
|
integrations: []
|
|
182
167
|
};
|
|
183
168
|
|
|
@@ -193,11 +178,114 @@ describe('composeServerlessDefinition', () => {
|
|
|
193
178
|
'kms:GenerateDataKey',
|
|
194
179
|
'kms:Decrypt'
|
|
195
180
|
],
|
|
196
|
-
Resource: ['
|
|
181
|
+
Resource: ['arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012']
|
|
197
182
|
});
|
|
198
183
|
|
|
199
184
|
// Check environment variable
|
|
200
|
-
expect(result.provider.environment.KMS_KEY_ARN).toBe('
|
|
185
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012');
|
|
186
|
+
|
|
187
|
+
// Check plugin
|
|
188
|
+
expect(result.plugins).toContain('serverless-kms-grants');
|
|
189
|
+
|
|
190
|
+
// Check custom configuration
|
|
191
|
+
expect(result.custom.kmsGrants).toEqual({
|
|
192
|
+
kmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should create new KMS key when encryption is enabled, no key found, and createResourceIfNoneFound is true', async () => {
|
|
197
|
+
// Mock AWS discovery to return no KMS key
|
|
198
|
+
const { AWSDiscovery } = require('./aws-discovery');
|
|
199
|
+
const mockDiscoverResources = jest.fn().mockResolvedValue({
|
|
200
|
+
defaultVpcId: 'vpc-123456',
|
|
201
|
+
defaultSecurityGroupId: 'sg-123456',
|
|
202
|
+
privateSubnetId1: 'subnet-123456',
|
|
203
|
+
privateSubnetId2: 'subnet-789012',
|
|
204
|
+
publicSubnetId: 'subnet-public',
|
|
205
|
+
defaultRouteTableId: 'rtb-123456',
|
|
206
|
+
defaultKmsKeyId: null // No KMS key found
|
|
207
|
+
});
|
|
208
|
+
AWSDiscovery.mockImplementation(() => ({
|
|
209
|
+
discoverResources: mockDiscoverResources
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
const appDefinition = {
|
|
213
|
+
encryption: {
|
|
214
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
215
|
+
createResourceIfNoneFound: true
|
|
216
|
+
},
|
|
217
|
+
integrations: []
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
221
|
+
|
|
222
|
+
// Check that KMS key resource was created
|
|
223
|
+
expect(result.resources.Resources.FriggKMSKey).toEqual({
|
|
224
|
+
Type: 'AWS::KMS::Key',
|
|
225
|
+
Properties: {
|
|
226
|
+
EnableKeyRotation: true,
|
|
227
|
+
Description: 'Frigg KMS key for field-level encryption',
|
|
228
|
+
KeyPolicy: {
|
|
229
|
+
Version: '2012-10-17',
|
|
230
|
+
Statement: [
|
|
231
|
+
{
|
|
232
|
+
Sid: 'AllowRootAccountAdmin',
|
|
233
|
+
Effect: 'Allow',
|
|
234
|
+
Principal: {
|
|
235
|
+
AWS: {
|
|
236
|
+
'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root'
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
Action: 'kms:*',
|
|
240
|
+
Resource: '*'
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
Sid: 'AllowLambdaService',
|
|
244
|
+
Effect: 'Allow',
|
|
245
|
+
Principal: {
|
|
246
|
+
Service: 'lambda.amazonaws.com'
|
|
247
|
+
},
|
|
248
|
+
Action: [
|
|
249
|
+
'kms:GenerateDataKey',
|
|
250
|
+
'kms:Decrypt',
|
|
251
|
+
'kms:DescribeKey'
|
|
252
|
+
],
|
|
253
|
+
Resource: '*',
|
|
254
|
+
Condition: {
|
|
255
|
+
StringEquals: {
|
|
256
|
+
'kms:ViaService': 'lambda.us-east-1.amazonaws.com'
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
Tags: [
|
|
263
|
+
{
|
|
264
|
+
Key: 'Name',
|
|
265
|
+
Value: '${self:service}-${self:provider.stage}-frigg-kms-key'
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
Key: 'Purpose',
|
|
269
|
+
Value: 'Field-level encryption for Frigg application'
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Check IAM permissions for the new key
|
|
276
|
+
const kmsPermission = result.provider.iamRoleStatements.find(
|
|
277
|
+
statement => statement.Action.includes('kms:GenerateDataKey')
|
|
278
|
+
);
|
|
279
|
+
expect(kmsPermission).toEqual({
|
|
280
|
+
Effect: 'Allow',
|
|
281
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
282
|
+
Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }]
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Check environment variable
|
|
286
|
+
expect(result.provider.environment.KMS_KEY_ARN).toEqual({
|
|
287
|
+
'Fn::GetAtt': ['FriggKMSKey', 'Arn']
|
|
288
|
+
});
|
|
201
289
|
|
|
202
290
|
// Check plugin
|
|
203
291
|
expect(result.plugins).toContain('serverless-kms-grants');
|
|
@@ -208,9 +296,69 @@ describe('composeServerlessDefinition', () => {
|
|
|
208
296
|
});
|
|
209
297
|
});
|
|
210
298
|
|
|
299
|
+
it('should throw error when encryption is enabled, no key found, and createResourceIfNoneFound is false', async () => {
|
|
300
|
+
// Mock AWS discovery to return no KMS key
|
|
301
|
+
const { AWSDiscovery } = require('./aws-discovery');
|
|
302
|
+
const mockDiscoverResources = jest.fn().mockResolvedValue({
|
|
303
|
+
defaultVpcId: 'vpc-123456',
|
|
304
|
+
defaultSecurityGroupId: 'sg-123456',
|
|
305
|
+
privateSubnetId1: 'subnet-123456',
|
|
306
|
+
privateSubnetId2: 'subnet-789012',
|
|
307
|
+
publicSubnetId: 'subnet-public',
|
|
308
|
+
defaultRouteTableId: 'rtb-123456',
|
|
309
|
+
defaultKmsKeyId: null // No KMS key found
|
|
310
|
+
});
|
|
311
|
+
AWSDiscovery.mockImplementation(() => ({
|
|
312
|
+
discoverResources: mockDiscoverResources
|
|
313
|
+
}));
|
|
314
|
+
|
|
315
|
+
const appDefinition = {
|
|
316
|
+
encryption: {
|
|
317
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
318
|
+
createResourceIfNoneFound: false
|
|
319
|
+
},
|
|
320
|
+
integrations: []
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
324
|
+
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
325
|
+
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should throw error when encryption is enabled, no key found, and createResourceIfNoneFound is not specified', async () => {
|
|
330
|
+
// Mock AWS discovery to return no KMS key
|
|
331
|
+
const { AWSDiscovery } = require('./aws-discovery');
|
|
332
|
+
const mockDiscoverResources = jest.fn().mockResolvedValue({
|
|
333
|
+
defaultVpcId: 'vpc-123456',
|
|
334
|
+
defaultSecurityGroupId: 'sg-123456',
|
|
335
|
+
privateSubnetId1: 'subnet-123456',
|
|
336
|
+
privateSubnetId2: 'subnet-789012',
|
|
337
|
+
publicSubnetId: 'subnet-public',
|
|
338
|
+
defaultRouteTableId: 'rtb-123456',
|
|
339
|
+
defaultKmsKeyId: null // No KMS key found
|
|
340
|
+
});
|
|
341
|
+
AWSDiscovery.mockImplementation(() => ({
|
|
342
|
+
discoverResources: mockDiscoverResources
|
|
343
|
+
}));
|
|
344
|
+
|
|
345
|
+
const appDefinition = {
|
|
346
|
+
encryption: {
|
|
347
|
+
fieldLevelEncryptionMethod: 'kms'
|
|
348
|
+
// createResourceIfNoneFound not specified, defaults to false
|
|
349
|
+
},
|
|
350
|
+
integrations: []
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
354
|
+
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
355
|
+
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
211
359
|
it('should not add KMS configuration when encryption is disabled', async () => {
|
|
212
360
|
const appDefinition = {
|
|
213
|
-
encryption: {
|
|
361
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
214
362
|
integrations: []
|
|
215
363
|
};
|
|
216
364
|
|
|
@@ -315,10 +463,9 @@ describe('composeServerlessDefinition', () => {
|
|
|
315
463
|
expect(result.functions.testIntegration).toEqual({
|
|
316
464
|
handler: 'node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.testIntegration.handler',
|
|
317
465
|
events: [{
|
|
318
|
-
|
|
466
|
+
httpApi: {
|
|
319
467
|
path: '/api/testIntegration-integration/{proxy+}',
|
|
320
|
-
method: 'ANY'
|
|
321
|
-
cors: true
|
|
468
|
+
method: 'ANY'
|
|
322
469
|
}
|
|
323
470
|
}]
|
|
324
471
|
});
|
|
@@ -389,7 +536,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
389
536
|
it('should combine VPC, KMS, and SSM configurations', async () => {
|
|
390
537
|
const appDefinition = {
|
|
391
538
|
vpc: { enable: true },
|
|
392
|
-
encryption: {
|
|
539
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
393
540
|
ssm: { enable: true },
|
|
394
541
|
integrations: [mockIntegration]
|
|
395
542
|
};
|
|
@@ -428,7 +575,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
428
575
|
it('should handle partial configuration combinations', async () => {
|
|
429
576
|
const appDefinition = {
|
|
430
577
|
vpc: { enable: true },
|
|
431
|
-
encryption: {
|
|
578
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
432
579
|
integrations: []
|
|
433
580
|
};
|
|
434
581
|
|
|
@@ -459,9 +606,9 @@ describe('composeServerlessDefinition', () => {
|
|
|
459
606
|
expect(result.resources.Resources.ApiGatewayAlarm5xx).toBeDefined();
|
|
460
607
|
|
|
461
608
|
// Check default functions
|
|
462
|
-
expect(result.functions.defaultWebsocket).toBeDefined();
|
|
463
609
|
expect(result.functions.auth).toBeDefined();
|
|
464
610
|
expect(result.functions.user).toBeDefined();
|
|
611
|
+
expect(result.functions.health).toBeDefined();
|
|
465
612
|
|
|
466
613
|
// Check default plugins
|
|
467
614
|
expect(result.plugins).toContain('serverless-jetpack');
|
|
@@ -496,11 +643,64 @@ describe('composeServerlessDefinition', () => {
|
|
|
496
643
|
|
|
497
644
|
const result = await composeServerlessDefinition(appDefinition);
|
|
498
645
|
|
|
499
|
-
expect(result.provider.environment.STAGE).toBe('${opt:stage}');
|
|
646
|
+
expect(result.provider.environment.STAGE).toBe('${opt:stage, "dev"}');
|
|
500
647
|
expect(result.provider.environment.AWS_NODEJS_CONNECTION_REUSE_ENABLED).toBe(1);
|
|
501
648
|
});
|
|
502
649
|
});
|
|
503
650
|
|
|
651
|
+
describe('WebSocket Configuration', () => {
|
|
652
|
+
it('should add websocket function when websockets.enable is true', async () => {
|
|
653
|
+
const appDefinition = {
|
|
654
|
+
websockets: { enable: true },
|
|
655
|
+
integrations: []
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
659
|
+
|
|
660
|
+
expect(result.functions.defaultWebsocket).toEqual({
|
|
661
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
662
|
+
events: [
|
|
663
|
+
{
|
|
664
|
+
websocket: {
|
|
665
|
+
route: '$connect',
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
websocket: {
|
|
670
|
+
route: '$default',
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
websocket: {
|
|
675
|
+
route: '$disconnect',
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('should not add websocket function when websockets.enable is false', async () => {
|
|
683
|
+
const appDefinition = {
|
|
684
|
+
websockets: { enable: false },
|
|
685
|
+
integrations: []
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
689
|
+
|
|
690
|
+
expect(result.functions.defaultWebsocket).toBeUndefined();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('should not add websocket function when websockets is not defined', async () => {
|
|
694
|
+
const appDefinition = {
|
|
695
|
+
integrations: []
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
699
|
+
|
|
700
|
+
expect(result.functions.defaultWebsocket).toBeUndefined();
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
|
|
504
704
|
describe('Edge Cases', () => {
|
|
505
705
|
it('should handle empty app definition', async () => {
|
|
506
706
|
const appDefinition = {};
|
|
@@ -515,7 +715,9 @@ describe('composeServerlessDefinition', () => {
|
|
|
515
715
|
integrations: null
|
|
516
716
|
};
|
|
517
717
|
|
|
518
|
-
|
|
718
|
+
// Should not throw, just ignore invalid integrations
|
|
719
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
720
|
+
expect(result).toBeDefined();
|
|
519
721
|
});
|
|
520
722
|
|
|
521
723
|
it('should handle integration with missing Definition', async () => {
|
|
@@ -524,7 +726,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
524
726
|
integrations: [invalidIntegration]
|
|
525
727
|
};
|
|
526
728
|
|
|
527
|
-
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
729
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow('Invalid integration: missing Definition or name');
|
|
528
730
|
});
|
|
529
731
|
|
|
530
732
|
it('should handle integration with missing name', async () => {
|
|
@@ -535,7 +737,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
535
737
|
integrations: [invalidIntegration]
|
|
536
738
|
};
|
|
537
739
|
|
|
538
|
-
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
740
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow('Invalid integration: missing Definition or name');
|
|
539
741
|
});
|
|
540
742
|
});
|
|
541
743
|
});
|
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.
|
|
4
|
+
"version": "2.0.0--canary.425.dd575ef.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.
|
|
13
|
-
"@friggframework/test": "2.0.0--canary.
|
|
12
|
+
"@friggframework/schemas": "2.0.0--canary.425.dd575ef.0",
|
|
13
|
+
"@friggframework/test": "2.0.0--canary.425.dd575ef.0",
|
|
14
14
|
"@hapi/boom": "^10.0.1",
|
|
15
15
|
"@inquirer/prompts": "^5.3.8",
|
|
16
16
|
"axios": "^1.7.2",
|
|
@@ -32,8 +32,9 @@
|
|
|
32
32
|
"serverless-http": "^2.7.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
36
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
35
|
+
"@friggframework/eslint-config": "2.0.0--canary.425.dd575ef.0",
|
|
36
|
+
"@friggframework/prettier-config": "2.0.0--canary.425.dd575ef.0",
|
|
37
|
+
"jest": "^30.1.3",
|
|
37
38
|
"prettier": "^2.7.1",
|
|
38
39
|
"serverless": "3.39.0",
|
|
39
40
|
"serverless-dotenv-plugin": "^6.0.0",
|
|
@@ -65,5 +66,5 @@
|
|
|
65
66
|
"publishConfig": {
|
|
66
67
|
"access": "public"
|
|
67
68
|
},
|
|
68
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "dd575ef50858dae736ba1b59ccb006ac706fac4f"
|
|
69
70
|
}
|