@friggframework/devtools 2.0.0--canary.428.7665532.0 โ†’ 2.0.0--canary.428.6b04c24.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.
@@ -510,6 +510,8 @@ class AWSDiscovery {
510
510
  console.warn('This is a Frigg-managed NAT Gateway that may have been misconfigured by route table changes');
511
511
  console.warn('Consider enabling selfHeal: true to fix this automatically');
512
512
  // Return it anyway if it's Frigg-managed - we can fix the routes
513
+ // Mark that it's in a private subnet
514
+ natGateway._isInPrivateSubnet = true;
513
515
  return natGateway;
514
516
  } else {
515
517
  console.warn('NAT Gateways MUST be placed in public subnets with Internet Gateway routes');
@@ -520,11 +522,13 @@ class AWSDiscovery {
520
522
 
521
523
  if (isFriggNat) {
522
524
  console.log(`Found existing Frigg-managed NAT Gateway: ${natGateway.NatGatewayId}`);
525
+ natGateway._isInPrivateSubnet = false;
523
526
  return natGateway;
524
527
  }
525
528
 
526
529
  // Return first valid NAT Gateway that's in a public subnet
527
530
  console.log(`Found existing NAT Gateway in public subnet: ${natGateway.NatGatewayId}`);
531
+ natGateway._isInPrivateSubnet = false;
528
532
  return natGateway;
529
533
  }
530
534
 
@@ -888,9 +892,13 @@ class AWSDiscovery {
888
892
  const existingNatGateway = await this.findExistingNatGateway(vpc.VpcId);
889
893
  let natGatewayId = null;
890
894
  let elasticIpAllocationId = null;
891
-
895
+ let natGatewayInPrivateSubnet = false;
896
+
892
897
  if (existingNatGateway) {
893
898
  natGatewayId = existingNatGateway.NatGatewayId;
899
+ // Check if NAT Gateway is in a private subnet (from our detection)
900
+ natGatewayInPrivateSubnet = existingNatGateway._isInPrivateSubnet || false;
901
+
894
902
  // Get the EIP allocation ID from the NAT Gateway
895
903
  if (existingNatGateway.NatGatewayAddresses && existingNatGateway.NatGatewayAddresses.length > 0) {
896
904
  elasticIpAllocationId = existingNatGateway.NatGatewayAddresses[0].AllocationId;
@@ -949,9 +957,18 @@ class AWSDiscovery {
949
957
  defaultKmsKeyId: kmsKeyArn,
950
958
  existingNatGatewayId: natGatewayId,
951
959
  existingElasticIpAllocationId: elasticIpAllocationId,
960
+ natGatewayInPrivateSubnet: natGatewayInPrivateSubnet,
952
961
  subnetConversionRequired: subnetStatus.requiresConversion,
953
- privateSubnetsWithWrongRoutes: subnetStatus.requiresConversion ?
954
- [privateSubnets[0]?.SubnetId, privateSubnets[1]?.SubnetId].filter(Boolean) : []
962
+ privateSubnetsWithWrongRoutes: (() => {
963
+ const wrongRoutes = [];
964
+ if (subnetStatus.subnet1NeedsConversion && privateSubnets[0]) {
965
+ wrongRoutes.push(privateSubnets[0].SubnetId);
966
+ }
967
+ if (subnetStatus.subnet2NeedsConversion && privateSubnets[1]) {
968
+ wrongRoutes.push(privateSubnets[1].SubnetId);
969
+ }
970
+ return wrongRoutes;
971
+ })()
955
972
  };
956
973
  } catch (error) {
957
974
  console.error('Error discovering AWS resources:', error);
@@ -1,446 +1,545 @@
1
- const AWS = require('aws-sdk');
1
+ const { mockClient } = require('aws-sdk-client-mock');
2
2
  const { AWSDiscovery } = require('./aws-discovery');
3
3
 
4
- // Mock AWS SDK
5
- jest.mock('@aws-sdk/client-ec2');
6
- jest.mock('@aws-sdk/client-kms');
7
- jest.mock('@aws-sdk/client-sts');
8
-
9
- const { EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, DescribeRouteTablesCommand } = require('@aws-sdk/client-ec2');
10
- const { KMSClient, ListKeysCommand, DescribeKeyCommand } = require('@aws-sdk/client-kms');
11
- const { STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts');
4
+ // Import AWS SDK commands
5
+ const {
6
+ EC2Client,
7
+ DescribeVpcsCommand,
8
+ DescribeSubnetsCommand,
9
+ DescribeSecurityGroupsCommand,
10
+ DescribeRouteTablesCommand,
11
+ DescribeNatGatewaysCommand,
12
+ DescribeAddressesCommand
13
+ } = require('@aws-sdk/client-ec2');
14
+ const {
15
+ KMSClient,
16
+ ListKeysCommand,
17
+ DescribeKeyCommand
18
+ } = require('@aws-sdk/client-kms');
19
+ const {
20
+ STSClient,
21
+ GetCallerIdentityCommand
22
+ } = require('@aws-sdk/client-sts');
23
+
24
+ // Create mock clients
25
+ const ec2Mock = mockClient(EC2Client);
26
+ const kmsMock = mockClient(KMSClient);
27
+ const stsMock = mockClient(STSClient);
12
28
 
13
29
  describe('AWSDiscovery', () => {
14
30
  let discovery;
15
- let mockEC2Send;
16
- let mockKMSSend;
17
- let mockSTSSend;
18
31
 
19
32
  beforeEach(() => {
33
+ // Reset all mocks before each test
34
+ ec2Mock.reset();
35
+ kmsMock.reset();
36
+ stsMock.reset();
37
+
20
38
  discovery = new AWSDiscovery('us-east-1');
21
-
22
- // Create mock send functions
23
- mockEC2Send = jest.fn();
24
- mockKMSSend = jest.fn();
25
- mockSTSSend = jest.fn();
26
-
27
- // Mock the client constructors and send methods
28
- EC2Client.mockImplementation(() => ({
29
- send: mockEC2Send
30
- }));
31
-
32
- KMSClient.mockImplementation(() => ({
33
- send: mockKMSSend
34
- }));
35
-
36
- STSClient.mockImplementation(() => ({
37
- send: mockSTSSend
38
- }));
39
-
40
- // Reset mocks
41
- jest.clearAllMocks();
42
39
  });
43
40
 
44
41
  describe('getAccountId', () => {
45
42
  it('should return AWS account ID', async () => {
46
43
  const mockAccountId = '123456789012';
47
- mockSTSSend.mockResolvedValue({
44
+ stsMock.on(GetCallerIdentityCommand).resolves({
48
45
  Account: mockAccountId
49
46
  });
50
47
 
51
- const result = await discovery.getAccountId();
52
-
53
- expect(result).toBe(mockAccountId);
54
- expect(mockSTSSend).toHaveBeenCalledWith(expect.any(GetCallerIdentityCommand));
48
+ const accountId = await discovery.getAccountId();
49
+ expect(accountId).toBe(mockAccountId);
55
50
  });
56
51
 
57
52
  it('should throw error when STS call fails', async () => {
58
- const error = new Error('STS Error');
59
- mockSTSSend.mockRejectedValue(error);
53
+ stsMock.on(GetCallerIdentityCommand).rejects(new Error('STS error'));
60
54
 
61
- await expect(discovery.getAccountId()).rejects.toThrow('STS Error');
55
+ await expect(discovery.getAccountId()).rejects.toThrow('STS error');
62
56
  });
63
57
  });
64
58
 
65
59
  describe('findDefaultVpc', () => {
66
- it('should return default VPC when found', async () => {
67
- const mockVpc = {
68
- VpcId: 'vpc-12345678',
69
- IsDefault: true,
70
- State: 'available'
71
- };
60
+ it('should return default VPC when available', async () => {
61
+ const mockVpc = { VpcId: 'vpc-12345678', IsDefault: true };
62
+ ec2Mock.on(DescribeVpcsCommand).resolves({
63
+ Vpcs: [mockVpc]
64
+ });
72
65
 
73
- mockEC2Send.mockResolvedValue({
66
+ const vpc = await discovery.findDefaultVpc();
67
+ expect(vpc).toEqual(mockVpc);
68
+ });
69
+
70
+ it('should return first VPC when no default VPC exists', async () => {
71
+ const mockVpc = { VpcId: 'vpc-12345678', IsDefault: false };
72
+ ec2Mock.on(DescribeVpcsCommand).resolves({
74
73
  Vpcs: [mockVpc]
75
74
  });
76
75
 
77
- const result = await discovery.findDefaultVpc();
76
+ const vpc = await discovery.findDefaultVpc();
77
+ expect(vpc).toEqual(mockVpc);
78
+ });
78
79
 
79
- expect(result).toEqual(mockVpc);
80
- expect(mockEC2Send).toHaveBeenCalledWith(expect.objectContaining({
81
- input: {
82
- Filters: [{
83
- Name: 'is-default',
84
- Values: ['true']
85
- }]
86
- }
87
- }));
80
+ it('should throw error when no VPCs found', async () => {
81
+ ec2Mock.on(DescribeVpcsCommand).resolves({
82
+ Vpcs: []
83
+ });
84
+
85
+ await expect(discovery.findDefaultVpc()).rejects.toThrow('No VPC found in the account');
88
86
  });
87
+ });
89
88
 
90
- it('should return first available VPC when no default VPC exists', async () => {
91
- const mockVpc = {
92
- VpcId: 'vpc-87654321',
93
- IsDefault: false,
94
- State: 'available'
95
- };
89
+ describe('isSubnetPrivate', () => {
90
+ const mockVpcId = 'vpc-12345678';
91
+ const mockSubnetId = 'subnet-12345678';
96
92
 
97
- mockEC2Send
98
- .mockResolvedValueOnce({ Vpcs: [] }) // No default VPC
99
- .mockResolvedValueOnce({ Vpcs: [mockVpc] }); // All VPCs
93
+ it('should return true for private subnet', async () => {
94
+ // Mock subnet lookup first
95
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
96
+ Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
97
+ });
100
98
 
101
- const result = await discovery.findDefaultVpc();
99
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
100
+ RouteTables: [{
101
+ Associations: [{ SubnetId: mockSubnetId }],
102
+ Routes: [
103
+ { GatewayId: 'local', DestinationCidrBlock: '10.0.0.0/16' }
104
+ ]
105
+ }]
106
+ });
102
107
 
103
- expect(result).toEqual(mockVpc);
104
- expect(mockEC2Send).toHaveBeenCalledTimes(2);
108
+ const isPrivate = await discovery.isSubnetPrivate(mockSubnetId, mockVpcId);
109
+ expect(isPrivate).toBe(true);
105
110
  });
106
111
 
107
- it('should throw error when no VPCs found', async () => {
108
- mockEC2Send
109
- .mockResolvedValueOnce({ Vpcs: [] }) // No default VPC
110
- .mockResolvedValueOnce({ Vpcs: [] }); // No VPCs at all
112
+ it('should return false for public subnet', async () => {
113
+ // Mock subnet lookup first
114
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
115
+ Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
116
+ });
111
117
 
112
- await expect(discovery.findDefaultVpc()).rejects.toThrow('No VPC found in the account');
118
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
119
+ RouteTables: [{
120
+ Associations: [{ SubnetId: mockSubnetId }],
121
+ Routes: [
122
+ { GatewayId: 'igw-12345', DestinationCidrBlock: '0.0.0.0/0' }
123
+ ]
124
+ }]
125
+ });
126
+
127
+ const isPrivate = await discovery.isSubnetPrivate(mockSubnetId, mockVpcId);
128
+ expect(isPrivate).toBe(false);
113
129
  });
114
130
  });
115
131
 
116
132
  describe('findPrivateSubnets', () => {
117
133
  const mockVpcId = 'vpc-12345678';
118
134
 
119
- it('should return private subnets when found', async () => {
135
+ it('should return private subnets', async () => {
120
136
  const mockSubnets = [
121
- { SubnetId: 'subnet-private-1', VpcId: mockVpcId, AvailabilityZone: 'us-east-1a' },
122
- { SubnetId: 'subnet-private-2', VpcId: mockVpcId, AvailabilityZone: 'us-east-1b' }
137
+ { SubnetId: 'subnet-private-1', AvailabilityZone: 'us-east-1a' },
138
+ { SubnetId: 'subnet-private-2', AvailabilityZone: 'us-east-1b' }
123
139
  ];
124
140
 
125
- mockEC2Send
126
- .mockResolvedValueOnce({ Subnets: mockSubnets }) // DescribeSubnets
127
- .mockResolvedValueOnce({ // Check subnet-private-1
128
- Subnets: [{ SubnetId: 'subnet-private-1', VpcId: mockVpcId }]
129
- })
130
- .mockResolvedValueOnce({ // Route tables for private-1
131
- RouteTables: [{
132
- Associations: [{ SubnetId: 'subnet-private-1' }],
133
- Routes: [{ GatewayId: 'local' }] // No IGW = private
134
- }]
135
- })
136
- .mockResolvedValueOnce({ // Check subnet-private-2
137
- Subnets: [{ SubnetId: 'subnet-private-2', VpcId: mockVpcId }]
138
- })
139
- .mockResolvedValueOnce({ // Route tables for private-2
140
- RouteTables: [{
141
- Associations: [{ SubnetId: 'subnet-private-2' }],
142
- Routes: [{ GatewayId: 'local' }] // No IGW = private
143
- }]
144
- });
141
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
142
+ Subnets: mockSubnets
143
+ });
145
144
 
146
- const result = await discovery.findPrivateSubnets(mockVpcId, false);
145
+ // Mock route tables - no IGW routes (private)
146
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
147
+ RouteTables: [{
148
+ Associations: [
149
+ { SubnetId: 'subnet-private-1' },
150
+ { SubnetId: 'subnet-private-2' }
151
+ ],
152
+ Routes: [
153
+ { GatewayId: 'local', DestinationCidrBlock: '10.0.0.0/16' }
154
+ ]
155
+ }]
156
+ });
147
157
 
148
- expect(result).toHaveLength(2);
149
- expect(result[0].SubnetId).toBe('subnet-private-1');
150
- expect(result[1].SubnetId).toBe('subnet-private-2');
158
+ const subnets = await discovery.findPrivateSubnets(mockVpcId);
159
+ expect(subnets).toEqual(mockSubnets);
151
160
  });
152
161
 
153
- it('should handle all public subnets with autoConvert enabled', async () => {
162
+ it('should throw error when no private subnets found and autoConvert is false', async () => {
154
163
  const mockSubnets = [
155
- { SubnetId: 'subnet-public-1', VpcId: mockVpcId, AvailabilityZone: 'us-east-1a' },
156
- { SubnetId: 'subnet-public-2', VpcId: mockVpcId, AvailabilityZone: 'us-east-1b' },
157
- { SubnetId: 'subnet-public-3', VpcId: mockVpcId, AvailabilityZone: 'us-east-1c' }
164
+ { SubnetId: 'subnet-public-1', AvailabilityZone: 'us-east-1a' },
165
+ { SubnetId: 'subnet-public-2', AvailabilityZone: 'us-east-1b' },
166
+ { SubnetId: 'subnet-public-3', AvailabilityZone: 'us-east-1c' }
158
167
  ];
159
168
 
160
- // Mock all subnets as public
161
- mockEC2Send
162
- .mockResolvedValueOnce({ Subnets: mockSubnets }) // DescribeSubnets
163
- .mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }] })
164
- .mockResolvedValueOnce({ // Route table for public-1
165
- RouteTables: [{
166
- Associations: [{ SubnetId: 'subnet-public-1' }],
167
- Routes: [{ GatewayId: 'igw-12345' }] // IGW = public
168
- }]
169
- })
170
- .mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-2', VpcId: mockVpcId }] })
171
- .mockResolvedValueOnce({ // Route table for public-2
172
- RouteTables: [{
173
- Associations: [{ SubnetId: 'subnet-public-2' }],
174
- Routes: [{ GatewayId: 'igw-12345' }] // IGW = public
175
- }]
176
- })
177
- .mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-3', VpcId: mockVpcId }] })
178
- .mockResolvedValueOnce({ // Route table for public-3
179
- RouteTables: [{
180
- Associations: [{ SubnetId: 'subnet-public-3' }],
181
- Routes: [{ GatewayId: 'igw-12345' }] // IGW = public
182
- }]
183
- });
169
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
170
+ Subnets: mockSubnets
171
+ });
184
172
 
185
- // With autoConvert=true, should return subnets to be converted
186
- const result = await discovery.findPrivateSubnets(mockVpcId, true);
173
+ // Mock route tables - has IGW routes (public)
174
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
175
+ RouteTables: [{
176
+ Associations: [
177
+ { SubnetId: 'subnet-public-1' },
178
+ { SubnetId: 'subnet-public-2' },
179
+ { SubnetId: 'subnet-public-3' }
180
+ ],
181
+ Routes: [
182
+ { GatewayId: 'igw-12345', DestinationCidrBlock: '0.0.0.0/0' }
183
+ ]
184
+ }]
185
+ });
187
186
 
188
- expect(result).toHaveLength(2);
189
- // Should return subnets 2 and 3 for conversion (keeping 1 as public for NAT)
190
- expect(result[0].SubnetId).toBe('subnet-public-2');
191
- expect(result[1].SubnetId).toBe('subnet-public-3');
187
+ await expect(discovery.findPrivateSubnets(mockVpcId, false))
188
+ .rejects.toThrow('No private subnets found in VPC');
192
189
  });
193
190
 
194
- it('should throw error when all subnets are public and autoConvert is false', async () => {
191
+ it('should return public subnets with warning when autoConvert is true', async () => {
195
192
  const mockSubnets = [
196
- { SubnetId: 'subnet-public-1', VpcId: mockVpcId, AvailabilityZone: 'us-east-1a' },
197
- { SubnetId: 'subnet-public-2', VpcId: mockVpcId, AvailabilityZone: 'us-east-1b' }
193
+ { SubnetId: 'subnet-public-1', AvailabilityZone: 'us-east-1a' },
194
+ { SubnetId: 'subnet-public-2', AvailabilityZone: 'us-east-1b' }
198
195
  ];
199
196
 
200
- mockEC2Send
201
- .mockResolvedValueOnce({ Subnets: mockSubnets })
202
- .mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }] })
203
- .mockResolvedValueOnce({ // Public route table
204
- RouteTables: [{
205
- Associations: [{ SubnetId: 'subnet-public-1' }],
206
- Routes: [{ GatewayId: 'igw-12345' }]
207
- }]
208
- })
209
- .mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-2', VpcId: mockVpcId }] })
210
- .mockResolvedValueOnce({ // Public route table
211
- RouteTables: [{
212
- Associations: [{ SubnetId: 'subnet-public-2' }],
213
- Routes: [{ GatewayId: 'igw-12345' }]
214
- }]
215
- });
197
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
198
+ Subnets: mockSubnets
199
+ });
200
+
201
+ // Mock route tables - has IGW routes (public)
202
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
203
+ RouteTables: [{
204
+ Associations: [
205
+ { SubnetId: 'subnet-public-1' },
206
+ { SubnetId: 'subnet-public-2' }
207
+ ],
208
+ Routes: [
209
+ { GatewayId: 'igw-12345', DestinationCidrBlock: '0.0.0.0/0' }
210
+ ]
211
+ }]
212
+ });
216
213
 
