@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.
Files changed (70) hide show
  1. package/frigg-cli/__tests__/unit/commands/build.test.js +6 -6
  2. package/frigg-cli/build-command/index.js +1 -1
  3. package/frigg-cli/deploy-command/index.js +6 -6
  4. package/frigg-cli/generate-command/index.js +2 -2
  5. package/frigg-cli/generate-iam-command.js +10 -10
  6. package/frigg-cli/start-command/index.js +1 -1
  7. package/frigg-cli/start-command/start-command.test.js +3 -3
  8. package/frigg-cli/utils/database-validator.js +14 -21
  9. package/infrastructure/REFACTOR.md +532 -0
  10. package/infrastructure/TRANSFORMATION-VISUAL.md +239 -0
  11. package/infrastructure/__tests__/postgres-config.test.js +1 -1
  12. package/infrastructure/create-frigg-infrastructure.js +1 -1
  13. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  14. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  15. package/infrastructure/domains/database/aurora-discovery.js +81 -0
  16. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  17. package/infrastructure/domains/integration/integration-builder.js +178 -0
  18. package/infrastructure/domains/integration/integration-builder.test.js +362 -0
  19. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  20. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  21. package/infrastructure/domains/networking/vpc-discovery.test.js +257 -0
  22. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  23. package/infrastructure/domains/parameters/ssm-builder.test.js +188 -0
  24. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  25. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  26. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  27. package/infrastructure/domains/security/kms-builder.js +169 -0
  28. package/infrastructure/domains/security/kms-builder.test.js +354 -0
  29. package/infrastructure/domains/security/kms-discovery.js +80 -0
  30. package/infrastructure/domains/security/kms-discovery.test.js +176 -0
  31. package/infrastructure/domains/shared/base-builder.js +112 -0
  32. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  33. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  34. package/infrastructure/domains/shared/environment-builder.js +118 -0
  35. package/infrastructure/domains/shared/environment-builder.test.js +246 -0
  36. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +366 -0
  37. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  38. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  39. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  40. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  41. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  42. package/infrastructure/domains/shared/resource-discovery.js +132 -0
  43. package/infrastructure/domains/shared/resource-discovery.test.js +410 -0
  44. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  45. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  46. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +259 -0
  47. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  48. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +134 -0
  49. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  50. package/infrastructure/esbuild.config.js +53 -0
  51. package/infrastructure/infrastructure-composer.js +85 -0
  52. package/infrastructure/scripts/build-prisma-layer.js +60 -47
  53. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  54. package/layers/prisma/nodejs/package.json +8 -0
  55. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  56. package/package.json +8 -8
  57. package/infrastructure/aws-discovery.js +0 -1704
  58. package/infrastructure/aws-discovery.test.js +0 -1666
  59. package/infrastructure/serverless-template.js +0 -2804
  60. package/infrastructure/serverless-template.test.js +0 -1897
  61. /package/infrastructure/{POSTGRES-CONFIGURATION.md → docs/POSTGRES-CONFIGURATION.md} +0 -0
  62. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  63. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  64. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  65. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  66. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  67. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  68. /package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +0 -0
  69. /package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +0 -0
  70. /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
+