@friggframework/devtools 2.0.0-next.6 → 2.0.0-next.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frigg-cli/README.md +1289 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +649 -0
- package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +397 -0
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +345 -0
- package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +287 -0
- package/frigg-cli/build-command/index.js +53 -14
- package/frigg-cli/db-setup-command/index.js +246 -0
- package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
- package/frigg-cli/deploy-command/index.js +295 -17
- package/frigg-cli/doctor-command/index.js +335 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +118 -0
- package/frigg-cli/index.js +142 -1
- package/frigg-cli/index.test.js +1 -4
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/index.js +1 -4
- package/frigg-cli/jest.config.js +124 -0
- package/frigg-cli/package.json +63 -0
- package/frigg-cli/repair-command/index.js +564 -0
- package/frigg-cli/start-command/index.js +125 -6
- package/frigg-cli/start-command/start-command.test.js +297 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +16 -17
- package/frigg-cli/utils/database-validator.js +167 -0
- package/frigg-cli/utils/error-messages.js +329 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/CLAUDE.md +481 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +522 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -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 +129 -20
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
- package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/docs/deployment-instructions.md +268 -0
- package/infrastructure/docs/generate-iam-command.md +278 -0
- package/infrastructure/docs/iam-policy-templates.md +193 -0
- package/infrastructure/domains/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 +701 -0
- package/infrastructure/domains/database/migration-builder.test.js +321 -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 +404 -0
- package/infrastructure/domains/integration/integration-builder.test.js +690 -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 +2051 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1960 -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 +505 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +801 -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/domains/security/iam-generator.js +816 -0
- package/infrastructure/domains/security/iam-generator.test.js +204 -0
- package/infrastructure/domains/security/kms-builder.js +415 -0
- package/infrastructure/domains/security/kms-builder.test.js +392 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +401 -0
- package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
- package/infrastructure/domains/security/templates/iam-policy-full.json +288 -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 +672 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +985 -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 +579 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
- package/infrastructure/domains/shared/resource-discovery.js +233 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +588 -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 +394 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +291 -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 +159 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
- package/infrastructure/domains/shared/validation/env-validator.js +78 -0
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +117 -0
- package/infrastructure/infrastructure-composer.test.js +1895 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/scripts/build-prisma-layer.js +701 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
- package/infrastructure/scripts/build-time-discovery.js +238 -0
- package/infrastructure/scripts/build-time-discovery.test.js +379 -0
- package/infrastructure/scripts/run-discovery.js +110 -0
- package/infrastructure/scripts/verify-prisma-layer.js +72 -0
- package/layers/prisma/.build-complete +3 -0
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/.eslintrc.js +22 -0
- package/management-ui/README.md +203 -0
- package/management-ui/components.json +21 -0
- package/management-ui/docs/phase2-integration-guide.md +320 -0
- package/management-ui/index.html +13 -0
- package/management-ui/package.json +76 -0
- package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
- package/management-ui/postcss.config.js +6 -0
- package/management-ui/server/api/backend.js +256 -0
- package/management-ui/server/api/cli.js +315 -0
- package/management-ui/server/api/codegen.js +663 -0
- package/management-ui/server/api/connections.js +857 -0
- package/management-ui/server/api/discovery.js +185 -0
- package/management-ui/server/api/environment/index.js +1 -0
- package/management-ui/server/api/environment/router.js +378 -0
- package/management-ui/server/api/environment.js +328 -0
- package/management-ui/server/api/integrations.js +876 -0
- package/management-ui/server/api/logs.js +248 -0
- package/management-ui/server/api/monitoring.js +282 -0
- package/management-ui/server/api/open-ide.js +31 -0
- package/management-ui/server/api/project.js +1029 -0
- package/management-ui/server/api/users/sessions.js +371 -0
- package/management-ui/server/api/users/simulation.js +254 -0
- package/management-ui/server/api/users.js +362 -0
- package/management-ui/server/api-contract.md +275 -0
- package/management-ui/server/index.js +873 -0
- package/management-ui/server/middleware/errorHandler.js +93 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +346 -0
- package/management-ui/server/services/aws-monitor.js +413 -0
- package/management-ui/server/services/npm-registry.js +347 -0
- package/management-ui/server/services/template-engine.js +538 -0
- package/management-ui/server/utils/cliIntegration.js +220 -0
- package/management-ui/server/utils/environment/auditLogger.js +471 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +275 -0
- package/management-ui/server/utils/environment/encryption.js +278 -0
- package/management-ui/server/utils/environment/envFileManager.js +286 -0
- package/management-ui/server/utils/import-commonjs.js +28 -0
- package/management-ui/server/utils/response.js +83 -0
- package/management-ui/server/websocket/handler.js +325 -0
- package/management-ui/src/App.jsx +25 -0
- package/management-ui/src/assets/FriggLogo.svg +1 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +70 -0
- package/management-ui/src/components/Card.jsx +97 -0
- package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
- package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
- package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
- package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
- package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
- package/management-ui/src/components/ErrorBoundary.jsx +73 -0
- package/management-ui/src/components/IntegrationCard.jsx +481 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +336 -0
- package/management-ui/src/components/Layout.jsx +716 -0
- package/management-ui/src/components/LoadingSpinner.jsx +113 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +350 -0
- package/management-ui/src/components/StatusBadge.jsx +208 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
- package/management-ui/src/components/UserSimulation.jsx +327 -0
- package/management-ui/src/components/Welcome.jsx +434 -0
- package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
- package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
- package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
- package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
- package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
- package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
- package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
- package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
- package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
- package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
- package/management-ui/src/components/codegen/index.js +10 -0
- package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
- package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
- package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
- package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
- package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
- package/management-ui/src/components/connections/index.js +5 -0
- package/management-ui/src/components/index.js +21 -0
- package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
- package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
- package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
- package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
- package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
- package/management-ui/src/components/monitoring/index.js +6 -0
- package/management-ui/src/components/monitoring/monitoring.css +218 -0
- package/management-ui/src/components/theme-provider.jsx +52 -0
- package/management-ui/src/components/theme-toggle.jsx +39 -0
- package/management-ui/src/components/ui/badge.tsx +36 -0
- package/management-ui/src/components/ui/button.test.jsx +56 -0
- package/management-ui/src/components/ui/button.tsx +57 -0
- package/management-ui/src/components/ui/card.tsx +76 -0
- package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
- package/management-ui/src/components/ui/select.tsx +157 -0
- package/management-ui/src/components/ui/skeleton.jsx +15 -0
- package/management-ui/src/hooks/useFrigg.jsx +387 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +193 -0
- package/management-ui/src/lib/utils.ts +6 -0
- package/management-ui/src/main.jsx +10 -0
- package/management-ui/src/pages/CodeGeneration.jsx +14 -0
- package/management-ui/src/pages/Connections.jsx +252 -0
- package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
- package/management-ui/src/pages/Dashboard.jsx +311 -0
- package/management-ui/src/pages/Environment.jsx +314 -0
- package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
- package/management-ui/src/pages/IntegrationTest.jsx +742 -0
- package/management-ui/src/pages/Integrations.jsx +253 -0
- package/management-ui/src/pages/Monitoring.jsx +17 -0
- package/management-ui/src/pages/Simulation.jsx +155 -0
- package/management-ui/src/pages/Users.jsx +492 -0
- package/management-ui/src/services/api.js +41 -0
- package/management-ui/src/services/apiModuleService.js +193 -0
- package/management-ui/src/services/websocket-handlers.js +120 -0
- package/management-ui/src/test/api/project.test.js +273 -0
- package/management-ui/src/test/components/Welcome.test.jsx +378 -0
- package/management-ui/src/test/mocks/server.js +178 -0
- package/management-ui/src/test/setup.js +61 -0
- package/management-ui/src/test/utils/test-utils.jsx +134 -0
- package/management-ui/src/utils/repository.js +98 -0
- package/management-ui/src/utils/repository.test.js +118 -0
- package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
- package/management-ui/tailwind.config.js +63 -0
- package/management-ui/tsconfig.json +37 -0
- package/management-ui/tsconfig.node.json +10 -0
- package/management-ui/vite.config.js +26 -0
- package/management-ui/vitest.config.js +38 -0
- package/package.json +35 -14
- package/test/index.js +2 -4
- package/test/mock-integration.js +4 -14
- package/infrastructure/app-handler-helpers.js +0 -57
- package/infrastructure/backend-utils.js +0 -87
- package/infrastructure/routers/auth.js +0 -26
- package/infrastructure/routers/integration-defined-routers.js +0 -42
- package/infrastructure/routers/middleware/loadUser.js +0 -15
- package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
- package/infrastructure/routers/user.js +0 -41
- package/infrastructure/routers/websocket.js +0 -55
- package/infrastructure/serverless-template.js +0 -291
- package/infrastructure/workers/integration-defined-workers.js +0 -24
- package/test/auther-definition-tester.js +0 -125
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
const VpcResourceResolver = require('./vpc-resolver');
|
|
2
|
+
const { ResourceOwnership } = require('../shared/types');
|
|
3
|
+
|
|
4
|
+
describe('VpcResourceResolver', () => {
|
|
5
|
+
let resolver;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
resolver = new VpcResourceResolver();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('resolveVpc', () => {
|
|
12
|
+
it('should resolve to EXTERNAL with hardcoded vpcId', () => {
|
|
13
|
+
const appDefinition = {
|
|
14
|
+
vpc: {
|
|
15
|
+
ownership: { vpc: 'external' },
|
|
16
|
+
external: { vpcId: 'vpc-external-123' }
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
20
|
+
|
|
21
|
+
const decision = resolver.resolveVpc(appDefinition, discovery);
|
|
22
|
+
|
|
23
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
24
|
+
expect(decision.physicalId).toBe('vpc-external-123');
|
|
25
|
+
expect(decision.reason).toContain('hardcoded vpcId');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should resolve to EXTERNAL using discovered VPC when no hardcoded ID', () => {
|
|
29
|
+
const appDefinition = {
|
|
30
|
+
vpc: {
|
|
31
|
+
ownership: { vpc: 'external' }
|
|
32
|
+
// No external.vpcId provided
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const discovery = {
|
|
36
|
+
stackManaged: [],
|
|
37
|
+
external: [],
|
|
38
|
+
fromCloudFormation: false,
|
|
39
|
+
defaultVpcId: 'vpc-discovered-123'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const decision = resolver.resolveVpc(appDefinition, discovery);
|
|
43
|
+
|
|
44
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
45
|
+
expect(decision.physicalId).toBe('vpc-discovered-123');
|
|
46
|
+
expect(decision.reason).toContain('discovered VPC');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should throw when external specified but no vpcId and no discovery', () => {
|
|
50
|
+
const appDefinition = {
|
|
51
|
+
vpc: {
|
|
52
|
+
ownership: { vpc: 'external' }
|
|
53
|
+
// No external.vpcId provided
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const discovery = {
|
|
57
|
+
stackManaged: [],
|
|
58
|
+
external: [],
|
|
59
|
+
fromCloudFormation: false
|
|
60
|
+
// No defaultVpcId discovered
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
|
|
64
|
+
/ownership='external' for VPC requires either/
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should resolve to STACK when user specifies stack', () => {
|
|
69
|
+
const appDefinition = {
|
|
70
|
+
vpc: {
|
|
71
|
+
ownership: { vpc: 'stack' }
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const discovery = {
|
|
75
|
+
stackManaged: [
|
|
76
|
+
{ logicalId: 'FriggVPC', physicalId: 'vpc-stack-123', resourceType: 'AWS::EC2::VPC' }
|
|
77
|
+
],
|
|
78
|
+
external: [],
|
|
79
|
+
fromCloudFormation: true
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const decision = resolver.resolveVpc(appDefinition, discovery);
|
|
83
|
+
|
|
84
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
85
|
+
expect(decision.physicalId).toBe('vpc-stack-123');
|
|
86
|
+
expect(decision.reason).toContain('User specified ownership=stack');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should auto-resolve to STACK when VPC in stack (CRITICAL)', () => {
|
|
90
|
+
const appDefinition = {
|
|
91
|
+
vpc: { ownership: { vpc: 'auto' } }
|
|
92
|
+
};
|
|
93
|
+
const discovery = {
|
|
94
|
+
stackManaged: [
|
|
95
|
+
{ logicalId: 'FriggVPC', physicalId: 'vpc-in-stack', resourceType: 'AWS::EC2::VPC' }
|
|
96
|
+
],
|
|
97
|
+
external: [],
|
|
98
|
+
fromCloudFormation: true
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const decision = resolver.resolveVpc(appDefinition, discovery);
|
|
102
|
+
|
|
103
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
104
|
+
expect(decision.physicalId).toBe('vpc-in-stack');
|
|
105
|
+
expect(decision.reason).toContain('Found in CloudFormation stack');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should auto-resolve to EXTERNAL when found externally', () => {
|
|
109
|
+
const appDefinition = {
|
|
110
|
+
vpc: { ownership: { vpc: 'auto' } }
|
|
111
|
+
};
|
|
112
|
+
const discovery = {
|
|
113
|
+
stackManaged: [],
|
|
114
|
+
external: [
|
|
115
|
+
{ physicalId: 'vpc-external', resourceType: 'AWS::EC2::VPC', source: 'tag-search' }
|
|
116
|
+
],
|
|
117
|
+
fromCloudFormation: false
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const decision = resolver.resolveVpc(appDefinition, discovery);
|
|
121
|
+
|
|
122
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
123
|
+
expect(decision.physicalId).toBe('vpc-external');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should throw error when auto mode finds no VPC (changed behavior)', () => {
|
|
127
|
+
const appDefinition = {
|
|
128
|
+
vpc: { ownership: { vpc: 'auto' } }
|
|
129
|
+
// No management specified - defaults to discover
|
|
130
|
+
};
|
|
131
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
132
|
+
|
|
133
|
+
// NEW BEHAVIOR: Auto mode with no VPC found should throw error (prevent accidental VPC creation)
|
|
134
|
+
expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
|
|
135
|
+
'VPC discovery failed: No VPC found'
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should throw error when auto mode finds no VPC and management is not create-new', () => {
|
|
140
|
+
const appDefinition = {
|
|
141
|
+
vpc: {
|
|
142
|
+
enable: true,
|
|
143
|
+
// No management specified - defaults to discover
|
|
144
|
+
// No ownership specified - defaults to auto
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const discovery = {
|
|
148
|
+
stackManaged: [],
|
|
149
|
+
external: [],
|
|
150
|
+
fromCloudFormation: false
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Should throw error instead of trying to create VPC
|
|
154
|
+
expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
|
|
155
|
+
'VPC discovery failed: No VPC found'
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should allow creating VPC when management is create-new', () => {
|
|
160
|
+
const appDefinition = {
|
|
161
|
+
vpc: {
|
|
162
|
+
enable: true,
|
|
163
|
+
management: 'create-new',
|
|
164
|
+
ownership: { vpc: 'auto' }
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const discovery = {
|
|
168
|
+
stackManaged: [],
|
|
169
|
+
external: [],
|
|
170
|
+
fromCloudFormation: false
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Should NOT throw - create-new explicitly allows VPC creation
|
|
174
|
+
const decision = resolver.resolveVpc(appDefinition, discovery);
|
|
175
|
+
|
|
176
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
177
|
+
expect(decision.physicalId).toBeUndefined(); // resolveResourceOwnership returns undefined, not null
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('resolveSecurityGroup', () => {
|
|
182
|
+
it('should resolve to EXTERNAL with user-provided hardcoded IDs', () => {
|
|
183
|
+
const appDefinition = {
|
|
184
|
+
vpc: {
|
|
185
|
+
ownership: { securityGroup: 'external' },
|
|
186
|
+
external: { securityGroupIds: ['sg-1', 'sg-2'] }
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
190
|
+
|
|
191
|
+
const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
|
|
192
|
+
|
|
193
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
194
|
+
expect(decision.physicalIds).toEqual(['sg-1', 'sg-2']);
|
|
195
|
+
expect(decision.reason).toContain('hardcoded');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should resolve to EXTERNAL using discovered default SG when no hardcoded IDs', () => {
|
|
199
|
+
const appDefinition = {
|
|
200
|
+
vpc: {
|
|
201
|
+
ownership: { securityGroup: 'external' }
|
|
202
|
+
// No external.securityGroupIds provided
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const discovery = {
|
|
206
|
+
stackManaged: [],
|
|
207
|
+
external: [],
|
|
208
|
+
fromCloudFormation: false,
|
|
209
|
+
defaultSecurityGroupId: 'sg-discovered-default'
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
|
|
213
|
+
|
|
214
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
215
|
+
expect(decision.physicalIds).toEqual(['sg-discovered-default']);
|
|
216
|
+
expect(decision.reason).toContain('discovered default security group');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should throw error when ownership=external but no IDs and no discovery', () => {
|
|
220
|
+
const appDefinition = {
|
|
221
|
+
vpc: {
|
|
222
|
+
ownership: { securityGroup: 'external' }
|
|
223
|
+
// No external.securityGroupIds provided
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const discovery = {
|
|
227
|
+
stackManaged: [],
|
|
228
|
+
external: [],
|
|
229
|
+
fromCloudFormation: false
|
|
230
|
+
// No defaultSecurityGroupId discovered
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
expect(() => resolver.resolveSecurityGroup(appDefinition, discovery)).toThrow(
|
|
234
|
+
/ownership='external' for securityGroup requires either/
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should prefer default SG over stack-managed SG when ownership=external and both discovered', () => {
|
|
239
|
+
const appDefinition = {
|
|
240
|
+
vpc: {
|
|
241
|
+
ownership: { securityGroup: 'external' }
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const discovery = {
|
|
245
|
+
stackManaged: [
|
|
246
|
+
{ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stack-managed', resourceType: 'AWS::EC2::SecurityGroup' }
|
|
247
|
+
],
|
|
248
|
+
external: [],
|
|
249
|
+
fromCloudFormation: true,
|
|
250
|
+
lambdaSecurityGroupId: 'sg-stack-managed', // Stack-managed SG
|
|
251
|
+
defaultSecurityGroupId: 'sg-default-vpc' // Default VPC SG
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
|
|
255
|
+
|
|
256
|
+
// Should use default SG, NOT the stack-managed one
|
|
257
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
258
|
+
expect(decision.physicalIds).toEqual(['sg-default-vpc']);
|
|
259
|
+
expect(decision.reason).toContain('discovered default security group');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should auto-resolve to STACK when FriggLambdaSecurityGroup in stack', () => {
|
|
263
|
+
const appDefinition = { vpc: { ownership: { securityGroup: 'auto' } } };
|
|
264
|
+
const discovery = {
|
|
265
|
+
stackManaged: [
|
|
266
|
+
{ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-069629001ade41c9a', resourceType: 'AWS::EC2::SecurityGroup' }
|
|
267
|
+
],
|
|
268
|
+
external: [],
|
|
269
|
+
fromCloudFormation: true
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
|
|
273
|
+
|
|
274
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
275
|
+
expect(decision.physicalId).toBe('sg-069629001ade41c9a');
|
|
276
|
+
expect(decision.reason).toContain('Found FriggLambdaSecurityGroup in CloudFormation stack');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('resolveSubnets', () => {
|
|
281
|
+
it('should resolve to EXTERNAL with user-provided hardcoded subnet IDs', () => {
|
|
282
|
+
const appDefinition = {
|
|
283
|
+
vpc: {
|
|
284
|
+
ownership: { subnets: 'external' },
|
|
285
|
+
external: { subnetIds: ['subnet-1', 'subnet-2', 'subnet-3'] }
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
289
|
+
|
|
290
|
+
const decision = resolver.resolveSubnets(appDefinition, discovery);
|
|
291
|
+
|
|
292
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
293
|
+
expect(decision.physicalIds).toEqual(['subnet-1', 'subnet-2', 'subnet-3']);
|
|
294
|
+
expect(decision.reason).toContain('hardcoded');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should resolve to EXTERNAL using discovered subnets when no hardcoded IDs', () => {
|
|
298
|
+
const appDefinition = {
|
|
299
|
+
vpc: {
|
|
300
|
+
ownership: { subnets: 'external' }
|
|
301
|
+
// No external.subnetIds provided
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const discovery = {
|
|
305
|
+
stackManaged: [],
|
|
306
|
+
external: [],
|
|
307
|
+
fromCloudFormation: false,
|
|
308
|
+
privateSubnetId1: 'subnet-discovered-1',
|
|
309
|
+
privateSubnetId2: 'subnet-discovered-2'
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const decision = resolver.resolveSubnets(appDefinition, discovery);
|
|
313
|
+
|
|
314
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
315
|
+
expect(decision.physicalIds).toEqual(['subnet-discovered-1', 'subnet-discovered-2']);
|
|
316
|
+
expect(decision.reason).toContain('discovered subnets');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should throw error when ownership=external but no IDs and no discovery', () => {
|
|
320
|
+
const appDefinition = {
|
|
321
|
+
vpc: {
|
|
322
|
+
ownership: { subnets: 'external' }
|
|
323
|
+
// No external.subnetIds provided
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const discovery = {
|
|
327
|
+
stackManaged: [],
|
|
328
|
+
external: [],
|
|
329
|
+
fromCloudFormation: false
|
|
330
|
+
// No privateSubnetId1/2 discovered
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
expect(() => resolver.resolveSubnets(appDefinition, discovery)).toThrow(
|
|
334
|
+
/ownership='external' for subnets requires either/
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should resolve to STACK when subnets found in stack', () => {
|
|
339
|
+
const appDefinition = { vpc: { ownership: { subnets: 'auto' } } };
|
|
340
|
+
const discovery = {
|
|
341
|
+
stackManaged: [
|
|
342
|
+
{ logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-a', resourceType: 'AWS::EC2::Subnet' },
|
|
343
|
+
{ logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-b', resourceType: 'AWS::EC2::Subnet' }
|
|
344
|
+
],
|
|
345
|
+
external: [],
|
|
346
|
+
fromCloudFormation: true
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const decision = resolver.resolveSubnets(appDefinition, discovery);
|
|
350
|
+
|
|
351
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
352
|
+
expect(decision.physicalIds).toEqual(['subnet-a', 'subnet-b']);
|
|
353
|
+
expect(decision.metadata.subnet1).toBe('subnet-a');
|
|
354
|
+
expect(decision.metadata.subnet2).toBe('subnet-b');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should resolve to EXTERNAL when found externally', () => {
|
|
358
|
+
const appDefinition = { vpc: { ownership: { subnets: 'auto' } } };
|
|
359
|
+
const discovery = {
|
|
360
|
+
stackManaged: [],
|
|
361
|
+
external: [
|
|
362
|
+
{ physicalId: 'subnet-ext-1', resourceType: 'AWS::EC2::Subnet', source: 'tag-search' },
|
|
363
|
+
{ physicalId: 'subnet-ext-2', resourceType: 'AWS::EC2::Subnet', source: 'tag-search' },
|
|
364
|
+
{ physicalId: 'subnet-ext-3', resourceType: 'AWS::EC2::Subnet', source: 'tag-search' }
|
|
365
|
+
],
|
|
366
|
+
fromCloudFormation: false
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const decision = resolver.resolveSubnets(appDefinition, discovery);
|
|
370
|
+
|
|
371
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
372
|
+
expect(decision.physicalIds).toHaveLength(2); // Takes first 2
|
|
373
|
+
expect(decision.physicalIds).toEqual(['subnet-ext-1', 'subnet-ext-2']);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should resolve to STACK when no subnets found (create new)', () => {
|
|
377
|
+
const appDefinition = { vpc: { ownership: { subnets: 'auto' } } };
|
|
378
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
379
|
+
|
|
380
|
+
const decision = resolver.resolveSubnets(appDefinition, discovery);
|
|
381
|
+
|
|
382
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
383
|
+
expect(decision.physicalId).toBeNull();
|
|
384
|
+
expect(decision.reason).toContain('No existing subnets found');
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe('resolveNatGateway', () => {
|
|
389
|
+
it('should return null decision when NAT disabled', () => {
|
|
390
|
+
const appDefinition = {
|
|
391
|
+
vpc: {
|
|
392
|
+
ownership: { natGateway: 'auto' },
|
|
393
|
+
config: { natGateway: { enable: false } }
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
397
|
+
|
|
398
|
+
const decision = resolver.resolveNatGateway(appDefinition, discovery);
|
|
399
|
+
|
|
400
|
+
expect(decision.ownership).toBeNull();
|
|
401
|
+
expect(decision.reason).toContain('NAT Gateway disabled');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should resolve to EXTERNAL with user-provided hardcoded ID', () => {
|
|
405
|
+
const appDefinition = {
|
|
406
|
+
vpc: {
|
|
407
|
+
ownership: { natGateway: 'external' },
|
|
408
|
+
external: { natGatewayId: 'nat-external-123' }
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
412
|
+
|
|
413
|
+
const decision = resolver.resolveNatGateway(appDefinition, discovery);
|
|
414
|
+
|
|
415
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
416
|
+
expect(decision.physicalId).toBe('nat-external-123');
|
|
417
|
+
expect(decision.reason).toContain('hardcoded');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('should resolve to EXTERNAL using discovered NAT when no hardcoded ID', () => {
|
|
421
|
+
const appDefinition = {
|
|
422
|
+
vpc: {
|
|
423
|
+
ownership: { natGateway: 'external' }
|
|
424
|
+
// No external.natGatewayId provided
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const discovery = {
|
|
428
|
+
stackManaged: [],
|
|
429
|
+
external: [],
|
|
430
|
+
fromCloudFormation: false,
|
|
431
|
+
natGatewayId: 'nat-discovered-123'
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const decision = resolver.resolveNatGateway(appDefinition, discovery);
|
|
435
|
+
|
|
436
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
437
|
+
expect(decision.physicalId).toBe('nat-discovered-123');
|
|
438
|
+
expect(decision.reason).toContain('discovered NAT gateway');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('should throw error when ownership=external but no ID and no discovery', () => {
|
|
442
|
+
const appDefinition = {
|
|
443
|
+
vpc: {
|
|
444
|
+
ownership: { natGateway: 'external' }
|
|
445
|
+
// No external.natGatewayId provided
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const discovery = {
|
|
449
|
+
stackManaged: [],
|
|
450
|
+
external: [],
|
|
451
|
+
fromCloudFormation: false
|
|
452
|
+
// No natGatewayId discovered
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
expect(() => resolver.resolveNatGateway(appDefinition, discovery)).toThrow(
|
|
456
|
+
/ownership='external' for NAT gateway requires either/
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should auto-resolve to STACK when found in stack', () => {
|
|
461
|
+
const appDefinition = { vpc: { ownership: { natGateway: 'auto' } } };
|
|
462
|
+
const discovery = {
|
|
463
|
+
stackManaged: [
|
|
464
|
+
{ logicalId: 'FriggNatGateway', physicalId: 'nat-stack-123', resourceType: 'AWS::EC2::NatGateway' }
|
|
465
|
+
],
|
|
466
|
+
external: [],
|
|
467
|
+
fromCloudFormation: true
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const decision = resolver.resolveNatGateway(appDefinition, discovery);
|
|
471
|
+
|
|
472
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
473
|
+
expect(decision.physicalId).toBe('nat-stack-123');
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('resolveVpcEndpoints', () => {
|
|
478
|
+
it('should skip DynamoDB endpoint when application uses MongoDB', () => {
|
|
479
|
+
const appDefinition = {
|
|
480
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
481
|
+
database: { mongoDB: { enable: true } }, // Using MongoDB, not DynamoDB
|
|
482
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' }
|
|
483
|
+
};
|
|
484
|
+
const discovery = {
|
|
485
|
+
stackManaged: [],
|
|
486
|
+
external: [],
|
|
487
|
+
fromCloudFormation: false
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
491
|
+
|
|
492
|
+
expect(decisions.s3.ownership).toBe('stack'); // S3 always needed
|
|
493
|
+
expect(decisions.dynamodb.ownership).toBeNull(); // DynamoDB NOT needed
|
|
494
|
+
expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
|
|
495
|
+
expect(decisions.kms.ownership).toBe('stack'); // KMS needed (encryption enabled)
|
|
496
|
+
expect(decisions.secretsManager.ownership).toBe('stack'); // SM always needed
|
|
497
|
+
expect(decisions.sqs.ownership).toBe('stack'); // SQS always needed
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('should skip DynamoDB endpoint when application uses PostgreSQL', () => {
|
|
501
|
+
const appDefinition = {
|
|
502
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
503
|
+
database: { postgres: { enable: true } }, // Using PostgreSQL, not DynamoDB
|
|
504
|
+
};
|
|
505
|
+
const discovery = {
|
|
506
|
+
stackManaged: [],
|
|
507
|
+
external: [],
|
|
508
|
+
fromCloudFormation: false
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
512
|
+
|
|
513
|
+
expect(decisions.dynamodb.ownership).toBeNull();
|
|
514
|
+
expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should create DynamoDB endpoint when explicitly enabled', () => {
|
|
518
|
+
const appDefinition = {
|
|
519
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
520
|
+
database: { dynamodb: { enable: true } }, // Explicitly using DynamoDB
|
|
521
|
+
};
|
|
522
|
+
const discovery = {
|
|
523
|
+
stackManaged: [],
|
|
524
|
+
external: [],
|
|
525
|
+
fromCloudFormation: false
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
529
|
+
|
|
530
|
+
expect(decisions.dynamodb.ownership).toBe('stack'); // DynamoDB needed
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should allow deletion of DynamoDB endpoint when not needed', () => {
|
|
534
|
+
const appDefinition = {
|
|
535
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
536
|
+
database: { mongoDB: { enable: true } }, // Using MongoDB (DynamoDB not needed)
|
|
537
|
+
};
|
|
538
|
+
const discovery = {
|
|
539
|
+
stackManaged: [
|
|
540
|
+
{ logicalId: 'FriggDynamoDBVPCEndpoint', physicalId: 'vpce-ddb-legacy', resourceType: 'AWS::EC2::VPCEndpoint' }
|
|
541
|
+
],
|
|
542
|
+
external: [],
|
|
543
|
+
fromCloudFormation: true
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
547
|
+
|
|
548
|
+
// Should return null (allow CloudFormation to delete it since not needed)
|
|
549
|
+
expect(decisions.dynamodb.ownership).toBeNull();
|
|
550
|
+
expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
it('should return null decisions when endpoints disabled', () => {
|
|
555
|
+
const appDefinition = {
|
|
556
|
+
vpc: {
|
|
557
|
+
ownership: { vpcEndpoints: 'auto' },
|
|
558
|
+
config: { enableVpcEndpoints: false }
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
562
|
+
|
|
563
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
564
|
+
|
|
565
|
+
expect(decisions.s3.ownership).toBeNull();
|
|
566
|
+
expect(decisions.dynamodb.ownership).toBeNull();
|
|
567
|
+
expect(decisions.kms.ownership).toBeNull();
|
|
568
|
+
expect(decisions.secretsManager.ownership).toBeNull();
|
|
569
|
+
expect(decisions.sqs.ownership).toBeNull();
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('should resolve to EXTERNAL with user-provided endpoint IDs', () => {
|
|
573
|
+
const appDefinition = {
|
|
574
|
+
vpc: {
|
|
575
|
+
ownership: { vpcEndpoints: 'external' },
|
|
576
|
+
external: {
|
|
577
|
+
vpcEndpointIds: {
|
|
578
|
+
s3: 'vpce-s3-123',
|
|
579
|
+
dynamodb: 'vpce-ddb-456'
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
database: { dynamodb: { enable: true } } // Enable DynamoDB
|
|
584
|
+
};
|
|
585
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
586
|
+
|
|
587
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
588
|
+
|
|
589
|
+
expect(decisions.s3.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
590
|
+
expect(decisions.s3.physicalId).toBe('vpce-s3-123');
|
|
591
|
+
expect(decisions.dynamodb.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
592
|
+
expect(decisions.dynamodb.physicalId).toBe('vpce-ddb-456');
|
|
593
|
+
expect(decisions.kms.ownership).toBeNull(); // Not provided
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('should auto-resolve to STACK when endpoints found in stack', () => {
|
|
597
|
+
const appDefinition = {
|
|
598
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
599
|
+
database: { dynamodb: { enable: true } } // Enable DynamoDB
|
|
600
|
+
};
|
|
601
|
+
const discovery = {
|
|
602
|
+
stackManaged: [
|
|
603
|
+
{ logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3-stack', resourceType: 'AWS::EC2::VPCEndpoint' },
|
|
604
|
+
{ logicalId: 'FriggDynamoDBVPCEndpoint', physicalId: 'vpce-ddb-stack', resourceType: 'AWS::EC2::VPCEndpoint' }
|
|
605
|
+
],
|
|
606
|
+
external: [],
|
|
607
|
+
fromCloudFormation: true
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
611
|
+
|
|
612
|
+
expect(decisions.s3.ownership).toBe(ResourceOwnership.STACK);
|
|
613
|
+
expect(decisions.s3.physicalId).toBe('vpce-s3-stack');
|
|
614
|
+
expect(decisions.dynamodb.ownership).toBe(ResourceOwnership.STACK);
|
|
615
|
+
expect(decisions.dynamodb.physicalId).toBe('vpce-ddb-stack');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('should auto-resolve mixed: some in stack, some new', () => {
|
|
619
|
+
const appDefinition = {
|
|
620
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
621
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' }, // Enable KMS endpoint
|
|
622
|
+
database: { dynamodb: { enable: true } } // Enable DynamoDB
|
|
623
|
+
};
|
|
624
|
+
const discovery = {
|
|
625
|
+
stackManaged: [
|
|
626
|
+
{ logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3-stack', resourceType: 'AWS::EC2::VPCEndpoint' }
|
|
627
|
+
],
|
|
628
|
+
external: [],
|
|
629
|
+
fromCloudFormation: true
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
633
|
+
|
|
634
|
+
expect(decisions.s3.ownership).toBe(ResourceOwnership.STACK);
|
|
635
|
+
expect(decisions.s3.physicalId).toBe('vpce-s3-stack');
|
|
636
|
+
|
|
637
|
+
// Others not in stack - should create new
|
|
638
|
+
expect(decisions.dynamodb.ownership).toBe(ResourceOwnership.STACK);
|
|
639
|
+
expect(decisions.dynamodb.physicalId).toBeUndefined();
|
|
640
|
+
expect(decisions.kms.ownership).toBe(ResourceOwnership.STACK);
|
|
641
|
+
expect(decisions.secretsManager.ownership).toBe(ResourceOwnership.STACK);
|
|
642
|
+
expect(decisions.sqs.ownership).toBe(ResourceOwnership.STACK);
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
describe('resolveAll', () => {
|
|
647
|
+
it('should resolve all VPC resources at once', () => {
|
|
648
|
+
const appDefinition = {
|
|
649
|
+
vpc: {
|
|
650
|
+
ownership: {
|
|
651
|
+
vpc: 'auto',
|
|
652
|
+
securityGroup: 'auto',
|
|
653
|
+
subnets: 'auto',
|
|
654
|
+
natGateway: 'auto',
|
|
655
|
+
vpcEndpoints: 'auto'
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
const discovery = {
|
|
660
|
+
stackManaged: [
|
|
661
|
+
{ logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' },
|
|
662
|
+
{ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-456', resourceType: 'AWS::EC2::SecurityGroup' },
|
|
663
|
+
{ logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-1', resourceType: 'AWS::EC2::Subnet' },
|
|
664
|
+
{ logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-2', resourceType: 'AWS::EC2::Subnet' },
|
|
665
|
+
{ logicalId: 'FriggNatGateway', physicalId: 'nat-789', resourceType: 'AWS::EC2::NatGateway' },
|
|
666
|
+
{ logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3', resourceType: 'AWS::EC2::VPCEndpoint' }
|
|
667
|
+
],
|
|
668
|
+
external: [],
|
|
669
|
+
fromCloudFormation: true
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
673
|
+
|
|
674
|
+
expect(decisions.vpc.ownership).toBe(ResourceOwnership.STACK);
|
|
675
|
+
expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
|
|
676
|
+
expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
|
|
677
|
+
expect(decisions.natGateway.ownership).toBe(ResourceOwnership.STACK);
|
|
678
|
+
expect(decisions.vpcEndpoints.s3.ownership).toBe(ResourceOwnership.STACK);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('should handle mixed ownership scenarios', () => {
|
|
682
|
+
const appDefinition = {
|
|
683
|
+
vpc: {
|
|
684
|
+
ownership: {
|
|
685
|
+
vpc: 'external',
|
|
686
|
+
securityGroup: 'stack',
|
|
687
|
+
subnets: 'stack',
|
|
688
|
+
natGateway: 'auto',
|
|
689
|
+
vpcEndpoints: 'auto'
|
|
690
|
+
},
|
|
691
|
+
external: {
|
|
692
|
+
vpcId: 'vpc-shared-production'
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
const discovery = {
|
|
697
|
+
stackManaged: [
|
|
698
|
+
{ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stack', resourceType: 'AWS::EC2::SecurityGroup' },
|
|
699
|
+
{ logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-1', resourceType: 'AWS::EC2::Subnet' },
|
|
700
|
+
{ logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-2', resourceType: 'AWS::EC2::Subnet' }
|
|
701
|
+
],
|
|
702
|
+
external: [],
|
|
703
|
+
fromCloudFormation: true
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
707
|
+
|
|
708
|
+
expect(decisions.vpc.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
709
|
+
expect(decisions.vpc.physicalId).toBe('vpc-shared-production');
|
|
710
|
+
expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
|
|
711
|
+
expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
|
|
712
|
+
expect(decisions.natGateway.ownership).toBe(ResourceOwnership.STACK); // Not found, create new
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
describe('real-world scenarios', () => {
|
|
717
|
+
it('scenario: fresh deploy, no resources exist', () => {
|
|
718
|
+
const appDefinition = {
|
|
719
|
+
vpc: {
|
|
720
|
+
enable: true,
|
|
721
|
+
ownership: {},
|
|
722
|
+
management: 'create-new' // Explicitly allow VPC creation
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
|
|
726
|
+
|
|
727
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
728
|
+
|
|
729
|
+
// All should be STACK (create new)
|
|
730
|
+
expect(decisions.vpc.ownership).toBe(ResourceOwnership.STACK);
|
|
731
|
+
expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
|
|
732
|
+
expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
|
|
733
|
+
expect(decisions.natGateway.ownership).toBe(ResourceOwnership.STACK);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it('scenario: redeploy existing stack (the original bug case)', () => {
|
|
737
|
+
const appDefinition = {
|
|
738
|
+
vpc: { enable: true, ownership: {} }
|
|
739
|
+
};
|
|
740
|
+
const discovery = {
|
|
741
|
+
stackManaged: [
|
|
742
|
+
{ logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' },
|
|
743
|
+
{ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-069629001ade41c9a', resourceType: 'AWS::EC2::SecurityGroup' },
|
|
744
|
+
{ logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-1', resourceType: 'AWS::EC2::Subnet' },
|
|
745
|
+
{ logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-2', resourceType: 'AWS::EC2::Subnet' }
|
|
746
|
+
],
|
|
747
|
+
external: [],
|
|
748
|
+
fromCloudFormation: true,
|
|
749
|
+
stackName: 'create-frigg-app-production'
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
753
|
+
|
|
754
|
+
// CRITICAL: All resources in stack must get STACK ownership
|
|
755
|
+
expect(decisions.vpc.ownership).toBe(ResourceOwnership.STACK);
|
|
756
|
+
expect(decisions.vpc.physicalId).toBe('vpc-123');
|
|
757
|
+
|
|
758
|
+
expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
|
|
759
|
+
expect(decisions.securityGroup.physicalId).toBe('sg-069629001ade41c9a');
|
|
760
|
+
expect(decisions.securityGroup.reason).toContain('Found FriggLambdaSecurityGroup in CloudFormation stack');
|
|
761
|
+
|
|
762
|
+
expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
|
|
763
|
+
expect(decisions.subnets.physicalIds).toEqual(['subnet-1', 'subnet-2']);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it('scenario: use shared VPC with stack-managed resources', () => {
|
|
767
|
+
const appDefinition = {
|
|
768
|
+
vpc: {
|
|
769
|
+
enable: true,
|
|
770
|
+
ownership: {
|
|
771
|
+
vpc: 'external',
|
|
772
|
+
securityGroup: 'auto',
|
|
773
|
+
subnets: 'auto'
|
|
774
|
+
},
|
|
775
|
+
external: {
|
|
776
|
+
vpcId: 'vpc-shared-across-stages'
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
const discovery = {
|
|
781
|
+
stackManaged: [
|
|
782
|
+
{ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stage-specific', resourceType: 'AWS::EC2::SecurityGroup' },
|
|
783
|
+
{ logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-1', resourceType: 'AWS::EC2::Subnet' },
|
|
784
|
+
{ logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-2', resourceType: 'AWS::EC2::Subnet' }
|
|
785
|
+
],
|
|
786
|
+
external: [],
|
|
787
|
+
fromCloudFormation: true
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
791
|
+
|
|
792
|
+
// VPC is external
|
|
793
|
+
expect(decisions.vpc.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
794
|
+
expect(decisions.vpc.physicalId).toBe('vpc-shared-across-stages');
|
|
795
|
+
|
|
796
|
+
// But security group and subnets are stack-managed
|
|
797
|
+
expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
|
|
798
|
+
expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
});
|