217
- await expect(discovery.findPrivateSubnets(mockVpcId, false)).rejects.toThrow(
218
- `No private subnets found in VPC ${mockVpcId}`
219
- );
214
+ const subnets = await discovery.findPrivateSubnets(mockVpcId, true);
215
+ expect(subnets).toHaveLength(2);
216
+ expect(subnets[0].SubnetId).toBe('subnet-public-1');
220
217
  });
218
+ });
221
219
 
222
- it('should handle mixed private/public subnets correctly', async () => {
223
- const mockSubnets = [
224
- { SubnetId: 'subnet-private-1', VpcId: mockVpcId, AvailabilityZone: 'us-east-1a' },
225
- { SubnetId: 'subnet-public-1', VpcId: mockVpcId, AvailabilityZone: 'us-east-1b' },
226
- { SubnetId: 'subnet-public-2', VpcId: mockVpcId, AvailabilityZone: 'us-east-1c' }
227
- ];
220
+ describe('findPublicSubnets', () => {
221
+ const mockVpcId = 'vpc-12345678';
228
222
 
229
- mockEC2Send
230
- .mockResolvedValueOnce({ Subnets: mockSubnets })
231
- .mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-private-1', VpcId: mockVpcId }] })
232
- .mockResolvedValueOnce({ // Private route table
233
- RouteTables: [{
234
- Associations: [{ SubnetId: 'subnet-private-1' }],
235
- Routes: [{ GatewayId: 'local' }] // No IGW
236
- }]
237
- })
238
- .mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }] })
239
- .mockResolvedValueOnce({ // Public route table
240
- RouteTables: [{
241
- Associations: [{ SubnetId: 'subnet-public-1' }],
242
- Routes: [{ GatewayId: 'igw-12345' }]
243
- }]
244
- })
245
- .mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-2', VpcId: mockVpcId }] })
246
- .mockResolvedValueOnce({ // Public route table
247
- RouteTables: [{
248
- Associations: [{ SubnetId: 'subnet-public-2' }],
249
- Routes: [{ GatewayId: 'igw-12345' }]
250
- }]
251
- });
223
+ it('should return public subnet', async () => {
224
+ const mockSubnet = { SubnetId: 'subnet-public-1' };
225
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
226
+ Subnets: [mockSubnet]
227
+ });
252
228
 
