@friggframework/devtools 2.0.0--canary.461.ec909cf.0 → 2.0.0--canary.461.7b36f0f.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/__tests__/unit/commands/build.test.js +6 -6
- package/frigg-cli/build-command/index.js +1 -1
- package/frigg-cli/deploy-command/index.js +6 -6
- package/frigg-cli/generate-command/index.js +2 -2
- package/frigg-cli/generate-iam-command.js +10 -10
- package/frigg-cli/start-command/index.js +1 -1
- package/frigg-cli/start-command/start-command.test.js +3 -3
- package/frigg-cli/utils/database-validator.js +14 -21
- package/infrastructure/REFACTOR.md +532 -0
- package/infrastructure/TRANSFORMATION-VISUAL.md +239 -0
- package/infrastructure/__tests__/postgres-config.test.js +1 -1
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- package/infrastructure/domains/database/aurora-discovery.js +81 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/integration/integration-builder.js +178 -0
- package/infrastructure/domains/integration/integration-builder.test.js +362 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +257 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +188 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +169 -0
- package/infrastructure/domains/security/kms-builder.test.js +354 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +176 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/environment-builder.js +118 -0
- package/infrastructure/domains/shared/environment-builder.test.js +246 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +366 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.js +132 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +410 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +259 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +134 -0
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +85 -0
- package/infrastructure/scripts/build-prisma-layer.js +60 -47
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +8 -8
- package/infrastructure/aws-discovery.js +0 -1704
- package/infrastructure/aws-discovery.test.js +0 -1666
- package/infrastructure/serverless-template.js +0 -2804
- package/infrastructure/serverless-template.test.js +0 -1897
- /package/infrastructure/{POSTGRES-CONFIGURATION.md → docs/POSTGRES-CONFIGURATION.md} +0 -0
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +0 -0
- /package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +0 -0
- /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Environment Builder Service
|
|
3
|
+
*
|
|
4
|
+
* Tests environment variable extraction and building
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { getAppEnvironmentVars, buildEnvironment } = require('./environment-builder');
|
|
8
|
+
|
|
9
|
+
describe('Environment Builder', () => {
|
|
10
|
+
describe('getAppEnvironmentVars()', () => {
|
|
11
|
+
it('should extract environment variables with value true', () => {
|
|
12
|
+
const appDefinition = {
|
|
13
|
+
environment: {
|
|
14
|
+
API_KEY: true,
|
|
15
|
+
DATABASE_URL: true,
|
|
16
|
+
CUSTOM_VAR: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
21
|
+
|
|
22
|
+
expect(result.API_KEY).toBe("${env:API_KEY, ''}");
|
|
23
|
+
expect(result.DATABASE_URL).toBe("${env:DATABASE_URL, ''}");
|
|
24
|
+
expect(result.CUSTOM_VAR).toBe("${env:CUSTOM_VAR, ''}");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should ignore environment variables with value false', () => {
|
|
28
|
+
const appDefinition = {
|
|
29
|
+
environment: {
|
|
30
|
+
ENABLED_VAR: true,
|
|
31
|
+
DISABLED_VAR: false,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
36
|
+
|
|
37
|
+
expect(result.ENABLED_VAR).toBeDefined();
|
|
38
|
+
expect(result.DISABLED_VAR).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should ignore environment variables with non-boolean values', () => {
|
|
42
|
+
const appDefinition = {
|
|
43
|
+
environment: {
|
|
44
|
+
VALID: true,
|
|
45
|
+
STRING_VALUE: 'some-value',
|
|
46
|
+
NUMBER_VALUE: 123,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
51
|
+
|
|
52
|
+
expect(result.VALID).toBeDefined();
|
|
53
|
+
expect(result.STRING_VALUE).toBeUndefined();
|
|
54
|
+
expect(result.NUMBER_VALUE).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should skip reserved AWS Lambda variables', () => {
|
|
58
|
+
const appDefinition = {
|
|
59
|
+
environment: {
|
|
60
|
+
CUSTOM_VAR: true,
|
|
61
|
+
AWS_REGION: true,
|
|
62
|
+
AWS_ACCESS_KEY_ID: true,
|
|
63
|
+
AWS_SECRET_ACCESS_KEY: true,
|
|
64
|
+
_HANDLER: true,
|
|
65
|
+
AWS_LAMBDA_FUNCTION_NAME: true,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
70
|
+
|
|
71
|
+
expect(result.CUSTOM_VAR).toBeDefined();
|
|
72
|
+
expect(result.AWS_REGION).toBeUndefined();
|
|
73
|
+
expect(result.AWS_ACCESS_KEY_ID).toBeUndefined();
|
|
74
|
+
expect(result.AWS_SECRET_ACCESS_KEY).toBeUndefined();
|
|
75
|
+
expect(result._HANDLER).toBeUndefined();
|
|
76
|
+
expect(result.AWS_LAMBDA_FUNCTION_NAME).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return empty object if no environment defined', () => {
|
|
80
|
+
const appDefinition = {};
|
|
81
|
+
|
|
82
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
83
|
+
|
|
84
|
+
expect(result).toEqual({});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle empty environment object', () => {
|
|
88
|
+
const appDefinition = {
|
|
89
|
+
environment: {},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
93
|
+
|
|
94
|
+
expect(result).toEqual({});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should create serverless variable references', () => {
|
|
98
|
+
const appDefinition = {
|
|
99
|
+
environment: {
|
|
100
|
+
MY_VAR: true,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
105
|
+
|
|
106
|
+
// Should use serverless variable syntax with empty string fallback
|
|
107
|
+
expect(result.MY_VAR).toBe("${env:MY_VAR, ''}");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('buildEnvironment()', () => {
|
|
112
|
+
it('should combine app vars with standard Frigg variables', () => {
|
|
113
|
+
const appEnvironmentVars = {
|
|
114
|
+
API_KEY: "${env:API_KEY, ''}",
|
|
115
|
+
};
|
|
116
|
+
const discoveredResources = {};
|
|
117
|
+
|
|
118
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
119
|
+
|
|
120
|
+
expect(result.API_KEY).toBe("${env:API_KEY, ''}");
|
|
121
|
+
expect(result.FRIGG_STACK).toBe('${self:service}');
|
|
122
|
+
expect(result.FRIGG_STAGE).toBe('${self:provider.stage}');
|
|
123
|
+
expect(result.FRIGG_REGION).toBe('${self:provider.region}');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should add KMS key ARN if discovered', () => {
|
|
127
|
+
const appEnvironmentVars = {};
|
|
128
|
+
const discoveredResources = {
|
|
129
|
+
kmsKeyId: 'arn:aws:kms:us-east-1:123456:key/abc-123',
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
133
|
+
|
|
134
|
+
expect(result.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456:key/abc-123');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should prefer kmsKeyId over kmsKeyArn if both present', () => {
|
|
138
|
+
const appEnvironmentVars = {};
|
|
139
|
+
const discoveredResources = {
|
|
140
|
+
kmsKeyId: 'arn:aws:kms:us-east-1:123456:key/primary',
|
|
141
|
+
kmsKeyArn: 'arn:aws:kms:us-east-1:123456:key/secondary',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
145
|
+
|
|
146
|
+
// Implementation uses if/else-if, so kmsKeyId takes priority
|
|
147
|
+
expect(result.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456:key/primary');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should add database connection info if discovered', () => {
|
|
151
|
+
const appEnvironmentVars = {};
|
|
152
|
+
const discoveredResources = {
|
|
153
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
154
|
+
auroraPort: 5432,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
158
|
+
|
|
159
|
+
expect(result.DATABASE_HOST).toBe('cluster.abc.us-east-1.rds.amazonaws.com');
|
|
160
|
+
expect(result.DATABASE_PORT).toBe('5432');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should default database port to 5432 if not specified', () => {
|
|
164
|
+
const appEnvironmentVars = {};
|
|
165
|
+
const discoveredResources = {
|
|
166
|
+
auroraClusterEndpoint: 'cluster.example.com',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
170
|
+
|
|
171
|
+
expect(result.DATABASE_HOST).toBe('cluster.example.com');
|
|
172
|
+
expect(result.DATABASE_PORT).toBe('5432');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should add database secret ARN if discovered', () => {
|
|
176
|
+
const appEnvironmentVars = {};
|
|
177
|
+
const discoveredResources = {
|
|
178
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123456:secret:db-secret',
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
182
|
+
|
|
183
|
+
expect(result.DATABASE_SECRET_ARN).toBe('arn:aws:secretsmanager:us-east-1:123456:secret:db-secret');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should combine all discovered resources', () => {
|
|
187
|
+
const appEnvironmentVars = {
|
|
188
|
+
CUSTOM_VAR: "${env:CUSTOM_VAR, ''}",
|
|
189
|
+
};
|
|
190
|
+
const discoveredResources = {
|
|
191
|
+
kmsKeyArn: 'arn:aws:kms:us-east-1:123456:key/abc',
|
|
192
|
+
auroraClusterEndpoint: 'db.example.com',
|
|
193
|
+
auroraPort: 3306,
|
|
194
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123456:secret:db',
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
198
|
+
|
|
199
|
+
expect(result.CUSTOM_VAR).toBe("${env:CUSTOM_VAR, ''}");
|
|
200
|
+
expect(result.FRIGG_STACK).toBe('${self:service}');
|
|
201
|
+
expect(result.FRIGG_STAGE).toBe('${self:provider.stage}');
|
|
202
|
+
expect(result.FRIGG_REGION).toBe('${self:provider.region}');
|
|
203
|
+
expect(result.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456:key/abc');
|
|
204
|
+
expect(result.DATABASE_HOST).toBe('db.example.com');
|
|
205
|
+
expect(result.DATABASE_PORT).toBe('3306');
|
|
206
|
+
expect(result.DATABASE_SECRET_ARN).toBe('arn:aws:secretsmanager:us-east-1:123456:secret:db');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should handle empty discoveredResources', () => {
|
|
210
|
+
const appEnvironmentVars = {
|
|
211
|
+
API_KEY: "${env:API_KEY, ''}",
|
|
212
|
+
};
|
|
213
|
+
const discoveredResources = {};
|
|
214
|
+
|
|
215
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
216
|
+
|
|
217
|
+
expect(result.API_KEY).toBe("${env:API_KEY, ''}");
|
|
218
|
+
expect(result.FRIGG_STACK).toBe('${self:service}');
|
|
219
|
+
expect(result.KMS_KEY_ARN).toBeUndefined();
|
|
220
|
+
expect(result.DATABASE_HOST).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should handle empty discoveredResources gracefully', () => {
|
|
224
|
+
const appEnvironmentVars = {};
|
|
225
|
+
|
|
226
|
+
const result = buildEnvironment(appEnvironmentVars, {});
|
|
227
|
+
|
|
228
|
+
expect(result.FRIGG_STACK).toBe('${self:service}');
|
|
229
|
+
expect(result.KMS_KEY_ARN).toBeUndefined();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should convert database port to string', () => {
|
|
233
|
+
const appEnvironmentVars = {};
|
|
234
|
+
const discoveredResources = {
|
|
235
|
+
auroraClusterEndpoint: 'db.example.com',
|
|
236
|
+
auroraPort: 3306, // Number
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
240
|
+
|
|
241
|
+
expect(result.DATABASE_PORT).toBe('3306'); // String
|
|
242
|
+
expect(typeof result.DATABASE_PORT).toBe('string');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AWS Provider Adapter
|
|
3
|
+
*
|
|
4
|
+
* Tests AWS-specific cloud resource discovery
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { AWSProviderAdapter } = require('./aws-provider-adapter');
|
|
8
|
+
|
|
9
|
+
// Mock AWS SDK v3 clients
|
|
10
|
+
jest.mock('@aws-sdk/client-ec2');
|
|
11
|
+
jest.mock('@aws-sdk/client-kms');
|
|
12
|
+
jest.mock('@aws-sdk/client-rds');
|
|
13
|
+
jest.mock('@aws-sdk/client-ssm');
|
|
14
|
+
jest.mock('@aws-sdk/client-secrets-manager');
|
|
15
|
+
|
|
16
|
+
describe('AWSProviderAdapter', () => {
|
|
17
|
+
let provider;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
delete process.env.AWS_REGION;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('constructor()', () => {
|
|
25
|
+
it('should initialize with provided region', () => {
|
|
26
|
+
provider = new AWSProviderAdapter('eu-west-1');
|
|
27
|
+
|
|
28
|
+
expect(provider.region).toBe('eu-west-1');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should default to us-east-1 if no region provided', () => {
|
|
32
|
+
provider = new AWSProviderAdapter();
|
|
33
|
+
|
|
34
|
+
expect(provider.region).toBe('us-east-1');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should use AWS_REGION environment variable', () => {
|
|
38
|
+
process.env.AWS_REGION = 'ap-southeast-1';
|
|
39
|
+
provider = new AWSProviderAdapter();
|
|
40
|
+
|
|
41
|
+
expect(provider.region).toBe('ap-southeast-1');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should prefer provided region over environment variable', () => {
|
|
45
|
+
process.env.AWS_REGION = 'us-west-2';
|
|
46
|
+
provider = new AWSProviderAdapter('eu-central-1');
|
|
47
|
+
|
|
48
|
+
expect(provider.region).toBe('eu-central-1');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should store credentials if provided', () => {
|
|
52
|
+
const credentials = {
|
|
53
|
+
accessKeyId: 'test-key',
|
|
54
|
+
secretAccessKey: 'test-secret',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
provider = new AWSProviderAdapter('us-east-1', credentials);
|
|
58
|
+
|
|
59
|
+
expect(provider.credentials).toEqual(credentials);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should lazy-load clients (not instantiated on construction)', () => {
|
|
63
|
+
provider = new AWSProviderAdapter('us-east-1');
|
|
64
|
+
|
|
65
|
+
expect(provider.ec2).toBeNull();
|
|
66
|
+
expect(provider.kms).toBeNull();
|
|
67
|
+
expect(provider.rds).toBeNull();
|
|
68
|
+
expect(provider.ssm).toBeNull();
|
|
69
|
+
expect(provider.secretsManager).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('getName()', () => {
|
|
74
|
+
it('should return "aws"', () => {
|
|
75
|
+
provider = new AWSProviderAdapter();
|
|
76
|
+
|
|
77
|
+
expect(provider.getName()).toBe('aws');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('getSupportedRegions()', () => {
|
|
82
|
+
it('should return array of AWS regions', () => {
|
|
83
|
+
provider = new AWSProviderAdapter();
|
|
84
|
+
|
|
85
|
+
const regions = provider.getSupportedRegions();
|
|
86
|
+
|
|
87
|
+
expect(Array.isArray(regions)).toBe(true);
|
|
88
|
+
expect(regions.length).toBeGreaterThan(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should include common US regions', () => {
|
|
92
|
+
provider = new AWSProviderAdapter();
|
|
93
|
+
|
|
94
|
+
const regions = provider.getSupportedRegions();
|
|
95
|
+
|
|
96
|
+
expect(regions).toContain('us-east-1');
|
|
97
|
+
expect(regions).toContain('us-east-2');
|
|
98
|
+
expect(regions).toContain('us-west-1');
|
|
99
|
+
expect(regions).toContain('us-west-2');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should include common EU regions', () => {
|
|
103
|
+
provider = new AWSProviderAdapter();
|
|
104
|
+
|
|
105
|
+
const regions = provider.getSupportedRegions();
|
|
106
|
+
|
|
107
|
+
expect(regions).toContain('eu-west-1');
|
|
108
|
+
expect(regions).toContain('eu-central-1');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should include common APAC regions', () => {
|
|
112
|
+
provider = new AWSProviderAdapter();
|
|
113
|
+
|
|
114
|
+
const regions = provider.getSupportedRegions();
|
|
115
|
+
|
|
116
|
+
expect(regions).toContain('ap-southeast-1');
|
|
117
|
+
expect(regions).toContain('ap-northeast-1');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Lazy loading clients', () => {
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
provider = new AWSProviderAdapter('us-east-1');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should lazy-load EC2 client', () => {
|
|
127
|
+
expect(provider.ec2).toBeNull();
|
|
128
|
+
|
|
129
|
+
const client = provider.getEC2Client();
|
|
130
|
+
|
|
131
|
+
expect(provider.ec2).not.toBeNull();
|
|
132
|
+
expect(client).toBe(provider.ec2);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should lazy-load KMS client', () => {
|
|
136
|
+
expect(provider.kms).toBeNull();
|
|
137
|
+
|
|
138
|
+
const client = provider.getKMSClient();
|
|
139
|
+
|
|
140
|
+
expect(provider.kms).not.toBeNull();
|
|
141
|
+
expect(client).toBe(provider.kms);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should lazy-load RDS client', () => {
|
|
145
|
+
expect(provider.rds).toBeNull();
|
|
146
|
+
|
|
147
|
+
const client = provider.getRDSClient();
|
|
148
|
+
|
|
149
|
+
expect(provider.rds).not.toBeNull();
|
|
150
|
+
expect(client).toBe(provider.rds);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should lazy-load SSM client', () => {
|
|
154
|
+
expect(provider.ssm).toBeNull();
|
|
155
|
+
|
|
156
|
+
const client = provider.getSSMClient();
|
|
157
|
+
|
|
158
|
+
expect(provider.ssm).not.toBeNull();
|
|
159
|
+
expect(client).toBe(provider.ssm);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should lazy-load Secrets Manager client', () => {
|
|
163
|
+
expect(provider.secretsManager).toBeNull();
|
|
164
|
+
|
|
165
|
+
const client = provider.getSecretsManagerClient();
|
|
166
|
+
|
|
167
|
+
expect(provider.secretsManager).not.toBeNull();
|
|
168
|
+
expect(client).toBe(provider.secretsManager);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should reuse client on subsequent calls', () => {
|
|
172
|
+
const client1 = provider.getEC2Client();
|
|
173
|
+
const client2 = provider.getEC2Client();
|
|
174
|
+
|
|
175
|
+
expect(client1).toBe(client2);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('discoverVpc()', () => {
|
|
180
|
+
beforeEach(() => {
|
|
181
|
+
provider = new AWSProviderAdapter('us-east-1');
|
|
182
|
+
const { EC2Client } = require('@aws-sdk/client-ec2');
|
|
183
|
+
EC2Client.mockImplementation(() => ({
|
|
184
|
+
send: jest.fn(),
|
|
185
|
+
}));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should return VPC discovery results', async () => {
|
|
189
|
+
const mockSend = jest.fn()
|
|
190
|
+
.mockResolvedValueOnce({ // VPCs
|
|
191
|
+
Vpcs: [
|
|
192
|
+
{ VpcId: 'vpc-123', CidrBlock: '172.31.0.0/16' },
|
|
193
|
+
],
|
|
194
|
+
})
|
|
195
|
+
.mockResolvedValueOnce({ Subnets: [] }) // Subnets
|
|
196
|
+
.mockResolvedValueOnce({ SecurityGroups: [] }) // Security Groups
|
|
197
|
+
.mockResolvedValueOnce({ RouteTables: [] }) // Route Tables
|
|
198
|
+
.mockResolvedValueOnce({ NatGateways: [] }) // NAT Gateways
|
|
199
|
+
.mockResolvedValueOnce({ InternetGateways: [] }); // Internet Gateways
|
|
200
|
+
|
|
201
|
+
provider.getEC2Client = jest.fn().mockReturnValue({ send: mockSend });
|
|
202
|
+
|
|
203
|
+
const result = await provider.discoverVpc({});
|
|
204
|
+
|
|
205
|
+
expect(result.vpcId).toBe('vpc-123');
|
|
206
|
+
expect(result.vpcCidr).toBe('172.31.0.0/16');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should handle discovery errors', async () => {
|
|
210
|
+
provider.getEC2Client = jest.fn().mockReturnValue({
|
|
211
|
+
send: jest.fn().mockRejectedValue(new Error('EC2 API Error')),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await expect(provider.discoverVpc({})).rejects.toThrow('Failed to discover AWS VPC');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should discover default VPC when no vpcId specified', async () => {
|
|
218
|
+
const mockSend = jest.fn();
|
|
219
|
+
provider.getEC2Client = jest.fn().mockReturnValue({ send: mockSend });
|
|
220
|
+
|
|
221
|
+
await provider.discoverVpc({}).catch(() => { });
|
|
222
|
+
|
|
223
|
+
// First call should filter for default VPC
|
|
224
|
+
expect(mockSend).toHaveBeenCalled();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('discoverKmsKeys()', () => {
|
|
229
|
+
beforeEach(() => {
|
|
230
|
+
provider = new AWSProviderAdapter('us-east-1');
|
|
231
|
+
const { KMSClient } = require('@aws-sdk/client-kms');
|
|
232
|
+
KMSClient.mockImplementation(() => ({
|
|
233
|
+
send: jest.fn(),
|
|
234
|
+
}));
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should return KMS discovery results', async () => {
|
|
238
|
+
const mockSend = jest.fn()
|
|
239
|
+
.mockResolvedValueOnce({ // ListKeys
|
|
240
|
+
Keys: [{ KeyId: 'key-123' }],
|
|
241
|
+
})
|
|
242
|
+
.mockResolvedValueOnce({ // DescribeKey
|
|
243
|
+
KeyMetadata: {
|
|
244
|
+
KeyId: 'key-123',
|
|
245
|
+
Arn: 'arn:aws:kms:us-east-1:123:key/abc',
|
|
246
|
+
Enabled: true,
|
|
247
|
+
},
|
|
248
|
+
})
|
|
249
|
+
.mockResolvedValueOnce({ // ListAliases
|
|
250
|
+
Aliases: [
|
|
251
|
+
{ AliasName: 'alias/my-key', TargetKeyId: 'key-123' },
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
provider.getKMSClient = jest.fn().mockReturnValue({ send: mockSend });
|
|
256
|
+
|
|
257
|
+
const result = await provider.discoverKmsKeys({});
|
|
258
|
+
|
|
259
|
+
expect(result.keys.length).toBeGreaterThan(0);
|
|
260
|
+
expect(result.defaultKey).toBeDefined();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should handle discovery errors', async () => {
|
|
264
|
+
provider.getKMSClient = jest.fn().mockReturnValue({
|
|
265
|
+
send: jest.fn().mockRejectedValue(new Error('KMS API Error')),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await expect(provider.discoverKmsKeys({})).rejects.toThrow('Failed to discover AWS KMS keys');
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('discoverDatabase()', () => {
|
|
273
|
+
beforeEach(() => {
|
|
274
|
+
provider = new AWSProviderAdapter('us-east-1');
|
|
275
|
+
const { RDSClient } = require('@aws-sdk/client-rds');
|
|
276
|
+
RDSClient.mockImplementation(() => ({
|
|
277
|
+
send: jest.fn(),
|
|
278
|
+
}));
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should return database discovery results', async () => {
|
|
282
|
+
const mockSend = jest.fn()
|
|
283
|
+
.mockResolvedValueOnce({ // Clusters
|
|
284
|
+
DBClusters: [
|
|
285
|
+
{
|
|
286
|
+
DBClusterIdentifier: 'cluster-1',
|
|
287
|
+
Endpoint: 'cluster-1.example.com',
|
|
288
|
+
Port: 5432,
|
|
289
|
+
Engine: 'aurora-postgresql',
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
})
|
|
293
|
+
.mockResolvedValueOnce({ DBInstances: [] }); // Instances
|
|
294
|
+
|
|
295
|
+
provider.getRDSClient = jest.fn().mockReturnValue({ send: mockSend });
|
|
296
|
+
|
|
297
|
+
const result = await provider.discoverDatabase({});
|
|
298
|
+
|
|
299
|
+
expect(result.endpoint).toBe('cluster-1.example.com');
|
|
300
|
+
expect(result.port).toBe(5432);
|
|
301
|
+
expect(result.engine).toBe('aurora-postgresql');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should handle discovery errors', async () => {
|
|
305
|
+
provider.getRDSClient = jest.fn().mockReturnValue({
|
|
306
|
+
send: jest.fn().mockRejectedValue(new Error('RDS API Error')),
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await expect(provider.discoverDatabase({})).rejects.toThrow('Failed to discover AWS databases');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('discoverParameters()', () => {
|
|
314
|
+
beforeEach(() => {
|
|
315
|
+
provider = new AWSProviderAdapter('us-east-1');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should return parameter discovery results', async () => {
|
|
319
|
+
const mockSSMSend = jest.fn().mockResolvedValue({
|
|
320
|
+
Parameters: [
|
|
321
|
+
{ Name: '/my-app/api-key', Value: 'encrypted' },
|
|
322
|
+
],
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const mockSMSend = jest.fn().mockResolvedValue({
|
|
326
|
+
SecretList: [
|
|
327
|
+
{ Name: 'my-app/secret', ARN: 'arn:aws:secretsmanager:us-east-1:123:secret:my-app/secret' },
|
|
328
|
+
],
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
provider.getSSMClient = jest.fn().mockReturnValue({ send: mockSSMSend });
|
|
332
|
+
provider.getSecretsManagerClient = jest.fn().mockReturnValue({ send: mockSMSend });
|
|
333
|
+
|
|
334
|
+
const result = await provider.discoverParameters({
|
|
335
|
+
parameterPath: '/my-app',
|
|
336
|
+
includeSecrets: true,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
expect(result.parameters).toHaveLength(1);
|
|
340
|
+
expect(result.secrets).toHaveLength(1);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should handle discovery errors', async () => {
|
|
344
|
+
provider.getSSMClient = jest.fn().mockReturnValue({
|
|
345
|
+
send: jest.fn().mockRejectedValue(new Error('SSM API Error')),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await expect(provider.discoverParameters({})).rejects.toThrow('Failed to discover AWS parameters');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should skip secrets when includeSecrets is false', async () => {
|
|
352
|
+
const mockSSMSend = jest.fn().mockResolvedValue({ Parameters: [] });
|
|
353
|
+
|
|
354
|
+
provider.getSSMClient = jest.fn().mockReturnValue({ send: mockSSMSend });
|
|
355
|
+
|
|
356
|
+
const result = await provider.discoverParameters({
|
|
357
|
+
includeSecrets: false,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
expect(result.parameters).toEqual([]);
|
|
361
|
+
expect(result.secrets).toEqual([]);
|
|
362
|
+
expect(provider.getSecretsManagerClient).not.toHaveBeenCalled();
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|