@friggframework/devtools 2.0.0--canary.397.4957a89.0 → 2.0.0--canary.398.bdb6d27.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/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 +594 -0
- package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +174 -0
- package/infrastructure/README-TESTING.md +332 -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 +10 -2
- package/infrastructure/frigg-deployment-iam-stack.yaml +377 -0
- package/infrastructure/iam-generator.js +696 -0
- package/infrastructure/iam-generator.test.js +169 -0
- package/infrastructure/iam-policy-basic.json +210 -0
- package/infrastructure/iam-policy-full.json +280 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +110 -0
- package/infrastructure/serverless-template.js +606 -27
- package/infrastructure/serverless-template.test.js +498 -0
- package/package.json +9 -5
- package/test/auther-definition-tester.js +125 -0
- package/test/index.js +4 -2
- package/test/mock-integration.js +14 -4
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
const { composeServerlessDefinition } = require('./serverless-template');
|
|
2
|
+
|
|
3
|
+
// Mock the node_modules path finding functions
|
|
4
|
+
jest.mock('./serverless-template', () => {
|
|
5
|
+
const originalModule = jest.requireActual('./serverless-template');
|
|
6
|
+
return {
|
|
7
|
+
...originalModule,
|
|
8
|
+
composeServerlessDefinition: originalModule.composeServerlessDefinition
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('composeServerlessDefinition', () => {
|
|
13
|
+
let mockIntegration;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockIntegration = {
|
|
17
|
+
Definition: {
|
|
18
|
+
name: 'testIntegration'
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Mock process.argv to avoid offline mode during tests
|
|
23
|
+
jest.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'test']);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
jest.restoreAllMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('Basic Configuration', () => {
|
|
31
|
+
it('should create basic serverless definition with minimal app definition', () => {
|
|
32
|
+
const appDefinition = {
|
|
33
|
+
name: 'test-app',
|
|
34
|
+
integrations: []
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
38
|
+
|
|
39
|
+
expect(result.service).toBe('test-app');
|
|
40
|
+
expect(result.provider.name).toBe('aws');
|
|
41
|
+
expect(result.provider.runtime).toBe('nodejs20.x');
|
|
42
|
+
expect(result.provider.region).toBe('us-east-1');
|
|
43
|
+
expect(result.provider.stage).toBe('${opt:stage}');
|
|
44
|
+
expect(result.frameworkVersion).toBe('>=3.17.0');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should use default service name when name not provided', () => {
|
|
48
|
+
const appDefinition = {
|
|
49
|
+
integrations: []
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
53
|
+
|
|
54
|
+
expect(result.service).toBe('create-frigg-app');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should use custom provider when specified', () => {
|
|
58
|
+
const appDefinition = {
|
|
59
|
+
provider: 'custom-provider',
|
|
60
|
+
integrations: []
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
64
|
+
|
|
65
|
+
expect(result.provider.name).toBe('custom-provider');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('VPC Configuration', () => {
|
|
70
|
+
it('should add VPC configuration when vpc.enable is true', () => {
|
|
71
|
+
const appDefinition = {
|
|
72
|
+
vpc: { enable: true },
|
|
73
|
+
integrations: []
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
77
|
+
|
|
78
|
+
expect(result.provider.vpc).toBe('${self:custom.vpc.${self:provider.stage}}');
|
|
79
|
+
expect(result.custom.vpc).toEqual({
|
|
80
|
+
'${self:provider.stage}': {
|
|
81
|
+
securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
|
|
82
|
+
subnetIds: [
|
|
83
|
+
'${env:AWS_DISCOVERY_SUBNET_ID_1}',
|
|
84
|
+
'${env:AWS_DISCOVERY_SUBNET_ID_2}'
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should add VPC endpoint for S3 when VPC is enabled', () => {
|
|
91
|
+
const appDefinition = {
|
|
92
|
+
vpc: { enable: true },
|
|
93
|
+
integrations: []
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
97
|
+
|
|
98
|
+
expect(result.resources.Resources.VPCEndpointS3).toEqual({
|
|
99
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
100
|
+
Properties: {
|
|
101
|
+
VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
|
|
102
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
103
|
+
VpcEndpointType: 'Gateway',
|
|
104
|
+
RouteTableIds: ['${env:AWS_DISCOVERY_ROUTE_TABLE_ID}']
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should not add VPC configuration when vpc.enable is false', () => {
|
|
110
|
+
const appDefinition = {
|
|
111
|
+
vpc: { enable: false },
|
|
112
|
+
integrations: []
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
116
|
+
|
|
117
|
+
expect(result.provider.vpc).toBeUndefined();
|
|
118
|
+
expect(result.custom.vpc).toBeUndefined();
|
|
119
|
+
expect(result.resources.Resources.VPCEndpointS3).toBeUndefined();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should not add VPC configuration when vpc is not defined', () => {
|
|
123
|
+
const appDefinition = {
|
|
124
|
+
integrations: []
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
128
|
+
|
|
129
|
+
expect(result.provider.vpc).toBeUndefined();
|
|
130
|
+
expect(result.custom.vpc).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('KMS Configuration', () => {
|
|
135
|
+
it('should add KMS configuration when encryption is enabled', () => {
|
|
136
|
+
const appDefinition = {
|
|
137
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
138
|
+
integrations: []
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
142
|
+
|
|
143
|
+
// Check IAM permissions
|
|
144
|
+
const kmsPermission = result.provider.iamRoleStatements.find(
|
|
145
|
+
statement => statement.Action.includes('kms:GenerateDataKey')
|
|
146
|
+
);
|
|
147
|
+
expect(kmsPermission).toEqual({
|
|
148
|
+
Effect: 'Allow',
|
|
149
|
+
Action: [
|
|
150
|
+
'kms:GenerateDataKey',
|
|
151
|
+
'kms:Decrypt'
|
|
152
|
+
],
|
|
153
|
+
Resource: ['${self:custom.kmsGrants.kmsKeyId}']
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Check environment variable
|
|
157
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBe('${self:custom.kmsGrants.kmsKeyId}');
|
|
158
|
+
|
|
159
|
+
// Check plugin
|
|
160
|
+
expect(result.plugins).toContain('serverless-kms-grants');
|
|
161
|
+
|
|
162
|
+
// Check custom configuration
|
|
163
|
+
expect(result.custom.kmsGrants).toEqual({
|
|
164
|
+
kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should not add KMS configuration when encryption is disabled', () => {
|
|
169
|
+
const appDefinition = {
|
|
170
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: false },
|
|
171
|
+
integrations: []
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
175
|
+
|
|
176
|
+
const kmsPermission = result.provider.iamRoleStatements.find(
|
|
177
|
+
statement => statement.Action && statement.Action.includes('kms:GenerateDataKey')
|
|
178
|
+
);
|
|
179
|
+
expect(kmsPermission).toBeUndefined();
|
|
180
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBeUndefined();
|
|
181
|
+
expect(result.plugins).not.toContain('serverless-kms-grants');
|
|
182
|
+
expect(result.custom.kmsGrants).toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should not add KMS configuration when encryption is not defined', () => {
|
|
186
|
+
const appDefinition = {
|
|
187
|
+
integrations: []
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
191
|
+
|
|
192
|
+
const kmsPermission = result.provider.iamRoleStatements.find(
|
|
193
|
+
statement => statement.Action && statement.Action.includes('kms:GenerateDataKey')
|
|
194
|
+
);
|
|
195
|
+
expect(kmsPermission).toBeUndefined();
|
|
196
|
+
expect(result.custom.kmsGrants).toBeUndefined();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('SSM Configuration', () => {
|
|
201
|
+
it('should add SSM configuration when ssm.enable is true', () => {
|
|
202
|
+
const appDefinition = {
|
|
203
|
+
ssm: { enable: true },
|
|
204
|
+
integrations: []
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
208
|
+
|
|
209
|
+
// Check lambda layers
|
|
210
|
+
expect(result.provider.layers).toEqual([
|
|
211
|
+
'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
// Check IAM permissions
|
|
215
|
+
const ssmPermission = result.provider.iamRoleStatements.find(
|
|
216
|
+
statement => statement.Action.includes('ssm:GetParameter')
|
|
217
|
+
);
|
|
218
|
+
expect(ssmPermission).toEqual({
|
|
219
|
+
Effect: 'Allow',
|
|
220
|
+
Action: [
|
|
221
|
+
'ssm:GetParameter',
|
|
222
|
+
'ssm:GetParameters',
|
|
223
|
+
'ssm:GetParametersByPath'
|
|
224
|
+
],
|
|
225
|
+
Resource: [
|
|
226
|
+
'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
|
|
227
|
+
]
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Check environment variable
|
|
231
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBe('/${self:service}/${self:provider.stage}');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should not add SSM configuration when ssm.enable is false', () => {
|
|
235
|
+
const appDefinition = {
|
|
236
|
+
ssm: { enable: false },
|
|
237
|
+
integrations: []
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
241
|
+
|
|
242
|
+
expect(result.provider.layers).toBeUndefined();
|
|
243
|
+
|
|
244
|
+
const ssmPermission = result.provider.iamRoleStatements.find(
|
|
245
|
+
statement => statement.Action && statement.Action.includes('ssm:GetParameter')
|
|
246
|
+
);
|
|
247
|
+
expect(ssmPermission).toBeUndefined();
|
|
248
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should not add SSM configuration when ssm is not defined', () => {
|
|
252
|
+
const appDefinition = {
|
|
253
|
+
integrations: []
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
257
|
+
|
|
258
|
+
expect(result.provider.layers).toBeUndefined();
|
|
259
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('Integration Configuration', () => {
|
|
264
|
+
it('should add integration-specific resources and functions', () => {
|
|
265
|
+
const appDefinition = {
|
|
266
|
+
integrations: [mockIntegration]
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
270
|
+
|
|
271
|
+
// Check integration function
|
|
272
|
+
expect(result.functions.testIntegration).toEqual({
|
|
273
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.testIntegration.handler',
|
|
274
|
+
events: [{
|
|
275
|
+
http: {
|
|
276
|
+
path: '/api/testIntegration-integration/{proxy+}',
|
|
277
|
+
method: 'ANY',
|
|
278
|
+
cors: true
|
|
279
|
+
}
|
|
280
|
+
}]
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Check SQS Queue
|
|
284
|
+
expect(result.resources.Resources.TestIntegrationQueue).toEqual({
|
|
285
|
+
Type: 'AWS::SQS::Queue',
|
|
286
|
+
Properties: {
|
|
287
|
+
QueueName: '${self:custom.TestIntegrationQueue}',
|
|
288
|
+
MessageRetentionPeriod: 60,
|
|
289
|
+
VisibilityTimeout: 1800,
|
|
290
|
+
RedrivePolicy: {
|
|
291
|
+
maxReceiveCount: 1,
|
|
292
|
+
deadLetterTargetArn: {
|
|
293
|
+
'Fn::GetAtt': ['InternalErrorQueue', 'Arn']
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Check Queue Worker
|
|
300
|
+
expect(result.functions.testIntegrationQueueWorker).toEqual({
|
|
301
|
+
handler: 'node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.testIntegration.queueWorker',
|
|
302
|
+
reservedConcurrency: 5,
|
|
303
|
+
events: [{
|
|
304
|
+
sqs: {
|
|
305
|
+
arn: {
|
|
306
|
+
'Fn::GetAtt': ['TestIntegrationQueue', 'Arn']
|
|
307
|
+
},
|
|
308
|
+
batchSize: 1
|
|
309
|
+
}
|
|
310
|
+
}],
|
|
311
|
+
timeout: 600
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Check environment variable
|
|
315
|
+
expect(result.provider.environment.TESTINTEGRATION_QUEUE_URL).toEqual({
|
|
316
|
+
Ref: 'TestIntegrationQueue'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Check custom queue name
|
|
320
|
+
expect(result.custom.TestIntegrationQueue).toBe('${self:service}--${self:provider.stage}-TestIntegrationQueue');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should handle multiple integrations', () => {
|
|
324
|
+
const secondIntegration = {
|
|
325
|
+
Definition: {
|
|
326
|
+
name: 'secondIntegration'
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const appDefinition = {
|
|
331
|
+
integrations: [mockIntegration, secondIntegration]
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
335
|
+
|
|
336
|
+
expect(result.functions.testIntegration).toBeDefined();
|
|
337
|
+
expect(result.functions.secondIntegration).toBeDefined();
|
|
338
|
+
expect(result.functions.testIntegrationQueueWorker).toBeDefined();
|
|
339
|
+
expect(result.functions.secondIntegrationQueueWorker).toBeDefined();
|
|
340
|
+
expect(result.resources.Resources.TestIntegrationQueue).toBeDefined();
|
|
341
|
+
expect(result.resources.Resources.SecondIntegrationQueue).toBeDefined();
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
describe('Combined Configurations', () => {
|
|
346
|
+
it('should combine VPC, KMS, and SSM configurations', () => {
|
|
347
|
+
const appDefinition = {
|
|
348
|
+
vpc: { enable: true },
|
|
349
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
350
|
+
ssm: { enable: true },
|
|
351
|
+
integrations: [mockIntegration]
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
355
|
+
|
|
356
|
+
// VPC
|
|
357
|
+
expect(result.provider.vpc).toBeDefined();
|
|
358
|
+
expect(result.custom.vpc).toBeDefined();
|
|
359
|
+
expect(result.resources.Resources.VPCEndpointS3).toBeDefined();
|
|
360
|
+
|
|
361
|
+
// KMS
|
|
362
|
+
expect(result.plugins).toContain('serverless-kms-grants');
|
|
363
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBeDefined();
|
|
364
|
+
expect(result.custom.kmsGrants).toBeDefined();
|
|
365
|
+
|
|
366
|
+
// SSM
|
|
367
|
+
expect(result.provider.layers).toBeDefined();
|
|
368
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeDefined();
|
|
369
|
+
|
|
370
|
+
// Integration
|
|
371
|
+
expect(result.functions.testIntegration).toBeDefined();
|
|
372
|
+
expect(result.resources.Resources.TestIntegrationQueue).toBeDefined();
|
|
373
|
+
|
|
374
|
+
// All plugins should be present
|
|
375
|
+
expect(result.plugins).toEqual([
|
|
376
|
+
'serverless-jetpack',
|
|
377
|
+
'serverless-dotenv-plugin',
|
|
378
|
+
'serverless-offline-sqs',
|
|
379
|
+
'serverless-offline',
|
|
380
|
+
'@friggframework/serverless-plugin',
|
|
381
|
+
'serverless-kms-grants'
|
|
382
|
+
]);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should handle partial configuration combinations', () => {
|
|
386
|
+
const appDefinition = {
|
|
387
|
+
vpc: { enable: true },
|
|
388
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
389
|
+
integrations: []
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
393
|
+
|
|
394
|
+
// VPC and KMS should be present
|
|
395
|
+
expect(result.provider.vpc).toBeDefined();
|
|
396
|
+
expect(result.custom.kmsGrants).toBeDefined();
|
|
397
|
+
|
|
398
|
+
// SSM should not be present
|
|
399
|
+
expect(result.provider.layers).toBeUndefined();
|
|
400
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe('Default Resources', () => {
|
|
405
|
+
it('should always include default resources', () => {
|
|
406
|
+
const appDefinition = {
|
|
407
|
+
integrations: []
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
411
|
+
|
|
412
|
+
// Check default resources are always present
|
|
413
|
+
expect(result.resources.Resources.InternalErrorQueue).toBeDefined();
|
|
414
|
+
expect(result.resources.Resources.InternalErrorBridgeTopic).toBeDefined();
|
|
415
|
+
expect(result.resources.Resources.InternalErrorBridgePolicy).toBeDefined();
|
|
416
|
+
expect(result.resources.Resources.ApiGatewayAlarm5xx).toBeDefined();
|
|
417
|
+
|
|
418
|
+
// Check default functions
|
|
419
|
+
expect(result.functions.defaultWebsocket).toBeDefined();
|
|
420
|
+
expect(result.functions.auth).toBeDefined();
|
|
421
|
+
expect(result.functions.user).toBeDefined();
|
|
422
|
+
|
|
423
|
+
// Check default plugins
|
|
424
|
+
expect(result.plugins).toContain('serverless-jetpack');
|
|
425
|
+
expect(result.plugins).toContain('serverless-dotenv-plugin');
|
|
426
|
+
expect(result.plugins).toContain('@friggframework/serverless-plugin');
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should always include default IAM permissions', () => {
|
|
430
|
+
const appDefinition = {
|
|
431
|
+
integrations: []
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
435
|
+
|
|
436
|
+
// Check SNS publish permission
|
|
437
|
+
const snsPermission = result.provider.iamRoleStatements.find(
|
|
438
|
+
statement => statement.Action.includes('sns:Publish')
|
|
439
|
+
);
|
|
440
|
+
expect(snsPermission).toBeDefined();
|
|
441
|
+
|
|
442
|
+
// Check SQS permissions
|
|
443
|
+
const sqsPermission = result.provider.iamRoleStatements.find(
|
|
444
|
+
statement => statement.Action.includes('sqs:SendMessage')
|
|
445
|
+
);
|
|
446
|
+
expect(sqsPermission).toBeDefined();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should include default environment variables', () => {
|
|
450
|
+
const appDefinition = {
|
|
451
|
+
integrations: []
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
455
|
+
|
|
456
|
+
expect(result.provider.environment.STAGE).toBe('${opt:stage}');
|
|
457
|
+
expect(result.provider.environment.AWS_NODEJS_CONNECTION_REUSE_ENABLED).toBe(1);
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
describe('Edge Cases', () => {
|
|
462
|
+
it('should handle empty app definition', () => {
|
|
463
|
+
const appDefinition = {};
|
|
464
|
+
|
|
465
|
+
expect(() => composeServerlessDefinition(appDefinition)).not.toThrow();
|
|
466
|
+
const result = composeServerlessDefinition(appDefinition);
|
|
467
|
+
expect(result.service).toBe('create-frigg-app');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should handle null/undefined integrations', () => {
|
|
471
|
+
const appDefinition = {
|
|
472
|
+
integrations: null
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
expect(() => composeServerlessDefinition(appDefinition)).toThrow();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should handle integration with missing Definition', () => {
|
|
479
|
+
const invalidIntegration = {};
|
|
480
|
+
const appDefinition = {
|
|
481
|
+
integrations: [invalidIntegration]
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
expect(() => composeServerlessDefinition(appDefinition)).toThrow();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should handle integration with missing name', () => {
|
|
488
|
+
const invalidIntegration = {
|
|
489
|
+
Definition: {}
|
|
490
|
+
};
|
|
491
|
+
const appDefinition = {
|
|
492
|
+
integrations: [invalidIntegration]
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
expect(() => composeServerlessDefinition(appDefinition)).toThrow();
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
});
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
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.398.bdb6d27.0",
|
|
5
5
|
"dependencies": {
|
|
6
|
+
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
|
+
"@aws-sdk/client-kms": "^3.835.0",
|
|
8
|
+
"@aws-sdk/client-sts": "^3.835.0",
|
|
6
9
|
"@babel/eslint-parser": "^7.18.9",
|
|
7
10
|
"@babel/parser": "^7.25.3",
|
|
8
11
|
"@babel/traverse": "^7.25.3",
|
|
9
|
-
"@friggframework/test": "2.0.0--canary.
|
|
12
|
+
"@friggframework/test": "2.0.0--canary.398.bdb6d27.0",
|
|
10
13
|
"@hapi/boom": "^10.0.1",
|
|
11
14
|
"@inquirer/prompts": "^5.3.8",
|
|
12
15
|
"axios": "^1.7.2",
|
|
@@ -23,12 +26,13 @@
|
|
|
23
26
|
"express": "^4.19.2",
|
|
24
27
|
"express-async-handler": "^1.2.0",
|
|
25
28
|
"fs-extra": "^11.2.0",
|
|
29
|
+
"js-yaml": "^4.1.0",
|
|
26
30
|
"lodash": "4.17.21",
|
|
27
31
|
"serverless-http": "^2.7.0"
|
|
28
32
|
},
|
|
29
33
|
"devDependencies": {
|
|
30
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
31
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
34
|
+
"@friggframework/eslint-config": "2.0.0--canary.398.bdb6d27.0",
|
|
35
|
+
"@friggframework/prettier-config": "2.0.0--canary.398.bdb6d27.0",
|
|
32
36
|
"prettier": "^2.7.1",
|
|
33
37
|
"serverless": "3.39.0",
|
|
34
38
|
"serverless-dotenv-plugin": "^6.0.0",
|
|
@@ -60,5 +64,5 @@
|
|
|
60
64
|
"publishConfig": {
|
|
61
65
|
"access": "public"
|
|
62
66
|
},
|
|
63
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "bdb6d27afba025fd602994dad9b2ab86f49a6556"
|
|
64
68
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const {
|
|
2
|
+
Auther,
|
|
3
|
+
ModuleConstants,
|
|
4
|
+
createObjectId,
|
|
5
|
+
connectToDatabase,
|
|
6
|
+
disconnectFromDatabase,
|
|
7
|
+
} = require('@friggframework/core');
|
|
8
|
+
const { createMockApiObject } = require("./mock-integration");
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function testAutherDefinition(definition, mocks) {
|
|
12
|
+
const getModule = async (params) => {
|
|
13
|
+
const module = await Auther.getInstance({
|
|
14
|
+
definition,
|
|
15
|
+
userId: createObjectId(),
|
|
16
|
+
...params,
|
|
17
|
+
});
|
|
18
|
+
if (mocks.tokenResponse) {
|
|
19
|
+
mocks.getTokenFrom = async function(code) {
|
|
20
|
+
await this.setTokens(mocks.tokenResponse);
|
|
21
|
+
return mocks.tokenResponse
|
|
22
|
+
}
|
|
23
|
+
mocks.getTokenFromCode = mocks.getTokenFromCode || mocks.getTokenFrom
|
|
24
|
+
mocks.getTokenFromCodeBasicAuthHeader = mocks.getTokenFromCodeBasicAuthHeader || mocks.getTokenFrom
|
|
25
|
+
mocks.getTokenFromClientCredentials = mocks.getTokenFromClientCredentials || mocks.getTokenFrom
|
|
26
|
+
mocks.getTokenFromUsernamePassword = mocks.getTokenFromUsernamePassword || mocks.getTokenFrom
|
|
27
|
+
}
|
|
28
|
+
if (mocks.refreshResponse) {
|
|
29
|
+
mocks.refreshAccessToken = async function(code) {
|
|
30
|
+
await this.setTokens(mocks.refreshResponse);
|
|
31
|
+
return mocks.refreshResponse
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
module.api = createMockApiObject(jest, module.api, mocks);
|
|
35
|
+
return module
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
describe(`${definition.moduleName} Module Tests`, () => {
|
|
40
|
+
let module, authUrl;
|
|
41
|
+
beforeAll(async () => {
|
|
42
|
+
await connectToDatabase();
|
|
43
|
+
module = await getModule();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(async () => {
|
|
47
|
+
await disconnectFromDatabase();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let requirements, authCallbackParams;
|
|
51
|
+
if (definition.API.requesterType === ModuleConstants.authType.oauth2) {
|
|
52
|
+
authCallbackParams = mocks.authorizeResponse || mocks.authorizeParams;
|
|
53
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
54
|
+
it('should return auth requirements', async () => {
|
|
55
|
+
requirements = await module.getAuthorizationRequirements();
|
|
56
|
+
expect(requirements).toBeDefined();
|
|
57
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.oauth2);
|
|
58
|
+
expect(requirements.url).toBeDefined();
|
|
59
|
+
authUrl = requirements.url;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
} else if (definition.API.requesterType === ModuleConstants.authType.basic) {
|
|
63
|
+
// could also confirm authCallbackParams against the auth requirements
|
|
64
|
+
authCallbackParams = mocks.authorizeParams
|
|
65
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
66
|
+
it('should return auth requirements', async () => {
|
|
67
|
+
requirements = module.getAuthorizationRequirements();
|
|
68
|
+
expect(requirements).toBeDefined();
|
|
69
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.basic);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
} else if (definition.API.requesterType === ModuleConstants.authType.apiKey) {
|
|
73
|
+
// could also confirm authCallbackParams against the auth requirements
|
|
74
|
+
authCallbackParams = mocks.authorizeParams
|
|
75
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
76
|
+
it('should return auth requirements', async () => {
|
|
77
|
+
requirements = module.getAuthorizationRequirements();
|
|
78
|
+
expect(requirements).toBeDefined();
|
|
79
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.apiKey);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe('Authorization requests', () => {
|
|
85
|
+
let firstRes;
|
|
86
|
+
it('processAuthorizationCallback()', async () => {
|
|
87
|
+
firstRes = await module.processAuthorizationCallback(authCallbackParams);
|
|
88
|
+
expect(firstRes).toBeDefined();
|
|
89
|
+
expect(firstRes.entity_id).toBeDefined();
|
|
90
|
+
expect(firstRes.credential_id).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('retrieves existing entity on subsequent calls', async () => {
|
|
93
|
+
const res = await module.processAuthorizationCallback(authCallbackParams);
|
|
94
|
+
expect(res).toEqual(firstRes);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('Test credential retrieval and module instantiation', () => {
|
|
99
|
+
it('retrieve by entity id', async () => {
|
|
100
|
+
const newModule = await getModule({
|
|
101
|
+
userId: module.userId,
|
|
102
|
+
entityId: module.entity.id
|
|
103
|
+
});
|
|
104
|
+
expect(newModule).toBeDefined();
|
|
105
|
+
expect(newModule.entity).toBeDefined();
|
|
106
|
+
expect(newModule.credential).toBeDefined();
|
|
107
|
+
expect(await newModule.testAuth()).toBeTruthy();
|
|
108
|
+
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('retrieve by credential id', async () => {
|
|
112
|
+
const newModule = await getModule({
|
|
113
|
+
userId: module.userId,
|
|
114
|
+
credentialId: module.credential.id
|
|
115
|
+
});
|
|
116
|
+
expect(newModule).toBeDefined();
|
|
117
|
+
expect(newModule.credential).toBeDefined();
|
|
118
|
+
expect(await newModule.testAuth()).toBeTruthy();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { testAutherDefinition }
|
|
125
|
+
|
package/test/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
const {
|
|
2
|
-
const {
|
|
1
|
+
const {testDefinitionRequiredAuthMethods} = require('./auther-definition-method-tester');
|
|
2
|
+
const {createMockIntegration, createMockApiObject} = require('./mock-integration');
|
|
3
|
+
const { testAutherDefinition } = require('./auther-definition-tester');
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
createMockIntegration,
|
|
7
8
|
createMockApiObject,
|
|
8
9
|
testDefinitionRequiredAuthMethods,
|
|
10
|
+
testAutherDefinition,
|
|
9
11
|
};
|