253
- const result = await discovery.findPrivateSubnets(mockVpcId, false);
229
+ // Mock route table check to show it's public
230
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
231
+ RouteTables: [{
232
+ Associations: [{ SubnetId: 'subnet-public-1' }],
233
+ Routes: [{ GatewayId: 'igw-12345' }] // Has IGW = public
234
+ }]
235
+ });
254
236
 
255
- expect(result).toHaveLength(2);
256
- // Should return the one private and one public subnet for HA
257
- expect(result[0].SubnetId).toBe('subnet-private-1');
258
- expect(result[1].SubnetId).toBe('subnet-public-1');
237
+ const subnet = await discovery.findPublicSubnets(mockVpcId);
238
+ expect(subnet).toEqual(mockSubnet);
259
239
  });
260
240
 
261
241
  it('should throw error when no subnets found', async () => {
262
- mockEC2Send.mockResolvedValue({ Subnets: [] });
242
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
243
+ Subnets: []
244
+ });
263
245
 
264
- await expect(discovery.findPrivateSubnets(mockVpcId)).rejects.toThrow(`No subnets found in VPC ${mockVpcId}`);
246
+ await expect(discovery.findPublicSubnets(mockVpcId))
247
+ .rejects.toThrow('No subnets found in VPC');
265
248
  });
