@friggframework/devtools 2.0.0-next.45 → 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.
Files changed (212) hide show
  1. package/infrastructure/ARCHITECTURE.md +487 -0
  2. package/infrastructure/HEALTH.md +468 -0
  3. package/infrastructure/README.md +51 -0
  4. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  5. package/infrastructure/__tests__/template-generation.test.js +687 -0
  6. package/infrastructure/create-frigg-infrastructure.js +1 -1
  7. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  8. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  9. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  10. package/infrastructure/domains/database/aurora-builder.js +809 -0
  11. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  12. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  13. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  14. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  15. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  16. package/infrastructure/domains/database/migration-builder.js +633 -0
  17. package/infrastructure/domains/database/migration-builder.test.js +294 -0
  18. package/infrastructure/domains/database/migration-resolver.js +163 -0
  19. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  20. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  21. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  22. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  23. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  24. package/infrastructure/domains/health/application/ports/index.js +26 -0
  25. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  26. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  27. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  28. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  29. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  30. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  31. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  32. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  33. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  34. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  35. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  36. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  37. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  38. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  39. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  40. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  41. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  42. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  43. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  44. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  45. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  46. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  47. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  48. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  49. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  50. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  51. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  52. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  53. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  54. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  55. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  56. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  57. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  58. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  59. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  60. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  61. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  62. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  63. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  64. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  65. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  66. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  67. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  68. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  69. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  70. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  71. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  72. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  73. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  74. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  75. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  76. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  77. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  78. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  79. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  80. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  81. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  82. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  83. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  84. package/infrastructure/domains/integration/integration-builder.js +397 -0
  85. package/infrastructure/domains/integration/integration-builder.test.js +593 -0
  86. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  87. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  88. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  89. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  90. package/infrastructure/domains/networking/vpc-builder.js +1829 -0
  91. package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
  92. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  93. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  94. package/infrastructure/domains/networking/vpc-resolver.js +324 -0
  95. package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
  96. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  97. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  98. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  99. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  100. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  101. package/infrastructure/domains/security/kms-builder.js +366 -0
  102. package/infrastructure/domains/security/kms-builder.test.js +374 -0
  103. package/infrastructure/domains/security/kms-discovery.js +80 -0
  104. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  105. package/infrastructure/domains/security/kms-resolver.js +96 -0
  106. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  107. package/infrastructure/domains/shared/base-builder.js +112 -0
  108. package/infrastructure/domains/shared/base-resolver.js +186 -0
  109. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  110. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  111. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  112. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  113. package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
  114. package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
  115. package/infrastructure/domains/shared/environment-builder.js +119 -0
  116. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  117. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
  118. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
  119. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  120. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  121. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  122. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  123. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  124. package/infrastructure/domains/shared/resource-discovery.js +192 -0
  125. package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
  126. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  127. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  128. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  129. package/infrastructure/domains/shared/types/index.js +46 -0
  130. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  131. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  132. package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
  133. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  134. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  135. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  136. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  137. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  138. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
  139. package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
  140. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  141. package/infrastructure/esbuild.config.js +53 -0
  142. package/infrastructure/infrastructure-composer.js +87 -0
  143. package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
  144. package/infrastructure/scripts/build-prisma-layer.js +553 -0
  145. package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
  146. package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
  147. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  148. package/layers/prisma/nodejs/package.json +8 -0
  149. package/management-ui/server/utils/cliIntegration.js +1 -1
  150. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  151. package/package.json +11 -11
  152. package/frigg-cli/.eslintrc.js +0 -141
  153. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
  154. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
  155. package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
  156. package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
  157. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
  158. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
  159. package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
  160. package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
  161. package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
  162. package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
  163. package/frigg-cli/__tests__/utils/test-setup.js +0 -287
  164. package/frigg-cli/build-command/index.js +0 -65
  165. package/frigg-cli/db-setup-command/index.js +0 -193
  166. package/frigg-cli/deploy-command/index.js +0 -175
  167. package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
  168. package/frigg-cli/generate-command/azure-generator.js +0 -43
  169. package/frigg-cli/generate-command/gcp-generator.js +0 -47
  170. package/frigg-cli/generate-command/index.js +0 -332
  171. package/frigg-cli/generate-command/terraform-generator.js +0 -555
  172. package/frigg-cli/generate-iam-command.js +0 -118
  173. package/frigg-cli/index.js +0 -75
  174. package/frigg-cli/index.test.js +0 -158
  175. package/frigg-cli/init-command/backend-first-handler.js +0 -756
  176. package/frigg-cli/init-command/index.js +0 -93
  177. package/frigg-cli/init-command/template-handler.js +0 -143
  178. package/frigg-cli/install-command/backend-js.js +0 -33
  179. package/frigg-cli/install-command/commit-changes.js +0 -16
  180. package/frigg-cli/install-command/environment-variables.js +0 -127
  181. package/frigg-cli/install-command/environment-variables.test.js +0 -136
  182. package/frigg-cli/install-command/index.js +0 -54
  183. package/frigg-cli/install-command/install-package.js +0 -13
  184. package/frigg-cli/install-command/integration-file.js +0 -30
  185. package/frigg-cli/install-command/logger.js +0 -12
  186. package/frigg-cli/install-command/template.js +0 -90
  187. package/frigg-cli/install-command/validate-package.js +0 -75
  188. package/frigg-cli/jest.config.js +0 -124
  189. package/frigg-cli/package.json +0 -54
  190. package/frigg-cli/start-command/index.js +0 -149
  191. package/frigg-cli/start-command/start-command.test.js +0 -297
  192. package/frigg-cli/test/init-command.test.js +0 -180
  193. package/frigg-cli/test/npm-registry.test.js +0 -319
  194. package/frigg-cli/ui-command/index.js +0 -154
  195. package/frigg-cli/utils/app-resolver.js +0 -319
  196. package/frigg-cli/utils/backend-path.js +0 -25
  197. package/frigg-cli/utils/database-validator.js +0 -161
  198. package/frigg-cli/utils/error-messages.js +0 -257
  199. package/frigg-cli/utils/npm-registry.js +0 -167
  200. package/frigg-cli/utils/prisma-runner.js +0 -280
  201. package/frigg-cli/utils/process-manager.js +0 -199
  202. package/frigg-cli/utils/repo-detection.js +0 -405
  203. package/infrastructure/aws-discovery.js +0 -1176
  204. package/infrastructure/aws-discovery.test.js +0 -1220
  205. package/infrastructure/serverless-template.js +0 -2094
  206. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  207. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  208. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  209. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  210. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  211. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  212. /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
