@friggframework/devtools 2.0.0--canary.474.86c5119.0 → 2.0.0--canary.474.6a0bba7.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/infrastructure/domains/database/migration-builder.js +199 -1
- package/infrastructure/domains/database/migration-builder.test.js +73 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +6 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +307 -1
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +38 -5
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +3 -3
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +50 -1
- package/infrastructure/domains/health/domain/entities/issue.test.js +111 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +174 -91
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +332 -228
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +407 -20
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +698 -26
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +108 -14
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +69 -12
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +392 -1
- package/package.json +6 -6
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogicalIdMapper Tests
|
|
3
|
+
*
|
|
4
|
+
* TDD tests for logical ID mapping functionality
|
|
5
|
+
* Domain Layer - Service Tests
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { LogicalIdMapper } = require('../logical-id-mapper');
|
|
9
|
+
|
|
10
|
+
describe('LogicalIdMapper', () => {
|
|
11
|
+
let mapper;
|
|
12
|
+
let mockEc2Client;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
// Mock EC2 client
|
|
16
|
+
mockEc2Client = {
|
|
17
|
+
send: jest.fn(),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
mapper = new LogicalIdMapper({ region: 'us-east-1' });
|
|
21
|
+
mapper.ec2Client = mockEc2Client;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('mapOrphanedResourcesToLogicalIds', () => {
|
|
25
|
+
it('should map orphaned resources using CloudFormation tags (highest confidence)', async () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
const orphanedResources = [
|
|
28
|
+
{
|
|
29
|
+
physicalId: 'vpc-12345678',
|
|
30
|
+
resourceType: 'AWS::EC2::VPC',
|
|
31
|
+
properties: {
|
|
32
|
+
VpcId: 'vpc-12345678',
|
|
33
|
+
CidrBlock: '10.0.0.0/16',
|
|
34
|
+
tags: {
|
|
35
|
+
'aws:cloudformation:stack-name': 'acme-integrations-dev',
|
|
36
|
+
'aws:cloudformation:logical-id': 'FriggVPC',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const buildTemplate = { resources: {} };
|
|
43
|
+
const deployedTemplate = { resources: {} };
|
|
44
|
+
|
|
45
|
+
// Act
|
|
46
|
+
const result = await mapper.mapOrphanedResourcesToLogicalIds({
|
|
47
|
+
orphanedResources,
|
|
48
|
+
buildTemplate,
|
|
49
|
+
deployedTemplate,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Assert
|
|
53
|
+
expect(result).toHaveLength(1);
|
|
54
|
+
expect(result[0]).toEqual({
|
|
55
|
+
logicalId: 'FriggVPC',
|
|
56
|
+
physicalId: 'vpc-12345678',
|
|
57
|
+
resourceType: 'AWS::EC2::VPC',
|
|
58
|
+
matchMethod: 'tag',
|
|
59
|
+
confidence: 'high',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should map VPC by contained resources when no tag found', async () => {
|
|
64
|
+
// Arrange
|
|
65
|
+
const orphanedResources = [
|
|
66
|
+
{
|
|
67
|
+
physicalId: 'vpc-12345678',
|
|
68
|
+
resourceType: 'AWS::EC2::VPC',
|
|
69
|
+
properties: {
|
|
70
|
+
VpcId: 'vpc-12345678',
|
|
71
|
+
CidrBlock: '10.0.0.0/16',
|
|
72
|
+
tags: {},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const buildTemplate = {
|
|
78
|
+
resources: {
|
|
79
|
+
FriggVPC: { Type: 'AWS::EC2::VPC' },
|
|
80
|
+
MyLambda: {
|
|
81
|
+
Type: 'AWS::Lambda::Function',
|
|
82
|
+
Properties: {
|
|
83
|
+
VpcConfig: {
|
|
84
|
+
SubnetIds: [
|
|
85
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
86
|
+
{ Ref: 'FriggPrivateSubnet2' },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const deployedTemplate = {
|
|
95
|
+
resources: {
|
|
96
|
+
MyLambda: {
|
|
97
|
+
Type: 'AWS::Lambda::Function',
|
|
98
|
+
Properties: {
|
|
99
|
+
VpcConfig: {
|
|
100
|
+
SubnetIds: ['subnet-11111111', 'subnet-22222222'],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Mock EC2 describe-subnets response
|
|
108
|
+
mockEc2Client.send.mockResolvedValueOnce({
|
|
109
|
+
Subnets: [
|
|
110
|
+
{ SubnetId: 'subnet-11111111', VpcId: 'vpc-12345678' },
|
|
111
|
+
{ SubnetId: 'subnet-22222222', VpcId: 'vpc-12345678' },
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Act
|
|
116
|
+
const result = await mapper.mapOrphanedResourcesToLogicalIds({
|
|
117
|
+
orphanedResources,
|
|
118
|
+
buildTemplate,
|
|
119
|
+
deployedTemplate,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Assert
|
|
123
|
+
expect(result).toHaveLength(1);
|
|
124
|
+
expect(result[0]).toEqual({
|
|
125
|
+
logicalId: 'FriggVPC',
|
|
126
|
+
physicalId: 'vpc-12345678',
|
|
127
|
+
resourceType: 'AWS::EC2::VPC',
|
|
128
|
+
matchMethod: 'contained-resources',
|
|
129
|
+
confidence: 'high',
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should map subnet by VPC usage in Lambda functions', async () => {
|
|
134
|
+
// Arrange
|
|
135
|
+
const orphanedResources = [
|
|
136
|
+
{
|
|
137
|
+
physicalId: 'subnet-11111111',
|
|
138
|
+
resourceType: 'AWS::EC2::Subnet',
|
|
139
|
+
properties: {
|
|
140
|
+
SubnetId: 'subnet-11111111',
|
|
141
|
+
VpcId: 'vpc-12345678',
|
|
142
|
+
tags: {},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const buildTemplate = {
|
|
148
|
+
resources: {
|
|
149
|
+
FriggPrivateSubnet1: { Type: 'AWS::EC2::Subnet' },
|
|
150
|
+
MyLambda: {
|
|
151
|
+
Type: 'AWS::Lambda::Function',
|
|
152
|
+
Properties: {
|
|
153
|
+
VpcConfig: {
|
|
154
|
+
SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const deployedTemplate = {
|
|
162
|
+
resources: {
|
|
163
|
+
MyLambda: {
|
|
164
|
+
Type: 'AWS::Lambda::Function',
|
|
165
|
+
Properties: {
|
|
166
|
+
VpcConfig: {
|
|
167
|
+
SubnetIds: ['subnet-11111111'],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Act
|
|
175
|
+
const result = await mapper.mapOrphanedResourcesToLogicalIds({
|
|
176
|
+
orphanedResources,
|
|
177
|
+
buildTemplate,
|
|
178
|
+
deployedTemplate,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(result).toHaveLength(1);
|
|
183
|
+
expect(result[0]).toEqual({
|
|
184
|
+
logicalId: 'FriggPrivateSubnet1',
|
|
185
|
+
physicalId: 'subnet-11111111',
|
|
186
|
+
resourceType: 'AWS::EC2::Subnet',
|
|
187
|
+
matchMethod: 'vpc-usage',
|
|
188
|
+
confidence: 'high',
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should map security group by usage in Lambda functions', async () => {
|
|
193
|
+
// Arrange
|
|
194
|
+
const orphanedResources = [
|
|
195
|
+
{
|
|
196
|
+
physicalId: 'sg-07c01370e830b6ad6',
|
|
197
|
+
resourceType: 'AWS::EC2::SecurityGroup',
|
|
198
|
+
properties: {
|
|
199
|
+
GroupId: 'sg-07c01370e830b6ad6',
|
|
200
|
+
tags: {},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
const buildTemplate = {
|
|
206
|
+
resources: {
|
|
207
|
+
FriggLambdaSecurityGroup: { Type: 'AWS::EC2::SecurityGroup' },
|
|
208
|
+
MyLambda: {
|
|
209
|
+
Type: 'AWS::Lambda::Function',
|
|
210
|
+
Properties: {
|
|
211
|
+
VpcConfig: {
|
|
212
|
+
SecurityGroupIds: [{ Ref: 'FriggLambdaSecurityGroup' }],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const deployedTemplate = {
|
|
220
|
+
resources: {
|
|
221
|
+
MyLambda: {
|
|
222
|
+
Type: 'AWS::Lambda::Function',
|
|
223
|
+
Properties: {
|
|
224
|
+
VpcConfig: {
|
|
225
|
+
SecurityGroupIds: ['sg-07c01370e830b6ad6'],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Act
|
|
233
|
+
const result = await mapper.mapOrphanedResourcesToLogicalIds({
|
|
234
|
+
orphanedResources,
|
|
235
|
+
buildTemplate,
|
|
236
|
+
deployedTemplate,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Assert
|
|
240
|
+
expect(result).toHaveLength(1);
|
|
241
|
+
expect(result[0]).toEqual({
|
|
242
|
+
logicalId: 'FriggLambdaSecurityGroup',
|
|
243
|
+
physicalId: 'sg-07c01370e830b6ad6',
|
|
244
|
+
resourceType: 'AWS::EC2::SecurityGroup',
|
|
245
|
+
matchMethod: 'usage',
|
|
246
|
+
confidence: 'medium',
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should return null logical ID if no match found', async () => {
|
|
251
|
+
// Arrange
|
|
252
|
+
const orphanedResources = [
|
|
253
|
+
{
|
|
254
|
+
physicalId: 'vpc-unknown',
|
|
255
|
+
resourceType: 'AWS::EC2::VPC',
|
|
256
|
+
properties: {
|
|
257
|
+
VpcId: 'vpc-unknown',
|
|
258
|
+
tags: {},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
const buildTemplate = { resources: {} };
|
|
264
|
+
const deployedTemplate = { resources: {} };
|
|
265
|
+
|
|
266
|
+
// Act
|
|
267
|
+
const result = await mapper.mapOrphanedResourcesToLogicalIds({
|
|
268
|
+
orphanedResources,
|
|
269
|
+
buildTemplate,
|
|
270
|
+
deployedTemplate,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Assert
|
|
274
|
+
expect(result).toHaveLength(1);
|
|
275
|
+
expect(result[0]).toEqual({
|
|
276
|
+
logicalId: null,
|
|
277
|
+
physicalId: 'vpc-unknown',
|
|
278
|
+
resourceType: 'AWS::EC2::VPC',
|
|
279
|
+
matchMethod: 'none',
|
|
280
|
+
confidence: 'none',
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should map multiple orphaned resources with different strategies', async () => {
|
|
285
|
+
// Arrange
|
|
286
|
+
const orphanedResources = [
|
|
287
|
+
{
|
|
288
|
+
physicalId: 'vpc-12345678',
|
|
289
|
+
resourceType: 'AWS::EC2::VPC',
|
|
290
|
+
properties: {
|
|
291
|
+
VpcId: 'vpc-12345678',
|
|
292
|
+
tags: {
|
|
293
|
+
'aws:cloudformation:logical-id': 'FriggVPC',
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
physicalId: 'subnet-11111111',
|
|
299
|
+
resourceType: 'AWS::EC2::Subnet',
|
|
300
|
+
properties: {
|
|
301
|
+
SubnetId: 'subnet-11111111',
|
|
302
|
+
VpcId: 'vpc-12345678',
|
|
303
|
+
tags: {},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
const buildTemplate = {
|
|
309
|
+
resources: {
|
|
310
|
+
FriggPrivateSubnet1: { Type: 'AWS::EC2::Subnet' },
|
|
311
|
+
MyLambda: {
|
|
312
|
+
Type: 'AWS::Lambda::Function',
|
|
313
|
+
Properties: {
|
|
314
|
+
VpcConfig: {
|
|
315
|
+
SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }],
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const deployedTemplate = {
|
|
323
|
+
resources: {
|
|
324
|
+
MyLambda: {
|
|
325
|
+
Type: 'AWS::Lambda::Function',
|
|
326
|
+
Properties: {
|
|
327
|
+
VpcConfig: {
|
|
328
|
+
SubnetIds: ['subnet-11111111'],
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Act
|
|
336
|
+
const result = await mapper.mapOrphanedResourcesToLogicalIds({
|
|
337
|
+
orphanedResources,
|
|
338
|
+
buildTemplate,
|
|
339
|
+
deployedTemplate,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Assert
|
|
343
|
+
expect(result).toHaveLength(2);
|
|
344
|
+
expect(result[0].matchMethod).toBe('tag');
|
|
345
|
+
expect(result[0].confidence).toBe('high');
|
|
346
|
+
expect(result[1].matchMethod).toBe('vpc-usage');
|
|
347
|
+
expect(result[1].confidence).toBe('high');
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe('_getLogicalIdFromTags', () => {
|
|
352
|
+
it('should extract logical ID from CloudFormation tags', () => {
|
|
353
|
+
// Arrange
|
|
354
|
+
const tags = [
|
|
355
|
+
{ Key: 'aws:cloudformation:stack-name', Value: 'acme-integrations-dev' },
|
|
356
|
+
{ Key: 'aws:cloudformation:logical-id', Value: 'FriggVPC' },
|
|
357
|
+
{ Key: 'Name', Value: 'My VPC' },
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
// Act
|
|
361
|
+
const result = mapper._getLogicalIdFromTags(tags);
|
|
362
|
+
|
|
363
|
+
// Assert
|
|
364
|
+
expect(result).toBe('FriggVPC');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should return null if logical-id tag not found', () => {
|
|
368
|
+
// Arrange
|
|
369
|
+
const tags = [
|
|
370
|
+
{ Key: 'Name', Value: 'My VPC' },
|
|
371
|
+
{ Key: 'Environment', Value: 'dev' },
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
// Act
|
|
375
|
+
const result = mapper._getLogicalIdFromTags(tags);
|
|
376
|
+
|
|
377
|
+
// Assert
|
|
378
|
+
expect(result).toBeNull();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should return null if tags is null or undefined', () => {
|
|
382
|
+
// Act
|
|
383
|
+
const resultNull = mapper._getLogicalIdFromTags(null);
|
|
384
|
+
const resultUndefined = mapper._getLogicalIdFromTags(undefined);
|
|
385
|
+
|
|
386
|
+
// Assert
|
|
387
|
+
expect(resultNull).toBeNull();
|
|
388
|
+
expect(resultUndefined).toBeNull();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should return null if tags is not an array', () => {
|
|
392
|
+
// Act
|
|
393
|
+
const result = mapper._getLogicalIdFromTags('not-an-array');
|
|
394
|
+
|
|
395
|
+
// Assert
|
|
396
|
+
expect(result).toBeNull();
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
describe('_matchVpcByContainedResources', () => {
|
|
401
|
+
it('should match VPC that contains all expected subnets', async () => {
|
|
402
|
+
// Arrange
|
|
403
|
+
const vpc = {
|
|
404
|
+
physicalId: 'vpc-12345678',
|
|
405
|
+
resourceType: 'AWS::EC2::VPC',
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const buildTemplate = {
|
|
409
|
+
resources: {
|
|
410
|
+
FriggVPC: { Type: 'AWS::EC2::VPC' },
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const deployedTemplate = {
|
|
415
|
+
resources: {
|
|
416
|
+
MyLambda: {
|
|
417
|
+
Type: 'AWS::Lambda::Function',
|
|
418
|
+
Properties: {
|
|
419
|
+
VpcConfig: {
|
|
420
|
+
SubnetIds: ['subnet-111', 'subnet-222'],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// Mock EC2 describe-subnets response
|
|
428
|
+
mockEc2Client.send.mockResolvedValueOnce({
|
|
429
|
+
Subnets: [
|
|
430
|
+
{ SubnetId: 'subnet-111', VpcId: 'vpc-12345678' },
|
|
431
|
+
{ SubnetId: 'subnet-222', VpcId: 'vpc-12345678' },
|
|
432
|
+
{ SubnetId: 'subnet-333', VpcId: 'vpc-12345678' },
|
|
433
|
+
],
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Act
|
|
437
|
+
const result = await mapper._matchVpcByContainedResources(
|
|
438
|
+
vpc,
|
|
439
|
+
buildTemplate,
|
|
440
|
+
deployedTemplate
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// Assert
|
|
444
|
+
expect(result).toBe('FriggVPC');
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should return null if VPC does not contain expected subnets', async () => {
|
|
448
|
+
// Arrange
|
|
449
|
+
const vpc = {
|
|
450
|
+
physicalId: 'vpc-wrong',
|
|
451
|
+
resourceType: 'AWS::EC2::VPC',
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const buildTemplate = {
|
|
455
|
+
resources: {
|
|
456
|
+
FriggVPC: { Type: 'AWS::EC2::VPC' },
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const deployedTemplate = {
|
|
461
|
+
resources: {
|
|
462
|
+
MyLambda: {
|
|
463
|
+
Type: 'AWS::Lambda::Function',
|
|
464
|
+
Properties: {
|
|
465
|
+
VpcConfig: {
|
|
466
|
+
SubnetIds: ['subnet-111', 'subnet-222'],
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// Mock EC2 describe-subnets response - VPC has different subnets
|
|
474
|
+
mockEc2Client.send.mockResolvedValueOnce({
|
|
475
|
+
Subnets: [
|
|
476
|
+
{ SubnetId: 'subnet-999', VpcId: 'vpc-wrong' },
|
|
477
|
+
{ SubnetId: 'subnet-888', VpcId: 'vpc-wrong' },
|
|
478
|
+
],
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Act
|
|
482
|
+
const result = await mapper._matchVpcByContainedResources(
|
|
483
|
+
vpc,
|
|
484
|
+
buildTemplate,
|
|
485
|
+
deployedTemplate
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// Assert
|
|
489
|
+
expect(result).toBeNull();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('should return null if no expected subnets in deployed template', async () => {
|
|
493
|
+
// Arrange
|
|
494
|
+
const vpc = {
|
|
495
|
+
physicalId: 'vpc-12345678',
|
|
496
|
+
resourceType: 'AWS::EC2::VPC',
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const buildTemplate = { resources: {} };
|
|
500
|
+
const deployedTemplate = { resources: {} };
|
|
501
|
+
|
|
502
|
+
// Act
|
|
503
|
+
const result = await mapper._matchVpcByContainedResources(
|
|
504
|
+
vpc,
|
|
505
|
+
buildTemplate,
|
|
506
|
+
deployedTemplate
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
// Assert
|
|
510
|
+
expect(result).toBeNull();
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
describe('_extractSubnetIdsFromTemplate', () => {
|
|
515
|
+
it('should extract subnet IDs from Lambda VpcConfig', () => {
|
|
516
|
+
// Arrange
|
|
517
|
+
const template = {
|
|
518
|
+
resources: {
|
|
519
|
+
Lambda1: {
|
|
520
|
+
Type: 'AWS::Lambda::Function',
|
|
521
|
+
Properties: {
|
|
522
|
+
VpcConfig: {
|
|
523
|
+
SubnetIds: ['subnet-111', 'subnet-222'],
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
Lambda2: {
|
|
528
|
+
Type: 'AWS::Lambda::Function',
|
|
529
|
+
Properties: {
|
|
530
|
+
VpcConfig: {
|
|
531
|
+
SubnetIds: ['subnet-333'],
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// Act
|
|
539
|
+
const result = mapper._extractSubnetIdsFromTemplate(template);
|
|
540
|
+
|
|
541
|
+
// Assert
|
|
542
|
+
expect(result).toEqual(['subnet-111', 'subnet-222', 'subnet-333']);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('should deduplicate subnet IDs', () => {
|
|
546
|
+
// Arrange
|
|
547
|
+
const template = {
|
|
548
|
+
resources: {
|
|
549
|
+
Lambda1: {
|
|
550
|
+
Type: 'AWS::Lambda::Function',
|
|
551
|
+
Properties: {
|
|
552
|
+
VpcConfig: {
|
|
553
|
+
SubnetIds: ['subnet-111', 'subnet-222'],
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
Lambda2: {
|
|
558
|
+
Type: 'AWS::Lambda::Function',
|
|
559
|
+
Properties: {
|
|
560
|
+
VpcConfig: {
|
|
561
|
+
SubnetIds: ['subnet-111', 'subnet-333'],
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// Act
|
|
569
|
+
const result = mapper._extractSubnetIdsFromTemplate(template);
|
|
570
|
+
|
|
571
|
+
// Assert
|
|
572
|
+
expect(result).toEqual(['subnet-111', 'subnet-222', 'subnet-333']);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should return empty array if no Lambda VPC configs', () => {
|
|
576
|
+
// Arrange
|
|
577
|
+
const template = {
|
|
578
|
+
resources: {
|
|
579
|
+
MyQueue: { Type: 'AWS::SQS::Queue' },
|
|
580
|
+
},
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// Act
|
|
584
|
+
const result = mapper._extractSubnetIdsFromTemplate(template);
|
|
585
|
+
|
|
586
|
+
// Assert
|
|
587
|
+
expect(result).toEqual([]);
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
describe('_extractSubnetRefsFromTemplate', () => {
|
|
592
|
+
it('should extract subnet Refs from build template', () => {
|
|
593
|
+
// Arrange
|
|
594
|
+
const template = {
|
|
595
|
+
resources: {
|
|
596
|
+
MyLambda: {
|
|
597
|
+
Type: 'AWS::Lambda::Function',
|
|
598
|
+
Properties: {
|
|
599
|
+
VpcConfig: {
|
|
600
|
+
SubnetIds: [
|
|
601
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
602
|
+
{ Ref: 'FriggPrivateSubnet2' },
|
|
603
|
+
],
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// Act
|
|
611
|
+
const result = mapper._extractSubnetRefsFromTemplate(template);
|
|
612
|
+
|
|
613
|
+
// Assert
|
|
614
|
+
expect(result).toEqual(['FriggPrivateSubnet1', 'FriggPrivateSubnet2']);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('should return empty array if no Refs found', () => {
|
|
618
|
+
// Arrange
|
|
619
|
+
const template = {
|
|
620
|
+
resources: {
|
|
621
|
+
MyLambda: {
|
|
622
|
+
Type: 'AWS::Lambda::Function',
|
|
623
|
+
Properties: {
|
|
624
|
+
VpcConfig: {
|
|
625
|
+
SubnetIds: ['subnet-111'],
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// Act
|
|
633
|
+
const result = mapper._extractSubnetRefsFromTemplate(template);
|
|
634
|
+
|
|
635
|
+
// Assert
|
|
636
|
+
expect(result).toEqual([]);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
describe('_findVpcLogicalIdInTemplate', () => {
|
|
641
|
+
it('should find VPC logical ID in build template', () => {
|
|
642
|
+
// Arrange
|
|
643
|
+
const template = {
|
|
644
|
+
resources: {
|
|
645
|
+
FriggVPC: { Type: 'AWS::EC2::VPC' },
|
|
646
|
+
MySubnet: { Type: 'AWS::EC2::Subnet' },
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// Act
|
|
651
|
+
const result = mapper._findVpcLogicalIdInTemplate(template);
|
|
652
|
+
|
|
653
|
+
// Assert
|
|
654
|
+
expect(result).toBe('FriggVPC');
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('should return null if no VPC in template', () => {
|
|
658
|
+
// Arrange
|
|
659
|
+
const template = {
|
|
660
|
+
resources: {
|
|
661
|
+
MyLambda: { Type: 'AWS::Lambda::Function' },
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// Act
|
|
666
|
+
const result = mapper._findVpcLogicalIdInTemplate(template);
|
|
667
|
+
|
|
668
|
+
// Assert
|
|
669
|
+
expect(result).toBeNull();
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
});
|