@friggframework/devtools 2.0.0-next.11 → 2.0.0-next.110
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/README.md +1289 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +649 -0
- package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
- package/frigg-cli/__tests__/unit/commands/generate-iam.test.js +97 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +397 -0
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +345 -0
- package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +287 -0
- package/frigg-cli/auth-command/CLAUDE.md +293 -0
- package/frigg-cli/auth-command/README.md +450 -0
- package/frigg-cli/auth-command/api-key-flow.js +153 -0
- package/frigg-cli/auth-command/auth-tester.js +344 -0
- package/frigg-cli/auth-command/credential-storage.js +182 -0
- package/frigg-cli/auth-command/index.js +256 -0
- package/frigg-cli/auth-command/json-schema-form.js +67 -0
- package/frigg-cli/auth-command/module-loader.js +172 -0
- package/frigg-cli/auth-command/oauth-callback-server.js +431 -0
- package/frigg-cli/auth-command/oauth-flow.js +195 -0
- package/frigg-cli/auth-command/utils/browser.js +30 -0
- package/frigg-cli/build-command/index.js +45 -12
- package/frigg-cli/db-setup-command/index.js +246 -0
- package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
- package/frigg-cli/deploy-command/index.js +339 -23
- package/frigg-cli/doctor-command/index.js +335 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +333 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +119 -0
- package/frigg-cli/index.js +189 -1
- package/frigg-cli/index.test.js +1 -4
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/index.js +1 -4
- package/frigg-cli/jest.config.js +124 -0
- package/frigg-cli/package.json +63 -0
- package/frigg-cli/repair-command/index.js +564 -0
- package/frigg-cli/ssm-command/index.js +308 -0
- package/frigg-cli/ssm-command/index.test.js +318 -0
- package/frigg-cli/start-command/index.js +118 -5
- package/frigg-cli/start-command/start-command.test.js +297 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +16 -17
- package/frigg-cli/utils/database-validator.js +167 -0
- package/frigg-cli/utils/error-messages.js +329 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/CLAUDE.md +481 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +540 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +275 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/scoped-environment.test.js +126 -0
- package/infrastructure/__tests__/ssm-preload-node-options.test.js +79 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +129 -20
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
- package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/docs/deployment-instructions.md +268 -0
- package/infrastructure/docs/generate-iam-command.md +278 -0
- package/infrastructure/docs/iam-policy-templates.md +193 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +567 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +1017 -0
- package/infrastructure/domains/admin-scripts/index.js +5 -0
- package/infrastructure/domains/database/aurora-builder.js +857 -0
- package/infrastructure/domains/database/aurora-builder.test.js +960 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +757 -0
- package/infrastructure/domains/database/migration-builder.test.js +454 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +656 -0
- package/infrastructure/domains/integration/integration-builder.test.js +1064 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -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-builder.js +2101 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +2001 -0
- package/infrastructure/domains/networking/vpc-discovery.js +184 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +368 -0
- package/infrastructure/domains/networking/vpc-resolver.js +514 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +841 -0
- package/infrastructure/domains/parameters/offload-utils.js +190 -0
- package/infrastructure/domains/parameters/offload-utils.test.js +193 -0
- package/infrastructure/domains/parameters/ssm-builder.js +132 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +346 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/domains/scheduler/scheduler-builder.js +247 -0
- package/infrastructure/domains/scheduler/scheduler-builder.test.js +118 -0
- package/infrastructure/domains/security/iam-generator.js +851 -0
- package/infrastructure/domains/security/iam-generator.test.js +287 -0
- package/infrastructure/domains/security/kms-builder.js +415 -0
- package/infrastructure/domains/security/kms-builder.test.js +392 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +418 -0
- package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
- package/infrastructure/domains/security/templates/iam-policy-full.json +293 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +226 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +258 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +681 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +1320 -0
- package/infrastructure/domains/shared/environment-builder.js +192 -0
- package/infrastructure/domains/shared/environment-builder.test.js +529 -0
- package/infrastructure/domains/shared/function-environments.js +97 -0
- package/infrastructure/domains/shared/function-environments.test.js +146 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +579 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -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.enhanced.test.js +306 -0
- package/infrastructure/domains/shared/resource-discovery.js +256 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +757 -0
- package/infrastructure/domains/shared/types/app-definition.js +225 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +409 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +319 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/nested-node-modules.js +53 -0
- package/infrastructure/domains/shared/utilities/nested-node-modules.test.js +61 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +159 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
- package/infrastructure/domains/shared/validation/env-validator.js +78 -0
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +168 -0
- package/infrastructure/infrastructure-composer.test.js +2042 -0
- package/infrastructure/integration.test.js +381 -0
- package/infrastructure/scripts/build-prisma-layer.js +701 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
- package/infrastructure/scripts/build-time-discovery.js +238 -0
- package/infrastructure/scripts/build-time-discovery.test.js +379 -0
- package/infrastructure/scripts/run-discovery.js +110 -0
- package/infrastructure/scripts/verify-prisma-layer.js +72 -0
- package/management-ui/README.md +203 -0
- package/package.json +48 -16
- package/test/index.js +2 -4
- package/test/mock-api.js +1 -3
- package/test/mock-integration.js +4 -14
- package/.eslintrc.json +0 -3
- package/CHANGELOG.md +0 -132
- package/infrastructure/serverless-template.js +0 -283
- package/infrastructure/webpack.config.js +0 -20
- package/test/auther-definition-tester.js +0 -125
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for SSM Builder
|
|
3
|
+
*
|
|
4
|
+
* Tests SSM Parameter Store configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { SsmBuilder } = require('./ssm-builder');
|
|
8
|
+
const { ValidationResult } = require('../shared/base-builder');
|
|
9
|
+
|
|
10
|
+
describe('SsmBuilder', () => {
|
|
11
|
+
let ssmBuilder;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
ssmBuilder = new SsmBuilder();
|
|
15
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('shouldExecute()', () => {
|
|
23
|
+
it('should return true when SSM is enabled', () => {
|
|
24
|
+
const appDefinition = {
|
|
25
|
+
ssm: { enable: true },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
expect(ssmBuilder.shouldExecute(appDefinition)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should return false when SSM is disabled', () => {
|
|
32
|
+
const appDefinition = {
|
|
33
|
+
ssm: { enable: false },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
expect(ssmBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return false when SSM is not defined', () => {
|
|
40
|
+
const appDefinition = {};
|
|
41
|
+
|
|
42
|
+
expect(ssmBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should return false when FRIGG_SKIP_AWS_DISCOVERY is set (local mode)', () => {
|
|
46
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
47
|
+
const appDefinition = {
|
|
48
|
+
ssm: { enable: true },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
expect(ssmBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('validate()', () => {
|
|
56
|
+
it('should pass validation for valid SSM config', () => {
|
|
57
|
+
const appDefinition = {
|
|
58
|
+
ssm: {
|
|
59
|
+
enable: true,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const result = ssmBuilder.validate(appDefinition);
|
|
64
|
+
|
|
65
|
+
expect(result).toBeInstanceOf(ValidationResult);
|
|
66
|
+
expect(result.valid).toBe(true);
|
|
67
|
+
expect(result.errors).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should pass validation with parameters object', () => {
|
|
71
|
+
const appDefinition = {
|
|
72
|
+
ssm: {
|
|
73
|
+
enable: true,
|
|
74
|
+
parameters: {
|
|
75
|
+
SERVICE_TOKEN: '/my-app/service-token',
|
|
76
|
+
API_KEY: '/my-app/api-key',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = ssmBuilder.validate(appDefinition);
|
|
82
|
+
|
|
83
|
+
expect(result.valid).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should error if SSM configuration is missing', () => {
|
|
87
|
+
const appDefinition = {};
|
|
88
|
+
|
|
89
|
+
const result = ssmBuilder.validate(appDefinition);
|
|
90
|
+
|
|
91
|
+
expect(result.valid).toBe(false);
|
|
92
|
+
expect(result.errors).toContain('SSM configuration is missing');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should error if parameters is not an object', () => {
|
|
96
|
+
const appDefinition = {
|
|
97
|
+
ssm: {
|
|
98
|
+
enable: true,
|
|
99
|
+
parameters: 'invalid',
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const result = ssmBuilder.validate(appDefinition);
|
|
104
|
+
|
|
105
|
+
expect(result.valid).toBe(false);
|
|
106
|
+
expect(result.errors.some(e => e.includes('ssm.parameters must be an object'))).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should error if parameters is an array', () => {
|
|
110
|
+
const appDefinition = {
|
|
111
|
+
ssm: {
|
|
112
|
+
enable: true,
|
|
113
|
+
parameters: ['param1', 'param2'],
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const result = ssmBuilder.validate(appDefinition);
|
|
118
|
+
|
|
119
|
+
expect(result.valid).toBe(false);
|
|
120
|
+
expect(result.errors.some(e => e.includes('ssm.parameters must be an object'))).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should error when keys are marked for offload but ssm.enable is not true', () => {
|
|
124
|
+
const appDefinition = {
|
|
125
|
+
ssm: { enable: false },
|
|
126
|
+
environment: { FOO: 'ssm' },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const result = ssmBuilder.validate(appDefinition);
|
|
130
|
+
|
|
131
|
+
expect(result.valid).toBe(false);
|
|
132
|
+
expect(result.errors.some(e => e.includes('ssm.enable is not true'))).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should error when a blocklisted key is marked for offload', () => {
|
|
136
|
+
const appDefinition = {
|
|
137
|
+
ssm: { enable: true },
|
|
138
|
+
environment: { DATABASE_URL: 'ssm' },
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const result = ssmBuilder.validate(appDefinition);
|
|
142
|
+
|
|
143
|
+
expect(result.valid).toBe(false);
|
|
144
|
+
expect(result.errors.some(e => e.includes('DATABASE_URL'))).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should error when an invalid env name is marked for offload', () => {
|
|
148
|
+
const appDefinition = {
|
|
149
|
+
ssm: { enable: true },
|
|
150
|
+
environment: { 'bad-name': 'ssm' },
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const result = ssmBuilder.validate(appDefinition);
|
|
154
|
+
|
|
155
|
+
expect(result.valid).toBe(false);
|
|
156
|
+
expect(result.errors.some(e => e.includes('bad-name'))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('build()', () => {
|
|
161
|
+
it('should add IAM permissions for SSM operations', async () => {
|
|
162
|
+
const appDefinition = {
|
|
163
|
+
ssm: {
|
|
164
|
+
enable: true,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
169
|
+
|
|
170
|
+
expect(result.iamStatements).toHaveLength(1);
|
|
171
|
+
expect(result.iamStatements[0]).toEqual({
|
|
172
|
+
Effect: 'Allow',
|
|
173
|
+
Action: [
|
|
174
|
+
'ssm:GetParameter',
|
|
175
|
+
'ssm:GetParameters',
|
|
176
|
+
'ssm:GetParametersByPath',
|
|
177
|
+
],
|
|
178
|
+
Resource: {
|
|
179
|
+
'Fn::Sub': 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*',
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should return environment object even if empty', async () => {
|
|
185
|
+
const appDefinition = {
|
|
186
|
+
ssm: {
|
|
187
|
+
enable: true,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
192
|
+
|
|
193
|
+
expect(result.environment).toBeDefined();
|
|
194
|
+
expect(typeof result.environment).toBe('object');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should not depend on discovered resources', async () => {
|
|
198
|
+
const appDefinition = {
|
|
199
|
+
ssm: {
|
|
200
|
+
enable: true,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result1 = await ssmBuilder.build(appDefinition, {});
|
|
205
|
+
const result2 = await ssmBuilder.build(appDefinition, { someResource: 'value' });
|
|
206
|
+
|
|
207
|
+
expect(result1.iamStatements).toEqual(result2.iamStatements);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should return empty environment when no keys are offloaded', async () => {
|
|
211
|
+
const appDefinition = {
|
|
212
|
+
ssm: { enable: true },
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
216
|
+
|
|
217
|
+
expect(result.environment).toEqual({});
|
|
218
|
+
expect(result.iamStatements).toHaveLength(1);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('build() - offload active', () => {
|
|
223
|
+
it('should add prefix and offloaded keys env vars', async () => {
|
|
224
|
+
const appDefinition = {
|
|
225
|
+
ssm: { enable: true },
|
|
226
|
+
environment: { FOO: 'ssm', BAR: 'ssm' },
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
230
|
+
|
|
231
|
+
expect(result.environment.SSM_PARAMETER_PREFIX).toBe(
|
|
232
|
+
'/frigg/${self:service}/${self:provider.stage}'
|
|
233
|
+
);
|
|
234
|
+
expect(result.environment.FRIGG_SSM_OFFLOADED_KEYS).toBe('BAR,FOO');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('does not set NODE_OPTIONS globally (the composer scopes it to skipEsbuild functions)', async () => {
|
|
238
|
+
const appDefinition = {
|
|
239
|
+
ssm: { enable: true },
|
|
240
|
+
environment: { FOO: 'ssm' },
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
244
|
+
|
|
245
|
+
// NODE_OPTIONS=--import would fatally abort esbuild-bundled functions
|
|
246
|
+
// that lack the preload file, so it must never be provider-global.
|
|
247
|
+
expect(result.environment.NODE_OPTIONS).toBeUndefined();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should add a prefix-scoped read statement while retaining the broad grant by default', async () => {
|
|
251
|
+
const appDefinition = {
|
|
252
|
+
ssm: { enable: true },
|
|
253
|
+
environment: { FOO: 'ssm' },
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
257
|
+
|
|
258
|
+
const broad = result.iamStatements.find(
|
|
259
|
+
s => s.Resource && s.Resource['Fn::Sub'] === 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*'
|
|
260
|
+
);
|
|
261
|
+
expect(broad).toBeDefined();
|
|
262
|
+
|
|
263
|
+
const scoped = result.iamStatements.find(
|
|
264
|
+
s => s.Resource === 'arn:aws:ssm:${self:provider.region}:${aws:accountId}:parameter/frigg/${self:service}/${self:provider.stage}/*'
|
|
265
|
+
);
|
|
266
|
+
expect(scoped).toBeDefined();
|
|
267
|
+
expect(scoped.Action).toEqual([
|
|
268
|
+
'ssm:GetParameter',
|
|
269
|
+
'ssm:GetParameters',
|
|
270
|
+
'ssm:GetParametersByPath',
|
|
271
|
+
]);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should drop the broad grant when restrictIamToPrefix is set', async () => {
|
|
275
|
+
const appDefinition = {
|
|
276
|
+
ssm: { enable: true, restrictIamToPrefix: true },
|
|
277
|
+
environment: { FOO: 'ssm' },
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
281
|
+
|
|
282
|
+
const broad = result.iamStatements.find(
|
|
283
|
+
s => s.Resource && s.Resource['Fn::Sub']
|
|
284
|
+
);
|
|
285
|
+
expect(broad).toBeUndefined();
|
|
286
|
+
|
|
287
|
+
const scoped = result.iamStatements.find(
|
|
288
|
+
s => s.Resource === 'arn:aws:ssm:${self:provider.region}:${aws:accountId}:parameter/frigg/${self:service}/${self:provider.stage}/*'
|
|
289
|
+
);
|
|
290
|
+
expect(scoped).toBeDefined();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should add kms:Decrypt when kmsKeyArn is set', async () => {
|
|
294
|
+
const appDefinition = {
|
|
295
|
+
ssm: {
|
|
296
|
+
enable: true,
|
|
297
|
+
kmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/abc-123',
|
|
298
|
+
},
|
|
299
|
+
environment: { FOO: 'ssm' },
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
303
|
+
|
|
304
|
+
const decrypt = result.iamStatements.find(
|
|
305
|
+
s => Array.isArray(s.Action) && s.Action.includes('kms:Decrypt')
|
|
306
|
+
);
|
|
307
|
+
expect(decrypt).toEqual({
|
|
308
|
+
Effect: 'Allow',
|
|
309
|
+
Action: ['kms:Decrypt'],
|
|
310
|
+
Resource: 'arn:aws:kms:us-east-1:123456789012:key/abc-123',
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should honor a custom parameterPrefix', async () => {
|
|
315
|
+
const appDefinition = {
|
|
316
|
+
ssm: { enable: true, parameterPrefix: '/custom/${self:provider.stage}' },
|
|
317
|
+
environment: { FOO: 'ssm' },
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const result = await ssmBuilder.build(appDefinition, {});
|
|
321
|
+
|
|
322
|
+
expect(result.environment.SSM_PARAMETER_PREFIX).toBe(
|
|
323
|
+
'/custom/${self:provider.stage}'
|
|
324
|
+
);
|
|
325
|
+
const scoped = result.iamStatements.find(
|
|
326
|
+
s => s.Resource === 'arn:aws:ssm:${self:provider.region}:${aws:accountId}:parameter/custom/${self:provider.stage}/*'
|
|
327
|
+
);
|
|
328
|
+
expect(scoped).toBeDefined();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('getDependencies()', () => {
|
|
333
|
+
it('should have no dependencies', () => {
|
|
334
|
+
const deps = ssmBuilder.getDependencies();
|
|
335
|
+
|
|
336
|
+
expect(deps).toEqual([]);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('getName()', () => {
|
|
341
|
+
it('should return SsmBuilder', () => {
|
|
342
|
+
expect(ssmBuilder.getName()).toBe('SsmBuilder');
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSM Discovery Service
|
|
3
|
+
*
|
|
4
|
+
* Domain Service - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Discovers SSM Parameter Store and Secrets Manager resources
|
|
7
|
+
* using the cloud provider adapter.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class SsmDiscovery {
|
|
11
|
+
/**
|
|
12
|
+
* @param {CloudProviderAdapter} provider - Cloud provider adapter instance
|
|
13
|
+
*/
|
|
14
|
+
constructor(provider) {
|
|
15
|
+
this.provider = provider;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Discover SSM parameters and secrets
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} config - Discovery configuration
|
|
22
|
+
* @param {string} [config.parameterPath] - SSM parameter path prefix
|
|
23
|
+
* @param {string} [config.serviceName] - Service name for filtering
|
|
24
|
+
* @param {string} [config.stage] - Deployment stage
|
|
25
|
+
* @param {boolean} [config.includeSecrets] - Whether to include Secrets Manager
|
|
26
|
+
* @returns {Promise<Object>} Discovered parameter resources
|
|
27
|
+
*/
|
|
28
|
+
async discover(config) {
|
|
29
|
+
console.log('🔍 Discovering SSM parameters...');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Build parameter path if not provided
|
|
33
|
+
if (!config.parameterPath && config.serviceName && config.stage) {
|
|
34
|
+
config.parameterPath = `/${config.serviceName}/${config.stage}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const rawResources = await this.provider.discoverParameters({
|
|
38
|
+
...config,
|
|
39
|
+
includeSecrets: config.includeSecrets !== false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const result = {
|
|
43
|
+
parameters: rawResources.parameters || [],
|
|
44
|
+
secrets: rawResources.secrets || [],
|
|
45
|
+
parameterPath: config.parameterPath,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Find database secret if exists
|
|
49
|
+
if (result.secrets.length > 0) {
|
|
50
|
+
const dbSecret = result.secrets.find(
|
|
51
|
+
s => s.Name?.includes('database') || s.Name?.includes('rds')
|
|
52
|
+
);
|
|
53
|
+
if (dbSecret) {
|
|
54
|
+
result.databaseSecretArn = dbSecret.ARN;
|
|
55
|
+
result.databaseSecretName = dbSecret.Name;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (result.parameters.length > 0) {
|
|
60
|
+
console.log(` ✓ Found ${result.parameters.length} SSM parameters`);
|
|
61
|
+
}
|
|
62
|
+
if (result.secrets.length > 0) {
|
|
63
|
+
console.log(` ✓ Found ${result.secrets.length} secrets`);
|
|
64
|
+
}
|
|
65
|
+
if (!result.parameters.length && !result.secrets.length) {
|
|
66
|
+
console.log(' ℹ No parameters or secrets found');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return result;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(' ✗ SSM discovery failed:', error.message);
|
|
72
|
+
return {
|
|
73
|
+
parameters: [],
|
|
74
|
+
secrets: [],
|
|
75
|
+
parameterPath: config.parameterPath,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
SsmDiscovery,
|
|
83
|
+
};
|
|
84
|
+
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for SSM Discovery Service
|
|
3
|
+
*
|
|
4
|
+
* Tests SSM Parameter Store and Secrets Manager discovery with mocked provider
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { SsmDiscovery } = require('./ssm-discovery');
|
|
8
|
+
|
|
9
|
+
describe('SsmDiscovery', () => {
|
|
10
|
+
let mockProvider;
|
|
11
|
+
let ssmDiscovery;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockProvider = {
|
|
15
|
+
discoverParameters: jest.fn(),
|
|
16
|
+
getName: jest.fn().mockReturnValue('aws'),
|
|
17
|
+
};
|
|
18
|
+
ssmDiscovery = new SsmDiscovery(mockProvider);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('discover()', () => {
|
|
22
|
+
it('should delegate to provider and transform results', async () => {
|
|
23
|
+
const mockProviderResponse = {
|
|
24
|
+
parameters: [
|
|
25
|
+
{
|
|
26
|
+
Name: '/my-service/prod/API_KEY',
|
|
27
|
+
Value: 'encrypted-value',
|
|
28
|
+
Type: 'SecureString',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
Name: '/my-service/prod/DATABASE_URL',
|
|
32
|
+
Value: 'postgres://...',
|
|
33
|
+
Type: 'SecureString',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
secrets: [
|
|
37
|
+
{
|
|
38
|
+
Name: 'my-service/database-credentials',
|
|
39
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456:secret:my-service/database-credentials',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
mockProvider.discoverParameters.mockResolvedValue(mockProviderResponse);
|
|
45
|
+
|
|
46
|
+
const result = await ssmDiscovery.discover({
|
|
47
|
+
serviceName: 'my-service',
|
|
48
|
+
stage: 'prod',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(mockProvider.discoverParameters).toHaveBeenCalled();
|
|
52
|
+
expect(result.parameters).toHaveLength(2);
|
|
53
|
+
expect(result.secrets).toHaveLength(1);
|
|
54
|
+
expect(result.parameterPath).toBe('/my-service/prod');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should build parameter path from serviceName and stage', async () => {
|
|
58
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
59
|
+
parameters: [],
|
|
60
|
+
secrets: [],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await ssmDiscovery.discover({
|
|
64
|
+
serviceName: 'test-app',
|
|
65
|
+
stage: 'dev',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(mockProvider.discoverParameters).toHaveBeenCalledWith(
|
|
69
|
+
expect.objectContaining({
|
|
70
|
+
parameterPath: '/test-app/dev',
|
|
71
|
+
includeSecrets: true,
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should use provided parameterPath if specified', async () => {
|
|
77
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
78
|
+
parameters: [],
|
|
79
|
+
secrets: [],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await ssmDiscovery.discover({
|
|
83
|
+
parameterPath: '/custom/path',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(mockProvider.discoverParameters).toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining({
|
|
88
|
+
parameterPath: '/custom/path',
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle no parameters found', async () => {
|
|
94
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
95
|
+
parameters: [],
|
|
96
|
+
secrets: [],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = await ssmDiscovery.discover({});
|
|
100
|
+
|
|
101
|
+
expect(result.parameters).toEqual([]);
|
|
102
|
+
expect(result.secrets).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should find database secret if exists', async () => {
|
|
106
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
107
|
+
parameters: [],
|
|
108
|
+
secrets: [
|
|
109
|
+
{
|
|
110
|
+
Name: 'my-app/config',
|
|
111
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456:secret:my-app/config',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
Name: 'my-app/database-secret',
|
|
115
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456:secret:my-app/database-secret',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const result = await ssmDiscovery.discover({});
|
|
121
|
+
|
|
122
|
+
expect(result.databaseSecretArn).toBe('arn:aws:secretsmanager:us-east-1:123456:secret:my-app/database-secret');
|
|
123
|
+
expect(result.databaseSecretName).toBe('my-app/database-secret');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should find RDS secret if exists', async () => {
|
|
127
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
128
|
+
parameters: [],
|
|
129
|
+
secrets: [
|
|
130
|
+
{
|
|
131
|
+
Name: 'rds/postgres/credentials',
|
|
132
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456:secret:rds/postgres/credentials',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = await ssmDiscovery.discover({});
|
|
138
|
+
|
|
139
|
+
expect(result.databaseSecretArn).toBe('arn:aws:secretsmanager:us-east-1:123456:secret:rds/postgres/credentials');
|
|
140
|
+
expect(result.databaseSecretName).toBe('rds/postgres/credentials');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle includeSecrets flag', async () => {
|
|
144
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
145
|
+
parameters: [],
|
|
146
|
+
secrets: [],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await ssmDiscovery.discover({
|
|
150
|
+
includeSecrets: false,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(mockProvider.discoverParameters).toHaveBeenCalledWith(
|
|
154
|
+
expect.objectContaining({
|
|
155
|
+
includeSecrets: false,
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should default includeSecrets to true', async () => {
|
|
161
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
162
|
+
parameters: [],
|
|
163
|
+
secrets: [],
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await ssmDiscovery.discover({});
|
|
167
|
+
|
|
168
|
+
expect(mockProvider.discoverParameters).toHaveBeenCalledWith(
|
|
169
|
+
expect.objectContaining({
|
|
170
|
+
includeSecrets: true,
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle discovery errors gracefully', async () => {
|
|
176
|
+
mockProvider.discoverParameters.mockRejectedValue(new Error('SSM API Error'));
|
|
177
|
+
|
|
178
|
+
const result = await ssmDiscovery.discover({});
|
|
179
|
+
|
|
180
|
+
expect(result.parameters).toEqual([]);
|
|
181
|
+
expect(result.secrets).toEqual([]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should preserve parameterPath in result', async () => {
|
|
185
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
186
|
+
parameters: [],
|
|
187
|
+
secrets: [],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const result = await ssmDiscovery.discover({
|
|
191
|
+
parameterPath: '/my-service/staging',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(result.parameterPath).toBe('/my-service/staging');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should handle null/undefined parameters and secrets', async () => {
|
|
198
|
+
mockProvider.discoverParameters.mockResolvedValue({
|
|
199
|
+
parameters: null,
|
|
200
|
+
secrets: undefined,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const result = await ssmDiscovery.discover({});
|
|
204
|
+
|
|
205
|
+
expect(result.parameters).toEqual([]);
|
|
206
|
+
expect(result.secrets).toEqual([]);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|