@@ -0,0 +1,380 @@
1
+ /**
2
+ * TDD Test for Percentage-Based Health Score Calculation
3
+ *
4
+ * PROBLEM: Current health score uses fixed penalties per issue:
5
+ * - Critical: 30 points
6
+ * - Warning: 10 points
7
+ * - Info: 5 points
8
+ *
9
+ * This causes misleading scores. Example from quo-integrations-dev:
10
+ * - 111 total resources
11
+ * - 16 Lambda functions with VPC config drift (32 warnings × 10 = 320 points)
12
+ * - Score: 0/100 (meaningless!)
13
+ *
14
+ * The 16 Lambdas with VPC drift are concerning but not catastrophic.
15
+ * VPC config drift on ALL Lambdas should score ~70/100, not 0/100.
16
+ *
17
+ * SOLUTION: Percentage-based scoring with resource criticality weighting:
18
+ *
19
+ * 1. Categorize resources by criticality:
20
+ * - Critical: Lambda, RDS, DynamoDB (affect application functionality)
21
+ * - Infrastructure: VPC, Subnet, SecurityGroup, KMS, etc.
22
+ *
23
+ * 2. Calculate impact percentages:
24
+ * - Critical impact % = critical issues / total resources
25
+ * - Functional drift % = functional drift / critical resources
26
+ * - Infra drift % = infra drift / infrastructure resources
27
+ *
28
+ * 3. Weighted penalties (max 100 points):
29
+ * - Critical issues: up to 50 points (orphaned, missing resources)
30
+ * - Functional drift: up to 30 points (drift on Lambda/RDS/DynamoDB)
31
+ * - Infrastructure drift: up to 20 points (drift on VPC/networking/KMS)
32
+ *
33
+ * EXAMPLES:
34
+ *
35
+ * Example 1: quo-integrations-dev
36
+ * - 111 resources (16 Lambda, 95 infrastructure)
37
+ * - 0 critical issues
38
+ * - 16 Lambdas with VPC drift = 100% functional drift
39
+ * - Penalty: (100% × 30) = 30 points
40
+ * - Score: 70/100 ✅ (was 0/100)
41
+ *
42
+ * Example 2: Stack with orphaned resources
43
+ * - 50 resources
44
+ * - 2 orphaned VPCs = 4% critical impact
45
+ * - Penalty: (4% × 50) = 2 points
46
+ * - Score: 98/100 ✅
47
+ *
48
+ * Example 3: Stack with missing Lambda
49
+ * - 10 resources (5 Lambda, 5 infrastructure)
50
+ * - 1 missing Lambda = 10% critical impact
51
+ * - Penalty: (10% × 50) = 5 points
52
+ * - Score: 95/100 ✅
53
+ *
54
+ * Example 4: Complete disaster
55
+ * - 20 resources (10 Lambda, 10 infrastructure)
56
+ * - 5 missing Lambdas = 25% critical impact
57
+ * - 5 Lambdas drifted = 50% functional drift
58
+ * - 10 infrastructure drifted = 100% infra drift
59
+ * - Penalty: (25% × 50) + (50% × 30) + (100% × 20) = 12.5 + 15 + 20 = 47.5
60
+ * - Score: 52.5/100 ✅ (reflects severity)
61
+ */
62
+
63
+ const HealthScoreCalculator = require('../health-score-calculator');
64
+ const HealthScore = require('../../value-objects/health-score');
65
+ const Issue = require('../../entities/issue');
66
+ const Resource = require('../../entities/resource');
67
+ const ResourceState = require('../../value-objects/resource-state');
68
+ const PropertyMismatch = require('../../entities/property-mismatch');
69
+ const PropertyMutability = require('../../value-objects/property-mutability');
70
+
71
+ describe('Percentage-Based Health Score Calculation (TDD)', () => {
72
+ let calculator;
73
+
74
+ beforeEach(() => {
75
+ calculator = new HealthScoreCalculator();
76
+ });
77
+
78
+ describe('Real-world scenario: quo-integrations-dev VPC drift', () => {
79
+ test('should score 70/100 for VPC config drift on all Lambdas (not 0/100)', () => {
80
+ // 111 resources: 16 Lambda functions, 95 infrastructure
81
+ const resources = [
82
+ // 16 Lambda functions with VPC drift
83
+ ...Array.from({ length: 16 }, (_, i) => ({
84
+ logicalId: `Lambda${i}`,
85
+ physicalId: `lambda-${i}`,
86
+ resourceType: 'AWS::Lambda::Function',
87
+ state: ResourceState.DRIFTED,
88
+ })),
89
+ // 95 infrastructure resources (in sync)
90
+ ...Array.from({ length: 95 }, (_, i) => ({
91
+ logicalId: `Infra${i}`,
92
+ physicalId: `infra-${i}`,
93
+ resourceType: 'AWS::EC2::SecurityGroup',
94
+ state: ResourceState.IN_STACK,
95
+ })),
96
+ ].map((r) => new Resource(r));
97
+
98
+ // 32 warnings: 2 per Lambda (SecurityGroupIds + SubnetIds drift)
99
+ const issues = Array.from({ length: 32 }, (_, i) => {
100
+ const lambdaIndex = Math.floor(i / 2);
101
+ const property = i % 2 === 0 ? 'VpcConfig.SecurityGroupIds' : 'VpcConfig.SubnetIds';
102
+
103
+ return Issue.propertyMismatch({
104
+ resourceType: 'AWS::Lambda::Function',
105
+ resourceId: `lambda-${lambdaIndex}`,
106
+ mismatch: new PropertyMismatch({
107
+ propertyPath: property,
108
+ expectedValue: 'expected',
109
+ actualValue: 'actual',
110
+ mutability: PropertyMutability.MUTABLE,
111
+ }),
112
+ });
113
+ });
114
+
115
+ // Act
116
+ const score = calculator.calculate({ resources, issues });
117
+
118
+ // Assert
119
+ // Functional drift: 16/16 = 100% of critical resources drifted
120
+ // Penalty: 100% × 30 = 30 points
121
+ // Score: 100 - 30 = 70
122
+ expect(score.value).toBe(70);
123
+ expect(score.isHealthy()).toBe(false); // Below 80 threshold
124
+ expect(score.isDegraded()).toBe(true); // 40-79 is degraded
125
+ });
126
+ });
127
+
128
+ describe('Critical issues (orphaned, missing resources)', () => {
129
+ test('should apply minimal penalty for small percentage of orphaned resources', () => {
130
+ // 50 resources, 2 orphaned VPCs
131
+ const resources = [
132
+ ...Array.from({ length: 48 }, (_, i) => ({
133
+ logicalId: `Resource${i}`,
134
+ physicalId: `resource-${i}`,
135
+ resourceType: 'AWS::Lambda::Function',
136
+ state: ResourceState.IN_STACK,
137
+ })),
138
+ new Resource({
139
+ logicalId: null,
140
+ physicalId: 'vpc-orphan-1',
141
+ resourceType: 'AWS::EC2::VPC',
142
+ state: ResourceState.ORPHANED,
143
+ }),
144
+ new Resource({
145
+ logicalId: null,
146
+ physicalId: 'vpc-orphan-2',
147
+ resourceType: 'AWS::EC2::VPC',
148
+ state: ResourceState.ORPHANED,
149
+ }),
150
+ ];
151
+
152
+ const issues = [
153
+ Issue.orphanedResource({
154
+ resourceType: 'AWS::EC2::VPC',
155
+ resourceId: 'vpc-orphan-1',
156
+ description: 'Orphaned VPC',
157
+ }),
158
+ Issue.orphanedResource({
159
+ resourceType: 'AWS::EC2::VPC',
160
+ resourceId: 'vpc-orphan-2',
161
+ description: 'Orphaned VPC',
162
+ }),
163
+ ];
164
+
165
+ const score = calculator.calculate({ resources, issues });
166
+
167
+ // Critical impact: 2/50 = 4%
168
+ // Penalty: 4% × 50 = 2 points
169
+ // Score: 100 - 2 = 98
170
+ expect(score.value).toBe(98);
171
+ expect(score.isHealthy()).toBe(true);
172
+ });
173
+
174
+ test('should apply significant penalty for high percentage of missing critical resources', () => {
175
+ // 10 resources: 5 Lambda, 5 infrastructure
176
+ // 2 Lambdas missing
177
+ const resources = [
178
+ ...Array.from({ length: 3 }, (_, i) => ({
179
+ logicalId: `Lambda${i}`,
180
+ physicalId: `lambda-${i}`,
181
+ resourceType: 'AWS::Lambda::Function',
182
+ state: ResourceState.IN_STACK,
183
+ })),
184
+ ...Array.from({ length: 2 }, (_, i) => ({
185
+ logicalId: `MissingLambda${i}`,
186
+ physicalId: `missing-MissingLambda${i}`,
187
+ resourceType: 'AWS::Lambda::Function',
188
+ state: ResourceState.MISSING,
189
+ })),
190
+ ...Array.from({ length: 5 }, (_, i) => ({
191
+ logicalId: `Infra${i}`,
192
+ physicalId: `infra-${i}`,
193
+ resourceType: 'AWS::EC2::VPC',
194
+ state: ResourceState.IN_STACK,
195
+ })),
196
+ ].map((r) => new Resource(r));
197
+
198
+ const issues = [
199
+ Issue.missingResource({
200
+ resourceType: 'AWS::Lambda::Function',
201
+ resourceId: 'MissingLambda0',
202
+ description: 'Lambda function missing',
203
+ }),
204
+ Issue.missingResource({
205
+ resourceType: 'AWS::Lambda::Function',
206
+ resourceId: 'MissingLambda1',
207
+ description: 'Lambda function missing',
208
+ }),
209
+ ];
210
+
211
+ const score = calculator.calculate({ resources, issues });
212
+
213
+ // Critical impact: 2/10 = 20%
214
+ // Penalty: 20% × 50 = 10 points
215
+ // Score: 100 - 10 = 90
216
+ expect(score.value).toBe(90);
217
+ expect(score.isHealthy()).toBe(true);
218
+ });
219
+ });
220
+
221
+ describe('Functional drift (Lambda, RDS, DynamoDB)', () => {
222
+ test('should weight functional drift higher than infrastructure drift', () => {
223
+ // 20 resources: 10 Lambda, 10 VPC
224
+ // 5 Lambdas drifted, 10 VPCs drifted
225
+ const resources = [
226
+ ...Array.from({ length: 5 }, (_, i) => ({
227
+ logicalId: `DriftedLambda${i}`,
228
+ physicalId: `lambda-${i}`,
229
+ resourceType: 'AWS::Lambda::Function',
230
+ state: ResourceState.DRIFTED,
231
+ })),
232
+ ...Array.from({ length: 5 }, (_, i) => ({
233
+ logicalId: `GoodLambda${i}`,
234
+ physicalId: `lambda-good-${i}`,
235
+ resourceType: 'AWS::Lambda::Function',
236
+ state: ResourceState.IN_STACK,
237
+ })),
238
+ ...Array.from({ length: 10 }, (_, i) => ({
239
+ logicalId: `DriftedVPC${i}`,
240
+ physicalId: `vpc-${i}`,
241
+ resourceType: 'AWS::EC2::VPC',
242
+ state: ResourceState.DRIFTED,
243
+ })),
244
+ ].map((r) => new Resource(r));
245
+
246
+ const issues = [
247
+ ...Array.from({ length: 5 }, (_, i) =>
248
+ Issue.propertyMismatch({
249
+ resourceType: 'AWS::Lambda::Function',
250
+ resourceId: `lambda-${i}`,
251
+ mismatch: new PropertyMismatch({
252
+ propertyPath: 'Environment',
253
+ expectedValue: 'expected',
254
+ actualValue: 'actual',
255
+ mutability: PropertyMutability.MUTABLE,
256
+ }),
257
+ })
258
+ ),
259
+ ...Array.from({ length: 10 }, (_, i) =>
260
+ Issue.propertyMismatch({
261
+ resourceType: 'AWS::EC2::VPC',
262
+ resourceId: `vpc-${i}`,
263
+ mismatch: new PropertyMismatch({
264
+ propertyPath: 'Tags',
265
+ expectedValue: 'expected',
266
+ actualValue: 'actual',
267
+ mutability: PropertyMutability.MUTABLE,
268
+ }),
269
+ })
270
+ ),
271
+ ];
272
+
273
+ const score = calculator.calculate({ resources, issues });
274
+
275
+ // Functional drift: 5/10 = 50% → penalty: 50% × 30 = 15 points
276
+ // Infra drift: 10/10 = 100% → penalty: 100% × 20 = 20 points
277
+ // Total penalty: 15 + 20 = 35 points
278
+ // Score: 100 - 35 = 65
279
+ expect(score.value).toBe(65);
280
+ expect(score.isHealthy()).toBe(false); // Below 80 threshold
281
+ expect(score.isDegraded()).toBe(true); // 40-79 is degraded
282
+ });
283
+ });
284
+
285
+ describe('Complete disaster scenario', () => {
286
+ test('should reflect severity when everything is broken', () => {
287
+ // 20 resources: 10 Lambda, 10 infrastructure
288
+ // 5 Lambdas missing, 5 Lambdas drifted, 10 infrastructure drifted
289
+ const resources = [
290
+ ...Array.from({ length: 5 }, (_, i) => ({
291
+ logicalId: `MissingLambda${i}`,
292
+ physicalId: `missing-MissingLambda${i}`,
293
+ resourceType: 'AWS::Lambda::Function',
294
+ state: ResourceState.MISSING,
295
+ })),
296
+ ...Array.from({ length: 5 }, (_, i) => ({
297
+ logicalId: `DriftedLambda${i}`,
298
+ physicalId: `lambda-${i}`,
299
+ resourceType: 'AWS::Lambda::Function',
300
+ state: ResourceState.DRIFTED,
301
+ })),
302
+ ...Array.from({ length: 10 }, (_, i) => ({
303
+ logicalId: `DriftedVPC${i}`,
304
+ physicalId: `vpc-${i}`,
305
+ resourceType: 'AWS::EC2::VPC',
306
+ state: ResourceState.DRIFTED,
307
+ })),
308
+ ].map((r) => new Resource(r));
309
+
310
+ const issues = [
311
+ ...Array.from({ length: 5 }, (_, i) =>
312
+ Issue.missingResource({
313
+ resourceType: 'AWS::Lambda::Function',
314
+ resourceId: `MissingLambda${i}`,
315
+ description: 'Missing Lambda',
316
+ })
317
+ ),
318
+ ...Array.from({ length: 5 }, (_, i) =>
319
+ Issue.propertyMismatch({
320
+ resourceType: 'AWS::Lambda::Function',
321
+ resourceId: `lambda-${i}`,
322
+ mismatch: new PropertyMismatch({
323
+ propertyPath: 'Environment',
324
+ expectedValue: 'expected',
325
+ actualValue: 'actual',
326
+ mutability: PropertyMutability.MUTABLE,
327
+ }),
328
+ })
329
+ ),
330
+ ...Array.from({ length: 10 }, (_, i) =>
331
+ Issue.propertyMismatch({
332
+ resourceType: 'AWS::EC2::VPC',
333
+ resourceId: `vpc-${i}`,
334
+ mismatch: new PropertyMismatch({
335
+ propertyPath: 'Tags',
336
+ expectedValue: 'expected',
337
+ actualValue: 'actual',
338
+ mutability: PropertyMutability.MUTABLE,
339
+ }),
340
+ })
341
+ ),
342
+ ];
343
+
344
+ const score = calculator.calculate({ resources, issues });
345
+
346
+ // Critical impact: 5/20 = 25% → penalty: 25% × 50 = 12.5 points
347
+ // Functional drift: 5/10 = 50% → penalty: 50% × 30 = 15 points
348
+ // Infra drift: 10/10 = 100% → penalty: 100% × 20 = 20 points
349
+ // Total penalty: 12.5 + 15 + 20 = 47.5 points
350
+ // Score: 100 - 47.5 = 52.5 (rounded to 53)
351
+ expect(score.value).toBeGreaterThanOrEqual(52);
352
+ expect(score.value).toBeLessThanOrEqual(53);
353
+ expect(score.isHealthy()).toBe(false); // Below 80 threshold
354
+ expect(score.isDegraded()).toBe(true); // 40-79 is degraded
355
+ });
356
+ });
357
+
358
+ describe('Edge cases', () => {
359
+ test('should handle stack with no resources gracefully', () => {
360
+ const score = calculator.calculate({ resources: [], issues: [] });
361
+
362
+ // No resources, no issues = perfect score
363
+ expect(score.value).toBe(100);
364
+ });
365
+
366
+ test('should handle stack with no issues', () => {
367
+ const resources = Array.from({ length: 10 }, (_, i) => ({
368
+ logicalId: `Resource${i}`,
369
+ physicalId: `resource-${i}`,
370
+ resourceType: 'AWS::Lambda::Function',
371
+ state: ResourceState.IN_STACK,
372
+ })).map((r) => new Resource(r));
373
+
374
+ const score = calculator.calculate({ resources, issues: [] });
375
+
376
+ // No issues = perfect score
377
+ expect(score.value).toBe(100);
378
+ });
379
+ });
380
+ });