@friggframework/devtools 2.0.0-next.44 → 2.0.0-next.46
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 +633 -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 -2074
- /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,601 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for StackHealthReport Aggregate Root
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const StackHealthReport = require('./stack-health-report');
|
|
6
|
+
const StackIdentifier = require('../value-objects/stack-identifier');
|
|
7
|
+
const HealthScore = require('../value-objects/health-score');
|
|
8
|
+
const ResourceState = require('../value-objects/resource-state');
|
|
9
|
+
const PropertyMutability = require('../value-objects/property-mutability');
|
|
10
|
+
const Resource = require('./resource');
|
|
11
|
+
const Issue = require('./issue');
|
|
12
|
+
const PropertyMismatch = require('./property-mismatch');
|
|
13
|
+
|
|
14
|
+
describe('StackHealthReport', () => {
|
|
15
|
+
describe('constructor', () => {
|
|
16
|
+
it('should create a health report with all fields', () => {
|
|
17
|
+
const stackId = new StackIdentifier({
|
|
18
|
+
stackName: 'my-app-prod',
|
|
19
|
+
region: 'us-east-1',
|
|
20
|
+
accountId: '123456789012',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const healthScore = new HealthScore(85);
|
|
24
|
+
const timestamp = new Date('2024-01-15T10:30:00Z');
|
|
25
|
+
|
|
26
|
+
const resource = new Resource({
|
|
27
|
+
logicalId: 'MyVPC',
|
|
28
|
+
physicalId: 'vpc-123',
|
|
29
|
+
resourceType: 'AWS::EC2::VPC',
|
|
30
|
+
state: ResourceState.IN_STACK,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const report = new StackHealthReport({
|
|
34
|
+
stackIdentifier: stackId,
|
|
35
|
+
healthScore,
|
|
36
|
+
resources: [resource],
|
|
37
|
+
issues: [],
|
|
38
|
+
timestamp,
|
|
39
|
+
metadata: { scanDuration: 5000 },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(report.stackIdentifier).toBe(stackId);
|
|
43
|
+
expect(report.healthScore).toBe(healthScore);
|
|
44
|
+
expect(report.resources).toHaveLength(1);
|
|
45
|
+
expect(report.resources[0]).toBe(resource);
|
|
46
|
+
expect(report.issues).toEqual([]);
|
|
47
|
+
expect(report.timestamp).toBe(timestamp);
|
|
48
|
+
expect(report.metadata.scanDuration).toBe(5000);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should require stackIdentifier', () => {
|
|
52
|
+
expect(() => {
|
|
53
|
+
new StackHealthReport({
|
|
54
|
+
healthScore: new HealthScore(100),
|
|
55
|
+
});
|
|
56
|
+
}).toThrow('stackIdentifier is required');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should require healthScore', () => {
|
|
60
|
+
expect(() => {
|
|
61
|
+
new StackHealthReport({
|
|
62
|
+
stackIdentifier: new StackIdentifier({
|
|
63
|
+
stackName: 'test',
|
|
64
|
+
region: 'us-east-1',
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
}).toThrow('healthScore is required');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should validate stackIdentifier is StackIdentifier instance', () => {
|
|
71
|
+
expect(() => {
|
|
72
|
+
new StackHealthReport({
|
|
73
|
+
stackIdentifier: { stackName: 'test', region: 'us-east-1' },
|
|
74
|
+
healthScore: new HealthScore(100),
|
|
75
|
+
});
|
|
76
|
+
}).toThrow('stackIdentifier must be a StackIdentifier instance');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should validate healthScore is HealthScore instance', () => {
|
|
80
|
+
expect(() => {
|
|
81
|
+
new StackHealthReport({
|
|
82
|
+
stackIdentifier: new StackIdentifier({
|
|
83
|
+
stackName: 'test',
|
|
84
|
+
region: 'us-east-1',
|
|
85
|
+
}),
|
|
86
|
+
healthScore: 85,
|
|
87
|
+
});
|
|
88
|
+
}).toThrow('healthScore must be a HealthScore instance');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should validate resources array contains only Resource instances', () => {
|
|
92
|
+
expect(() => {
|
|
93
|
+
new StackHealthReport({
|
|
94
|
+
stackIdentifier: new StackIdentifier({
|
|
95
|
+
stackName: 'test',
|
|
96
|
+
region: 'us-east-1',
|
|
97
|
+
}),
|
|
98
|
+
healthScore: new HealthScore(100),
|
|
99
|
+
resources: [{ logicalId: 'test', physicalId: 'test' }],
|
|
100
|
+
});
|
|
101
|
+
}).toThrow('All resources must be Resource instances');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should validate issues array contains only Issue instances', () => {
|
|
105
|
+
expect(() => {
|
|
106
|
+
new StackHealthReport({
|
|
107
|
+
stackIdentifier: new StackIdentifier({
|
|
108
|
+
stackName: 'test',
|
|
109
|
+
region: 'us-east-1',
|
|
110
|
+
}),
|
|
111
|
+
healthScore: new HealthScore(100),
|
|
112
|
+
issues: [{ type: 'ORPHANED_RESOURCE', severity: 'critical' }],
|
|
113
|
+
});
|
|
114
|
+
}).toThrow('All issues must be Issue instances');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should default resources to empty array', () => {
|
|
118
|
+
const report = new StackHealthReport({
|
|
119
|
+
stackIdentifier: new StackIdentifier({
|
|
120
|
+
stackName: 'test',
|
|
121
|
+
region: 'us-east-1',
|
|
122
|
+
}),
|
|
123
|
+
healthScore: new HealthScore(100),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(report.resources).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should default issues to empty array', () => {
|
|
130
|
+
const report = new StackHealthReport({
|
|
131
|
+
stackIdentifier: new StackIdentifier({
|
|
132
|
+
stackName: 'test',
|
|
133
|
+
region: 'us-east-1',
|
|
134
|
+
}),
|
|
135
|
+
healthScore: new HealthScore(100),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(report.issues).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should default timestamp to current date', () => {
|
|
142
|
+
const before = new Date();
|
|
143
|
+
const report = new StackHealthReport({
|
|
144
|
+
stackIdentifier: new StackIdentifier({
|
|
145
|
+
stackName: 'test',
|
|
146
|
+
region: 'us-east-1',
|
|
147
|
+
}),
|
|
148
|
+
healthScore: new HealthScore(100),
|
|
149
|
+
});
|
|
150
|
+
const after = new Date();
|
|
151
|
+
|
|
152
|
+
expect(report.timestamp.getTime()).toBeGreaterThanOrEqual(before.getTime());
|
|
153
|
+
expect(report.timestamp.getTime()).toBeLessThanOrEqual(after.getTime());
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should default metadata to empty object', () => {
|
|
157
|
+
const report = new StackHealthReport({
|
|
158
|
+
stackIdentifier: new StackIdentifier({
|
|
159
|
+
stackName: 'test',
|
|
160
|
+
region: 'us-east-1',
|
|
161
|
+
}),
|
|
162
|
+
healthScore: new HealthScore(100),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(report.metadata).toEqual({});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('resource queries', () => {
|
|
170
|
+
let report;
|
|
171
|
+
let orphanedResource;
|
|
172
|
+
let missingResource;
|
|
173
|
+
let driftedResource;
|
|
174
|
+
let healthyResource;
|
|
175
|
+
|
|
176
|
+
beforeEach(() => {
|
|
177
|
+
orphanedResource = new Resource({
|
|
178
|
+
logicalId: null,
|
|
179
|
+
physicalId: 'orphan-123',
|
|
180
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
181
|
+
state: ResourceState.ORPHANED,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
missingResource = new Resource({
|
|
185
|
+
logicalId: 'MissingKey',
|
|
186
|
+
physicalId: 'key-missing',
|
|
187
|
+
resourceType: 'AWS::KMS::Key',
|
|
188
|
+
state: ResourceState.MISSING,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
driftedResource = new Resource({
|
|
192
|
+
logicalId: 'DriftedVPC',
|
|
193
|
+
physicalId: 'vpc-drift',
|
|
194
|
+
resourceType: 'AWS::EC2::VPC',
|
|
195
|
+
state: ResourceState.DRIFTED,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
healthyResource = new Resource({
|
|
199
|
+
logicalId: 'HealthyVPC',
|
|
200
|
+
physicalId: 'vpc-healthy',
|
|
201
|
+
resourceType: 'AWS::EC2::VPC',
|
|
202
|
+
state: ResourceState.IN_STACK,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
report = new StackHealthReport({
|
|
206
|
+
stackIdentifier: new StackIdentifier({
|
|
207
|
+
stackName: 'test',
|
|
208
|
+
region: 'us-east-1',
|
|
209
|
+
}),
|
|
210
|
+
healthScore: new HealthScore(70),
|
|
211
|
+
resources: [orphanedResource, missingResource, driftedResource, healthyResource],
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should get orphaned resources', () => {
|
|
216
|
+
const orphaned = report.getOrphanedResources();
|
|
217
|
+
expect(orphaned).toHaveLength(1);
|
|
218
|
+
expect(orphaned[0]).toBe(orphanedResource);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should get missing resources', () => {
|
|
222
|
+
const missing = report.getMissingResources();
|
|
223
|
+
expect(missing).toHaveLength(1);
|
|
224
|
+
expect(missing[0]).toBe(missingResource);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should get drifted resources', () => {
|
|
228
|
+
const drifted = report.getDriftedResources();
|
|
229
|
+
expect(drifted).toHaveLength(1);
|
|
230
|
+
expect(drifted[0]).toBe(driftedResource);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should get healthy resources', () => {
|
|
234
|
+
const healthy = report.getHealthyResources();
|
|
235
|
+
expect(healthy).toHaveLength(1);
|
|
236
|
+
expect(healthy[0]).toBe(healthyResource);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should get resources in stack', () => {
|
|
240
|
+
const inStack = report.getResourcesInStack();
|
|
241
|
+
expect(inStack).toHaveLength(1);
|
|
242
|
+
expect(inStack[0]).toBe(healthyResource);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should get resource count', () => {
|
|
246
|
+
expect(report.getResourceCount()).toBe(4);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should get resource count by state', () => {
|
|
250
|
+
expect(report.getOrphanedResourceCount()).toBe(1);
|
|
251
|
+
expect(report.getMissingResourceCount()).toBe(1);
|
|
252
|
+
expect(report.getDriftedResourceCount()).toBe(1);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('issue queries', () => {
|
|
257
|
+
let report;
|
|
258
|
+
let criticalIssue1;
|
|
259
|
+
let criticalIssue2;
|
|
260
|
+
let warningIssue;
|
|
261
|
+
let infoIssue;
|
|
262
|
+
|
|
263
|
+
beforeEach(() => {
|
|
264
|
+
criticalIssue1 = Issue.orphanedResource({
|
|
265
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
266
|
+
resourceId: 'orphan-123',
|
|
267
|
+
description: 'Orphaned cluster',
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
criticalIssue2 = Issue.missingResource({
|
|
271
|
+
resourceType: 'AWS::KMS::Key',
|
|
272
|
+
resourceId: 'MissingKey',
|
|
273
|
+
description: 'Missing key',
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
warningIssue = new Issue({
|
|
277
|
+
type: 'PROPERTY_MISMATCH',
|
|
278
|
+
severity: 'warning',
|
|
279
|
+
resourceType: 'AWS::EC2::VPC',
|
|
280
|
+
resourceId: 'vpc-123',
|
|
281
|
+
description: 'Property mismatch',
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
infoIssue = new Issue({
|
|
285
|
+
type: 'MISSING_TAG',
|
|
286
|
+
severity: 'info',
|
|
287
|
+
resourceType: 'AWS::Lambda::Function',
|
|
288
|
+
resourceId: 'my-function',
|
|
289
|
+
description: 'Missing tag',
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
report = new StackHealthReport({
|
|
293
|
+
stackIdentifier: new StackIdentifier({
|
|
294
|
+
stackName: 'test',
|
|
295
|
+
region: 'us-east-1',
|
|
296
|
+
}),
|
|
297
|
+
healthScore: new HealthScore(60),
|
|
298
|
+
issues: [criticalIssue1, criticalIssue2, warningIssue, infoIssue],
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should get critical issues', () => {
|
|
303
|
+
const critical = report.getCriticalIssues();
|
|
304
|
+
expect(critical).toHaveLength(2);
|
|
305
|
+
expect(critical).toContain(criticalIssue1);
|
|
306
|
+
expect(critical).toContain(criticalIssue2);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should get warnings', () => {
|
|
310
|
+
const warnings = report.getWarnings();
|
|
311
|
+
expect(warnings).toHaveLength(1);
|
|
312
|
+
expect(warnings[0]).toBe(warningIssue);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should get info issues', () => {
|
|
316
|
+
const info = report.getInfoIssues();
|
|
317
|
+
expect(info).toHaveLength(1);
|
|
318
|
+
expect(info[0]).toBe(infoIssue);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should get issue count', () => {
|
|
322
|
+
expect(report.getIssueCount()).toBe(4);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should get issue count by severity', () => {
|
|
326
|
+
expect(report.getCriticalIssueCount()).toBe(2);
|
|
327
|
+
expect(report.getWarningCount()).toBe(1);
|
|
328
|
+
expect(report.getInfoIssueCount()).toBe(1);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should check if has critical issues', () => {
|
|
332
|
+
expect(report.hasCriticalIssues()).toBe(true);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should check if has no critical issues', () => {
|
|
336
|
+
const cleanReport = new StackHealthReport({
|
|
337
|
+
stackIdentifier: new StackIdentifier({
|
|
338
|
+
stackName: 'test',
|
|
339
|
+
region: 'us-east-1',
|
|
340
|
+
}),
|
|
341
|
+
healthScore: new HealthScore(90),
|
|
342
|
+
issues: [warningIssue],
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(cleanReport.hasCriticalIssues()).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
describe('health assessment', () => {
|
|
350
|
+
it('should be healthy with high score and no issues', () => {
|
|
351
|
+
const report = new StackHealthReport({
|
|
352
|
+
stackIdentifier: new StackIdentifier({
|
|
353
|
+
stackName: 'test',
|
|
354
|
+
region: 'us-east-1',
|
|
355
|
+
}),
|
|
356
|
+
healthScore: new HealthScore(85),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(report.isHealthy()).toBe(true);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should not be healthy with low score', () => {
|
|
363
|
+
const report = new StackHealthReport({
|
|
364
|
+
stackIdentifier: new StackIdentifier({
|
|
365
|
+
stackName: 'test',
|
|
366
|
+
region: 'us-east-1',
|
|
367
|
+
}),
|
|
368
|
+
healthScore: new HealthScore(70),
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
expect(report.isHealthy()).toBe(false);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('should get qualitative assessment from health score', () => {
|
|
375
|
+
const healthyReport = new StackHealthReport({
|
|
376
|
+
stackIdentifier: new StackIdentifier({
|
|
377
|
+
stackName: 'test',
|
|
378
|
+
region: 'us-east-1',
|
|
379
|
+
}),
|
|
380
|
+
healthScore: new HealthScore(90),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(healthyReport.getQualitativeAssessment()).toBe('healthy');
|
|
384
|
+
|
|
385
|
+
const degradedReport = new StackHealthReport({
|
|
386
|
+
stackIdentifier: new StackIdentifier({
|
|
387
|
+
stackName: 'test',
|
|
388
|
+
region: 'us-east-1',
|
|
389
|
+
}),
|
|
390
|
+
healthScore: new HealthScore(60),
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
expect(degradedReport.getQualitativeAssessment()).toBe('degraded');
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe('summary', () => {
|
|
398
|
+
it('should generate summary', () => {
|
|
399
|
+
const orphanedResource = new Resource({
|
|
400
|
+
logicalId: null,
|
|
401
|
+
physicalId: 'orphan-123',
|
|
402
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
403
|
+
state: ResourceState.ORPHANED,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const healthyResource = new Resource({
|
|
407
|
+
logicalId: 'MyVPC',
|
|
408
|
+
physicalId: 'vpc-123',
|
|
409
|
+
resourceType: 'AWS::EC2::VPC',
|
|
410
|
+
state: ResourceState.IN_STACK,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const criticalIssue = Issue.orphanedResource({
|
|
414
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
415
|
+
resourceId: 'orphan-123',
|
|
416
|
+
description: 'Orphaned cluster',
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const report = new StackHealthReport({
|
|
420
|
+
stackIdentifier: new StackIdentifier({
|
|
421
|
+
stackName: 'my-app-prod',
|
|
422
|
+
region: 'us-east-1',
|
|
423
|
+
}),
|
|
424
|
+
healthScore: new HealthScore(75),
|
|
425
|
+
resources: [orphanedResource, healthyResource],
|
|
426
|
+
issues: [criticalIssue],
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const summary = report.getSummary();
|
|
430
|
+
|
|
431
|
+
expect(summary).toEqual({
|
|
432
|
+
stackName: 'my-app-prod',
|
|
433
|
+
region: 'us-east-1',
|
|
434
|
+
healthScore: 75,
|
|
435
|
+
qualitativeAssessment: 'degraded',
|
|
436
|
+
isHealthy: false,
|
|
437
|
+
resourceCount: 2,
|
|
438
|
+
issueCount: 1,
|
|
439
|
+
criticalIssueCount: 1,
|
|
440
|
+
warningCount: 0,
|
|
441
|
+
orphanedResourceCount: 1,
|
|
442
|
+
missingResourceCount: 0,
|
|
443
|
+
driftedResourceCount: 0,
|
|
444
|
+
timestamp: report.timestamp.toISOString(),
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
describe('toString', () => {
|
|
450
|
+
it('should return string representation', () => {
|
|
451
|
+
const report = new StackHealthReport({
|
|
452
|
+
stackIdentifier: new StackIdentifier({
|
|
453
|
+
stackName: 'my-app-prod',
|
|
454
|
+
region: 'us-east-1',
|
|
455
|
+
accountId: '123456789012',
|
|
456
|
+
}),
|
|
457
|
+
healthScore: new HealthScore(85),
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const str = report.toString();
|
|
461
|
+
expect(str).toContain('StackHealthReport');
|
|
462
|
+
expect(str).toContain('my-app-prod');
|
|
463
|
+
expect(str).toContain('us-east-1');
|
|
464
|
+
expect(str).toContain('85');
|
|
465
|
+
expect(str).toContain('healthy');
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe('toJSON', () => {
|
|
470
|
+
it('should serialize to JSON', () => {
|
|
471
|
+
const stackId = new StackIdentifier({
|
|
472
|
+
stackName: 'my-app-prod',
|
|
473
|
+
region: 'us-east-1',
|
|
474
|
+
accountId: '123456789012',
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const resource = new Resource({
|
|
478
|
+
logicalId: 'MyVPC',
|
|
479
|
+
physicalId: 'vpc-123',
|
|
480
|
+
resourceType: 'AWS::EC2::VPC',
|
|
481
|
+
state: ResourceState.IN_STACK,
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const issue = Issue.orphanedResource({
|
|
485
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
486
|
+
resourceId: 'orphan-123',
|
|
487
|
+
description: 'Orphaned cluster',
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const timestamp = new Date('2024-01-15T10:30:00Z');
|
|
491
|
+
|
|
492
|
+
const report = new StackHealthReport({
|
|
493
|
+
stackIdentifier: stackId,
|
|
494
|
+
healthScore: new HealthScore(75),
|
|
495
|
+
resources: [resource],
|
|
496
|
+
issues: [issue],
|
|
497
|
+
timestamp,
|
|
498
|
+
metadata: { scanDuration: 5000 },
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const json = report.toJSON();
|
|
502
|
+
|
|
503
|
+
expect(json).toEqual({
|
|
504
|
+
stackIdentifier: stackId.toJSON(),
|
|
505
|
+
healthScore: 75,
|
|
506
|
+
qualitativeAssessment: 'degraded',
|
|
507
|
+
isHealthy: false,
|
|
508
|
+
resources: [resource.toJSON()],
|
|
509
|
+
issues: [issue.toJSON()],
|
|
510
|
+
resourceCount: 1,
|
|
511
|
+
issueCount: 1,
|
|
512
|
+
summary: report.getSummary(),
|
|
513
|
+
timestamp: '2024-01-15T10:30:00.000Z',
|
|
514
|
+
metadata: { scanDuration: 5000 },
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('should serialize empty report', () => {
|
|
519
|
+
const report = new StackHealthReport({
|
|
520
|
+
stackIdentifier: new StackIdentifier({
|
|
521
|
+
stackName: 'test',
|
|
522
|
+
region: 'us-east-1',
|
|
523
|
+
}),
|
|
524
|
+
healthScore: HealthScore.perfect(),
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const json = report.toJSON();
|
|
528
|
+
|
|
529
|
+
expect(json.resources).toEqual([]);
|
|
530
|
+
expect(json.issues).toEqual([]);
|
|
531
|
+
expect(json.healthScore).toBe(100);
|
|
532
|
+
expect(json.isHealthy).toBe(true);
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe('immutability', () => {
|
|
537
|
+
it('should copy resources array to prevent external mutation', () => {
|
|
538
|
+
const resources = [
|
|
539
|
+
new Resource({
|
|
540
|
+
logicalId: 'MyVPC',
|
|
541
|
+
physicalId: 'vpc-123',
|
|
542
|
+
resourceType: 'AWS::EC2::VPC',
|
|
543
|
+
state: ResourceState.IN_STACK,
|
|
544
|
+
}),
|
|
545
|
+
];
|
|
546
|
+
|
|
547
|
+
const report = new StackHealthReport({
|
|
548
|
+
stackIdentifier: new StackIdentifier({
|
|
549
|
+
stackName: 'test',
|
|
550
|
+
region: 'us-east-1',
|
|
551
|
+
}),
|
|
552
|
+
healthScore: new HealthScore(100),
|
|
553
|
+
resources,
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Mutate original array
|
|
557
|
+
resources.push(
|
|
558
|
+
new Resource({
|
|
559
|
+
logicalId: 'Another',
|
|
560
|
+
physicalId: 'another-123',
|
|
561
|
+
resourceType: 'AWS::S3::Bucket',
|
|
562
|
+
state: ResourceState.IN_STACK,
|
|
563
|
+
})
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
// Report should not be affected
|
|
567
|
+
expect(report.resources).toHaveLength(1);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('should copy issues array to prevent external mutation', () => {
|
|
571
|
+
const issues = [
|
|
572
|
+
Issue.orphanedResource({
|
|
573
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
574
|
+
resourceId: 'orphan-123',
|
|
575
|
+
description: 'Test',
|
|
576
|
+
}),
|
|
577
|
+
];
|
|
578
|
+
|
|
579
|
+
const report = new StackHealthReport({
|
|
580
|
+
stackIdentifier: new StackIdentifier({
|
|
581
|
+
stackName: 'test',
|
|
582
|
+
region: 'us-east-1',
|
|
583
|
+
}),
|
|
584
|
+
healthScore: new HealthScore(100),
|
|
585
|
+
issues,
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// Mutate original array
|
|
589
|
+
issues.push(
|
|
590
|
+
Issue.missingResource({
|
|
591
|
+
resourceType: 'AWS::KMS::Key',
|
|
592
|
+
resourceId: 'key-123',
|
|
593
|
+
description: 'Test',
|
|
594
|
+
})
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
// Report should not be affected
|
|
598
|
+
expect(report.issues).toHaveLength(1);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
});
|