@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,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Issue Entity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const Issue = require('./issue');
|
|
6
|
+
const ResourceState = require('../value-objects/resource-state');
|
|
7
|
+
const PropertyMismatch = require('./property-mismatch');
|
|
8
|
+
const PropertyMutability = require('../value-objects/property-mutability');
|
|
9
|
+
|
|
10
|
+
describe('Issue', () => {
|
|
11
|
+
describe('constructor', () => {
|
|
12
|
+
it('should create an orphaned resource issue', () => {
|
|
13
|
+
const issue = new Issue({
|
|
14
|
+
type: 'ORPHANED_RESOURCE',
|
|
15
|
+
severity: 'critical',
|
|
16
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
17
|
+
resourceId: 'my-app-prod-aurora',
|
|
18
|
+
description: 'Aurora cluster exists in AWS but not managed by CloudFormation',
|
|
19
|
+
resolution: 'Import resource into CloudFormation stack',
|
|
20
|
+
canAutoFix: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(issue.type).toBe('ORPHANED_RESOURCE');
|
|
24
|
+
expect(issue.severity).toBe('critical');
|
|
25
|
+
expect(issue.resourceType).toBe('AWS::RDS::DBCluster');
|
|
26
|
+
expect(issue.resourceId).toBe('my-app-prod-aurora');
|
|
27
|
+
expect(issue.description).toBe('Aurora cluster exists in AWS but not managed by CloudFormation');
|
|
28
|
+
expect(issue.resolution).toBe('Import resource into CloudFormation stack');
|
|
29
|
+
expect(issue.canAutoFix).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should create a missing resource issue', () => {
|
|
33
|
+
const issue = new Issue({
|
|
34
|
+
type: 'MISSING_RESOURCE',
|
|
35
|
+
severity: 'critical',
|
|
36
|
+
resourceType: 'AWS::KMS::Key',
|
|
37
|
+
resourceId: 'FriggKMSKey',
|
|
38
|
+
description: 'KMS key defined in stack but does not exist in AWS',
|
|
39
|
+
resolution: 'Verify resource was not manually deleted',
|
|
40
|
+
canAutoFix: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(issue.type).toBe('MISSING_RESOURCE');
|
|
44
|
+
expect(issue.severity).toBe('critical');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should create a property mismatch issue', () => {
|
|
48
|
+
const mismatch = new PropertyMismatch({
|
|
49
|
+
propertyPath: 'Properties.Tags',
|
|
50
|
+
expectedValue: [{ Key: 'Environment', Value: 'production' }],
|
|
51
|
+
actualValue: [{ Key: 'Env', Value: 'prod' }],
|
|
52
|
+
mutability: PropertyMutability.MUTABLE,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const issue = new Issue({
|
|
56
|
+
type: 'PROPERTY_MISMATCH',
|
|
57
|
+
severity: 'warning',
|
|
58
|
+
resourceType: 'AWS::EC2::VPC',
|
|
59
|
+
resourceId: 'vpc-0abc123',
|
|
60
|
+
description: 'VPC tags differ from expected configuration',
|
|
61
|
+
resolution: 'Update VPC tags to match desired state',
|
|
62
|
+
canAutoFix: true,
|
|
63
|
+
propertyMismatch: mismatch,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(issue.type).toBe('PROPERTY_MISMATCH');
|
|
67
|
+
expect(issue.propertyMismatch).toBe(mismatch);
|
|
68
|
+
expect(issue.canAutoFix).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should require type', () => {
|
|
72
|
+
expect(() => {
|
|
73
|
+
new Issue({
|
|
74
|
+
severity: 'critical',
|
|
75
|
+
resourceType: 'AWS::S3::Bucket',
|
|
76
|
+
resourceId: 'my-bucket',
|
|
77
|
+
description: 'Test',
|
|
78
|
+
resolution: 'Fix it',
|
|
79
|
+
});
|
|
80
|
+
}).toThrow('type is required');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should require severity', () => {
|
|
84
|
+
expect(() => {
|
|
85
|
+
new Issue({
|
|
86
|
+
type: 'ORPHANED_RESOURCE',
|
|
87
|
+
resourceType: 'AWS::S3::Bucket',
|
|
88
|
+
resourceId: 'my-bucket',
|
|
89
|
+
description: 'Test',
|
|
90
|
+
resolution: 'Fix it',
|
|
91
|
+
});
|
|
92
|
+
}).toThrow('severity is required');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should require resourceType', () => {
|
|
96
|
+
expect(() => {
|
|
97
|
+
new Issue({
|
|
98
|
+
type: 'ORPHANED_RESOURCE',
|
|
99
|
+
severity: 'critical',
|
|
100
|
+
resourceId: 'my-bucket',
|
|
101
|
+
description: 'Test',
|
|
102
|
+
resolution: 'Fix it',
|
|
103
|
+
});
|
|
104
|
+
}).toThrow('resourceType is required');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should require resourceId', () => {
|
|
108
|
+
expect(() => {
|
|
109
|
+
new Issue({
|
|
110
|
+
type: 'ORPHANED_RESOURCE',
|
|
111
|
+
severity: 'critical',
|
|
112
|
+
resourceType: 'AWS::S3::Bucket',
|
|
113
|
+
description: 'Test',
|
|
114
|
+
resolution: 'Fix it',
|
|
115
|
+
});
|
|
116
|
+
}).toThrow('resourceId is required');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should require description', () => {
|
|
120
|
+
expect(() => {
|
|
121
|
+
new Issue({
|
|
122
|
+
type: 'ORPHANED_RESOURCE',
|
|
123
|
+
severity: 'critical',
|
|
124
|
+
resourceType: 'AWS::S3::Bucket',
|
|
125
|
+
resourceId: 'my-bucket',
|
|
126
|
+
resolution: 'Fix it',
|
|
127
|
+
});
|
|
128
|
+
}).toThrow('description is required');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should validate issue type', () => {
|
|
132
|
+
expect(() => {
|
|
133
|
+
new Issue({
|
|
134
|
+
type: 'INVALID_TYPE',
|
|
135
|
+
severity: 'critical',
|
|
136
|
+
resourceType: 'AWS::S3::Bucket',
|
|
137
|
+
resourceId: 'my-bucket',
|
|
138
|
+
description: 'Test',
|
|
139
|
+
resolution: 'Fix it',
|
|
140
|
+
});
|
|
141
|
+
}).toThrow('Invalid issue type: INVALID_TYPE');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should validate severity', () => {
|
|
145
|
+
expect(() => {
|
|
146
|
+
new Issue({
|
|
147
|
+
type: 'ORPHANED_RESOURCE',
|
|
148
|
+
severity: 'invalid',
|
|
149
|
+
resourceType: 'AWS::S3::Bucket',
|
|
150
|
+
resourceId: 'my-bucket',
|
|
151
|
+
description: 'Test',
|
|
152
|
+
resolution: 'Fix it',
|
|
153
|
+
});
|
|
154
|
+
}).toThrow('Invalid severity: invalid');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should default canAutoFix to false', () => {
|
|
158
|
+
const issue = new Issue({
|
|
159
|
+
type: 'ORPHANED_RESOURCE',
|
|
160
|
+
severity: 'critical',
|
|
161
|
+
resourceType: 'AWS::S3::Bucket',
|
|
162
|
+
resourceId: 'my-bucket',
|
|
163
|
+
description: 'Test',
|
|
164
|
+
resolution: 'Fix it',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(issue.canAutoFix).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('type checks', () => {
|
|
172
|
+
it('should check if issue is orphaned resource', () => {
|
|
173
|
+
const issue = new Issue({
|
|
174
|
+
type: 'ORPHANED_RESOURCE',
|
|
175
|
+
severity: 'critical',
|
|
176
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
177
|
+
resourceId: 'my-cluster',
|
|
178
|
+
description: 'Test',
|
|
179
|
+
resolution: 'Import',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(issue.isOrphanedResource()).toBe(true);
|
|
183
|
+
expect(issue.isMissingResource()).toBe(false);
|
|
184
|
+
expect(issue.isPropertyMismatch()).toBe(false);
|
|
185
|
+
expect(issue.isDrifted()).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should check if issue is missing resource', () => {
|
|
189
|
+
const issue = new Issue({
|
|
190
|
+
type: 'MISSING_RESOURCE',
|
|
191
|
+
severity: 'critical',
|
|
192
|
+
resourceType: 'AWS::KMS::Key',
|
|
193
|
+
resourceId: 'FriggKMSKey',
|
|
194
|
+
description: 'Test',
|
|
195
|
+
resolution: 'Verify deletion',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(issue.isOrphanedResource()).toBe(false);
|
|
199
|
+
expect(issue.isMissingResource()).toBe(true);
|
|
200
|
+
expect(issue.isPropertyMismatch()).toBe(false);
|
|
201
|
+
expect(issue.isDrifted()).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should check if issue is property mismatch', () => {
|
|
205
|
+
const issue = new Issue({
|
|
206
|
+
type: 'PROPERTY_MISMATCH',
|
|
207
|
+
severity: 'warning',
|
|
208
|
+
resourceType: 'AWS::EC2::VPC',
|
|
209
|
+
resourceId: 'vpc-123',
|
|
210
|
+
description: 'Test',
|
|
211
|
+
resolution: 'Update',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(issue.isOrphanedResource()).toBe(false);
|
|
215
|
+
expect(issue.isMissingResource()).toBe(false);
|
|
216
|
+
expect(issue.isPropertyMismatch()).toBe(true);
|
|
217
|
+
expect(issue.isDrifted()).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should check if issue is drifted', () => {
|
|
221
|
+
const issue = new Issue({
|
|
222
|
+
type: 'DRIFTED_RESOURCE',
|
|
223
|
+
severity: 'warning',
|
|
224
|
+
resourceType: 'AWS::S3::Bucket',
|
|
225
|
+
resourceId: 'my-bucket',
|
|
226
|
+
description: 'Test',
|
|
227
|
+
resolution: 'Reconcile',
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(issue.isOrphanedResource()).toBe(false);
|
|
231
|
+
expect(issue.isMissingResource()).toBe(false);
|
|
232
|
+
expect(issue.isPropertyMismatch()).toBe(false);
|
|
233
|
+
expect(issue.isDrifted()).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('severity checks', () => {
|
|
238
|
+
it('should check if issue is critical', () => {
|
|
239
|
+
const issue = new Issue({
|
|
240
|
+
type: 'ORPHANED_RESOURCE',
|
|
241
|
+
severity: 'critical',
|
|
242
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
243
|
+
resourceId: 'my-cluster',
|
|
244
|
+
description: 'Test',
|
|
245
|
+
resolution: 'Import',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(issue.isCritical()).toBe(true);
|
|
249
|
+
expect(issue.isWarning()).toBe(false);
|
|
250
|
+
expect(issue.isInfo()).toBe(false);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should check if issue is warning', () => {
|
|
254
|
+
const issue = new Issue({
|
|
255
|
+
type: 'PROPERTY_MISMATCH',
|
|
256
|
+
severity: 'warning',
|
|
257
|
+
resourceType: 'AWS::EC2::VPC',
|
|
258
|
+
resourceId: 'vpc-123',
|
|
259
|
+
description: 'Test',
|
|
260
|
+
resolution: 'Update',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
expect(issue.isCritical()).toBe(false);
|
|
264
|
+
expect(issue.isWarning()).toBe(true);
|
|
265
|
+
expect(issue.isInfo()).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should check if issue is info', () => {
|
|
269
|
+
const issue = new Issue({
|
|
270
|
+
type: 'MISSING_TAG',
|
|
271
|
+
severity: 'info',
|
|
272
|
+
resourceType: 'AWS::Lambda::Function',
|
|
273
|
+
resourceId: 'my-function',
|
|
274
|
+
description: 'Test',
|
|
275
|
+
resolution: 'Add tag',
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(issue.isCritical()).toBe(false);
|
|
279
|
+
expect(issue.isWarning()).toBe(false);
|
|
280
|
+
expect(issue.isInfo()).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('toString', () => {
|
|
285
|
+
it('should return string representation', () => {
|
|
286
|
+
const issue = new Issue({
|
|
287
|
+
type: 'ORPHANED_RESOURCE',
|
|
288
|
+
severity: 'critical',
|
|
289
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
290
|
+
resourceId: 'my-app-prod-aurora',
|
|
291
|
+
description: 'Aurora cluster exists in AWS but not managed by CloudFormation',
|
|
292
|
+
resolution: 'Import resource into CloudFormation stack',
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const str = issue.toString();
|
|
296
|
+
expect(str).toContain('ORPHANED_RESOURCE');
|
|
297
|
+
expect(str).toContain('critical');
|
|
298
|
+
expect(str).toContain('AWS::RDS::DBCluster');
|
|
299
|
+
expect(str).toContain('my-app-prod-aurora');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('toJSON', () => {
|
|
304
|
+
it('should serialize to JSON', () => {
|
|
305
|
+
const issue = new Issue({
|
|
306
|
+
type: 'ORPHANED_RESOURCE',
|
|
307
|
+
severity: 'critical',
|
|
308
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
309
|
+
resourceId: 'my-app-prod-aurora',
|
|
310
|
+
description: 'Aurora cluster exists in AWS but not managed by CloudFormation',
|
|
311
|
+
resolution: 'Import resource into CloudFormation stack',
|
|
312
|
+
canAutoFix: true,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const json = issue.toJSON();
|
|
316
|
+
|
|
317
|
+
expect(json).toEqual({
|
|
318
|
+
type: 'ORPHANED_RESOURCE',
|
|
319
|
+
severity: 'critical',
|
|
320
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
321
|
+
resourceId: 'my-app-prod-aurora',
|
|
322
|
+
description: 'Aurora cluster exists in AWS but not managed by CloudFormation',
|
|
323
|
+
resolution: 'Import resource into CloudFormation stack',
|
|
324
|
+
canAutoFix: true,
|
|
325
|
+
propertyMismatch: null,
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should include property mismatch in JSON', () => {
|
|
330
|
+
const mismatch = new PropertyMismatch({
|
|
331
|
+
propertyPath: 'Properties.Tags',
|
|
332
|
+
expectedValue: [{ Key: 'Env', Value: 'prod' }],
|
|
333
|
+
actualValue: [{ Key: 'Env', Value: 'dev' }],
|
|
334
|
+
mutability: PropertyMutability.MUTABLE,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const issue = new Issue({
|
|
338
|
+
type: 'PROPERTY_MISMATCH',
|
|
339
|
+
severity: 'warning',
|
|
340
|
+
resourceType: 'AWS::EC2::VPC',
|
|
341
|
+
resourceId: 'vpc-123',
|
|
342
|
+
description: 'VPC tags differ',
|
|
343
|
+
resolution: 'Update tags',
|
|
344
|
+
canAutoFix: true,
|
|
345
|
+
propertyMismatch: mismatch,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const json = issue.toJSON();
|
|
349
|
+
|
|
350
|
+
expect(json.propertyMismatch).toBeDefined();
|
|
351
|
+
expect(json.propertyMismatch.propertyPath).toBe('Properties.Tags');
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe('static factory methods', () => {
|
|
356
|
+
it('should create orphaned resource issue', () => {
|
|
357
|
+
const issue = Issue.orphanedResource({
|
|
358
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
359
|
+
resourceId: 'my-cluster',
|
|
360
|
+
description: 'Cluster not in stack',
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(issue.type).toBe('ORPHANED_RESOURCE');
|
|
364
|
+
expect(issue.severity).toBe('critical');
|
|
365
|
+
expect(issue.canAutoFix).toBe(true);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should create missing resource issue', () => {
|
|
369
|
+
const issue = Issue.missingResource({
|
|
370
|
+
resourceType: 'AWS::KMS::Key',
|
|
371
|
+
resourceId: 'FriggKMSKey',
|
|
372
|
+
description: 'Key not in AWS',
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(issue.type).toBe('MISSING_RESOURCE');
|
|
376
|
+
expect(issue.severity).toBe('critical');
|
|
377
|
+
expect(issue.canAutoFix).toBe(false);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should create property mismatch issue', () => {
|
|
381
|
+
const mismatch = new PropertyMismatch({
|
|
382
|
+
propertyPath: 'Properties.Tags',
|
|
383
|
+
expectedValue: ['tag1'],
|
|
384
|
+
actualValue: ['tag2'],
|
|
385
|
+
mutability: PropertyMutability.MUTABLE,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const issue = Issue.propertyMismatch({
|
|
389
|
+
resourceType: 'AWS::EC2::VPC',
|
|
390
|
+
resourceId: 'vpc-123',
|
|
391
|
+
mismatch,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(issue.type).toBe('PROPERTY_MISMATCH');
|
|
395
|
+
expect(issue.severity).toBe('warning'); // Default for mutable
|
|
396
|
+
expect(issue.propertyMismatch).toBe(mismatch);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should create property mismatch with critical severity for immutable', () => {
|
|
400
|
+
const mismatch = new PropertyMismatch({
|
|
401
|
+
propertyPath: 'Properties.BucketName',
|
|
402
|
+
expectedValue: 'bucket-v2',
|
|
403
|
+
actualValue: 'bucket-v1',
|
|
404
|
+
mutability: PropertyMutability.IMMUTABLE,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const issue = Issue.propertyMismatch({
|
|
408
|
+
resourceType: 'AWS::S3::Bucket',
|
|
409
|
+
resourceId: 'my-bucket',
|
|
410
|
+
mismatch,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(issue.severity).toBe('critical'); // Critical for immutable
|
|
414
|
+
expect(issue.canAutoFix).toBe(false); // Can't auto-fix immutable
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe('_formatValue', () => {
|
|
419
|
+
it('should format primitive values', () => {
|
|
420
|
+
expect(Issue._formatValue('test')).toBe('test');
|
|
421
|
+
expect(Issue._formatValue(123)).toBe('123');
|
|
422
|
+
expect(Issue._formatValue(true)).toBe('true');
|
|
423
|
+
expect(Issue._formatValue(null)).toBe('null');
|
|
424
|
+
expect(Issue._formatValue(undefined)).toBe('undefined');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('should format simple arrays', () => {
|
|
428
|
+
expect(Issue._formatValue(['a', 'b', 'c'])).toBe('["a","b","c"]');
|
|
429
|
+
expect(Issue._formatValue([1, 2, 3])).toBe('[1,2,3]');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should format arrays of objects (like Tags)', () => {
|
|
433
|
+
const tags = [
|
|
434
|
+
{ Key: 'Name', Value: 'test' },
|
|
435
|
+
{ Key: 'Environment', Value: 'prod' },
|
|
436
|
+
];
|
|
437
|
+
const result = Issue._formatValue(tags);
|
|
438
|
+
expect(result).toContain('Key');
|
|
439
|
+
expect(result).toContain('Name');
|
|
440
|
+
expect(result).toContain('test');
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should truncate long arrays of objects', () => {
|
|
444
|
+
const manyTags = [
|
|
445
|
+
{ Key: 'Tag1', Value: 'val1' },
|
|
446
|
+
{ Key: 'Tag2', Value: 'val2' },
|
|
447
|
+
{ Key: 'Tag3', Value: 'val3' },
|
|
448
|
+
{ Key: 'Tag4', Value: 'val4' },
|
|
449
|
+
];
|
|
450
|
+
const result = Issue._formatValue(manyTags);
|
|
451
|
+
expect(result).toContain('4 total');
|
|
452
|
+
expect(result).toContain('...');
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('should format small objects', () => {
|
|
456
|
+
const obj = { a: 1, b: 2 };
|
|
457
|
+
expect(Issue._formatValue(obj)).toBe('{"a":1,"b":2}');
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should truncate large objects', () => {
|
|
461
|
+
const largeObj = { a: 1, b: 2, c: 3, d: 4, e: 5 };
|
|
462
|
+
const result = Issue._formatValue(largeObj);
|
|
463
|
+
expect(result).toContain('5 keys total');
|
|
464
|
+
expect(result).toContain('...');
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should format empty collections', () => {
|
|
468
|
+
expect(Issue._formatValue([])).toBe('[]');
|
|
469
|
+
expect(Issue._formatValue({})).toBe('{}');
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe('propertyMismatch with formatted values', () => {
|
|
474
|
+
it('should format Tags arrays correctly in description', () => {
|
|
475
|
+
const mismatch = new PropertyMismatch({
|
|
476
|
+
propertyPath: 'Properties.Tags',
|
|
477
|
+
expectedValue: [
|
|
478
|
+
{ Key: 'Name', Value: 'test' },
|
|
479
|
+
{ Key: 'Environment', Value: 'prod' },
|
|
480
|
+
],
|
|
481
|
+
actualValue: [
|
|
482
|
+
{ Key: 'Name', Value: 'test' },
|
|
483
|
+
{ Key: 'Environment', Value: 'prod' },
|
|
484
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
485
|
+
],
|
|
486
|
+
mutability: PropertyMutability.MUTABLE,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const issue = Issue.propertyMismatch({
|
|
490
|
+
resourceType: 'AWS::EC2::Subnet',
|
|
491
|
+
resourceId: 'subnet-123',
|
|
492
|
+
mismatch,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Should not contain [object Object]
|
|
496
|
+
expect(issue.description).not.toContain('[object Object]');
|
|
497
|
+
// Should contain JSON representation
|
|
498
|
+
expect(issue.description).toContain('Key');
|
|
499
|
+
expect(issue.description).toContain('Name');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('should handle complex nested objects', () => {
|
|
503
|
+
const mismatch = new PropertyMismatch({
|
|
504
|
+
propertyPath: 'Properties.VpcConfig',
|
|
505
|
+
expectedValue: {
|
|
506
|
+
SubnetIds: ['subnet-1', 'subnet-2'],
|
|
507
|
+
SecurityGroupIds: ['sg-1'],
|
|
508
|
+
},
|
|
509
|
+
actualValue: {
|
|
510
|
+
SubnetIds: ['subnet-3', 'subnet-4'],
|
|
511
|
+
SecurityGroupIds: ['sg-2'],
|
|
512
|
+
},
|
|
513
|
+
mutability: PropertyMutability.MUTABLE,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const issue = Issue.propertyMismatch({
|
|
517
|
+
resourceType: 'AWS::Lambda::Function',
|
|
518
|
+
resourceId: 'my-function',
|
|
519
|
+
mismatch,
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// Should not contain [object Object]
|
|
523
|
+
expect(issue.description).not.toContain('[object Object]');
|
|
524
|
+
// Should contain structured representation
|
|
525
|
+
expect(issue.description).toContain('SubnetIds');
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PropertyMismatch Entity
|
|
3
|
+
*
|
|
4
|
+
* Represents a difference between expected and actual property values
|
|
5
|
+
* for a CloudFormation resource.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const PropertyMutability = require('../value-objects/property-mutability');
|
|
9
|
+
|
|
10
|
+
class PropertyMismatch {
|
|
11
|
+
/**
|
|
12
|
+
* Create a new PropertyMismatch
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} params
|
|
15
|
+
* @param {string} params.propertyPath - Path to the property (e.g., 'Properties.BucketName')
|
|
16
|
+
* @param {*} params.expectedValue - Expected property value
|
|
17
|
+
* @param {*} params.actualValue - Actual property value
|
|
18
|
+
* @param {PropertyMutability} params.mutability - Property mutability
|
|
19
|
+
*/
|
|
20
|
+
constructor({ propertyPath, expectedValue, actualValue, mutability }) {
|
|
21
|
+
// Validate required fields
|
|
22
|
+
if (!propertyPath) {
|
|
23
|
+
throw new Error('propertyPath is required');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Note: expectedValue and actualValue can be undefined (for missing properties)
|
|
27
|
+
// They can also be null (explicit null value)
|
|
28
|
+
// Only check if they're provided in the params object at all
|
|
29
|
+
if (!('expectedValue' in arguments[0])) {
|
|
30
|
+
throw new Error('expectedValue must be provided (can be null or undefined)');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!('actualValue' in arguments[0])) {
|
|
34
|
+
throw new Error('actualValue must be provided (can be null or undefined)');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!mutability) {
|
|
38
|
+
throw new Error('mutability is required');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!(mutability instanceof PropertyMutability)) {
|
|
42
|
+
throw new Error('mutability must be a PropertyMutability instance');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.propertyPath = propertyPath;
|
|
46
|
+
this.expectedValue = expectedValue;
|
|
47
|
+
this.actualValue = actualValue;
|
|
48
|
+
this.mutability = mutability;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if fixing this mismatch requires resource replacement
|
|
53
|
+
*
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
requiresReplacement() {
|
|
57
|
+
return this.mutability.requiresReplacement();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if this mismatch can be automatically fixed
|
|
62
|
+
*
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
canAutoFix() {
|
|
66
|
+
return this.mutability.canChange();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get severity level of this mismatch
|
|
71
|
+
*
|
|
72
|
+
* @returns {'critical' | 'warning'}
|
|
73
|
+
*/
|
|
74
|
+
getSeverity() {
|
|
75
|
+
return this.mutability.isImmutable() ? 'critical' : 'warning';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get string representation
|
|
80
|
+
*
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
toString() {
|
|
84
|
+
const expectedStr = this.expectedValue === null ? 'null' : this.expectedValue;
|
|
85
|
+
const actualStr = this.actualValue === null ? 'null' : this.actualValue;
|
|
86
|
+
|
|
87
|
+
return `PropertyMismatch: ${this.propertyPath} (expected: ${expectedStr}, actual: ${actualStr}, mutability: ${this.mutability.toString()})`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Serialize to JSON
|
|
92
|
+
*
|
|
93
|
+
* @returns {Object}
|
|
94
|
+
*/
|
|
95
|
+
toJSON() {
|
|
96
|
+
return {
|
|
97
|
+
propertyPath: this.propertyPath,
|
|
98
|
+
expectedValue: this.expectedValue,
|
|
99
|
+
actualValue: this.actualValue,
|
|
100
|
+
mutability: this.mutability.toString(),
|
|
101
|
+
severity: this.getSeverity(),
|
|
102
|
+
canAutoFix: this.canAutoFix(),
|
|
103
|
+
requiresReplacement: this.requiresReplacement(),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = PropertyMismatch;
|