@friggframework/devtools 2.0.0-next.44 → 2.0.0-next.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- package/infrastructure/domains/database/aurora-builder.js +809 -0
- package/infrastructure/domains/database/aurora-builder.test.js +950 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +633 -0
- package/infrastructure/domains/database/migration-builder.test.js +294 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +397 -0
- package/infrastructure/domains/integration/integration-builder.test.js +593 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-builder.js +1829 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
- package/infrastructure/domains/networking/vpc-discovery.js +177 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
- package/infrastructure/domains/networking/vpc-resolver.js +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +366 -0
- package/infrastructure/domains/security/kms-builder.test.js +374 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
- package/infrastructure/domains/shared/environment-builder.js +119 -0
- package/infrastructure/domains/shared/environment-builder.test.js +247 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.js +192 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
- package/infrastructure/domains/shared/types/app-definition.js +205 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
- package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +87 -0
- package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
- package/infrastructure/scripts/build-prisma-layer.js +553 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
- package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/cliIntegration.js +1 -1
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +11 -11
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -65
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -175
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -75
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -54
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -161
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/prisma-runner.js +0 -280
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- package/infrastructure/aws-discovery.js +0 -1176
- package/infrastructure/aws-discovery.test.js +0 -1220
- package/infrastructure/serverless-template.js +0 -2074
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for PostgreSQL (Aurora) configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { composeServerlessDefinition } = require('../infrastructure-composer');
|
|
6
|
+
const { AWSDiscovery } = require('../aws-discovery');
|
|
7
|
+
const { mockClient } = require('aws-sdk-client-mock');
|
|
8
|
+
const { RDSClient, DescribeDBClustersCommand, DescribeDBSubnetGroupsCommand } = require('@aws-sdk/client-rds');
|
|
9
|
+
const { SecretsManagerClient, ListSecretsCommand, DescribeSecretCommand } = require('@aws-sdk/client-secrets-manager');
|
|
10
|
+
const { EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, DescribeRouteTablesCommand } = require('@aws-sdk/client-ec2');
|
|
11
|
+
const { KMSClient, ListAliasesCommand } = require('@aws-sdk/client-kms');
|
|
12
|
+
const { STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts');
|
|
13
|
+
|
|
14
|
+
// Create mocks for AWS SDK clients
|
|
15
|
+
const rdsMock = mockClient(RDSClient);
|
|
16
|
+
const secretsManagerMock = mockClient(SecretsManagerClient);
|
|
17
|
+
const ec2Mock = mockClient(EC2Client);
|
|
18
|
+
const kmsMock = mockClient(KMSClient);
|
|
19
|
+
const stsMock = mockClient(STSClient);
|
|
20
|
+
|
|
21
|
+
describe('PostgreSQL Aurora Configuration', () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
// Reset all mocks
|
|
24
|
+
rdsMock.reset();
|
|
25
|
+
secretsManagerMock.reset();
|
|
26
|
+
ec2Mock.reset();
|
|
27
|
+
kmsMock.reset();
|
|
28
|
+
stsMock.reset();
|
|
29
|
+
|
|
30
|
+
// Setup default AWS mocks for discovery
|
|
31
|
+
ec2Mock.on(DescribeVpcsCommand).resolves({
|
|
32
|
+
Vpcs: [{ VpcId: 'vpc-12345', IsDefault: true }]
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
36
|
+
Subnets: [
|
|
37
|
+
{ SubnetId: 'subnet-1', AvailabilityZone: 'us-east-1a', MapPublicIpOnLaunch: false },
|
|
38
|
+
{ SubnetId: 'subnet-2', AvailabilityZone: 'us-east-1b', MapPublicIpOnLaunch: false }
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
ec2Mock.on(DescribeSecurityGroupsCommand).resolves({
|
|
43
|
+
SecurityGroups: [{ GroupId: 'sg-12345', GroupName: 'default' }]
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
stsMock.on(GetCallerIdentityCommand).resolves({
|
|
47
|
+
Account: '123456789012'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
51
|
+
RouteTables: [{ RouteTableId: 'rtb-12345', VpcId: 'vpc-12345' }]
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
jest.clearAllMocks();
|
|
57
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('configurePostgres()', () => {
|
|
61
|
+
test('should skip configuration when postgres not enabled', async () => {
|
|
62
|
+
// Skip discovery since postgres not enabled
|
|
63
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
64
|
+
|
|
65
|
+
const appDefinition = {
|
|
66
|
+
name: 'test-app',
|
|
67
|
+
integrations: [],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
71
|
+
|
|
72
|
+
expect(definition.resources.Resources.FriggAuroraCluster).toBeUndefined();
|
|
73
|
+
expect(definition.provider.environment.DATABASE_URL).toBeUndefined();
|
|
74
|
+
expect(definition.provider.environment.DB_TYPE).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('should create Aurora infrastructure in create-new mode', async () => {
|
|
78
|
+
// Skip discovery for create-new mode test
|
|
79
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
80
|
+
const appDefinition = {
|
|
81
|
+
name: 'test-app',
|
|
82
|
+
integrations: [],
|
|
83
|
+
vpc: { enable: true, management: 'create-new' },
|
|
84
|
+
database: {
|
|
85
|
+
postgres: {
|
|
86
|
+
enable: true,
|
|
87
|
+
management: 'create-new',
|
|
88
|
+
databaseName: 'test_db',
|
|
89
|
+
masterUsername: 'test_admin',
|
|
90
|
+
scaling: {
|
|
91
|
+
minCapacity: 0.5,
|
|
92
|
+
maxCapacity: 1.0,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const discoveredResources = {
|
|
99
|
+
defaultVpcId: 'vpc-12345',
|
|
100
|
+
defaultSecurityGroupId: 'sg-12345',
|
|
101
|
+
privateSubnetId1: 'subnet-1',
|
|
102
|
+
privateSubnetId2: 'subnet-2',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
106
|
+
|
|
107
|
+
// Should create Aurora cluster resources
|
|
108
|
+
expect(definition.resources.Resources.FriggAuroraCluster).toBeDefined();
|
|
109
|
+
expect(definition.resources.Resources.FriggAuroraCluster.Type).toBe('AWS::RDS::DBCluster');
|
|
110
|
+
expect(definition.resources.Resources.FriggAuroraCluster.Properties.Engine).toBe('aurora-postgresql');
|
|
111
|
+
expect(definition.resources.Resources.FriggAuroraCluster.Properties.DatabaseName).toBe('test_db');
|
|
112
|
+
|
|
113
|
+
// Should create Aurora instance
|
|
114
|
+
expect(definition.resources.Resources.FriggAuroraInstance).toBeDefined();
|
|
115
|
+
expect(definition.resources.Resources.FriggAuroraInstance.Properties.DBInstanceClass).toBe('db.serverless');
|
|
116
|
+
|
|
117
|
+
// Should create DB subnet group
|
|
118
|
+
expect(definition.resources.Resources.FriggDBSubnetGroup).toBeDefined();
|
|
119
|
+
|
|
120
|
+
// Should create security group
|
|
121
|
+
expect(definition.resources.Resources.FriggAuroraSecurityGroup).toBeDefined();
|
|
122
|
+
expect(definition.resources.Resources.FriggAuroraSecurityGroup.Properties.SecurityGroupIngress[0].FromPort).toBe(5432);
|
|
123
|
+
|
|
124
|
+
// Should create Secrets Manager secret
|
|
125
|
+
expect(definition.resources.Resources.FriggDatabaseSecret).toBeDefined();
|
|
126
|
+
|
|
127
|
+
// Should create secret attachment
|
|
128
|
+
expect(definition.resources.Resources.FriggSecretAttachment).toBeDefined();
|
|
129
|
+
|
|
130
|
+
// Should set environment variables
|
|
131
|
+
expect(definition.provider.environment.DATABASE_URL).toBeDefined();
|
|
132
|
+
expect(definition.provider.environment.DB_TYPE).toBe('postgresql');
|
|
133
|
+
|
|
134
|
+
// Should add IAM permissions for Secrets Manager
|
|
135
|
+
const secretsManagerPolicy = definition.provider.iamRoleStatements.find(
|
|
136
|
+
stmt => stmt.Action.includes('secretsmanager:GetSecretValue')
|
|
137
|
+
);
|
|
138
|
+
expect(secretsManagerPolicy).toBeDefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('should configure Aurora with custom settings', async () => {
|
|
142
|
+
// Skip discovery for create-new mode test
|
|
143
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
144
|
+
|
|
145
|
+
const appDefinition = {
|
|
146
|
+
name: 'test-app',
|
|
147
|
+
integrations: [],
|
|
148
|
+
vpc: { enable: true, management: 'create-new' },
|
|
149
|
+
database: {
|
|
150
|
+
postgres: {
|
|
151
|
+
enable: true,
|
|
152
|
+
management: 'create-new',
|
|
153
|
+
engineVersion: '14.6',
|
|
154
|
+
scaling: {
|
|
155
|
+
minCapacity: 1.0,
|
|
156
|
+
maxCapacity: 4.0,
|
|
157
|
+
},
|
|
158
|
+
backupRetentionDays: 14,
|
|
159
|
+
deletionProtection: false,
|
|
160
|
+
enablePerformanceInsights: true,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
166
|
+
|
|
167
|
+
const cluster = definition.resources.Resources.FriggAuroraCluster;
|
|
168
|
+
expect(cluster.Properties.EngineVersion).toBe('14.6');
|
|
169
|
+
expect(cluster.Properties.ServerlessV2ScalingConfiguration.MinCapacity).toBe(1.0);
|
|
170
|
+
expect(cluster.Properties.ServerlessV2ScalingConfiguration.MaxCapacity).toBe(4.0);
|
|
171
|
+
expect(cluster.Properties.BackupRetentionPeriod).toBe(14);
|
|
172
|
+
expect(cluster.Properties.DeletionProtection).toBe(false);
|
|
173
|
+
|
|
174
|
+
const instance = definition.resources.Resources.FriggAuroraInstance;
|
|
175
|
+
expect(instance.Properties.EnablePerformanceInsights).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('should set deletion protection by default', async () => {
|
|
179
|
+
// Skip discovery for create-new mode test
|
|
180
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
181
|
+
|
|
182
|
+
const appDefinition = {
|
|
183
|
+
name: 'test-app',
|
|
184
|
+
integrations: [],
|
|
185
|
+
vpc: { enable: true, management: 'create-new' },
|
|
186
|
+
database: {
|
|
187
|
+
postgres: {
|
|
188
|
+
enable: true,
|
|
189
|
+
management: 'create-new',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
195
|
+
|
|
196
|
+
const cluster = definition.resources.Resources.FriggAuroraCluster;
|
|
197
|
+
expect(cluster.Properties.DeletionProtection).toBe(true);
|
|
198
|
+
expect(cluster.DeletionPolicy).toBe('Snapshot');
|
|
199
|
+
expect(cluster.UpdateReplacePolicy).toBe('Snapshot');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('should enable CloudWatch Logs exports', async () => {
|
|
203
|
+
// Skip discovery for create-new mode test
|
|
204
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
205
|
+
|
|
206
|
+
const appDefinition = {
|
|
207
|
+
name: 'test-app',
|
|
208
|
+
integrations: [],
|
|
209
|
+
vpc: { enable: true, management: 'create-new' },
|
|
210
|
+
database: {
|
|
211
|
+
postgres: {
|
|
212
|
+
enable: true,
|
|
213
|
+
management: 'create-new',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
219
|
+
|
|
220
|
+
const cluster = definition.resources.Resources.FriggAuroraCluster;
|
|
221
|
+
expect(cluster.Properties.EnableCloudwatchLogsExports).toContain('postgresql');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('should throw error when use-existing without cluster identifier', async () => {
|
|
225
|
+
// Skip discovery and mock RDS to return empty clusters
|
|
226
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
227
|
+
|
|
228
|
+
const appDefinition = {
|
|
229
|
+
name: 'test-app',
|
|
230
|
+
integrations: [],
|
|
231
|
+
vpc: { enable: true },
|
|
232
|
+
database: {
|
|
233
|
+
postgres: {
|
|
234
|
+
enable: true,
|
|
235
|
+
management: 'use-existing',
|
|
236
|
+
// Missing clusterIdentifier
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Should throw error during configuration
|
|
242
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('buildEnvironment() with Aurora mappings', () => {
|
|
247
|
+
test('should add Aurora environment variables', async () => {
|
|
248
|
+
// Skip discovery for create-new mode test
|
|
249
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
250
|
+
|
|
251
|
+
const appDefinition = {
|
|
252
|
+
name: 'test-app',
|
|
253
|
+
integrations: [],
|
|
254
|
+
vpc: { enable: true, management: 'create-new' },
|
|
255
|
+
database: {
|
|
256
|
+
postgres: {
|
|
257
|
+
enable: true,
|
|
258
|
+
management: 'create-new',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
264
|
+
|
|
265
|
+
// Should have standard env vars
|
|
266
|
+
expect(definition.provider.environment.STAGE).toBe('${opt:stage, "dev"}');
|
|
267
|
+
expect(definition.provider.environment.AWS_NODEJS_CONNECTION_REUSE_ENABLED).toBe(1);
|
|
268
|
+
|
|
269
|
+
// Should have DATABASE_URL and DB_TYPE
|
|
270
|
+
expect(definition.provider.environment.DATABASE_URL).toBeDefined();
|
|
271
|
+
expect(definition.provider.environment.DB_TYPE).toBe('postgresql');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('should not add Aurora env vars when postgres disabled', async () => {
|
|
275
|
+
// Skip discovery since postgres not enabled
|
|
276
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
277
|
+
|
|
278
|
+
const appDefinition = {
|
|
279
|
+
name: 'test-app',
|
|
280
|
+
integrations: [],
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
284
|
+
|
|
285
|
+
expect(definition.provider.environment.DATABASE_URL).toBeUndefined();
|
|
286
|
+
expect(definition.provider.environment.DB_TYPE).toBeUndefined();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('Database URL construction', () => {
|
|
291
|
+
test('should construct DATABASE_URL with Fn::Sub for create-new mode', async () => {
|
|
292
|
+
// Skip discovery for create-new mode test
|
|
293
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
294
|
+
|
|
295
|
+
const appDefinition = {
|
|
296
|
+
name: 'test-app',
|
|
297
|
+
integrations: [],
|
|
298
|
+
vpc: { enable: true, management: 'create-new' },
|
|
299
|
+
database: {
|
|
300
|
+
postgres: {
|
|
301
|
+
enable: true,
|
|
302
|
+
management: 'create-new',
|
|
303
|
+
databaseName: 'my_db',
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
309
|
+
|
|
310
|
+
const databaseUrl = definition.provider.environment.DATABASE_URL;
|
|
311
|
+
expect(databaseUrl['Fn::Sub']).toBeDefined();
|
|
312
|
+
expect(databaseUrl['Fn::Sub'][0]).toContain('postgresql://');
|
|
313
|
+
expect(databaseUrl['Fn::Sub'][0]).toContain('${Username}');
|
|
314
|
+
expect(databaseUrl['Fn::Sub'][0]).toContain('${Password}');
|
|
315
|
+
expect(databaseUrl['Fn::Sub'][0]).toContain('${Endpoint}');
|
|
316
|
+
expect(databaseUrl['Fn::Sub'][0]).toContain('${Port}');
|
|
317
|
+
expect(databaseUrl['Fn::Sub'][0]).toContain('${DatabaseName}');
|
|
318
|
+
|
|
319
|
+
const substitutions = databaseUrl['Fn::Sub'][1];
|
|
320
|
+
expect(substitutions.Username).toBeDefined();
|
|
321
|
+
expect(substitutions.Password).toBeDefined();
|
|
322
|
+
expect(substitutions.Endpoint).toBeDefined();
|
|
323
|
+
expect(substitutions.Port).toBeDefined();
|
|
324
|
+
expect(substitutions.DatabaseName).toBe('my_db');
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('Secrets Manager integration', () => {
|
|
329
|
+
test('should create secret with correct template', async () => {
|
|
330
|
+
// Skip discovery for create-new mode test
|
|
331
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
332
|
+
|
|
333
|
+
const appDefinition = {
|
|
334
|
+
name: 'test-app',
|
|
335
|
+
integrations: [],
|
|
336
|
+
vpc: { enable: true, management: 'create-new' },
|
|
337
|
+
database: {
|
|
338
|
+
postgres: {
|
|
339
|
+
enable: true,
|
|
340
|
+
management: 'create-new',
|
|
341
|
+
masterUsername: 'custom_admin',
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
347
|
+
|
|
348
|
+
const secret = definition.resources.Resources.FriggDatabaseSecret;
|
|
349
|
+
expect(secret.Properties.GenerateSecretString.SecretStringTemplate).toBe(
|
|
350
|
+
JSON.stringify({ username: 'custom_admin' })
|
|
351
|
+
);
|
|
352
|
+
expect(secret.Properties.GenerateSecretString.GenerateStringKey).toBe('password');
|
|
353
|
+
expect(secret.Properties.GenerateSecretString.PasswordLength).toBe(32);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test('should exclude special characters from password', async () => {
|
|
357
|
+
// Skip discovery for create-new mode test
|
|
358
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
359
|
+
|
|
360
|
+
const appDefinition = {
|
|
361
|
+
name: 'test-app',
|
|
362
|
+
integrations: [],
|
|
363
|
+
vpc: { enable: true, management: 'create-new' },
|
|
364
|
+
database: {
|
|
365
|
+
postgres: {
|
|
366
|
+
enable: true,
|
|
367
|
+
management: 'create-new',
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
373
|
+
|
|
374
|
+
const secret = definition.resources.Resources.FriggDatabaseSecret;
|
|
375
|
+
expect(secret.Properties.GenerateSecretString.ExcludeCharacters).toBe('"@/\\');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test('should add Frigg tags to secret', async () => {
|
|
379
|
+
// Skip discovery for create-new mode test
|
|
380
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
381
|
+
|
|
382
|
+
const appDefinition = {
|
|
383
|
+
name: 'test-app',
|
|
384
|
+
integrations: [],
|
|
385
|
+
vpc: { enable: true, management: 'create-new' },
|
|
386
|
+
database: {
|
|
387
|
+
postgres: {
|
|
388
|
+
enable: true,
|
|
389
|
+
management: 'create-new',
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
395
|
+
|
|
396
|
+
const secret = definition.resources.Resources.FriggDatabaseSecret;
|
|
397
|
+
const tags = secret.Properties.Tags;
|
|
398
|
+
|
|
399
|
+
expect(tags.find(t => t.Key === 'ManagedBy' && t.Value === 'Frigg')).toBeDefined();
|
|
400
|
+
expect(tags.find(t => t.Key === 'Service' && t.Value === '${self:service}')).toBeDefined();
|
|
401
|
+
expect(tags.find(t => t.Key === 'Stage' && t.Value === '${self:provider.stage}')).toBeDefined();
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe('Security Group configuration', () => {
|
|
406
|
+
test('should allow Lambda SG to access Aurora on port 5432', async () => {
|
|
407
|
+
// Skip discovery for create-new mode test
|
|
408
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
409
|
+
|
|
410
|
+
const appDefinition = {
|
|
411
|
+
name: 'test-app',
|
|
412
|
+
integrations: [],
|
|
413
|
+
vpc: { enable: true, management: 'create-new' },
|
|
414
|
+
database: {
|
|
415
|
+
postgres: {
|
|
416
|
+
enable: true,
|
|
417
|
+
management: 'create-new',
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
423
|
+
|
|
424
|
+
const sg = definition.resources.Resources.FriggAuroraSecurityGroup;
|
|
425
|
+
const ingressRule = sg.Properties.SecurityGroupIngress[0];
|
|
426
|
+
|
|
427
|
+
expect(ingressRule.IpProtocol).toBe('tcp');
|
|
428
|
+
expect(ingressRule.FromPort).toBe(5432);
|
|
429
|
+
expect(ingressRule.ToPort).toBe(5432);
|
|
430
|
+
expect(ingressRule.Description).toContain('PostgreSQL');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('should add Frigg tags to security group', async () => {
|
|
434
|
+
// Skip discovery for create-new mode test
|
|
435
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
436
|
+
|
|
437
|
+
const appDefinition = {
|
|
438
|
+
name: 'test-app',
|
|
439
|
+
integrations: [],
|
|
440
|
+
vpc: { enable: true, management: 'create-new' },
|
|
441
|
+
database: {
|
|
442
|
+
postgres: {
|
|
443
|
+
enable: true,
|
|
444
|
+
management: 'create-new',
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
450
|
+
|
|
451
|
+
const sg = definition.resources.Resources.FriggAuroraSecurityGroup;
|
|
452
|
+
const tags = sg.Properties.Tags;
|
|
453
|
+
|
|
454
|
+
expect(tags.find(t => t.Key === 'ManagedBy' && t.Value === 'Frigg')).toBeDefined();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
describe('IAM Permissions', () => {
|
|
459
|
+
test('should add Secrets Manager permissions', async () => {
|
|
460
|
+
// Skip discovery for create-new mode test
|
|
461
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
462
|
+
|
|
463
|
+
const appDefinition = {
|
|
464
|
+
name: 'test-app',
|
|
465
|
+
integrations: [],
|
|
466
|
+
vpc: { enable: true, management: 'create-new' },
|
|
467
|
+
database: {
|
|
468
|
+
postgres: {
|
|
469
|
+
enable: true,
|
|
470
|
+
management: 'create-new',
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
476
|
+
|
|
477
|
+
const secretsManagerPolicy = definition.provider.iamRoleStatements.find(
|
|
478
|
+
stmt => stmt.Action.includes('secretsmanager:GetSecretValue')
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
expect(secretsManagerPolicy).toBeDefined();
|
|
482
|
+
expect(secretsManagerPolicy.Effect).toBe('Allow');
|
|
483
|
+
expect(secretsManagerPolicy.Action).toContain('secretsmanager:GetSecretValue');
|
|
484
|
+
expect(secretsManagerPolicy.Action).toContain('secretsmanager:DescribeSecret');
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
describe('Default values', () => {
|
|
489
|
+
test('should use sensible defaults for optional parameters', async () => {
|
|
490
|
+
// Skip discovery for create-new mode test
|
|
491
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
492
|
+
|
|
493
|
+
const appDefinition = {
|
|
494
|
+
name: 'test-app',
|
|
495
|
+
integrations: [],
|
|
496
|
+
vpc: { enable: true, management: 'create-new' },
|
|
497
|
+
database: {
|
|
498
|
+
postgres: {
|
|
499
|
+
enable: true,
|
|
500
|
+
management: 'create-new',
|
|
501
|
+
// All optional parameters omitted
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
507
|
+
|
|
508
|
+
const cluster = definition.resources.Resources.FriggAuroraCluster;
|
|
509
|
+
expect(cluster.Properties.EngineVersion).toBe('15.3');
|
|
510
|
+
expect(cluster.Properties.DatabaseName).toBe('frigg_db');
|
|
511
|
+
expect(cluster.Properties.ServerlessV2ScalingConfiguration.MinCapacity).toBe(0.5);
|
|
512
|
+
expect(cluster.Properties.ServerlessV2ScalingConfiguration.MaxCapacity).toBe(1.0);
|
|
513
|
+
expect(cluster.Properties.BackupRetentionPeriod).toBe(7);
|
|
514
|
+
expect(cluster.Properties.PreferredBackupWindow).toBe('03:00-04:00');
|
|
515
|
+
expect(cluster.Properties.DeletionProtection).toBe(true);
|
|
516
|
+
|
|
517
|
+
const secret = definition.resources.Resources.FriggDatabaseSecret;
|
|
518
|
+
const secretTemplate = JSON.parse(secret.Properties.GenerateSecretString.SecretStringTemplate);
|
|
519
|
+
expect(secretTemplate.username).toBe('frigg_admin');
|
|
520
|
+
|
|
521
|
+
const instance = definition.resources.Resources.FriggAuroraInstance;
|
|
522
|
+
expect(instance.Properties.EnablePerformanceInsights).toBe(false);
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
describe('Validation Tests', () => {
|
|
527
|
+
test('should throw error when postgres enabled but VPC disabled', async () => {
|
|
528
|
+
// Skip discovery
|
|
529
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
530
|
+
|
|
531
|
+
const appDefinition = {
|
|
532
|
+
name: 'test-app',
|
|
533
|
+
integrations: [],
|
|
534
|
+
// VPC not enabled
|
|
535
|
+
database: {
|
|
536
|
+
postgres: {
|
|
537
|
+
enable: true,
|
|
538
|
+
management: 'create-new',
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
544
|
+
/Aurora PostgreSQL requires VPC deployment/
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('should throw error when less than 2 private subnets found', async () => {
|
|
549
|
+
// Mock discovery to return only 1 subnet
|
|
550
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
551
|
+
Subnets: [
|
|
552
|
+
{ SubnetId: 'subnet-1', AvailabilityZone: 'us-east-1a', MapPublicIpOnLaunch: false }
|
|
553
|
+
]
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
rdsMock.on(DescribeDBClustersCommand).resolves({ DBClusters: [] });
|
|
557
|
+
rdsMock.on(DescribeDBSubnetGroupsCommand).resolves({ DBSubnetGroups: [] });
|
|
558
|
+
secretsManagerMock.on(ListSecretsCommand).resolves({ SecretList: [] });
|
|
559
|
+
|
|
560
|
+
const appDefinition = {
|
|
561
|
+
name: 'test-app',
|
|
562
|
+
integrations: [],
|
|
563
|
+
vpc: { enable: true }, // Use default 'discover' mode to trigger subnet validation
|
|
564
|
+
database: {
|
|
565
|
+
postgres: {
|
|
566
|
+
enable: true,
|
|
567
|
+
management: 'create-new',
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
573
|
+
/Aurora PostgreSQL requires at least 2 private subnets/
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test('should throw error when use-existing without clusterIdentifier', async () => {
|
|
578
|
+
// Skip discovery
|
|
579
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
580
|
+
|
|
581
|
+
const appDefinition = {
|
|
582
|
+
name: 'test-app',
|
|
583
|
+
integrations: [],
|
|
584
|
+
vpc: { enable: true, management: 'create-new' },
|
|
585
|
+
database: {
|
|
586
|
+
postgres: {
|
|
587
|
+
enable: true,
|
|
588
|
+
management: 'use-existing',
|
|
589
|
+
// Missing clusterIdentifier
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
595
|
+
/clusterIdentifier/
|
|
596
|
+
);
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
describe('Discover Mode Tests', () => {
|
|
601
|
+
test('should use discovered cluster when found', async () => {
|
|
602
|
+
// Mock discovered cluster
|
|
603
|
+
rdsMock.on(DescribeDBClustersCommand).resolves({
|
|
604
|
+
DBClusters: [{
|
|
605
|
+
DBClusterIdentifier: 'discovered-cluster',
|
|
606
|
+
Engine: 'aurora-postgresql',
|
|
607
|
+
Status: 'available',
|
|
608
|
+
Endpoint: 'discovered.cluster.us-east-1.rds.amazonaws.com',
|
|
609
|
+
Port: 5432,
|
|
610
|
+
DatabaseName: 'discovered_db',
|
|
611
|
+
DBSubnetGroup: 'discovered-subnet-group',
|
|
612
|
+
VpcSecurityGroups: [{ VpcSecurityGroupId: 'sg-discovered' }],
|
|
613
|
+
TagList: [
|
|
614
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
615
|
+
{ Key: 'Service', Value: 'test-app' },
|
|
616
|
+
{ Key: 'Stage', Value: 'dev' }
|
|
617
|
+
]
|
|
618
|
+
}]
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
rdsMock.on(DescribeDBSubnetGroupsCommand).resolves({
|
|
622
|
+
DBSubnetGroups: [{
|
|
623
|
+
DBSubnetGroupName: 'discovered-subnet-group',
|
|
624
|
+
VpcId: 'vpc-12345',
|
|
625
|
+
Subnets: [
|
|
626
|
+
{ SubnetIdentifier: 'subnet-1' },
|
|
627
|
+
{ SubnetIdentifier: 'subnet-2' }
|
|
628
|
+
]
|
|
629
|
+
}]
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
secretsManagerMock.on(ListSecretsCommand).resolves({
|
|
633
|
+
SecretList: [{
|
|
634
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:frigg-db-secret',
|
|
635
|
+
Name: 'frigg-db-secret',
|
|
636
|
+
Tags: [
|
|
637
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
638
|
+
{ Key: 'Service', Value: 'test-app' },
|
|
639
|
+
{ Key: 'Stage', Value: 'dev' }
|
|
640
|
+
]
|
|
641
|
+
}]
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
const appDefinition = {
|
|
645
|
+
name: 'test-app',
|
|
646
|
+
integrations: [],
|
|
647
|
+
vpc: { enable: true },
|
|
648
|
+
database: {
|
|
649
|
+
postgres: {
|
|
650
|
+
enable: true,
|
|
651
|
+
management: 'discover',
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
657
|
+
|
|
658
|
+
// Should use discovered cluster, not create new one
|
|
659
|
+
expect(definition.resources.Resources.FriggAuroraCluster).toBeUndefined();
|
|
660
|
+
expect(definition.provider.environment.DATABASE_URL).toBeDefined();
|
|
661
|
+
expect(definition.provider.environment.DATABASE_URL['Fn::Sub']).toBeDefined();
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
test('should create new cluster when none found', async () => {
|
|
665
|
+
// Mock no clusters found
|
|
666
|
+
rdsMock.on(DescribeDBClustersCommand).resolves({ DBClusters: [] });
|
|
667
|
+
rdsMock.on(DescribeDBSubnetGroupsCommand).resolves({ DBSubnetGroups: [] });
|
|
668
|
+
secretsManagerMock.on(ListSecretsCommand).resolves({ SecretList: [] });
|
|
669
|
+
|
|
670
|
+
const appDefinition = {
|
|
671
|
+
name: 'test-app',
|
|
672
|
+
integrations: [],
|
|
673
|
+
vpc: { enable: true },
|
|
674
|
+
database: {
|
|
675
|
+
postgres: {
|
|
676
|
+
enable: true,
|
|
677
|
+
management: 'discover',
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
683
|
+
|
|
684
|
+
// Should create new cluster
|
|
685
|
+
expect(definition.resources.Resources.FriggAuroraCluster).toBeDefined();
|
|
686
|
+
expect(definition.resources.Resources.FriggAuroraCluster.Type).toBe('AWS::RDS::DBCluster');
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test('should use discovered secret ARN when available', async () => {
|
|
690
|
+
// Mock discovered resources
|
|
691
|
+
rdsMock.on(DescribeDBClustersCommand).resolves({
|
|
692
|
+
DBClusters: [{
|
|
693
|
+
DBClusterIdentifier: 'discovered-cluster',
|
|
694
|
+
Engine: 'aurora-postgresql',
|
|
695
|
+
Status: 'available',
|
|
696
|
+
Endpoint: 'discovered.cluster.us-east-1.rds.amazonaws.com',
|
|
697
|
+
Port: 5432,
|
|
698
|
+
DatabaseName: 'discovered_db',
|
|
699
|
+
DBSubnetGroup: 'discovered-subnet-group',
|
|
700
|
+
VpcSecurityGroups: [{ VpcSecurityGroupId: 'sg-discovered' }],
|
|
701
|
+
TagList: [
|
|
702
|
+
{ Key: 'ManagedBy', Value: 'Frigg' }
|
|
703
|
+
]
|
|
704
|
+
}]
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
rdsMock.on(DescribeDBSubnetGroupsCommand).resolves({
|
|
708
|
+
DBSubnetGroups: [{
|
|
709
|
+
DBSubnetGroupName: 'discovered-subnet-group',
|
|
710
|
+
VpcId: 'vpc-12345',
|
|
711
|
+
Subnets: [
|
|
712
|
+
{ SubnetIdentifier: 'subnet-1' },
|
|
713
|
+
{ SubnetIdentifier: 'subnet-2' }
|
|
714
|
+
]
|
|
715
|
+
}]
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
secretsManagerMock.on(ListSecretsCommand).resolves({
|
|
719
|
+
SecretList: [{
|
|
720
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:discovered-secret',
|
|
721
|
+
Name: 'discovered-secret',
|
|
722
|
+
Tags: [
|
|
723
|
+
{ Key: 'ManagedBy', Value: 'Frigg' }
|
|
724
|
+
]
|
|
725
|
+
}]
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
const appDefinition = {
|
|
729
|
+
name: 'test-app',
|
|
730
|
+
integrations: [],
|
|
731
|
+
vpc: { enable: true },
|
|
732
|
+
database: {
|
|
733
|
+
postgres: {
|
|
734
|
+
enable: true,
|
|
735
|
+
management: 'discover',
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
741
|
+
|
|
742
|
+
// Should reference discovered secret in IAM policy
|
|
743
|
+
const secretsManagerPolicy = definition.provider.iamRoleStatements.find(
|
|
744
|
+
stmt => stmt.Action.includes('secretsmanager:GetSecretValue')
|
|
745
|
+
);
|
|
746
|
+
expect(secretsManagerPolicy).toBeDefined();
|
|
747
|
+
expect(secretsManagerPolicy.Resource).toContain('discovered-secret');
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
describe('Integration Tests', () => {
|
|
752
|
+
test('should use discovered cluster endpoint in DATABASE_URL', async () => {
|
|
753
|
+
// Mock discovered cluster
|
|
754
|
+
rdsMock.on(DescribeDBClustersCommand).resolves({
|
|
755
|
+
DBClusters: [{
|
|
756
|
+
DBClusterIdentifier: 'integration-cluster',
|
|
757
|
+
Engine: 'aurora-postgresql',
|
|
758
|
+
Status: 'available',
|
|
759
|
+
Endpoint: 'integration.cluster.us-east-1.rds.amazonaws.com',
|
|
760
|
+
Port: 5432,
|
|
761
|
+
DatabaseName: 'integration_db',
|
|
762
|
+
DBSubnetGroup: 'integration-subnet-group',
|
|
763
|
+
VpcSecurityGroups: [{ VpcSecurityGroupId: 'sg-integration' }],
|
|
764
|
+
TagList: [{ Key: 'ManagedBy', Value: 'Frigg' }]
|
|
765
|
+
}]
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
rdsMock.on(DescribeDBSubnetGroupsCommand).resolves({
|
|
769
|
+
DBSubnetGroups: [{
|
|
770
|
+
DBSubnetGroupName: 'integration-subnet-group',
|
|
771
|
+
VpcId: 'vpc-12345',
|
|
772
|
+
Subnets: [
|
|
773
|
+
{ SubnetIdentifier: 'subnet-1' },
|
|
774
|
+
{ SubnetIdentifier: 'subnet-2' }
|
|
775
|
+
]
|
|
776
|
+
}]
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
secretsManagerMock.on(ListSecretsCommand).resolves({
|
|
780
|
+
SecretList: [{
|
|
781
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:integration-secret',
|
|
782
|
+
Name: 'integration-secret',
|
|
783
|
+
Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }]
|
|
784
|
+
}]
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
const appDefinition = {
|
|
788
|
+
name: 'test-app',
|
|
789
|
+
integrations: [],
|
|
790
|
+
vpc: { enable: true },
|
|
791
|
+
database: {
|
|
792
|
+
postgres: {
|
|
793
|
+
enable: true,
|
|
794
|
+
management: 'discover',
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
800
|
+
|
|
801
|
+
// DATABASE_URL should reference discovered endpoint
|
|
802
|
+
const databaseUrl = definition.provider.environment.DATABASE_URL;
|
|
803
|
+
expect(databaseUrl['Fn::Sub']).toBeDefined();
|
|
804
|
+
expect(databaseUrl['Fn::Sub'][0]).toContain('integration.cluster.us-east-1.rds.amazonaws.com');
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
test('should create Secrets Manager VPC endpoint when Aurora enabled', async () => {
|
|
808
|
+
// Skip discovery for create-new mode test
|
|
809
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
810
|
+
|
|
811
|
+
const appDefinition = {
|
|
812
|
+
name: 'test-app',
|
|
813
|
+
integrations: [],
|
|
814
|
+
vpc: { enable: true, management: 'create-new' },
|
|
815
|
+
database: {
|
|
816
|
+
postgres: {
|
|
817
|
+
enable: true,
|
|
818
|
+
management: 'create-new',
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
824
|
+
|
|
825
|
+
// Should create Secrets Manager VPC endpoint
|
|
826
|
+
// Note: For 'create-new' VPC mode, the resource is named FriggSecretsManagerVPCEndpoint
|
|
827
|
+
expect(definition.resources.Resources.FriggSecretsManagerVPCEndpoint).toBeDefined();
|
|
828
|
+
expect(definition.resources.Resources.FriggSecretsManagerVPCEndpoint.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
829
|
+
expect(definition.resources.Resources.FriggSecretsManagerVPCEndpoint.Properties.ServiceName).toContain('secretsmanager');
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test('should use correct security group source in create-new VPC mode', async () => {
|
|
833
|
+
// Skip discovery for create-new mode test
|
|
834
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
835
|
+
|
|
836
|
+
const appDefinition = {
|
|
837
|
+
name: 'test-app',
|
|
838
|
+
integrations: [],
|
|
839
|
+
vpc: { enable: true, management: 'create-new' },
|
|
840
|
+
database: {
|
|
841
|
+
postgres: {
|
|
842
|
+
enable: true,
|
|
843
|
+
management: 'create-new',
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
849
|
+
|
|
850
|
+
const sg = definition.resources.Resources.FriggAuroraSecurityGroup;
|
|
851
|
+
const ingressRule = sg.Properties.SecurityGroupIngress[0];
|
|
852
|
+
|
|
853
|
+
// Should reference FriggLambdaSecurityGroup (created by VPC)
|
|
854
|
+
expect(ingressRule.SourceSecurityGroupId).toEqual({ Ref: 'FriggLambdaSecurityGroup' });
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test('should use discovered secret ARN in IAM permissions', async () => {
|
|
858
|
+
// Mock discovered resources
|
|
859
|
+
rdsMock.on(DescribeDBClustersCommand).resolves({
|
|
860
|
+
DBClusters: [{
|
|
861
|
+
DBClusterIdentifier: 'iam-test-cluster',
|
|
862
|
+
Engine: 'aurora-postgresql',
|
|
863
|
+
Status: 'available',
|
|
864
|
+
Endpoint: 'iam.cluster.us-east-1.rds.amazonaws.com',
|
|
865
|
+
Port: 5432,
|
|
866
|
+
DatabaseName: 'iam_db',
|
|
867
|
+
DBSubnetGroup: 'iam-subnet-group',
|
|
868
|
+
VpcSecurityGroups: [{ VpcSecurityGroupId: 'sg-iam' }],
|
|
869
|
+
TagList: [{ Key: 'ManagedBy', Value: 'Frigg' }]
|
|
870
|
+
}]
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
rdsMock.on(DescribeDBSubnetGroupsCommand).resolves({
|
|
874
|
+
DBSubnetGroups: [{
|
|
875
|
+
DBSubnetGroupName: 'iam-subnet-group',
|
|
876
|
+
VpcId: 'vpc-12345',
|
|
877
|
+
Subnets: [
|
|
878
|
+
{ SubnetIdentifier: 'subnet-1' },
|
|
879
|
+
{ SubnetIdentifier: 'subnet-2' }
|
|
880
|
+
]
|
|
881
|
+
}]
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
secretsManagerMock.on(ListSecretsCommand).resolves({
|
|
885
|
+
SecretList: [{
|
|
886
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:specific-iam-secret-abc123',
|
|
887
|
+
Name: 'specific-iam-secret',
|
|
888
|
+
Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }]
|
|
889
|
+
}]
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
const appDefinition = {
|
|
893
|
+
name: 'test-app',
|
|
894
|
+
integrations: [],
|
|
895
|
+
vpc: { enable: true },
|
|
896
|
+
database: {
|
|
897
|
+
postgres: {
|
|
898
|
+
enable: true,
|
|
899
|
+
management: 'discover',
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
const definition = await composeServerlessDefinition(appDefinition);
|
|
905
|
+
|
|
906
|
+
// IAM policy should reference the specific discovered secret ARN
|
|
907
|
+
const secretsManagerPolicy = definition.provider.iamRoleStatements.find(
|
|
908
|
+
stmt => stmt.Action.includes('secretsmanager:GetSecretValue')
|
|
909
|
+
);
|
|
910
|
+
expect(secretsManagerPolicy).toBeDefined();
|
|
911
|
+
expect(secretsManagerPolicy.Resource).toContain('specific-iam-secret-abc123');
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
});
|