@friggframework/devtools 2.0.0-next.8 → 2.0.0-next.81
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/auth-command/CLAUDE.md +293 -0
- package/frigg-cli/auth-command/README.md +450 -0
- package/frigg-cli/auth-command/api-key-flow.js +153 -0
- package/frigg-cli/auth-command/auth-tester.js +344 -0
- package/frigg-cli/auth-command/credential-storage.js +182 -0
- package/frigg-cli/auth-command/index.js +256 -0
- package/frigg-cli/auth-command/json-schema-form.js +67 -0
- package/frigg-cli/auth-command/module-loader.js +172 -0
- package/frigg-cli/auth-command/oauth-callback-server.js +431 -0
- package/frigg-cli/auth-command/oauth-flow.js +195 -0
- package/frigg-cli/auth-command/utils/browser.js +30 -0
- package/frigg-cli/build-command/index.js +45 -12
- 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 -23
- 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 +174 -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 +118 -5
- 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 +857 -0
- package/infrastructure/domains/database/aurora-builder.test.js +960 -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 +547 -0
- package/infrastructure/domains/integration/integration-builder.test.js +798 -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/scheduler/scheduler-builder.js +211 -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 +681 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +1320 -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 +256 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +757 -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 +408 -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 +119 -0
- package/infrastructure/infrastructure-composer.test.js +1896 -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/management-ui/README.md +203 -0
- package/package.json +44 -14
- package/test/index.js +2 -4
- package/test/mock-api.js +1 -3
- package/test/mock-integration.js +4 -14
- package/.eslintrc.json +0 -3
- package/CHANGELOG.md +0 -132
- 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,757 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Resource Discovery Service
|
|
3
|
+
*
|
|
4
|
+
* Tests orchestration of cloud resource discovery
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { shouldRunDiscovery, gatherDiscoveredResources } = require('./resource-discovery');
|
|
8
|
+
|
|
9
|
+
// Mock the provider factory and discovery classes
|
|
10
|
+
jest.mock('./providers/provider-factory');
|
|
11
|
+
jest.mock('../networking/vpc-discovery');
|
|
12
|
+
jest.mock('../security/kms-discovery');
|
|
13
|
+
jest.mock('../database/aurora-discovery');
|
|
14
|
+
jest.mock('../parameters/ssm-discovery');
|
|
15
|
+
jest.mock('./cloudformation-discovery');
|
|
16
|
+
jest.mock('@aws-sdk/client-ec2', () => ({
|
|
17
|
+
AssociateRouteTableCommand: jest.fn().mockImplementation((params) => params),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
const { CloudProviderFactory } = require('./providers/provider-factory');
|
|
21
|
+
const { VpcDiscovery } = require('../networking/vpc-discovery');
|
|
22
|
+
const { KmsDiscovery } = require('../security/kms-discovery');
|
|
23
|
+
const { AuroraDiscovery } = require('../database/aurora-discovery');
|
|
24
|
+
const { SsmDiscovery } = require('../parameters/ssm-discovery');
|
|
25
|
+
const { CloudFormationDiscovery } = require('./cloudformation-discovery');
|
|
26
|
+
const { AssociateRouteTableCommand } = require('@aws-sdk/client-ec2');
|
|
27
|
+
|
|
28
|
+
describe('Resource Discovery', () => {
|
|
29
|
+
let mockProvider;
|
|
30
|
+
let mockVpcDiscovery;
|
|
31
|
+
let mockKmsDiscovery;
|
|
32
|
+
let mockAuroraDiscovery;
|
|
33
|
+
let mockSsmDiscovery;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
// Reset environment
|
|
37
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
38
|
+
delete process.env.CLOUD_PROVIDER;
|
|
39
|
+
delete process.env.AWS_REGION;
|
|
40
|
+
delete process.env.SLS_STAGE;
|
|
41
|
+
|
|
42
|
+
// Create mock provider
|
|
43
|
+
mockProvider = {
|
|
44
|
+
getName: jest.fn().mockReturnValue('aws'),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Create mock discoveries with default responses
|
|
48
|
+
mockVpcDiscovery = {
|
|
49
|
+
discover: jest.fn().mockResolvedValue({
|
|
50
|
+
defaultVpcId: 'vpc-123',
|
|
51
|
+
privateSubnetId1: 'subnet-1',
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
mockKmsDiscovery = {
|
|
56
|
+
discover: jest.fn().mockResolvedValue({
|
|
57
|
+
kmsKeyId: 'arn:aws:kms:us-east-1:123:key/abc',
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
mockAuroraDiscovery = {
|
|
62
|
+
discover: jest.fn().mockResolvedValue({
|
|
63
|
+
auroraClusterEndpoint: 'db.example.com',
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
mockSsmDiscovery = {
|
|
68
|
+
discover: jest.fn().mockResolvedValue({
|
|
69
|
+
parameters: [],
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Mock factory and discovery constructors
|
|
74
|
+
CloudFormationDiscovery.mockImplementation(() => ({
|
|
75
|
+
discoverFromStack: jest.fn().mockResolvedValue(null),
|
|
76
|
+
}));
|
|
77
|
+
CloudProviderFactory.create = jest.fn().mockReturnValue(mockProvider);
|
|
78
|
+
VpcDiscovery.mockImplementation(() => mockVpcDiscovery);
|
|
79
|
+
KmsDiscovery.mockImplementation(() => mockKmsDiscovery);
|
|
80
|
+
AuroraDiscovery.mockImplementation(() => mockAuroraDiscovery);
|
|
81
|
+
SsmDiscovery.mockImplementation(() => mockSsmDiscovery);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('shouldRunDiscovery()', () => {
|
|
85
|
+
it('should return false when FRIGG_SKIP_AWS_DISCOVERY is true', () => {
|
|
86
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
87
|
+
|
|
88
|
+
const result = shouldRunDiscovery({ vpc: { enable: true } });
|
|
89
|
+
|
|
90
|
+
expect(result).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return true when VPC is enabled', () => {
|
|
94
|
+
const result = shouldRunDiscovery({ vpc: { enable: true } });
|
|
95
|
+
|
|
96
|
+
expect(result).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should return true when KMS encryption is enabled', () => {
|
|
100
|
+
const result = shouldRunDiscovery({
|
|
101
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(result).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return true when SSM is enabled', () => {
|
|
108
|
+
const result = shouldRunDiscovery({ ssm: { enable: true } });
|
|
109
|
+
|
|
110
|
+
expect(result).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return true when Postgres database is enabled', () => {
|
|
114
|
+
const result = shouldRunDiscovery({
|
|
115
|
+
database: { postgres: { enable: true } },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(result).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return false when no features require discovery', () => {
|
|
122
|
+
const result = shouldRunDiscovery({
|
|
123
|
+
vpc: { enable: false },
|
|
124
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
125
|
+
ssm: { enable: false },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(result).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should return false for empty app definition', () => {
|
|
132
|
+
const result = shouldRunDiscovery({});
|
|
133
|
+
|
|
134
|
+
expect(result).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('gatherDiscoveredResources()', () => {
|
|
139
|
+
it('should skip discovery when not needed', async () => {
|
|
140
|
+
const appDefinition = {
|
|
141
|
+
vpc: { enable: false },
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
145
|
+
|
|
146
|
+
expect(result).toEqual({});
|
|
147
|
+
expect(CloudProviderFactory.create).not.toHaveBeenCalled();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should create AWS provider by default', async () => {
|
|
151
|
+
const appDefinition = {
|
|
152
|
+
vpc: { enable: true },
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
await gatherDiscoveredResources(appDefinition);
|
|
156
|
+
|
|
157
|
+
expect(CloudProviderFactory.create).toHaveBeenCalledWith('aws', 'us-east-1');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should respect CLOUD_PROVIDER environment variable', async () => {
|
|
161
|
+
process.env.CLOUD_PROVIDER = 'gcp';
|
|
162
|
+
process.env.AWS_REGION = 'us-central1';
|
|
163
|
+
|
|
164
|
+
const appDefinition = {
|
|
165
|
+
vpc: { enable: true },
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
await gatherDiscoveredResources(appDefinition);
|
|
169
|
+
|
|
170
|
+
expect(CloudProviderFactory.create).toHaveBeenCalledWith('gcp', 'us-central1');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should respect AWS_REGION environment variable', async () => {
|
|
174
|
+
process.env.AWS_REGION = 'eu-west-1';
|
|
175
|
+
|
|
176
|
+
const appDefinition = {
|
|
177
|
+
vpc: { enable: true },
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
await gatherDiscoveredResources(appDefinition);
|
|
181
|
+
|
|
182
|
+
expect(CloudProviderFactory.create).toHaveBeenCalledWith('aws', 'eu-west-1');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should create discovery services with provider', async () => {
|
|
186
|
+
const appDefinition = {
|
|
187
|
+
vpc: { enable: true },
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
await gatherDiscoveredResources(appDefinition);
|
|
191
|
+
|
|
192
|
+
expect(VpcDiscovery).toHaveBeenCalledWith(mockProvider);
|
|
193
|
+
expect(KmsDiscovery).toHaveBeenCalledWith(mockProvider);
|
|
194
|
+
expect(AuroraDiscovery).toHaveBeenCalledWith(mockProvider);
|
|
195
|
+
expect(SsmDiscovery).toHaveBeenCalledWith(mockProvider);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should run VPC discovery when enabled', async () => {
|
|
199
|
+
const appDefinition = {
|
|
200
|
+
name: 'test-app',
|
|
201
|
+
vpc: { enable: true },
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
await gatherDiscoveredResources(appDefinition);
|
|
205
|
+
|
|
206
|
+
expect(mockVpcDiscovery.discover).toHaveBeenCalledWith(
|
|
207
|
+
expect.objectContaining({
|
|
208
|
+
serviceName: 'test-app',
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should skip VPC discovery when disabled', async () => {
|
|
214
|
+
const appDefinition = {
|
|
215
|
+
vpc: { enable: false },
|
|
216
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
await gatherDiscoveredResources(appDefinition);
|
|
220
|
+
|
|
221
|
+
expect(mockVpcDiscovery.discover).not.toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should run KMS discovery when encryption is kms', async () => {
|
|
225
|
+
const appDefinition = {
|
|
226
|
+
name: 'test-app',
|
|
227
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
await gatherDiscoveredResources(appDefinition);
|
|
231
|
+
|
|
232
|
+
expect(mockKmsDiscovery.discover).toHaveBeenCalled();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should skip KMS discovery when encryption is not kms', async () => {
|
|
236
|
+
const appDefinition = {
|
|
237
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
238
|
+
vpc: { enable: true },
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
await gatherDiscoveredResources(appDefinition);
|
|
242
|
+
|
|
243
|
+
expect(mockKmsDiscovery.discover).not.toHaveBeenCalled();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should run database discovery when postgres is enabled', async () => {
|
|
247
|
+
const appDefinition = {
|
|
248
|
+
database: { postgres: { enable: true } },
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
await gatherDiscoveredResources(appDefinition);
|
|
252
|
+
|
|
253
|
+
expect(mockAuroraDiscovery.discover).toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should skip database discovery when postgres is disabled', async () => {
|
|
257
|
+
const appDefinition = {
|
|
258
|
+
database: { postgres: { enable: false } },
|
|
259
|
+
vpc: { enable: true },
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
await gatherDiscoveredResources(appDefinition);
|
|
263
|
+
|
|
264
|
+
expect(mockAuroraDiscovery.discover).not.toHaveBeenCalled();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should run SSM discovery when enabled', async () => {
|
|
268
|
+
const appDefinition = {
|
|
269
|
+
ssm: { enable: true },
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
await gatherDiscoveredResources(appDefinition);
|
|
273
|
+
|
|
274
|
+
expect(mockSsmDiscovery.discover).toHaveBeenCalled();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should aggregate results from all discoveries', async () => {
|
|
278
|
+
mockVpcDiscovery.discover.mockResolvedValue({
|
|
279
|
+
defaultVpcId: 'vpc-123',
|
|
280
|
+
privateSubnetId1: 'subnet-1',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
mockKmsDiscovery.discover.mockResolvedValue({
|
|
284
|
+
kmsKeyId: 'arn:aws:kms:key/abc',
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
mockAuroraDiscovery.discover.mockResolvedValue({
|
|
288
|
+
auroraClusterEndpoint: 'db.example.com',
|
|
289
|
+
auroraPort: 5432,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
mockSsmDiscovery.discover.mockResolvedValue({
|
|
293
|
+
parameters: ['param1'],
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const appDefinition = {
|
|
297
|
+
vpc: { enable: true },
|
|
298
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
299
|
+
database: { postgres: { enable: true } },
|
|
300
|
+
ssm: { enable: true },
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
304
|
+
|
|
305
|
+
expect(result).toEqual({
|
|
306
|
+
defaultVpcId: 'vpc-123',
|
|
307
|
+
privateSubnetId1: 'subnet-1',
|
|
308
|
+
kmsKeyId: 'arn:aws:kms:key/abc',
|
|
309
|
+
auroraClusterEndpoint: 'db.example.com',
|
|
310
|
+
auroraPort: 5432,
|
|
311
|
+
parameters: ['param1'],
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should run discoveries in parallel for performance', async () => {
|
|
316
|
+
const appDefinition = {
|
|
317
|
+
vpc: { enable: true },
|
|
318
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
319
|
+
database: { postgres: { enable: true } },
|
|
320
|
+
ssm: { enable: true },
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
await gatherDiscoveredResources(appDefinition);
|
|
324
|
+
|
|
325
|
+
// All should have been called (proving parallel execution)
|
|
326
|
+
expect(mockVpcDiscovery.discover).toHaveBeenCalled();
|
|
327
|
+
expect(mockKmsDiscovery.discover).toHaveBeenCalled();
|
|
328
|
+
expect(mockAuroraDiscovery.discover).toHaveBeenCalled();
|
|
329
|
+
expect(mockSsmDiscovery.discover).toHaveBeenCalled();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should handle discovery errors gracefully', async () => {
|
|
333
|
+
mockVpcDiscovery.discover.mockRejectedValue(new Error('VPC API Error'));
|
|
334
|
+
|
|
335
|
+
const appDefinition = {
|
|
336
|
+
vpc: { enable: true },
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
340
|
+
|
|
341
|
+
// Should return empty object instead of throwing
|
|
342
|
+
expect(result).toEqual({});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should pass configuration to discoveries', async () => {
|
|
346
|
+
const appDefinition = {
|
|
347
|
+
name: 'my-service',
|
|
348
|
+
vpc: {
|
|
349
|
+
enable: true,
|
|
350
|
+
vpcId: 'vpc-custom',
|
|
351
|
+
},
|
|
352
|
+
database: {
|
|
353
|
+
postgres: {
|
|
354
|
+
enable: true,
|
|
355
|
+
clusterId: 'my-cluster',
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
encryption: {
|
|
359
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
360
|
+
keyAlias: 'alias/my-key',
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
process.env.SLS_STAGE = 'production';
|
|
365
|
+
|
|
366
|
+
await gatherDiscoveredResources(appDefinition);
|
|
367
|
+
|
|
368
|
+
expect(mockVpcDiscovery.discover).toHaveBeenCalledWith(
|
|
369
|
+
expect.objectContaining({
|
|
370
|
+
serviceName: 'my-service',
|
|
371
|
+
stage: 'production',
|
|
372
|
+
vpcId: 'vpc-custom',
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
expect(mockAuroraDiscovery.discover).toHaveBeenCalledWith(
|
|
377
|
+
expect.objectContaining({
|
|
378
|
+
databaseId: 'my-cluster',
|
|
379
|
+
})
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
expect(mockKmsDiscovery.discover).toHaveBeenCalledWith(
|
|
383
|
+
expect.objectContaining({
|
|
384
|
+
keyAlias: 'alias/my-key',
|
|
385
|
+
})
|
|
386
|
+
);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should default stage to dev', async () => {
|
|
390
|
+
const appDefinition = {
|
|
391
|
+
vpc: { enable: true },
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
await gatherDiscoveredResources(appDefinition);
|
|
395
|
+
|
|
396
|
+
expect(mockVpcDiscovery.discover).toHaveBeenCalledWith(
|
|
397
|
+
expect.objectContaining({
|
|
398
|
+
stage: 'dev',
|
|
399
|
+
})
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should read stage from SLS_STAGE environment variable for CLI integration', async () => {
|
|
404
|
+
// This test documents the contract between frigg CLI and discovery
|
|
405
|
+
// The CLI sets SLS_STAGE environment variable when user passes --stage flag
|
|
406
|
+
process.env.SLS_STAGE = 'qa';
|
|
407
|
+
|
|
408
|
+
const appDefinition = {
|
|
409
|
+
name: 'quo-integrations',
|
|
410
|
+
vpc: { enable: true },
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
await gatherDiscoveredResources(appDefinition);
|
|
414
|
+
|
|
415
|
+
// Verify stage is read from SLS_STAGE
|
|
416
|
+
expect(mockVpcDiscovery.discover).toHaveBeenCalledWith(
|
|
417
|
+
expect.objectContaining({
|
|
418
|
+
serviceName: 'quo-integrations',
|
|
419
|
+
stage: 'qa',
|
|
420
|
+
})
|
|
421
|
+
);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should recognize routing infrastructure as useful data', async () => {
|
|
425
|
+
const appDefinition = {
|
|
426
|
+
name: 'test-app',
|
|
427
|
+
vpc: { enable: true },
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
process.env.SLS_STAGE = 'production';
|
|
431
|
+
|
|
432
|
+
CloudFormationDiscovery.mockImplementation(() => ({
|
|
433
|
+
discoverFromStack: jest.fn().mockResolvedValue({
|
|
434
|
+
fromCloudFormationStack: true,
|
|
435
|
+
routeTableId: 'rtb-123',
|
|
436
|
+
natRoute: 'rtb-123|0.0.0.0/0',
|
|
437
|
+
vpcEndpoints: {
|
|
438
|
+
s3: 'vpce-s3',
|
|
439
|
+
dynamodb: 'vpce-ddb'
|
|
440
|
+
},
|
|
441
|
+
existingLogicalIds: ['FriggLambdaRouteTable', 'FriggNATRoute']
|
|
442
|
+
})
|
|
443
|
+
}));
|
|
444
|
+
|
|
445
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
446
|
+
|
|
447
|
+
expect(result.routeTableId).toBe('rtb-123');
|
|
448
|
+
expect(result.vpcEndpoints.s3).toBe('vpce-s3');
|
|
449
|
+
expect(mockVpcDiscovery.discover).not.toHaveBeenCalled();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should include secrets in SSM discovery by default', async () => {
|
|
453
|
+
const appDefinition = {
|
|
454
|
+
ssm: { enable: true },
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
await gatherDiscoveredResources(appDefinition);
|
|
458
|
+
|
|
459
|
+
expect(mockSsmDiscovery.discover).toHaveBeenCalledWith(
|
|
460
|
+
expect.objectContaining({
|
|
461
|
+
includeSecrets: true,
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe('Isolated Mode Discovery', () => {
|
|
468
|
+
beforeEach(() => {
|
|
469
|
+
CloudFormationDiscovery.mockImplementation(() => ({
|
|
470
|
+
discoverFromStack: jest.fn().mockResolvedValue({}),
|
|
471
|
+
}));
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('should discover KMS but not VPC/Aurora in isolated mode', async () => {
|
|
475
|
+
const appDefinition = {
|
|
476
|
+
name: 'test-app',
|
|
477
|
+
managementMode: 'managed',
|
|
478
|
+
vpcIsolation: 'isolated',
|
|
479
|
+
vpc: { enable: true },
|
|
480
|
+
database: { postgres: { enable: true } },
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
process.env.SLS_STAGE = 'dev';
|
|
484
|
+
|
|
485
|
+
// Mock KMS discovery returning a shared key
|
|
486
|
+
mockKmsDiscovery.discover.mockResolvedValue({
|
|
487
|
+
defaultKmsKeyId: 'shared-kms-key-123',
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
491
|
+
|
|
492
|
+
// Should return KMS (shareable) but not VPC/Aurora (isolated)
|
|
493
|
+
expect(result).toEqual({
|
|
494
|
+
defaultKmsKeyId: 'shared-kms-key-123',
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Should call KMS discovery (shared) but NOT VPC/Aurora discovery (isolated)
|
|
498
|
+
expect(mockKmsDiscovery.discover).toHaveBeenCalled();
|
|
499
|
+
expect(mockVpcDiscovery.discover).not.toHaveBeenCalled();
|
|
500
|
+
expect(mockAuroraDiscovery.discover).not.toHaveBeenCalled();
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('should return empty if no KMS found in isolated mode (fresh infrastructure)', async () => {
|
|
504
|
+
|
|
505
|
+
const appDefinition = {
|
|
506
|
+
name: 'test-app',
|
|
507
|
+
managementMode: 'managed',
|
|
508
|
+
vpcIsolation: 'isolated',
|
|
509
|
+
vpc: { enable: true },
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
process.env.SLS_STAGE = 'dev';
|
|
513
|
+
|
|
514
|
+
// Mock KMS discovery finding nothing
|
|
515
|
+
mockKmsDiscovery.discover.mockResolvedValue({});
|
|
516
|
+
|
|
517
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
518
|
+
|
|
519
|
+
// Should return empty (no VPC/Aurora, and KMS not found)
|
|
520
|
+
// This will trigger fresh KMS creation
|
|
521
|
+
expect(result).toEqual({});
|
|
522
|
+
|
|
523
|
+
// Should call KMS discovery but NOT VPC/Aurora
|
|
524
|
+
expect(mockKmsDiscovery.discover).toHaveBeenCalled();
|
|
525
|
+
expect(mockVpcDiscovery.discover).not.toHaveBeenCalled();
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('should use AWS API discovery in shared mode', async () => {
|
|
529
|
+
const appDefinition = {
|
|
530
|
+
name: 'test-app',
|
|
531
|
+
managementMode: 'managed',
|
|
532
|
+
vpcIsolation: 'shared', // NOT isolated
|
|
533
|
+
vpc: { enable: true },
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
await gatherDiscoveredResources(appDefinition);
|
|
537
|
+
|
|
538
|
+
// Should call AWS API discovery (shared mode finds resources across stages)
|
|
539
|
+
expect(mockVpcDiscovery.discover).toHaveBeenCalled();
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('should search for specific KMS alias in isolated mode to find orphaned keys', async () => {
|
|
543
|
+
const appDefinition = {
|
|
544
|
+
name: 'quo-integrations',
|
|
545
|
+
managementMode: 'managed',
|
|
546
|
+
vpcIsolation: 'isolated',
|
|
547
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
process.env.SLS_STAGE = 'dev';
|
|
551
|
+
|
|
552
|
+
// Mock KMS discovery finding key via alias search
|
|
553
|
+
mockKmsDiscovery.discover.mockResolvedValue({
|
|
554
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123:key/found-via-alias',
|
|
555
|
+
kmsKeyAlias: 'alias/quo-integrations-dev-frigg-kms',
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
559
|
+
|
|
560
|
+
// Should pass keyAlias to discover orphaned KMS keys
|
|
561
|
+
expect(mockKmsDiscovery.discover).toHaveBeenCalledWith(
|
|
562
|
+
expect.objectContaining({
|
|
563
|
+
serviceName: 'quo-integrations',
|
|
564
|
+
stage: 'dev',
|
|
565
|
+
keyAlias: 'alias/quo-integrations-dev-frigg-kms',
|
|
566
|
+
})
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
// Should return discovered KMS key (even if orphaned from stack)
|
|
570
|
+
expect(result).toEqual({
|
|
571
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123:key/found-via-alias',
|
|
572
|
+
kmsKeyAlias: 'alias/quo-integrations-dev-frigg-kms',
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
describe('VPC Self-Heal', () => {
|
|
578
|
+
let mockEc2Send;
|
|
579
|
+
|
|
580
|
+
beforeEach(() => {
|
|
581
|
+
AssociateRouteTableCommand.mockClear();
|
|
582
|
+
mockEc2Send = jest.fn().mockResolvedValue({ AssociationId: 'rtbassoc-new-123' });
|
|
583
|
+
|
|
584
|
+
CloudFormationDiscovery.mockImplementation(() => ({
|
|
585
|
+
discoverFromStack: jest.fn().mockResolvedValue({
|
|
586
|
+
fromCloudFormationStack: true,
|
|
587
|
+
routeTableId: 'rtb-123',
|
|
588
|
+
routeTableAssociationCount: 0,
|
|
589
|
+
privateSubnetId1: 'subnet-priv-1',
|
|
590
|
+
privateSubnetId2: 'subnet-priv-2',
|
|
591
|
+
defaultVpcId: 'vpc-123',
|
|
592
|
+
}),
|
|
593
|
+
}));
|
|
594
|
+
|
|
595
|
+
mockProvider.getEC2Client = jest.fn().mockReturnValue({
|
|
596
|
+
send: mockEc2Send,
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('should self-heal when route table has 0 associations and selfHeal is enabled', async () => {
|
|
601
|
+
const appDefinition = {
|
|
602
|
+
name: 'test-app',
|
|
603
|
+
vpc: { enable: true, selfHeal: true },
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
process.env.SLS_STAGE = 'production';
|
|
607
|
+
|
|
608
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
609
|
+
|
|
610
|
+
expect(mockEc2Send).toHaveBeenCalledTimes(2);
|
|
611
|
+
expect(AssociateRouteTableCommand).toHaveBeenCalledWith({
|
|
612
|
+
RouteTableId: 'rtb-123',
|
|
613
|
+
SubnetId: 'subnet-priv-1',
|
|
614
|
+
});
|
|
615
|
+
expect(AssociateRouteTableCommand).toHaveBeenCalledWith({
|
|
616
|
+
RouteTableId: 'rtb-123',
|
|
617
|
+
SubnetId: 'subnet-priv-2',
|
|
618
|
+
});
|
|
619
|
+
expect(result.routeTableId).toBe('rtb-123');
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it('should only associate private subnets with lambda route table (not public subnet)', async () => {
|
|
623
|
+
// Simulates CF discovery output for: 3 subnets in VPC (1 public, 2 private),
|
|
624
|
+
// IGW route table has all 3, Frigg lambda route table has 0 associations.
|
|
625
|
+
// CF discovery already filtered by !MapPublicIpOnLaunch, so only private IDs arrive here.
|
|
626
|
+
CloudFormationDiscovery.mockImplementation(() => ({
|
|
627
|
+
discoverFromStack: jest.fn().mockResolvedValue({
|
|
628
|
+
fromCloudFormationStack: true,
|
|
629
|
+
routeTableId: 'rtb-lambda',
|
|
630
|
+
routeTableAssociationCount: 0,
|
|
631
|
+
privateSubnetId1: 'subnet-priv-1',
|
|
632
|
+
privateSubnetId2: 'subnet-priv-2',
|
|
633
|
+
defaultVpcId: 'vpc-123',
|
|
634
|
+
}),
|
|
635
|
+
}));
|
|
636
|
+
|
|
637
|
+
const appDefinition = {
|
|
638
|
+
name: 'test-app',
|
|
639
|
+
vpc: { enable: true, selfHeal: true },
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
process.env.SLS_STAGE = 'production';
|
|
643
|
+
|
|
644
|
+
await gatherDiscoveredResources(appDefinition);
|
|
645
|
+
|
|
646
|
+
// Self-heal should associate ONLY the 2 private subnets
|
|
647
|
+
expect(mockEc2Send).toHaveBeenCalledTimes(2);
|
|
648
|
+
expect(AssociateRouteTableCommand).toHaveBeenCalledWith({
|
|
649
|
+
RouteTableId: 'rtb-lambda',
|
|
650
|
+
SubnetId: 'subnet-priv-1',
|
|
651
|
+
});
|
|
652
|
+
expect(AssociateRouteTableCommand).toHaveBeenCalledWith({
|
|
653
|
+
RouteTableId: 'rtb-lambda',
|
|
654
|
+
SubnetId: 'subnet-priv-2',
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Public subnet (subnet-public) should never appear in any AssociateRouteTableCommand call
|
|
658
|
+
const allCalls = AssociateRouteTableCommand.mock.calls.map(c => c[0].SubnetId);
|
|
659
|
+
expect(allCalls).not.toContain('subnet-public');
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('should not self-heal when selfHeal is disabled', async () => {
|
|
663
|
+
const appDefinition = {
|
|
664
|
+
name: 'test-app',
|
|
665
|
+
vpc: { enable: true, selfHeal: false },
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
process.env.SLS_STAGE = 'production';
|
|
669
|
+
|
|
670
|
+
await gatherDiscoveredResources(appDefinition);
|
|
671
|
+
|
|
672
|
+
expect(mockEc2Send).not.toHaveBeenCalled();
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('should not self-heal when routeTableAssociationCount is not 0', async () => {
|
|
676
|
+
CloudFormationDiscovery.mockImplementation(() => ({
|
|
677
|
+
discoverFromStack: jest.fn().mockResolvedValue({
|
|
678
|
+
fromCloudFormationStack: true,
|
|
679
|
+
routeTableId: 'rtb-123',
|
|
680
|
+
routeTableAssociationCount: 2,
|
|
681
|
+
privateSubnetId1: 'subnet-priv-1',
|
|
682
|
+
privateSubnetId2: 'subnet-priv-2',
|
|
683
|
+
defaultVpcId: 'vpc-123',
|
|
684
|
+
}),
|
|
685
|
+
}));
|
|
686
|
+
|
|
687
|
+
const appDefinition = {
|
|
688
|
+
name: 'test-app',
|
|
689
|
+
vpc: { enable: true, selfHeal: true },
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
process.env.SLS_STAGE = 'production';
|
|
693
|
+
|
|
694
|
+
await gatherDiscoveredResources(appDefinition);
|
|
695
|
+
|
|
696
|
+
expect(mockEc2Send).not.toHaveBeenCalled();
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
it('should continue associating subnet 2 if subnet 1 fails', async () => {
|
|
700
|
+
mockEc2Send
|
|
701
|
+
.mockRejectedValueOnce(new Error('Resource.AlreadyAssociated'))
|
|
702
|
+
.mockResolvedValueOnce({ AssociationId: 'rtbassoc-456' });
|
|
703
|
+
|
|
704
|
+
const appDefinition = {
|
|
705
|
+
name: 'test-app',
|
|
706
|
+
vpc: { enable: true, selfHeal: true },
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
process.env.SLS_STAGE = 'production';
|
|
710
|
+
|
|
711
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
712
|
+
|
|
713
|
+
expect(mockEc2Send).toHaveBeenCalledTimes(2);
|
|
714
|
+
expect(result.routeTableId).toBe('rtb-123');
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it('should handle both subnet associations failing gracefully', async () => {
|
|
718
|
+
mockEc2Send.mockRejectedValue(new Error('AccessDenied'));
|
|
719
|
+
|
|
720
|
+
const appDefinition = {
|
|
721
|
+
name: 'test-app',
|
|
722
|
+
vpc: { enable: true, selfHeal: true },
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
process.env.SLS_STAGE = 'production';
|
|
726
|
+
|
|
727
|
+
const result = await gatherDiscoveredResources(appDefinition);
|
|
728
|
+
|
|
729
|
+
expect(mockEc2Send).toHaveBeenCalledTimes(2);
|
|
730
|
+
expect(result.routeTableId).toBe('rtb-123');
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('should not self-heal when privateSubnetId2 is missing', async () => {
|
|
734
|
+
CloudFormationDiscovery.mockImplementation(() => ({
|
|
735
|
+
discoverFromStack: jest.fn().mockResolvedValue({
|
|
736
|
+
fromCloudFormationStack: true,
|
|
737
|
+
routeTableId: 'rtb-123',
|
|
738
|
+
routeTableAssociationCount: 0,
|
|
739
|
+
privateSubnetId1: 'subnet-priv-1',
|
|
740
|
+
// privateSubnetId2 missing
|
|
741
|
+
defaultVpcId: 'vpc-123',
|
|
742
|
+
}),
|
|
743
|
+
}));
|
|
744
|
+
|
|
745
|
+
const appDefinition = {
|
|
746
|
+
name: 'test-app',
|
|
747
|
+
vpc: { enable: true, selfHeal: true },
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
process.env.SLS_STAGE = 'production';
|
|
751
|
+
|
|
752
|
+
await gatherDiscoveredResources(appDefinition);
|
|
753
|
+
|
|
754
|
+
expect(mockEc2Send).not.toHaveBeenCalled();
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
});
|