@friggframework/devtools 2.0.0-next.28 → 2.0.0-next.29
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/build-command/index.js +4 -2
- package/frigg-cli/deploy-command/index.js +5 -2
- package/frigg-cli/generate-iam-command.js +115 -0
- package/frigg-cli/index.js +11 -1
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
- package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
- package/infrastructure/README-TESTING.md +332 -0
- package/infrastructure/README.md +421 -0
- package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -0
- package/infrastructure/aws-discovery.js +568 -0
- package/infrastructure/aws-discovery.test.js +373 -0
- package/infrastructure/build-time-discovery.js +206 -0
- package/infrastructure/build-time-discovery.test.js +375 -0
- package/infrastructure/create-frigg-infrastructure.js +2 -2
- package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
- package/infrastructure/iam-generator.js +687 -0
- package/infrastructure/iam-generator.test.js +169 -0
- package/infrastructure/iam-policy-basic.json +212 -0
- package/infrastructure/iam-policy-full.json +282 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +110 -0
- package/infrastructure/serverless-template.js +514 -167
- package/infrastructure/serverless-template.test.js +541 -0
- package/management-ui/dist/assets/FriggLogo-B7Xx8ZW1.svg +1 -0
- package/management-ui/dist/assets/index-BA21WgFa.js +1221 -0
- package/management-ui/dist/assets/index-CbM64Oba.js +1221 -0
- package/management-ui/dist/assets/index-CkvseXTC.css +1 -0
- package/management-ui/dist/index.html +14 -0
- package/package.json +9 -5
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
const { composeServerlessDefinition } = require('./serverless-template');
|
|
2
|
+
|
|
3
|
+
// Mock AWS Discovery to prevent actual AWS calls
|
|
4
|
+
jest.mock('./aws-discovery', () => {
|
|
5
|
+
return {
|
|
6
|
+
AWSDiscovery: jest.fn().mockImplementation(() => {
|
|
7
|
+
return {
|
|
8
|
+
discoverResources: jest.fn().mockResolvedValue({
|
|
9
|
+
defaultVpcId: 'vpc-123456',
|
|
10
|
+
defaultSecurityGroupId: 'sg-123456',
|
|
11
|
+
privateSubnetId1: 'subnet-123456',
|
|
12
|
+
privateSubnetId2: 'subnet-789012',
|
|
13
|
+
publicSubnetId: 'subnet-public',
|
|
14
|
+
defaultRouteTableId: 'rtb-123456',
|
|
15
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
})
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('composeServerlessDefinition', () => {
|
|
23
|
+
let mockIntegration;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
mockIntegration = {
|
|
27
|
+
Definition: {
|
|
28
|
+
name: 'testIntegration'
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Mock process.argv to avoid offline mode during tests
|
|
33
|
+
process.argv = ['node', 'test'];
|
|
34
|
+
|
|
35
|
+
// Clear AWS_REGION for tests
|
|
36
|
+
delete process.env.AWS_REGION;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.restoreAllMocks();
|
|
41
|
+
// Restore env
|
|
42
|
+
delete process.env.AWS_REGION;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('Basic Configuration', () => {
|
|
46
|
+
it('should create basic serverless definition with minimal app definition', async () => {
|
|
47
|
+
const appDefinition = {
|
|
48
|
+
name: 'test-app',
|
|
49
|
+
integrations: []
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
53
|
+
|
|
54
|
+
expect(result.service).toBe('test-app');
|
|
55
|
+
expect(result.provider.name).toBe('aws');
|
|
56
|
+
expect(result.provider.runtime).toBe('nodejs20.x');
|
|
57
|
+
expect(result.provider.region).toBe('us-east-1');
|
|
58
|
+
expect(result.provider.stage).toBe('${opt:stage}');
|
|
59
|
+
expect(result.frameworkVersion).toBe('>=3.17.0');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should use default service name when name not provided', async () => {
|
|
63
|
+
const appDefinition = {
|
|
64
|
+
integrations: []
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
68
|
+
|
|
69
|
+
expect(result.service).toBe('create-frigg-app');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should use custom provider when specified', async () => {
|
|
73
|
+
const appDefinition = {
|
|
74
|
+
provider: 'custom-provider',
|
|
75
|
+
integrations: []
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
79
|
+
|
|
80
|
+
expect(result.provider.name).toBe('custom-provider');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should use AWS_REGION environment variable when set', async () => {
|
|
84
|
+
process.env.AWS_REGION = 'eu-west-1';
|
|
85
|
+
|
|
86
|
+
const appDefinition = {
|
|
87
|
+
name: 'test-app',
|
|
88
|
+
integrations: []
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
92
|
+
|
|
93
|
+
expect(result.provider.region).toBe('eu-west-1');
|
|
94
|
+
expect(result.custom['serverless-offline-sqs'].region).toBe('eu-west-1');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should default to us-east-1 when AWS_REGION is not set', async () => {
|
|
98
|
+
delete process.env.AWS_REGION;
|
|
99
|
+
|
|
100
|
+
const appDefinition = {
|
|
101
|
+
name: 'test-app',
|
|
102
|
+
integrations: []
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
106
|
+
|
|
107
|
+
expect(result.provider.region).toBe('us-east-1');
|
|
108
|
+
expect(result.custom['serverless-offline-sqs'].region).toBe('us-east-1');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('VPC Configuration', () => {
|
|
113
|
+
it('should add VPC configuration when vpc.enable is true', async () => {
|
|
114
|
+
const appDefinition = {
|
|
115
|
+
vpc: { enable: true },
|
|
116
|
+
integrations: []
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
120
|
+
|
|
121
|
+
expect(result.provider.vpc).toBe('${self:custom.vpc.${self:provider.stage}}');
|
|
122
|
+
expect(result.custom.vpc).toEqual({
|
|
123
|
+
'${self:provider.stage}': {
|
|
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
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should add VPC endpoint for S3 when VPC is enabled', async () => {
|
|
134
|
+
const appDefinition = {
|
|
135
|
+
vpc: { enable: true },
|
|
136
|
+
integrations: []
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
140
|
+
|
|
141
|
+
expect(result.resources.Resources.VPCEndpointS3).toEqual({
|
|
142
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
143
|
+
Properties: {
|
|
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
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should not add VPC configuration when vpc.enable is false', async () => {
|
|
153
|
+
const appDefinition = {
|
|
154
|
+
vpc: { enable: false },
|
|
155
|
+
integrations: []
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
159
|
+
|
|
160
|
+
expect(result.provider.vpc).toBeUndefined();
|
|
161
|
+
expect(result.custom.vpc).toBeUndefined();
|
|
162
|
+
expect(result.resources.Resources.VPCEndpointS3).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should not add VPC configuration when vpc is not defined', async () => {
|
|
166
|
+
const appDefinition = {
|
|
167
|
+
integrations: []
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
171
|
+
|
|
172
|
+
expect(result.provider.vpc).toBeUndefined();
|
|
173
|
+
expect(result.custom.vpc).toBeUndefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('KMS Configuration', () => {
|
|
178
|
+
it('should add KMS configuration when encryption is enabled', async () => {
|
|
179
|
+
const appDefinition = {
|
|
180
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
181
|
+
integrations: []
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
185
|
+
|
|
186
|
+
// Check IAM permissions
|
|
187
|
+
const kmsPermission = result.provider.iamRoleStatements.find(
|
|
188
|
+
statement => statement.Action.includes('kms:GenerateDataKey')
|
|
189
|
+
);
|
|
190
|
+
expect(kmsPermission).toEqual({
|
|
191
|
+
Effect: 'Allow',
|
|
192
|
+
Action: [
|
|
193
|
+
'kms:GenerateDataKey',
|
|
194
|
+
'kms:Decrypt'
|
|
195
|
+
],
|
|
196
|
+
Resource: ['${self:custom.kmsGrants.kmsKeyId}']
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Check environment variable
|
|
200
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBe('${self:custom.kmsGrants.kmsKeyId}');
|
|
201
|
+
|
|
202
|
+
// Check plugin
|
|
203
|
+
expect(result.plugins).toContain('serverless-kms-grants');
|
|
204
|
+
|
|
205
|
+
// Check custom configuration
|
|
206
|
+
expect(result.custom.kmsGrants).toEqual({
|
|
207
|
+
kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should not add KMS configuration when encryption is disabled', async () => {
|
|
212
|
+
const appDefinition = {
|
|
213
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: false },
|
|
214
|
+
integrations: []
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
218
|
+
|
|
219
|
+
const kmsPermission = result.provider.iamRoleStatements.find(
|
|
220
|
+
statement => statement.Action && statement.Action.includes('kms:GenerateDataKey')
|
|
221
|
+
);
|
|
222
|
+
expect(kmsPermission).toBeUndefined();
|
|
223
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBeUndefined();
|
|
224
|
+
expect(result.plugins).not.toContain('serverless-kms-grants');
|
|
225
|
+
expect(result.custom.kmsGrants).toBeUndefined();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should not add KMS configuration when encryption is not defined', async () => {
|
|
229
|
+
const appDefinition = {
|
|
230
|
+
integrations: []
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
234
|
+
|
|
235
|
+
const kmsPermission = result.provider.iamRoleStatements.find(
|
|
236
|
+
statement => statement.Action && statement.Action.includes('kms:GenerateDataKey')
|
|
237
|
+
);
|
|
238
|
+
expect(kmsPermission).toBeUndefined();
|
|
239
|
+
expect(result.custom.kmsGrants).toBeUndefined();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('SSM Configuration', () => {
|
|
244
|
+
it('should add SSM configuration when ssm.enable is true', async () => {
|
|
245
|
+
const appDefinition = {
|
|
246
|
+
ssm: { enable: true },
|
|
247
|
+
integrations: []
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
251
|
+
|
|
252
|
+
// Check lambda layers
|
|
253
|
+
expect(result.provider.layers).toEqual([
|
|
254
|
+
'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
// Check IAM permissions
|
|
258
|
+
const ssmPermission = result.provider.iamRoleStatements.find(
|
|
259
|
+
statement => statement.Action.includes('ssm:GetParameter')
|
|
260
|
+
);
|
|
261
|
+
expect(ssmPermission).toEqual({
|
|
262
|
+
Effect: 'Allow',
|
|
263
|
+
Action: [
|
|
264
|
+
'ssm:GetParameter',
|
|
265
|
+
'ssm:GetParameters',
|
|
266
|
+
'ssm:GetParametersByPath'
|
|
267
|
+
],
|
|
268
|
+
Resource: [
|
|
269
|
+
'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
|
|
270
|
+
]
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Check environment variable
|
|
274
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBe('/${self:service}/${self:provider.stage}');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should not add SSM configuration when ssm.enable is false', async () => {
|
|
278
|
+
const appDefinition = {
|
|
279
|
+
ssm: { enable: false },
|
|
280
|
+
integrations: []
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
284
|
+
|
|
285
|
+
expect(result.provider.layers).toBeUndefined();
|
|
286
|
+
|
|
287
|
+
const ssmPermission = result.provider.iamRoleStatements.find(
|
|
288
|
+
statement => statement.Action && statement.Action.includes('ssm:GetParameter')
|
|
289
|
+
);
|
|
290
|
+
expect(ssmPermission).toBeUndefined();
|
|
291
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should not add SSM configuration when ssm is not defined', async () => {
|
|
295
|
+
const appDefinition = {
|
|
296
|
+
integrations: []
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
300
|
+
|
|
301
|
+
expect(result.provider.layers).toBeUndefined();
|
|
302
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('Integration Configuration', () => {
|
|
307
|
+
it('should add integration-specific resources and functions', async () => {
|
|
308
|
+
const appDefinition = {
|
|
309
|
+
integrations: [mockIntegration]
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
313
|
+
|
|
314
|
+
// Check integration function
|
|
315
|
+
expect(result.functions.testIntegration).toEqual({
|
|
316
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.testIntegration.handler',
|
|
317
|
+
events: [{
|
|
318
|
+
http: {
|
|
319
|
+
path: '/api/testIntegration-integration/{proxy+}',
|
|
320
|
+
method: 'ANY',
|
|
321
|
+
cors: true
|
|
322
|
+
}
|
|
323
|
+
}]
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Check SQS Queue
|
|
327
|
+
expect(result.resources.Resources.TestIntegrationQueue).toEqual({
|
|
328
|
+
Type: 'AWS::SQS::Queue',
|
|
329
|
+
Properties: {
|
|
330
|
+
QueueName: '${self:custom.TestIntegrationQueue}',
|
|
331
|
+
MessageRetentionPeriod: 60,
|
|
332
|
+
VisibilityTimeout: 1800,
|
|
333
|
+
RedrivePolicy: {
|
|
334
|
+
maxReceiveCount: 1,
|
|
335
|
+
deadLetterTargetArn: {
|
|
336
|
+
'Fn::GetAtt': ['InternalErrorQueue', 'Arn']
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Check Queue Worker
|
|
343
|
+
expect(result.functions.testIntegrationQueueWorker).toEqual({
|
|
344
|
+
handler: 'node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.testIntegration.queueWorker',
|
|
345
|
+
reservedConcurrency: 5,
|
|
346
|
+
events: [{
|
|
347
|
+
sqs: {
|
|
348
|
+
arn: {
|
|
349
|
+
'Fn::GetAtt': ['TestIntegrationQueue', 'Arn']
|
|
350
|
+
},
|
|
351
|
+
batchSize: 1
|
|
352
|
+
}
|
|
353
|
+
}],
|
|
354
|
+
timeout: 600
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Check environment variable
|
|
358
|
+
expect(result.provider.environment.TESTINTEGRATION_QUEUE_URL).toEqual({
|
|
359
|
+
Ref: 'TestIntegrationQueue'
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Check custom queue name
|
|
363
|
+
expect(result.custom.TestIntegrationQueue).toBe('${self:service}--${self:provider.stage}-TestIntegrationQueue');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should handle multiple integrations', async () => {
|
|
367
|
+
const secondIntegration = {
|
|
368
|
+
Definition: {
|
|
369
|
+
name: 'secondIntegration'
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const appDefinition = {
|
|
374
|
+
integrations: [mockIntegration, secondIntegration]
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
378
|
+
|
|
379
|
+
expect(result.functions.testIntegration).toBeDefined();
|
|
380
|
+
expect(result.functions.secondIntegration).toBeDefined();
|
|
381
|
+
expect(result.functions.testIntegrationQueueWorker).toBeDefined();
|
|
382
|
+
expect(result.functions.secondIntegrationQueueWorker).toBeDefined();
|
|
383
|
+
expect(result.resources.Resources.TestIntegrationQueue).toBeDefined();
|
|
384
|
+
expect(result.resources.Resources.SecondIntegrationQueue).toBeDefined();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe('Combined Configurations', () => {
|
|
389
|
+
it('should combine VPC, KMS, and SSM configurations', async () => {
|
|
390
|
+
const appDefinition = {
|
|
391
|
+
vpc: { enable: true },
|
|
392
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
393
|
+
ssm: { enable: true },
|
|
394
|
+
integrations: [mockIntegration]
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
398
|
+
|
|
399
|
+
// VPC
|
|
400
|
+
expect(result.provider.vpc).toBeDefined();
|
|
401
|
+
expect(result.custom.vpc).toBeDefined();
|
|
402
|
+
expect(result.resources.Resources.VPCEndpointS3).toBeDefined();
|
|
403
|
+
|
|
404
|
+
// KMS
|
|
405
|
+
expect(result.plugins).toContain('serverless-kms-grants');
|
|
406
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBeDefined();
|
|
407
|
+
expect(result.custom.kmsGrants).toBeDefined();
|
|
408
|
+
|
|
409
|
+
// SSM
|
|
410
|
+
expect(result.provider.layers).toBeDefined();
|
|
411
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeDefined();
|
|
412
|
+
|
|
413
|
+
// Integration
|
|
414
|
+
expect(result.functions.testIntegration).toBeDefined();
|
|
415
|
+
expect(result.resources.Resources.TestIntegrationQueue).toBeDefined();
|
|
416
|
+
|
|
417
|
+
// All plugins should be present
|
|
418
|
+
expect(result.plugins).toEqual([
|
|
419
|
+
'serverless-jetpack',
|
|
420
|
+
'serverless-dotenv-plugin',
|
|
421
|
+
'serverless-offline-sqs',
|
|
422
|
+
'serverless-offline',
|
|
423
|
+
'@friggframework/serverless-plugin',
|
|
424
|
+
'serverless-kms-grants'
|
|
425
|
+
]);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should handle partial configuration combinations', async () => {
|
|
429
|
+
const appDefinition = {
|
|
430
|
+
vpc: { enable: true },
|
|
431
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
432
|
+
integrations: []
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
436
|
+
|
|
437
|
+
// VPC and KMS should be present
|
|
438
|
+
expect(result.provider.vpc).toBeDefined();
|
|
439
|
+
expect(result.custom.kmsGrants).toBeDefined();
|
|
440
|
+
|
|
441
|
+
// SSM should not be present
|
|
442
|
+
expect(result.provider.layers).toBeUndefined();
|
|
443
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe('Default Resources', () => {
|
|
448
|
+
it('should always include default resources', async () => {
|
|
449
|
+
const appDefinition = {
|
|
450
|
+
integrations: []
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
454
|
+
|
|
455
|
+
// Check default resources are always present
|
|
456
|
+
expect(result.resources.Resources.InternalErrorQueue).toBeDefined();
|
|
457
|
+
expect(result.resources.Resources.InternalErrorBridgeTopic).toBeDefined();
|
|
458
|
+
expect(result.resources.Resources.InternalErrorBridgePolicy).toBeDefined();
|
|
459
|
+
expect(result.resources.Resources.ApiGatewayAlarm5xx).toBeDefined();
|
|
460
|
+
|
|
461
|
+
// Check default functions
|
|
462
|
+
expect(result.functions.defaultWebsocket).toBeDefined();
|
|
463
|
+
expect(result.functions.auth).toBeDefined();
|
|
464
|
+
expect(result.functions.user).toBeDefined();
|
|
465
|
+
|
|
466
|
+
// Check default plugins
|
|
467
|
+
expect(result.plugins).toContain('serverless-jetpack');
|
|
468
|
+
expect(result.plugins).toContain('serverless-dotenv-plugin');
|
|
469
|
+
expect(result.plugins).toContain('@friggframework/serverless-plugin');
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should always include default IAM permissions', async () => {
|
|
473
|
+
const appDefinition = {
|
|
474
|
+
integrations: []
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
478
|
+
|
|
479
|
+
// Check SNS publish permission
|
|
480
|
+
const snsPermission = result.provider.iamRoleStatements.find(
|
|
481
|
+
statement => statement.Action.includes('sns:Publish')
|
|
482
|
+
);
|
|
483
|
+
expect(snsPermission).toBeDefined();
|
|
484
|
+
|
|
485
|
+
// Check SQS permissions
|
|
486
|
+
const sqsPermission = result.provider.iamRoleStatements.find(
|
|
487
|
+
statement => statement.Action.includes('sqs:SendMessage')
|
|
488
|
+
);
|
|
489
|
+
expect(sqsPermission).toBeDefined();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('should include default environment variables', async () => {
|
|
493
|
+
const appDefinition = {
|
|
494
|
+
integrations: []
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
498
|
+
|
|
499
|
+
expect(result.provider.environment.STAGE).toBe('${opt:stage}');
|
|
500
|
+
expect(result.provider.environment.AWS_NODEJS_CONNECTION_REUSE_ENABLED).toBe(1);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe('Edge Cases', () => {
|
|
505
|
+
it('should handle empty app definition', async () => {
|
|
506
|
+
const appDefinition = {};
|
|
507
|
+
|
|
508
|
+
await expect(composeServerlessDefinition(appDefinition)).resolves.not.toThrow();
|
|
509
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
510
|
+
expect(result.service).toBe('create-frigg-app');
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('should handle null/undefined integrations', async () => {
|
|
514
|
+
const appDefinition = {
|
|
515
|
+
integrations: null
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('should handle integration with missing Definition', async () => {
|
|
522
|
+
const invalidIntegration = {};
|
|
523
|
+
const appDefinition = {
|
|
524
|
+
integrations: [invalidIntegration]
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should handle integration with missing name', async () => {
|
|
531
|
+
const invalidIntegration = {
|
|
532
|
+
Definition: {}
|
|
533
|
+
};
|
|
534
|
+
const appDefinition = {
|
|
535
|
+
integrations: [invalidIntegration]
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 201.65"><defs><style>.cls-1,.cls-2{fill:#71a087;}.cls-3{letter-spacing:.02em;}.cls-3,.cls-4,.cls-5{font-family:DINCondensed-Bold, 'DIN Condensed';}.cls-3,.cls-5{fill:#fff;font-size:56.17px;}.cls-4{font-size:114.37px;}.cls-4,.cls-6{stroke:#616061;stroke-miterlimit:10;}.cls-4,.cls-6,.cls-7{fill:#565859;}.cls-5{letter-spacing:.03em;}.cls-7,.cls-2{fill-rule:evenodd;}</style></defs><g id="BADGE"><rect class="cls-1" x="0" y="121.65" width="400" height="80" rx="13" ry="13"/><g><text class="cls-5" transform="translate(132.49 179.9)"><tspan x="0" y="0">LEFT</tspan></text><text class="cls-5" transform="translate(235.62 179.14)"><tspan x="0" y="0">HOOK</tspan></text><text class="cls-3" transform="translate(64.35 179.9)"><tspan x="0" y="0">BY</tspan></text></g></g><g id="FRIGG"><path class="cls-6" d="M147.74,16.97h35.79v11.06h-23.78v24.89h20.72v11.06h-20.72v35.04h-12.01V16.97Z"/><path class="cls-6" d="M193.68,16.97h19.31c14.13,0,21.19,8.03,21.19,24.09,0,4.76-.77,8.86-2.3,12.27-1.53,3.42-4.22,6.17-8.06,8.24l12.95,37.46h-12.71l-11.18-35.04h-7.18v35.04h-12.01V16.97Zm12.01,36.65h6.83c2.12,0,3.81-.29,5.06-.87,1.25-.58,2.22-1.4,2.88-2.48,.67-1.08,1.12-2.4,1.35-3.98,.23-1.57,.35-3.4,.35-5.47s-.12-3.9-.35-5.47c-.24-1.57-.73-2.92-1.47-4.03-.75-1.11-1.79-1.94-3.12-2.48-1.34-.54-3.1-.81-5.3-.81h-6.24v25.59Z"/><path class="cls-6" d="M278.29,36.69c0-3.25,.58-6.12,1.74-8.61,1.16-2.49,2.68-4.55,4.57-6.18,1.89-1.62,4.01-2.85,6.37-3.68,2.35-.83,4.73-1.25,7.12-1.25s4.76,.42,7.12,1.25c2.35,.83,4.48,2.06,6.37,3.68,1.89,1.63,3.41,3.68,4.57,6.18,1.16,2.49,1.74,5.37,1.74,8.61v4.08h-11.81v-4.08c0-2.79-.79-4.85-2.37-6.18-1.58-1.32-3.45-1.98-5.61-1.98s-4.03,.66-5.61,1.98c-1.58,1.32-2.37,3.38-2.37,6.18v42.61c0,2.8,.79,4.86,2.37,6.18,1.58,1.32,3.45,1.98,5.61,1.98s4.03-.66,5.61-1.98c1.58-1.32,2.37-3.38,2.37-6.18v-15.19h-9.38v-10.2h21.19v25.39c0,3.33-.58,6.22-1.74,8.67-1.16,2.46-2.68,4.5-4.57,6.12-1.89,1.63-4.01,2.85-6.37,3.68-2.35,.83-4.73,1.25-7.12,1.25s-4.77-.42-7.12-1.25c-2.35-.83-4.48-2.06-6.37-3.68-1.89-1.62-3.41-3.66-4.57-6.12-1.16-2.45-1.74-5.34-1.74-8.67V36.69Z"/><path class="cls-6" d="M329.06,36.69c0-3.25,.58-6.12,1.74-8.61s2.68-4.55,4.57-6.18c1.89-1.62,4.01-2.85,6.37-3.68,2.35-.83,4.73-1.25,7.12-1.25s4.76,.42,7.12,1.25c2.35,.83,4.48,2.06,6.37,3.68,1.89,1.63,3.41,3.68,4.57,6.18,1.16,2.49,1.74,5.37,1.74,8.61v4.08h-11.81v-4.08c0-2.79-.79-4.85-2.37-6.18-1.58-1.32-3.45-1.98-5.61-1.98s-4.03,.66-5.61,1.98c-1.58,1.32-2.37,3.38-2.37,6.18v42.61c0,2.8,.79,4.86,2.37,6.18,1.58,1.32,3.45,1.98,5.61,1.98s4.03-.66,5.61-1.98c1.58-1.32,2.37-3.38,2.37-6.18v-15.19h-9.38v-10.2h21.19v25.39c0,3.33-.58,6.22-1.74,8.67-1.16,2.46-2.68,4.5-4.57,6.12-1.89,1.63-4.01,2.85-6.37,3.68-2.35,.83-4.73,1.25-7.12,1.25s-4.77-.42-7.12-1.25c-2.35-.83-4.48-2.06-6.37-3.68-1.89-1.62-3.41-3.66-4.57-6.12-1.16-2.45-1.74-5.34-1.74-8.67V36.69Z"/><text class="cls-4" transform="translate(243.51 98.4) scale(1.02 1)"><tspan x="0" y="0">I</tspan></text></g><g id="ICON"><path class="cls-2" d="M93.72,73.79c7.86-7.18,14-7.02,20.8,.72,2.94,4.35,.48,8.46-1.78,10.48-.02,.02-.04,.03-.05,.05l-2.28,1.92c2.32-4.48-1.91-5.1-3.52-4.05-.12,.08-.23,.16-.34,.26l-.03,.03-34.94,30.92c-.18,.15-.45,.13-.6-.04l-10.23-11.58c-.16-.18-.14-.45,.04-.61l32.94-28.1Z"/><path class="cls-7" d="M125.17,11.41l.32-8.7c.04-1.23-.41-1.26-1.85-.05l-42.08,35.62,7.98,7.85,33.42-29.03c1.42-1.25,2.16-4.16,2.21-5.69h0Z"/><path class="cls-2" d="M124.79,32.92l.32-8.7c.04-1.23-.41-1.26-1.85-.05l-30.28,25.64,8.05,7.51,21.54-18.71c1.42-1.25,2.16-4.16,2.21-5.69h0Z"/><path class="cls-7" d="M124.96,54.26l.32-8.7c.04-1.23-.41-1.26-1.85-.05l-18.01,15.24,7.88,7.37,9.43-8.17c1.42-1.25,2.16-4.16,2.23-5.69h0Z"/><path class="cls-2" d="M88.65,69.04L35.29,21.17c-3.14-2.81-3.26-5.55-3.12-8.58l.38-9.17L97.11,61.67l-8.46,7.37Z"/><path class="cls-7" d="M76.09,79.78L34.75,42.73c-1.65-1.49-2.15-6.15-2.08-7.85l.65-8.58,51.14,46.26-8.37,7.23Z"/><path class="cls-2" d="M62.39,91.85l-27.96-26.7c-1.93-1.84-3.17-4.71-3.07-6.67l.56-9.77,39.27,35.47-8.79,7.66Z"/></g></svg>
|