@friggframework/devtools 2.0.0-next.45 → 2.0.0-next.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- 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-builder.js +809 -0
- package/infrastructure/domains/database/aurora-builder.test.js +950 -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 +695 -0
- package/infrastructure/domains/database/migration-builder.test.js +294 -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 +397 -0
- package/infrastructure/domains/integration/integration-builder.test.js +593 -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 +1829 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
- package/infrastructure/domains/networking/vpc-discovery.js +177 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
- package/infrastructure/domains/networking/vpc-resolver.js +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +189 -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 +366 -0
- package/infrastructure/domains/security/kms-builder.test.js +374 -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/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 +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
- package/infrastructure/domains/shared/environment-builder.js +119 -0
- package/infrastructure/domains/shared/environment-builder.test.js +247 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -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 +192 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
- package/infrastructure/domains/shared/types/app-definition.js +205 -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 +380 -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.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
- package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +87 -0
- package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
- package/infrastructure/scripts/build-prisma-layer.js +553 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
- package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
- 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/cliIntegration.js +1 -1
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +11 -11
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -65
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -175
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -75
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -54
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -161
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/prisma-runner.js +0 -280
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- package/infrastructure/aws-discovery.js +0 -1176
- package/infrastructure/aws-discovery.test.js +0 -1220
- package/infrastructure/serverless-template.js +0 -2094
- /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/{run-discovery.js → scripts/run-discovery.js} +0 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ResourceState Value Object
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const ResourceState = require('./resource-state');
|
|
6
|
+
|
|
7
|
+
describe('ResourceState', () => {
|
|
8
|
+
describe('valid states', () => {
|
|
9
|
+
it('should accept IN_STACK state', () => {
|
|
10
|
+
const state = new ResourceState('IN_STACK');
|
|
11
|
+
|
|
12
|
+
expect(state.value).toBe('IN_STACK');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should accept ORPHANED state', () => {
|
|
16
|
+
const state = new ResourceState('ORPHANED');
|
|
17
|
+
|
|
18
|
+
expect(state.value).toBe('ORPHANED');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should accept MISSING state', () => {
|
|
22
|
+
const state = new ResourceState('MISSING');
|
|
23
|
+
|
|
24
|
+
expect(state.value).toBe('MISSING');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should accept DRIFTED state', () => {
|
|
28
|
+
const state = new ResourceState('DRIFTED');
|
|
29
|
+
|
|
30
|
+
expect(state.value).toBe('DRIFTED');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should accept EXTERNAL state', () => {
|
|
34
|
+
const state = new ResourceState('EXTERNAL');
|
|
35
|
+
|
|
36
|
+
expect(state.value).toBe('EXTERNAL');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('invalid states', () => {
|
|
41
|
+
it('should reject invalid state', () => {
|
|
42
|
+
expect(() => {
|
|
43
|
+
new ResourceState('INVALID');
|
|
44
|
+
}).toThrow('Invalid resource state: INVALID');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should reject lowercase state', () => {
|
|
48
|
+
expect(() => {
|
|
49
|
+
new ResourceState('in_stack');
|
|
50
|
+
}).toThrow('Invalid resource state: in_stack');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should reject null', () => {
|
|
54
|
+
expect(() => {
|
|
55
|
+
new ResourceState(null);
|
|
56
|
+
}).toThrow('Resource state is required');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should reject undefined', () => {
|
|
60
|
+
expect(() => {
|
|
61
|
+
new ResourceState(undefined);
|
|
62
|
+
}).toThrow('Resource state is required');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('state checks', () => {
|
|
67
|
+
it('should check if resource is in stack', () => {
|
|
68
|
+
const state = new ResourceState('IN_STACK');
|
|
69
|
+
|
|
70
|
+
expect(state.isInStack()).toBe(true);
|
|
71
|
+
expect(state.isOrphaned()).toBe(false);
|
|
72
|
+
expect(state.isMissing()).toBe(false);
|
|
73
|
+
expect(state.isDrifted()).toBe(false);
|
|
74
|
+
expect(state.isExternal()).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should check if resource is orphaned', () => {
|
|
78
|
+
const state = new ResourceState('ORPHANED');
|
|
79
|
+
|
|
80
|
+
expect(state.isInStack()).toBe(false);
|
|
81
|
+
expect(state.isOrphaned()).toBe(true);
|
|
82
|
+
expect(state.isMissing()).toBe(false);
|
|
83
|
+
expect(state.isDrifted()).toBe(false);
|
|
84
|
+
expect(state.isExternal()).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should check if resource is missing', () => {
|
|
88
|
+
const state = new ResourceState('MISSING');
|
|
89
|
+
|
|
90
|
+
expect(state.isInStack()).toBe(false);
|
|
91
|
+
expect(state.isOrphaned()).toBe(false);
|
|
92
|
+
expect(state.isMissing()).toBe(true);
|
|
93
|
+
expect(state.isDrifted()).toBe(false);
|
|
94
|
+
expect(state.isExternal()).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should check if resource is drifted', () => {
|
|
98
|
+
const state = new ResourceState('DRIFTED');
|
|
99
|
+
|
|
100
|
+
expect(state.isInStack()).toBe(false);
|
|
101
|
+
expect(state.isOrphaned()).toBe(false);
|
|
102
|
+
expect(state.isMissing()).toBe(false);
|
|
103
|
+
expect(state.isDrifted()).toBe(true);
|
|
104
|
+
expect(state.isExternal()).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should check if resource is external', () => {
|
|
108
|
+
const state = new ResourceState('EXTERNAL');
|
|
109
|
+
|
|
110
|
+
expect(state.isInStack()).toBe(false);
|
|
111
|
+
expect(state.isOrphaned()).toBe(false);
|
|
112
|
+
expect(state.isMissing()).toBe(false);
|
|
113
|
+
expect(state.isDrifted()).toBe(false);
|
|
114
|
+
expect(state.isExternal()).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('equality', () => {
|
|
119
|
+
it('should be equal to same state', () => {
|
|
120
|
+
const state1 = new ResourceState('IN_STACK');
|
|
121
|
+
const state2 = new ResourceState('IN_STACK');
|
|
122
|
+
|
|
123
|
+
expect(state1.equals(state2)).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should not be equal to different state', () => {
|
|
127
|
+
const state1 = new ResourceState('IN_STACK');
|
|
128
|
+
const state2 = new ResourceState('ORPHANED');
|
|
129
|
+
|
|
130
|
+
expect(state1.equals(state2)).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should not be equal to non-ResourceState', () => {
|
|
134
|
+
const state = new ResourceState('IN_STACK');
|
|
135
|
+
|
|
136
|
+
expect(state.equals('IN_STACK')).toBe(false);
|
|
137
|
+
expect(state.equals(null)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('toString', () => {
|
|
142
|
+
it('should return string representation', () => {
|
|
143
|
+
const state = new ResourceState('IN_STACK');
|
|
144
|
+
|
|
145
|
+
expect(state.toString()).toBe('IN_STACK');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('static constants', () => {
|
|
150
|
+
it('should provide IN_STACK constant', () => {
|
|
151
|
+
expect(ResourceState.IN_STACK.value).toBe('IN_STACK');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should provide ORPHANED constant', () => {
|
|
155
|
+
expect(ResourceState.ORPHANED.value).toBe('ORPHANED');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should provide MISSING constant', () => {
|
|
159
|
+
expect(ResourceState.MISSING.value).toBe('MISSING');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should provide DRIFTED constant', () => {
|
|
163
|
+
expect(ResourceState.DRIFTED.value).toBe('DRIFTED');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should provide EXTERNAL constant', () => {
|
|
167
|
+
expect(ResourceState.EXTERNAL.value).toBe('EXTERNAL');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should provide VALID_STATES array', () => {
|
|
171
|
+
expect(ResourceState.VALID_STATES).toEqual([
|
|
172
|
+
'IN_STACK',
|
|
173
|
+
'ORPHANED',
|
|
174
|
+
'MISSING',
|
|
175
|
+
'DRIFTED',
|
|
176
|
+
'EXTERNAL',
|
|
177
|
+
]);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('immutability', () => {
|
|
182
|
+
it('should not allow modification of value', () => {
|
|
183
|
+
const state = new ResourceState('IN_STACK');
|
|
184
|
+
|
|
185
|
+
expect(() => {
|
|
186
|
+
state.value = 'ORPHANED';
|
|
187
|
+
}).toThrow();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should be frozen', () => {
|
|
191
|
+
const state = new ResourceState('IN_STACK');
|
|
192
|
+
|
|
193
|
+
expect(Object.isFrozen(state)).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StackIdentifier Value Object
|
|
3
|
+
*
|
|
4
|
+
* Immutable identifier for a CloudFormation stack
|
|
5
|
+
* Combines stack name, region, and optional account ID
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class StackIdentifier {
|
|
9
|
+
/**
|
|
10
|
+
* Valid AWS regions
|
|
11
|
+
* @private
|
|
12
|
+
*/
|
|
13
|
+
static VALID_REGIONS = [
|
|
14
|
+
'us-east-1',
|
|
15
|
+
'us-east-2',
|
|
16
|
+
'us-west-1',
|
|
17
|
+
'us-west-2',
|
|
18
|
+
'af-south-1',
|
|
19
|
+
'ap-east-1',
|
|
20
|
+
'ap-south-1',
|
|
21
|
+
'ap-northeast-1',
|
|
22
|
+
'ap-northeast-2',
|
|
23
|
+
'ap-northeast-3',
|
|
24
|
+
'ap-southeast-1',
|
|
25
|
+
'ap-southeast-2',
|
|
26
|
+
'ca-central-1',
|
|
27
|
+
'eu-central-1',
|
|
28
|
+
'eu-west-1',
|
|
29
|
+
'eu-west-2',
|
|
30
|
+
'eu-west-3',
|
|
31
|
+
'eu-north-1',
|
|
32
|
+
'eu-south-1',
|
|
33
|
+
'me-south-1',
|
|
34
|
+
'sa-east-1',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a new StackIdentifier
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} params
|
|
41
|
+
* @param {string} params.stackName - CloudFormation stack name
|
|
42
|
+
* @param {string} params.region - AWS region
|
|
43
|
+
* @param {string} [params.accountId] - AWS account ID (12 digits)
|
|
44
|
+
*/
|
|
45
|
+
constructor({ stackName, region, accountId = null }) {
|
|
46
|
+
// Validate required fields
|
|
47
|
+
if (stackName === undefined || stackName === null) {
|
|
48
|
+
throw new Error('stackName is required');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (region === undefined || region === null) {
|
|
52
|
+
throw new Error('region is required');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Validate formats
|
|
56
|
+
if (typeof stackName === 'string' && stackName.trim() === '') {
|
|
57
|
+
throw new Error('stackName cannot be empty');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!StackIdentifier.VALID_REGIONS.includes(region)) {
|
|
61
|
+
throw new Error('region must be a valid AWS region');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (accountId !== null && !/^\d{12}$/.test(accountId)) {
|
|
65
|
+
throw new Error('accountId must be a 12-digit number');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Assign properties
|
|
69
|
+
this._stackName = stackName;
|
|
70
|
+
this._region = region;
|
|
71
|
+
this._accountId = accountId;
|
|
72
|
+
|
|
73
|
+
// Make immutable
|
|
74
|
+
Object.freeze(this);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get stack name
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
get stackName() {
|
|
82
|
+
return this._stackName;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Prevent modification of stackName
|
|
87
|
+
* @throws {TypeError}
|
|
88
|
+
*/
|
|
89
|
+
set stackName(value) {
|
|
90
|
+
throw new TypeError('Cannot modify immutable property stackName');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get region
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
get region() {
|
|
98
|
+
return this._region;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Prevent modification of region
|
|
103
|
+
* @throws {TypeError}
|
|
104
|
+
*/
|
|
105
|
+
set region(value) {
|
|
106
|
+
throw new TypeError('Cannot modify immutable property region');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get account ID
|
|
111
|
+
* @returns {string|null}
|
|
112
|
+
*/
|
|
113
|
+
get accountId() {
|
|
114
|
+
return this._accountId;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Prevent modification of accountId
|
|
119
|
+
* @throws {TypeError}
|
|
120
|
+
*/
|
|
121
|
+
set accountId(value) {
|
|
122
|
+
throw new TypeError('Cannot modify immutable property accountId');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check equality with another StackIdentifier
|
|
127
|
+
*
|
|
128
|
+
* @param {StackIdentifier} other
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
equals(other) {
|
|
132
|
+
if (!(other instanceof StackIdentifier)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
this.stackName === other.stackName &&
|
|
138
|
+
this.region === other.region &&
|
|
139
|
+
this.accountId === other.accountId
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get string representation
|
|
145
|
+
*
|
|
146
|
+
* @returns {string}
|
|
147
|
+
*/
|
|
148
|
+
toString() {
|
|
149
|
+
if (this.accountId) {
|
|
150
|
+
return `${this.stackName} (${this.region}, ${this.accountId})`;
|
|
151
|
+
}
|
|
152
|
+
return `${this.stackName} (${this.region})`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Serialize to JSON
|
|
157
|
+
*
|
|
158
|
+
* @returns {Object}
|
|
159
|
+
*/
|
|
160
|
+
toJSON() {
|
|
161
|
+
return {
|
|
162
|
+
stackName: this.stackName,
|
|
163
|
+
region: this.region,
|
|
164
|
+
accountId: this.accountId,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create StackIdentifier from ARN
|
|
170
|
+
*
|
|
171
|
+
* @param {string} arn - CloudFormation stack ARN
|
|
172
|
+
* @returns {StackIdentifier}
|
|
173
|
+
*/
|
|
174
|
+
static fromArn(arn) {
|
|
175
|
+
// arn:aws:cloudformation:region:account-id:stack/stack-name/guid
|
|
176
|
+
const match = arn.match(/^arn:aws:cloudformation:([^:]+):(\d{12}):stack\/([^\/]+)/);
|
|
177
|
+
|
|
178
|
+
if (!match) {
|
|
179
|
+
throw new Error('Invalid CloudFormation stack ARN');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const [, region, accountId, stackName] = match;
|
|
183
|
+
|
|
184
|
+
return new StackIdentifier({
|
|
185
|
+
stackName,
|
|
186
|
+
region,
|
|
187
|
+
accountId,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = StackIdentifier;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for StackIdentifier Value Object
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const StackIdentifier = require('./stack-identifier');
|
|
6
|
+
|
|
7
|
+
describe('StackIdentifier', () => {
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('should create a valid stack identifier', () => {
|
|
10
|
+
const identifier = new StackIdentifier({
|
|
11
|
+
stackName: 'my-app-prod',
|
|
12
|
+
region: 'us-east-1',
|
|
13
|
+
accountId: '123456789012',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(identifier.stackName).toBe('my-app-prod');
|
|
17
|
+
expect(identifier.region).toBe('us-east-1');
|
|
18
|
+
expect(identifier.accountId).toBe('123456789012');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should require stackName', () => {
|
|
22
|
+
expect(() => {
|
|
23
|
+
new StackIdentifier({
|
|
24
|
+
region: 'us-east-1',
|
|
25
|
+
accountId: '123456789012',
|
|
26
|
+
});
|
|
27
|
+
}).toThrow('stackName is required');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should require region', () => {
|
|
31
|
+
expect(() => {
|
|
32
|
+
new StackIdentifier({
|
|
33
|
+
stackName: 'my-app-prod',
|
|
34
|
+
accountId: '123456789012',
|
|
35
|
+
});
|
|
36
|
+
}).toThrow('region is required');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should allow accountId to be optional', () => {
|
|
40
|
+
const identifier = new StackIdentifier({
|
|
41
|
+
stackName: 'my-app-prod',
|
|
42
|
+
region: 'us-east-1',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(identifier.stackName).toBe('my-app-prod');
|
|
46
|
+
expect(identifier.region).toBe('us-east-1');
|
|
47
|
+
expect(identifier.accountId).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should validate stackName format', () => {
|
|
51
|
+
expect(() => {
|
|
52
|
+
new StackIdentifier({
|
|
53
|
+
stackName: '',
|
|
54
|
+
region: 'us-east-1',
|
|
55
|
+
});
|
|
56
|
+
}).toThrow('stackName cannot be empty');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should validate region format', () => {
|
|
60
|
+
expect(() => {
|
|
61
|
+
new StackIdentifier({
|
|
62
|
+
stackName: 'my-app-prod',
|
|
63
|
+
region: 'invalid-region',
|
|
64
|
+
});
|
|
65
|
+
}).toThrow('region must be a valid AWS region');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should validate accountId format when provided', () => {
|
|
69
|
+
expect(() => {
|
|
70
|
+
new StackIdentifier({
|
|
71
|
+
stackName: 'my-app-prod',
|
|
72
|
+
region: 'us-east-1',
|
|
73
|
+
accountId: '123',
|
|
74
|
+
});
|
|
75
|
+
}).toThrow('accountId must be a 12-digit number');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should accept valid AWS regions', () => {
|
|
79
|
+
const validRegions = [
|
|
80
|
+
'us-east-1',
|
|
81
|
+
'us-east-2',
|
|
82
|
+
'us-west-1',
|
|
83
|
+
'us-west-2',
|
|
84
|
+
'eu-west-1',
|
|
85
|
+
'eu-central-1',
|
|
86
|
+
'ap-southeast-1',
|
|
87
|
+
'ap-northeast-1',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
validRegions.forEach(region => {
|
|
91
|
+
expect(() => {
|
|
92
|
+
new StackIdentifier({
|
|
93
|
+
stackName: 'my-app-prod',
|
|
94
|
+
region,
|
|
95
|
+
});
|
|
96
|
+
}).not.toThrow();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('equals', () => {
|
|
102
|
+
it('should return true for identical identifiers', () => {
|
|
103
|
+
const id1 = new StackIdentifier({
|
|
104
|
+
stackName: 'my-app-prod',
|
|
105
|
+
region: 'us-east-1',
|
|
106
|
+
accountId: '123456789012',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const id2 = new StackIdentifier({
|
|
110
|
+
stackName: 'my-app-prod',
|
|
111
|
+
region: 'us-east-1',
|
|
112
|
+
accountId: '123456789012',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(id1.equals(id2)).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should return false for different stack names', () => {
|
|
119
|
+
const id1 = new StackIdentifier({
|
|
120
|
+
stackName: 'my-app-prod',
|
|
121
|
+
region: 'us-east-1',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const id2 = new StackIdentifier({
|
|
125
|
+
stackName: 'my-app-dev',
|
|
126
|
+
region: 'us-east-1',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(id1.equals(id2)).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should return false for different regions', () => {
|
|
133
|
+
const id1 = new StackIdentifier({
|
|
134
|
+
stackName: 'my-app-prod',
|
|
135
|
+
region: 'us-east-1',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const id2 = new StackIdentifier({
|
|
139
|
+
stackName: 'my-app-prod',
|
|
140
|
+
region: 'us-west-2',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(id1.equals(id2)).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should return false for different account IDs', () => {
|
|
147
|
+
const id1 = new StackIdentifier({
|
|
148
|
+
stackName: 'my-app-prod',
|
|
149
|
+
region: 'us-east-1',
|
|
150
|
+
accountId: '123456789012',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const id2 = new StackIdentifier({
|
|
154
|
+
stackName: 'my-app-prod',
|
|
155
|
+
region: 'us-east-1',
|
|
156
|
+
accountId: '987654321098',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(id1.equals(id2)).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should handle null accountId comparison', () => {
|
|
163
|
+
const id1 = new StackIdentifier({
|
|
164
|
+
stackName: 'my-app-prod',
|
|
165
|
+
region: 'us-east-1',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const id2 = new StackIdentifier({
|
|
169
|
+
stackName: 'my-app-prod',
|
|
170
|
+
region: 'us-east-1',
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(id1.equals(id2)).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('toString', () => {
|
|
178
|
+
it('should return string representation with account ID', () => {
|
|
179
|
+
const identifier = new StackIdentifier({
|
|
180
|
+
stackName: 'my-app-prod',
|
|
181
|
+
region: 'us-east-1',
|
|
182
|
+
accountId: '123456789012',
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(identifier.toString()).toBe('my-app-prod (us-east-1, 123456789012)');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should return string representation without account ID', () => {
|
|
189
|
+
const identifier = new StackIdentifier({
|
|
190
|
+
stackName: 'my-app-prod',
|
|
191
|
+
region: 'us-east-1',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(identifier.toString()).toBe('my-app-prod (us-east-1)');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('toJSON', () => {
|
|
199
|
+
it('should serialize to JSON with account ID', () => {
|
|
200
|
+
const identifier = new StackIdentifier({
|
|
201
|
+
stackName: 'my-app-prod',
|
|
202
|
+
region: 'us-east-1',
|
|
203
|
+
accountId: '123456789012',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(identifier.toJSON()).toEqual({
|
|
207
|
+
stackName: 'my-app-prod',
|
|
208
|
+
region: 'us-east-1',
|
|
209
|
+
accountId: '123456789012',
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should serialize to JSON without account ID', () => {
|
|
214
|
+
const identifier = new StackIdentifier({
|
|
215
|
+
stackName: 'my-app-prod',
|
|
216
|
+
region: 'us-east-1',
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(identifier.toJSON()).toEqual({
|
|
220
|
+
stackName: 'my-app-prod',
|
|
221
|
+
region: 'us-east-1',
|
|
222
|
+
accountId: null,
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('immutability', () => {
|
|
228
|
+
it('should not allow modification of stackName', () => {
|
|
229
|
+
const identifier = new StackIdentifier({
|
|
230
|
+
stackName: 'my-app-prod',
|
|
231
|
+
region: 'us-east-1',
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(() => {
|
|
235
|
+
identifier.stackName = 'modified';
|
|
236
|
+
}).toThrow();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should not allow modification of region', () => {
|
|
240
|
+
const identifier = new StackIdentifier({
|
|
241
|
+
stackName: 'my-app-prod',
|
|
242
|
+
region: 'us-east-1',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(() => {
|
|
246
|
+
identifier.region = 'us-west-2';
|
|
247
|
+
}).toThrow();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should not allow modification of accountId', () => {
|
|
251
|
+
const identifier = new StackIdentifier({
|
|
252
|
+
stackName: 'my-app-prod',
|
|
253
|
+
region: 'us-east-1',
|
|
254
|
+
accountId: '123456789012',
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(() => {
|
|
258
|
+
identifier.accountId = '987654321098';
|
|
259
|
+
}).toThrow();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|