266
249
  });
267
250
 
268
- describe('isSubnetPrivate', () => {
269
- const mockSubnetId = 'subnet-12345678';
251
+ describe('findDefaultSecurityGroup', () => {
270
252
  const mockVpcId = 'vpc-12345678';
271
253
 
272
- it('should return false for public subnet (has IGW route)', async () => {
273
- mockEC2Send
274
- .mockResolvedValueOnce({ // DescribeSubnets
275
- Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
276
- })
277
- .mockResolvedValueOnce({ // DescribeRouteTables
278
- RouteTables: [{
279
- Associations: [{ SubnetId: mockSubnetId }],
280
- Routes: [{
281
- GatewayId: 'igw-12345678',
282
- DestinationCidrBlock: '0.0.0.0/0'
283
- }]
284
- }]
285
- });
254
+ it('should return default security group', async () => {
255
+ const mockSecurityGroup = {
256
+ GroupId: 'sg-12345678',
257
+ GroupName: 'default'
258
+ };
286
259
 
287
- const result = await discovery.isSubnetPrivate(mockSubnetId);
260
+ ec2Mock.on(DescribeSecurityGroupsCommand).resolves({
261
+ SecurityGroups: [mockSecurityGroup]
262
+ });
288
263
 
289
- expect(result).toBe(false);
264
+ const sg = await discovery.findDefaultSecurityGroup(mockVpcId);
265
+ expect(sg).toEqual(mockSecurityGroup);
290
266
  });
291
267
 
292
- it('should return true for private subnet (no IGW route)', async () => {
293
- mockEC2Send
294
- .mockResolvedValueOnce({ // DescribeSubnets
295
- Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
296
- })
297
- .mockResolvedValueOnce({ // DescribeRouteTables
298
- RouteTables: [{
299
- Associations: [{ SubnetId: mockSubnetId }],
300
- Routes: [{
301
- GatewayId: 'local',
302
- DestinationCidrBlock: '10.0.0.0/16'
303
- }]
304
- }]
305
- });
268
+ it('should throw error when no default security group found', async () => {
269
+ ec2Mock.on(DescribeSecurityGroupsCommand).resolves({
270
+ SecurityGroups: []
271
+ });
272
+
273
+ await expect(discovery.findDefaultSecurityGroup(mockVpcId))
274
+ .rejects.toThrow('No security group found for VPC');
275
+ });
276
+ });
277
+
278
+ describe('findPrivateRouteTable', () => {
279
+ const mockVpcId = 'vpc-12345678';
280
+
281
+ it('should return private route table', async () => {
282
+ const mockRouteTable = {
283
+ RouteTableId: 'rtb-12345678',
284
+ Routes: [
285
+ { GatewayId: 'local', DestinationCidrBlock: '10.0.0.0/16' }
286
+ ]
287
+ };
306
288
 
307
- const result = await discovery.isSubnetPrivate(mockSubnetId);
289
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
290
+ RouteTables: [mockRouteTable]
291
+ });
308
292
 
309
- expect(result).toBe(true);
293
+ const rt = await discovery.findPrivateRouteTable(mockVpcId);
294
+ expect(rt).toEqual(mockRouteTable);
310
295
  });
311
296
 
312
- it('should use main route table if no explicit association', async () => {
313
- mockEC2Send
314
- .mockResolvedValueOnce({ // DescribeSubnets
315
- Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
316
- })
317
- .mockResolvedValueOnce({ // DescribeRouteTables
318
- RouteTables: [{
319
- Associations: [{ Main: true }], // Main route table
320
- Routes: [{
321
- GatewayId: 'igw-12345678',
322
- DestinationCidrBlock: '0.0.0.0/0'
323
- }]
324
- }]
325
- });
297
+ it('should return first route table when no private route table found', async () => {
298
+ const mockRouteTable = {
299
+ RouteTableId: 'rtb-12345678',
300
+ Routes: [
301
+ { GatewayId: 'igw-12345', DestinationCidrBlock: '0.0.0.0/0' }
302
+ ]
303
+ };
326
304
 
327
- const result = await discovery.isSubnetPrivate(mockSubnetId);
305
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
306
+ RouteTables: [mockRouteTable]
307
+ });
328
308
 
329
- expect(result).toBe(false); // Public because main route has IGW
309
+ const rt = await discovery.findPrivateRouteTable(mockVpcId);
310
+ expect(rt).toEqual(mockRouteTable);
330
311
  });
312
+ });
331
313
 
332
- it('should default to private on error', async () => {
333
- mockEC2Send
334
- .mockResolvedValueOnce({ // DescribeSubnets
335
- Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
336
- })
337
- .mockRejectedValue(new Error('Route table error'));
314
+ describe('findDefaultKmsKey', () => {
315
+ it('should return default KMS key ARN', async () => {
316
+ const mockKeyId = '12345678-1234-1234-1234-123456789012';
317
+ const mockKeyArn = `arn:aws:kms:us-east-1:123456789012:key/${mockKeyId}`;
318
+
319
+ kmsMock.on(ListKeysCommand).resolves({
320
+ Keys: [{ KeyId: mockKeyId }]
321
+ });
338
322
 
339
- const result = await discovery.isSubnetPrivate(mockSubnetId);
323
+ kmsMock.on(DescribeKeyCommand).resolves({
324
+ KeyMetadata: {
325
+ Arn: mockKeyArn,
326
+ KeyManager: 'CUSTOMER',
327
+ KeyState: 'Enabled'
328
+ }
329
+ });
340
330
 
341
- expect(result).toBe(true);
331
+ const keyArn = await discovery.findDefaultKmsKey();
332
+ expect(keyArn).toBe(mockKeyArn);
342
333
  });
343
334
 
344
- it('should throw error when subnet not found', async () => {
345
- mockEC2Send.mockResolvedValueOnce({ Subnets: [] });
335
+ it('should return null when no AWS-managed keys found', async () => {
336
+ kmsMock.on(ListKeysCommand).resolves({
337
+ Keys: []
338
+ });
346
339
 
347
- await expect(discovery.isSubnetPrivate(mockSubnetId)).rejects.toThrow(
348
- `Subnet ${mockSubnetId} not found`
349
- );
340
+ const keyArn = await discovery.findDefaultKmsKey();
341
+ expect(keyArn).toBeNull();
350
342
  });
351
343
  });
352
344
 
353
- describe('findDefaultSecurityGroup', () => {
345
+ describe('findAvailableElasticIP', () => {
346
+ it('should return available Elastic IP', async () => {
347
+ const mockElasticIP = {
348
+ AllocationId: 'eipalloc-12345',
349
+ PublicIp: '52.1.2.3'
350
+ };
351
+
352
+ ec2Mock.on(DescribeAddressesCommand).resolves({
353
+ Addresses: [mockElasticIP]
354
+ });
355
+
356
+ const eip = await discovery.findAvailableElasticIP();
357
+ expect(eip).toEqual(mockElasticIP);
358
+ });
359
+
360
+ it('should return null when no available Elastic IPs', async () => {
361
+ ec2Mock.on(DescribeAddressesCommand).resolves({
362
+ Addresses: []
363
+ });
364
+
365
+ const eip = await discovery.findAvailableElasticIP();
366
+ expect(eip).toBeNull();
367
+ });
368
+ });
369
+
370
+ describe('findExistingNatGateway', () => {
354
371
  const mockVpcId = 'vpc-12345678';
355
372
 
356
- it('should return Frigg security group when found', async () => {
357
- const mockFriggSg = {
358
- GroupId: 'sg-frigg-123',
359
- GroupName: 'frigg-lambda-sg',
360
- VpcId: mockVpcId
373
+ beforeEach(() => {
374
+ // Create a fresh discovery instance for each test
375
+ discovery = new AWSDiscovery('us-east-1');
376
+ });
377
+
378
+ it('should return NAT Gateway in public subnet', async () => {
379
+ const mockNatGateway = {
380
+ NatGatewayId: 'nat-12345678',
381
+ SubnetId: 'subnet-public-1',
382
+ State: 'available',
383
+ NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }],
384
+ Tags: []
361
385
  };
362
386
 
363
- mockEC2Send.mockResolvedValue({
364
- SecurityGroups: [mockFriggSg]
387
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({
388
+ NatGateways: [mockNatGateway]
365
389
  });
366
390
 
367
- const result = await discovery.findDefaultSecurityGroup(mockVpcId);
391
+ // Mock subnet lookup
392
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
393
+ Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }]
394
+ });
368
395
 
369
- expect(result).toEqual(mockFriggSg);
370
- expect(mockEC2Send).toHaveBeenCalledWith(expect.objectContaining({
371
- input: {
372
- Filters: [
373
- { Name: 'vpc-id', Values: [mockVpcId] },
374
- { Name: 'group-name', Values: ['frigg-lambda-sg'] }
375
- ]
376
- }
377
- }));
396
+ // Mock route table - has IGW (public)
397
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
398
+ RouteTables: [{
399
+ Associations: [{ SubnetId: 'subnet-public-1' }],
400
+ Routes: [{ GatewayId: 'igw-12345' }]
401
+ }]
402
+ });
403
+
404
+ const result = await discovery.findExistingNatGateway(mockVpcId);
405
+
406
+ expect(result).toBeDefined();
407
+ expect(result.NatGatewayId).toBe('nat-12345678');
408
+ expect(result._isInPrivateSubnet).toBe(false);
378
409
  });
379
410
 
380
- it('should fallback to default security group', async () => {
381
- const mockDefaultSg = {
382
- GroupId: 'sg-default-123',
383
- GroupName: 'default',
384
- VpcId: mockVpcId
411
+ it('should detect NAT Gateway in private subnet', async () => {
412
+ const mockNatGateway = {
413
+ NatGatewayId: 'nat-12345678',
414
+ SubnetId: 'subnet-private-1',
415
+ State: 'available',
416
+ NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }],
417
+ Tags: [
418
+ { Key: 'ManagedBy', Value: 'Frigg' }
419
+ ]
385
420
  };
386
421
 
387
- mockEC2Send
388
- .mockResolvedValueOnce({ SecurityGroups: [] }) // No Frigg SG
389
- .mockResolvedValueOnce({ SecurityGroups: [mockDefaultSg] }); // Default SG
422
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({
423
+ NatGateways: [mockNatGateway]
424
+ });
390
425
 
391
- const result = await discovery.findDefaultSecurityGroup(mockVpcId);
426
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
427
+ Subnets: [{ SubnetId: 'subnet-private-1', VpcId: mockVpcId }]
428
+ });
392
429
 
393
- expect(result).toEqual(mockDefaultSg);
394
- expect(mockEC2Send).toHaveBeenCalledTimes(2);
395
- });
430
+ // Mock route table - no IGW (private)
431
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
432
+ RouteTables: [{
433
+ Associations: [{ SubnetId: 'subnet-private-1' }],
434
+ Routes: [{ GatewayId: 'local' }]
435
+ }]
436
+ });
396
437
 
397
- it('should throw error when no security groups found', async () => {
398
- mockEC2Send.mockResolvedValue({ SecurityGroups: [] });
438
+ const result = await discovery.findExistingNatGateway(mockVpcId);
399
439
 
400
- await expect(discovery.findDefaultSecurityGroup(mockVpcId)).rejects.toThrow(`No security group found for VPC ${mockVpcId}`);
440
+ expect(result).toBeDefined();
441
+ expect(result.NatGatewayId).toBe('nat-12345678');
442
+ expect(result._isInPrivateSubnet).toBe(true);
401
443
  });
402
- });
403
444
 
404
- describe('findDefaultKmsKey', () => {
405
- it('should return customer managed key when found', async () => {
406
- const mockKeyId = 'key-12345678';
407
- const mockKeyArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012';
445
+ it('should skip non-Frigg NAT Gateway in private subnet', async () => {
446
+ const mockNatGateways = [
447
+ {
448
+ NatGatewayId: 'nat-other-12345',
449
+ SubnetId: 'subnet-private-1',
450
+ State: 'available',
451
+ Tags: [] // No Frigg tags
452
+ },
453
+ {
454
+ NatGatewayId: 'nat-good-12345',
455
+ SubnetId: 'subnet-public-1',
456
+ State: 'available',
457
+ Tags: []
458
+ }
459
+ ];
460
+
461
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({
462
+ NatGateways: mockNatGateways
463
+ });
408
464
 
409
- mockKMSSend
410
- .mockResolvedValueOnce({ // ListKeys
411
- Keys: [{ KeyId: mockKeyId }]
465
+ // First call for subnet-private-1
466
+ ec2Mock.on(DescribeSubnetsCommand)
467
+ .resolvesOnce({
468
+ Subnets: [{ SubnetId: 'subnet-private-1', VpcId: mockVpcId }]
412
469
  })
413
- .mockResolvedValueOnce({ // DescribeKey
414
- KeyMetadata: {
415
- KeyId: mockKeyId,
416
- Arn: mockKeyArn,
417
- KeyManager: 'CUSTOMER',
418
- KeyState: 'Enabled'
419
- }
470
+ // Second call for subnet-public-1
471
+ .resolvesOnce({
472
+ Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }]
420
473
  });
421
474
 
422
- mockSTSSend.mockResolvedValue({ Account: '123456789012' });
475
+ // First call for private subnet route table
476
+ ec2Mock.on(DescribeRouteTablesCommand)
477
+ .resolvesOnce({
478
+ RouteTables: [{
479
+ Associations: [{ SubnetId: 'subnet-private-1' }],
480
+ Routes: [{ GatewayId: 'local' }] // Private
481
+ }]
482
+ })
483
+ // Second call for public subnet route table
484
+ .resolvesOnce({
485
+ RouteTables: [{
486
+ Associations: [{ SubnetId: 'subnet-public-1' }],
487
+ Routes: [{ GatewayId: 'igw-12345' }] // Public
488
+ }]
489
+ });
423
490
 
424
- const result = await discovery.findDefaultKmsKey();
491
+ const result = await discovery.findExistingNatGateway(mockVpcId);
425
492
 
426
- expect(result).toBe(mockKeyArn);
493
+ expect(result).toBeDefined();
494
+ expect(result.NatGatewayId).toBe('nat-good-12345');
495
+ expect(result._isInPrivateSubnet).toBe(false);
427
496
  });
428
497
 
429
- it('should return wildcard pattern when no customer keys found', async () => {
430
- mockKMSSend.mockResolvedValue({ Keys: [] });
431
- mockSTSSend.mockResolvedValue({ Account: '123456789012' });
498
+ it('should prioritize Frigg-managed NAT Gateways', async () => {
499
+ const mockNatGateways = [
500
+ {
501
+ NatGatewayId: 'nat-other-12345',
502
+ SubnetId: 'subnet-public-1',
503
+ State: 'available',
504
+ Tags: []
505
+ },
506
+ {
507
+ NatGatewayId: 'nat-frigg-12345',
508
+ SubnetId: 'subnet-public-2',
509
+ State: 'available',
510
+ Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }]
511
+ }
512
+ ];
513
+
514
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({
515
+ NatGateways: mockNatGateways
516
+ });
432
517
 
433
- const result = await discovery.findDefaultKmsKey();
518
+ ec2Mock.on(DescribeSubnetsCommand).resolves({
519
+ Subnets: [{ SubnetId: 'subnet-public-2', VpcId: mockVpcId }]
520
+ });
434
521
 
435
- expect(result).toBe('arn:aws:kms:us-east-1:123456789012:key/*');
436
- });
522
+ ec2Mock.on(DescribeRouteTablesCommand).resolves({
523
+ RouteTables: [{
524
+ Associations: [{ SubnetId: 'subnet-public-2' }],
525
+ Routes: [{ GatewayId: 'igw-12345' }] // Public
526
+ }]
527
+ });
528
+
529
+ const result = await discovery.findExistingNatGateway(mockVpcId);
437
530
 
438
- it('should return fallback on error', async () => {
439
- mockKMSSend.mockRejectedValue(new Error('KMS Error'));
531
+ expect(result).toBeDefined();
532
+ expect(result.NatGatewayId).toBe('nat-frigg-12345');
533
+ expect(result._isInPrivateSubnet).toBe(false);
534
+ });
440
535
 
441
- const result = await discovery.findDefaultKmsKey();
536
+ it('should return null when no NAT Gateways found', async () => {
537
+ ec2Mock.on(DescribeNatGatewaysCommand).resolves({
538
+ NatGateways: []
539
+ });
442
540
 
443
- expect(result).toBe('*');
541
+ const result = await discovery.findExistingNatGateway(mockVpcId);
542
+ expect(result).toBeNull();
444
543
  });
445
544
  });
446
545
 
@@ -457,7 +556,9 @@ describe('AWSDiscovery', () => {
457
556
  const mockKmsArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678';
458
557
  const mockNatGateway = {
459
558
  NatGatewayId: 'nat-12345678',
460
- NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }]
559
+ SubnetId: 'subnet-public-1',
560
+ NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }],
561
+ _isInPrivateSubnet: false
461
562
  };
462
563
 
463
564
  // Mock all the discovery methods
@@ -484,6 +585,7 @@ describe('AWSDiscovery', () => {
484
585
  defaultKmsKeyId: mockKmsArn,
485
586
  existingNatGatewayId: 'nat-12345678',
486
587
  existingElasticIpAllocationId: 'eipalloc-12345',
588
+ natGatewayInPrivateSubnet: false,
487
589
  subnetConversionRequired: false,
488
590
  privateSubnetsWithWrongRoutes: []
489
591
  });
@@ -517,8 +619,10 @@ describe('AWSDiscovery', () => {
517
619
  jest.spyOn(discovery, 'findExistingNatGateway').mockResolvedValue(null);
518
620
  jest.spyOn(discovery, 'findAvailableElasticIP').mockResolvedValue(null);
519
621
  jest.spyOn(discovery, 'isSubnetPrivate')
520
- .mockResolvedValueOnce(false) // subnet-1 is actually public
521
- .mockResolvedValueOnce(true); // subnet-2 is private
622
+ .mockImplementation((subnetId) => {
623
+ // subnet-1 is public, subnet-2 is private
624
+ return Promise.resolve(subnetId === 'subnet-2');
625
+ });
522
626
 
523
627
  const result = await discovery.discoverResources({ selfHeal: true });
524
628
 
@@ -560,15 +664,54 @@ describe('AWSDiscovery', () => {
560
664
  });
561
665
  });
562
666
 
667
+ it('should detect NAT Gateway in private subnet in discoverResources', async () => {
668
+ const mockVpc = { VpcId: 'vpc-12345678' };
669
+ const mockSubnets = [
670
+ { SubnetId: 'subnet-1' },
671
+ { SubnetId: 'subnet-2' }
672
+ ];
673
+ const mockPublicSubnet = { SubnetId: 'subnet-public-1' };
674
+ const mockSecurityGroup = { GroupId: 'sg-12345678' };
675
+ const mockRouteTable = { RouteTableId: 'rtb-12345678' };
676
+ const mockNatGateway = {
677
+ NatGatewayId: 'nat-12345678',
678
+ NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }],
679
+ _isInPrivateSubnet: true // NAT is in private subnet
680
+ };
681
+
682
+ jest.spyOn(discovery, 'findDefaultVpc').mockResolvedValue(mockVpc);
683
+ jest.spyOn(discovery, 'findPrivateSubnets').mockResolvedValue(mockSubnets);
684
+ jest.spyOn(discovery, 'findPublicSubnets').mockResolvedValue(mockPublicSubnet);
685
+ jest.spyOn(discovery, 'findDefaultSecurityGroup').mockResolvedValue(mockSecurityGroup);
686
+ jest.spyOn(discovery, 'findPrivateRouteTable').mockResolvedValue(mockRouteTable);
687
+ jest.spyOn(discovery, 'findDefaultKmsKey').mockResolvedValue(null);
688
+ jest.spyOn(discovery, 'findExistingNatGateway').mockResolvedValue(mockNatGateway);
689
+ jest.spyOn(discovery, 'isSubnetPrivate')
690
+ .mockResolvedValueOnce(true)
691
+ .mockResolvedValueOnce(true);
692
+
693
+ const result = await discovery.discoverResources();
694
+
695
+ expect(result).toMatchObject({
696
+ defaultVpcId: 'vpc-12345678',
697
+ existingNatGatewayId: 'nat-12345678',
698
+ natGatewayInPrivateSubnet: true, // Should be true
699
+ subnetConversionRequired: false,
700
+ privateSubnetsWithWrongRoutes: []
701
+ });
702
+ });
703
+
563
704
  it('should handle single subnet scenario', async () => {
564
705
  const mockVpc = { VpcId: 'vpc-12345678' };
565
706
  const mockSubnets = [{ SubnetId: 'subnet-1' }]; // Only one subnet
707
+ const mockPublicSubnet = { SubnetId: 'subnet-public-1' };
566
708
  const mockSecurityGroup = { GroupId: 'sg-12345678' };
567
709
  const mockRouteTable = { RouteTableId: 'rtb-12345678' };
568
710
  const mockKmsArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678';
569
711
 
570
712
  jest.spyOn(discovery, 'findDefaultVpc').mockResolvedValue(mockVpc);
571
713
  jest.spyOn(discovery, 'findPrivateSubnets').mockResolvedValue(mockSubnets);
714
+ jest.spyOn(discovery, 'findPublicSubnets').mockResolvedValue(mockPublicSubnet);
572
715
  jest.spyOn(discovery, 'findDefaultSecurityGroup').mockResolvedValue(mockSecurityGroup);
573
716
  jest.spyOn(discovery, 'findPrivateRouteTable').mockResolvedValue(mockRouteTable);
574
717
  jest.spyOn(discovery, 'findDefaultKmsKey').mockResolvedValue(mockKmsArn);
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Test script to verify subnet detection and classification logic
5
+ */
6
+
7
+ const { AWSDiscovery } = require('./aws-discovery');
8
+
9
+ async function testSubnetLogic() {
10
+ console.log('๐Ÿงช Testing Subnet Detection and Classification Logic');
11
+ console.log('โ•'.repeat(60));
12
+
13
+ try {
14
+ const discovery = new AWSDiscovery();
15
+
16
+ // Test with selfHeal enabled
17
+ console.log('\n๐Ÿ“‹ Testing with selfHeal: true');
18
+ console.log('-'.repeat(40));
19
+
20
+ const resources = await discovery.discoverResources({ selfHeal: true });
21
+
22
+ console.log('\n๐Ÿ“Š Results:');
23
+ console.log(` VPC ID: ${resources.defaultVpcId}`);
24
+ console.log(` Private Subnet 1: ${resources.privateSubnetId1}`);
25
+ console.log(` Private Subnet 2: ${resources.privateSubnetId2}`);
26
+ console.log(` Public Subnet: ${resources.publicSubnetId}`);
27
+ console.log(` Conversion Required: ${resources.subnetConversionRequired}`);
28
+
29
+ if (resources.privateSubnetsWithWrongRoutes && resources.privateSubnetsWithWrongRoutes.length > 0) {
30
+ console.log(`\nโš ๏ธ Subnets needing conversion:`);
31
+ resources.privateSubnetsWithWrongRoutes.forEach(subnet => {
32
+ console.log(` - ${subnet}`);
33
+ });
34
+ }
35
+
36
+ console.log('\nโœ… Test completed successfully!');
37
+
38
+ } catch (error) {
39
+ console.error('\nโŒ Test failed:', error.message);
40
+ console.error('\nStack trace:', error.stack);
41
+ process.exit(1);
42
+ }
43
+ }
44
+
45
+ // Run the test
46
+ testSubnetLogic().catch(error => {
47
+ console.error('Unhandled error:', error);
48
+ process.exit(1);
49
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.428.7665532.0",
4
+ "version": "2.0.0--canary.428.6b04c24.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-ec2": "^3.835.0",
7
7
  "@aws-sdk/client-kms": "^3.835.0",
@@ -9,8 +9,8 @@
9
9
  "@babel/eslint-parser": "^7.18.9",
10
10
  "@babel/parser": "^7.25.3",
11
11
  "@babel/traverse": "^7.25.3",
12
- "@friggframework/schemas": "2.0.0--canary.428.7665532.0",
13
- "@friggframework/test": "2.0.0--canary.428.7665532.0",
12
+ "@friggframework/schemas": "2.0.0--canary.428.6b04c24.0",
13
+ "@friggframework/test": "2.0.0--canary.428.6b04c24.0",
14
14
  "@hapi/boom": "^10.0.1",
15
15
  "@inquirer/prompts": "^5.3.8",
16
16
  "axios": "^1.7.2",
@@ -32,8 +32,10 @@
32
32
  "serverless-http": "^2.7.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@friggframework/eslint-config": "2.0.0--canary.428.7665532.0",
36
- "@friggframework/prettier-config": "2.0.0--canary.428.7665532.0",
35
+ "@friggframework/eslint-config": "2.0.0--canary.428.6b04c24.0",
36
+ "@friggframework/prettier-config": "2.0.0--canary.428.6b04c24.0",
37
+ "aws-sdk-client-mock": "^4.1.0",
38
+ "aws-sdk-client-mock-jest": "^4.1.0",
37
39
  "jest": "^30.1.3",
38
40
  "prettier": "^2.7.1",
39
41
  "serverless": "3.39.0",
@@ -66,5 +68,5 @@
66
68
  "publishConfig": {
67
69
  "access": "public"
68
70
  },
69
- "gitHead": "7665532bd0d3fa2968e2575d9f9e79dd3d119375"
71
+ "gitHead": "6b04c24df1e253fd23afd6acb39ec9b5ad61456f"
70
72
  }