@friggframework/devtools 2.0.0--canary.397.155fecd.0 → 2.0.0--canary.398.e2147f7.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.
@@ -0,0 +1,383 @@
1
+ const fs = require('fs');
2
+ const { composeServerlessDefinition } = require('./serverless-template');
3
+ const { AWSDiscovery } = require('./aws-discovery');
4
+ const { BuildTimeDiscovery } = require('./build-time-discovery');
5
+ const FriggServerlessPlugin = require('../../serverless-plugin/index');
6
+
7
+ // Integration tests for end-to-end AWS discovery and serverless config generation
8
+ describe('VPC/KMS/SSM Integration Tests', () => {
9
+ let mockAWSDiscovery;
10
+ let buildTimeDiscovery;
11
+
12
+ const mockAWSResources = {
13
+ defaultVpcId: 'vpc-12345678',
14
+ defaultSecurityGroupId: 'sg-12345678',
15
+ privateSubnetId1: 'subnet-private-1',
16
+ privateSubnetId2: 'subnet-private-2',
17
+ privateRouteTableId: 'rtb-12345678',
18
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
19
+ };
20
+
21
+ beforeEach(() => {
22
+ // Mock AWSDiscovery to return consistent test data
23
+ mockAWSDiscovery = {
24
+ discoverResources: jest.fn().mockResolvedValue(mockAWSResources),
25
+ findDefaultVpc: jest.fn().mockResolvedValue({ VpcId: mockAWSResources.defaultVpcId }),
26
+ findPrivateSubnets: jest.fn().mockResolvedValue([
27
+ { SubnetId: mockAWSResources.privateSubnetId1 },
28
+ { SubnetId: mockAWSResources.privateSubnetId2 }
29
+ ]),
30
+ findDefaultSecurityGroup: jest.fn().mockResolvedValue({ GroupId: mockAWSResources.defaultSecurityGroupId }),
31
+ findPrivateRouteTable: jest.fn().mockResolvedValue({ RouteTableId: mockAWSResources.privateRouteTableId }),
32
+ findDefaultKmsKey: jest.fn().mockResolvedValue(mockAWSResources.defaultKmsKeyId)
33
+ };
34
+
35
+ jest.doMock('./aws-discovery', () => ({
36
+ AWSDiscovery: jest.fn(() => mockAWSDiscovery)
37
+ }));
38
+
39
+ buildTimeDiscovery = new BuildTimeDiscovery('us-east-1');
40
+ });
41
+
42
+ afterEach(() => {
43
+ jest.clearAllMocks();
44
+ jest.resetModules();
45
+ });
46
+
47
+ describe('End-to-End Serverless Configuration Generation', () => {
48
+ it('should generate complete serverless config with VPC, KMS, and SSM enabled', async () => {
49
+ const appDefinition = {
50
+ name: 'test-frigg-app',
51
+ vpc: { enable: true },
52
+ encryption: { useDefaultKMSForFieldLevelEncryption: true },
53
+ ssm: { enable: true },
54
+ integrations: [{
55
+ Definition: {
56
+ name: 'testIntegration'
57
+ }
58
+ }]
59
+ };
60
+
61
+ // Run AWS discovery
62
+ const discoveredResources = await mockAWSDiscovery.discoverResources();
63
+
64
+ // Set environment variables as would happen in build
65
+ process.env.AWS_DISCOVERY_VPC_ID = discoveredResources.defaultVpcId;
66
+ process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = discoveredResources.defaultSecurityGroupId;
67
+ process.env.AWS_DISCOVERY_SUBNET_ID_1 = discoveredResources.privateSubnetId1;
68
+ process.env.AWS_DISCOVERY_SUBNET_ID_2 = discoveredResources.privateSubnetId2;
69
+ process.env.AWS_DISCOVERY_ROUTE_TABLE_ID = discoveredResources.privateRouteTableId;
70
+ process.env.AWS_DISCOVERY_KMS_KEY_ID = discoveredResources.defaultKmsKeyId;
71
+
72
+ // Generate serverless configuration
73
+ const serverlessConfig = composeServerlessDefinition(appDefinition);
74
+
75
+ // Verify VPC configuration
76
+ expect(serverlessConfig.provider.vpc).toBe('${self:custom.vpc.${self:provider.stage}}');
77
+ expect(serverlessConfig.custom.vpc).toEqual({
78
+ '${self:provider.stage}': {
79
+ securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
80
+ subnetIds: [
81
+ '${env:AWS_DISCOVERY_SUBNET_ID_1}',
82
+ '${env:AWS_DISCOVERY_SUBNET_ID_2}'
83
+ ]
84
+ }
85
+ });
86
+
87
+ // Verify VPC Endpoint
88
+ expect(serverlessConfig.resources.Resources.VPCEndpointS3).toEqual({
89
+ Type: 'AWS::EC2::VPCEndpoint',
90
+ Properties: {
91
+ VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
92
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
93
+ VpcEndpointType: 'Gateway',
94
+ RouteTableIds: ['${env:AWS_DISCOVERY_ROUTE_TABLE_ID}']
95
+ }
96
+ });
97
+
98
+ // Verify KMS configuration
99
+ expect(serverlessConfig.plugins).toContain('serverless-kms-grants');
100
+ expect(serverlessConfig.provider.environment.KMS_KEY_ARN).toBe('${self:custom.kmsGrants.kmsKeyId}');
101
+ expect(serverlessConfig.custom.kmsGrants).toEqual({
102
+ kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
103
+ });
104
+
105
+ // Verify KMS IAM permissions
106
+ const kmsPermission = serverlessConfig.provider.iamRoleStatements.find(
107
+ statement => statement.Action.includes('kms:GenerateDataKey')
108
+ );
109
+ expect(kmsPermission).toBeDefined();
110
+
111
+ // Verify SSM configuration
112
+ expect(serverlessConfig.provider.layers).toEqual([
113
+ 'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
114
+ ]);
115
+ expect(serverlessConfig.provider.environment.SSM_PARAMETER_PREFIX).toBe('/${self:service}/${self:provider.stage}');
116
+
117
+ // Verify SSM IAM permissions
118
+ const ssmPermission = serverlessConfig.provider.iamRoleStatements.find(
119
+ statement => statement.Action.includes('ssm:GetParameter')
120
+ );
121
+ expect(ssmPermission).toBeDefined();
122
+
123
+ // Verify integration resources
124
+ expect(serverlessConfig.functions.testIntegration).toBeDefined();
125
+ expect(serverlessConfig.functions.testIntegrationQueueWorker).toBeDefined();
126
+ expect(serverlessConfig.resources.Resources.TestIntegrationQueue).toBeDefined();
127
+
128
+ // Clean up environment
129
+ delete process.env.AWS_DISCOVERY_VPC_ID;
130
+ delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
131
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
132
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
133
+ delete process.env.AWS_DISCOVERY_ROUTE_TABLE_ID;
134
+ delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
135
+ });
136
+
137
+ it('should generate config with only VPC enabled', async () => {
138
+ const appDefinition = {
139
+ name: 'vpc-only-app',
140
+ vpc: { enable: true },
141
+ integrations: []
142
+ };
143
+
144
+ process.env.AWS_DISCOVERY_VPC_ID = mockAWSResources.defaultVpcId;
145
+ process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = mockAWSResources.defaultSecurityGroupId;
146
+ process.env.AWS_DISCOVERY_SUBNET_ID_1 = mockAWSResources.privateSubnetId1;
147
+ process.env.AWS_DISCOVERY_SUBNET_ID_2 = mockAWSResources.privateSubnetId2;
148
+ process.env.AWS_DISCOVERY_ROUTE_TABLE_ID = mockAWSResources.privateRouteTableId;
149
+
150
+ const serverlessConfig = composeServerlessDefinition(appDefinition);
151
+
152
+ // Should have VPC config
153
+ expect(serverlessConfig.provider.vpc).toBeDefined();
154
+ expect(serverlessConfig.custom.vpc).toBeDefined();
155
+ expect(serverlessConfig.resources.Resources.VPCEndpointS3).toBeDefined();
156
+
157
+ // Should not have KMS config
158
+ expect(serverlessConfig.plugins).not.toContain('serverless-kms-grants');
159
+ expect(serverlessConfig.provider.environment.KMS_KEY_ARN).toBeUndefined();
160
+
161
+ // Should not have SSM config
162
+ expect(serverlessConfig.provider.layers).toBeUndefined();
163
+ expect(serverlessConfig.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
164
+
165
+ // Clean up
166
+ delete process.env.AWS_DISCOVERY_VPC_ID;
167
+ delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
168
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
169
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
170
+ delete process.env.AWS_DISCOVERY_ROUTE_TABLE_ID;
171
+ });
172
+
173
+ it('should generate config with only KMS enabled', async () => {
174
+ const appDefinition = {
175
+ name: 'kms-only-app',
176
+ encryption: { useDefaultKMSForFieldLevelEncryption: true },
177
+ integrations: []
178
+ };
179
+
180
+ process.env.AWS_DISCOVERY_KMS_KEY_ID = mockAWSResources.defaultKmsKeyId;
181
+
182
+ const serverlessConfig = composeServerlessDefinition(appDefinition);
183
+
184
+ // Should have KMS config
185
+ expect(serverlessConfig.plugins).toContain('serverless-kms-grants');
186
+ expect(serverlessConfig.custom.kmsGrants).toBeDefined();
187
+
188
+ // Should not have VPC config
189
+ expect(serverlessConfig.provider.vpc).toBeUndefined();
190
+
191
+ // Should not have SSM config
192
+ expect(serverlessConfig.provider.layers).toBeUndefined();
193
+
194
+ delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
195
+ });
196
+ });
197
+
198
+ describe('Plugin Integration', () => {
199
+ it('should trigger AWS discovery through serverless plugin', async () => {
200
+ const mockServerless = {
201
+ cli: { log: jest.fn() },
202
+ service: {
203
+ provider: {
204
+ name: 'aws',
205
+ region: 'us-east-1',
206
+ vpc: '${self:custom.vpc.${self:provider.stage}}'
207
+ },
208
+ plugins: ['serverless-kms-grants'],
209
+ custom: {},
210
+ functions: {}
211
+ },
212
+ processedInput: { commands: [] },
213
+ getProvider: jest.fn(() => ({})),
214
+ extendConfiguration: jest.fn()
215
+ };
216
+
217
+ const plugin = new FriggServerlessPlugin(mockServerless, { stage: 'test' });
218
+
219
+ // Mock BuildTimeDiscovery
220
+ const mockBuildTimeDiscovery = {
221
+ preBuildHook: jest.fn().mockResolvedValue(mockAWSResources)
222
+ };
223
+
224
+ jest.doMock('./build-time-discovery', () => ({
225
+ BuildTimeDiscovery: jest.fn(() => mockBuildTimeDiscovery)
226
+ }));
227
+
228
+ // Test the beforePackageInitialize hook
229
+ await plugin.beforePackageInitialize();
230
+
231
+ expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
232
+ expect.objectContaining({
233
+ vpc: { enable: true },
234
+ encryption: { useDefaultKMSForFieldLevelEncryption: true }
235
+ }),
236
+ 'us-east-1'
237
+ );
238
+
239
+ expect(mockServerless.cli.log).toHaveBeenCalledWith('AWS discovery completed successfully');
240
+ });
241
+
242
+ it('should handle plugin discovery failure gracefully', async () => {
243
+ const mockServerless = {
244
+ cli: { log: jest.fn() },
245
+ service: {
246
+ provider: {
247
+ name: 'aws',
248
+ region: 'us-east-1',
249
+ vpc: '${self:custom.vpc}'
250
+ },
251
+ plugins: [],
252
+ custom: {},
253
+ functions: {}
254
+ },
255
+ processedInput: { commands: [] },
256
+ getProvider: jest.fn(() => ({})),
257
+ extendConfiguration: jest.fn()
258
+ };
259
+
260
+ const plugin = new FriggServerlessPlugin(mockServerless, { stage: 'test' });
261
+
262
+ // Mock BuildTimeDiscovery to fail
263
+ const mockBuildTimeDiscovery = {
264
+ preBuildHook: jest.fn().mockRejectedValue(new Error('AWS API Error'))
265
+ };
266
+
267
+ jest.doMock('./build-time-discovery', () => ({
268
+ BuildTimeDiscovery: jest.fn(() => mockBuildTimeDiscovery)
269
+ }));
270
+
271
+ await plugin.beforePackageInitialize();
272
+
273
+ expect(mockServerless.cli.log).toHaveBeenCalledWith('AWS discovery failed, continuing with deployment...');
274
+ expect(mockServerless.cli.log).toHaveBeenCalledWith('Using fallback values for AWS resources');
275
+
276
+ // Verify fallback values are set
277
+ expect(process.env.AWS_DISCOVERY_VPC_ID).toBe('vpc-fallback');
278
+ expect(process.env.AWS_DISCOVERY_KMS_KEY_ID).toBe('arn:aws:kms:*:*:key/*');
279
+ });
280
+ });
281
+
282
+ describe('Template Variable Replacement', () => {
283
+ it('should replace environment variable placeholders with actual values', () => {
284
+ const template = `
285
+ provider:
286
+ vpc:
287
+ securityGroupIds:
288
+ - \${env:AWS_DISCOVERY_SECURITY_GROUP_ID}
289
+ subnetIds:
290
+ - \${env:AWS_DISCOVERY_SUBNET_ID_1}
291
+ - \${env:AWS_DISCOVERY_SUBNET_ID_2}
292
+ environment:
293
+ KMS_KEY_ARN: \${env:AWS_DISCOVERY_KMS_KEY_ID}
294
+ resources:
295
+ VPCEndpoint:
296
+ Properties:
297
+ VpcId: \${env:AWS_DISCOVERY_VPC_ID}
298
+ `;
299
+
300
+ // Set environment variables
301
+ process.env.AWS_DISCOVERY_VPC_ID = mockAWSResources.defaultVpcId;
302
+ process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = mockAWSResources.defaultSecurityGroupId;
303
+ process.env.AWS_DISCOVERY_SUBNET_ID_1 = mockAWSResources.privateSubnetId1;
304
+ process.env.AWS_DISCOVERY_SUBNET_ID_2 = mockAWSResources.privateSubnetId2;
305
+ process.env.AWS_DISCOVERY_KMS_KEY_ID = mockAWSResources.defaultKmsKeyId;
306
+
307
+ // In a real deployment, serverless framework would resolve these environment variables
308
+ // For testing, we can verify the placeholders are correctly formatted
309
+ expect(template).toContain('${env:AWS_DISCOVERY_VPC_ID}');
310
+ expect(template).toContain('${env:AWS_DISCOVERY_SECURITY_GROUP_ID}');
311
+ expect(template).toContain('${env:AWS_DISCOVERY_SUBNET_ID_1}');
312
+ expect(template).toContain('${env:AWS_DISCOVERY_SUBNET_ID_2}');
313
+ expect(template).toContain('${env:AWS_DISCOVERY_KMS_KEY_ID}');
314
+
315
+ // Clean up
316
+ delete process.env.AWS_DISCOVERY_VPC_ID;
317
+ delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
318
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
319
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
320
+ delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
321
+ });
322
+ });
323
+
324
+ describe('Error Scenarios', () => {
325
+ it('should handle AWS discovery timeout gracefully', async () => {
326
+ const mockFailingDiscovery = {
327
+ discoverResources: jest.fn().mockRejectedValue(new Error('Request timeout'))
328
+ };
329
+
330
+ jest.doMock('./aws-discovery', () => ({
331
+ AWSDiscovery: jest.fn(() => mockFailingDiscovery)
332
+ }));
333
+
334
+ const appDefinition = {
335
+ vpc: { enable: true },
336
+ integrations: []
337
+ };
338
+
339
+ await expect(buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1')).rejects.toThrow('Request timeout');
340
+ });
341
+
342
+ it('should handle partial AWS resource discovery', async () => {
343
+ const partialResources = {
344
+ defaultVpcId: 'vpc-12345678',
345
+ defaultSecurityGroupId: 'sg-12345678',
346
+ privateSubnetId1: 'subnet-1',
347
+ privateSubnetId2: 'subnet-1', // Same subnet used twice
348
+ privateRouteTableId: 'rtb-12345678',
349
+ defaultKmsKeyId: '*' // Fallback KMS key
350
+ };
351
+
352
+ mockAWSDiscovery.discoverResources.mockResolvedValue(partialResources);
353
+
354
+ const appDefinition = {
355
+ vpc: { enable: true },
356
+ encryption: { useDefaultKMSForFieldLevelEncryption: true },
357
+ integrations: []
358
+ };
359
+
360
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
361
+
362
+ expect(result).toEqual(partialResources);
363
+ expect(result.privateSubnetId2).toBe(result.privateSubnetId1); // Should handle single subnet scenario
364
+ expect(result.defaultKmsKeyId).toBe('*'); // Should handle KMS fallback
365
+ });
366
+ });
367
+
368
+ describe('Multi-Region Support', () => {
369
+ it('should support different AWS regions', async () => {
370
+ const appDefinition = {
371
+ vpc: { enable: true },
372
+ integrations: []
373
+ };
374
+
375
+ const euWestDiscovery = new BuildTimeDiscovery('eu-west-1');
376
+
377
+ await euWestDiscovery.preBuildHook(appDefinition, 'eu-west-1');
378
+
379
+ // Verify that AWSDiscovery was instantiated with correct region
380
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
381
+ });
382
+ });
383
+ });