@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.
Files changed (212) hide show
  1. package/infrastructure/ARCHITECTURE.md +487 -0
  2. package/infrastructure/HEALTH.md +468 -0
  3. package/infrastructure/README.md +51 -0
  4. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  5. package/infrastructure/__tests__/template-generation.test.js +687 -0
  6. package/infrastructure/create-frigg-infrastructure.js +1 -1
  7. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  8. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  9. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  10. package/infrastructure/domains/database/aurora-builder.js +809 -0
  11. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  12. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  13. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  14. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  15. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  16. package/infrastructure/domains/database/migration-builder.js +633 -0
  17. package/infrastructure/domains/database/migration-builder.test.js +294 -0
  18. package/infrastructure/domains/database/migration-resolver.js +163 -0
  19. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  20. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  21. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  22. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  23. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  24. package/infrastructure/domains/health/application/ports/index.js +26 -0
  25. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  26. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  27. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  28. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  29. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  30. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  31. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  32. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  33. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  34. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  35. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  36. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  37. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  38. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  39. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  40. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  41. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  42. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  43. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  44. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  45. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  46. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  47. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  48. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  49. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  50. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  51. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  52. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  53. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  54. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  55. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  56. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  57. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  58. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  59. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  60. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  61. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  62. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  63. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  64. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  65. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  66. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  67. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  68. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  69. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  70. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  71. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  72. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  73. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  74. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  75. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  76. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  77. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  78. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  79. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  80. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  81. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  82. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  83. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  84. package/infrastructure/domains/integration/integration-builder.js +397 -0
  85. package/infrastructure/domains/integration/integration-builder.test.js +593 -0
  86. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  87. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  88. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  89. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  90. package/infrastructure/domains/networking/vpc-builder.js +1829 -0
  91. package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
  92. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  93. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  94. package/infrastructure/domains/networking/vpc-resolver.js +324 -0
  95. package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
  96. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  97. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  98. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  99. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  100. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  101. package/infrastructure/domains/security/kms-builder.js +366 -0
  102. package/infrastructure/domains/security/kms-builder.test.js +374 -0
  103. package/infrastructure/domains/security/kms-discovery.js +80 -0
  104. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  105. package/infrastructure/domains/security/kms-resolver.js +96 -0
  106. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  107. package/infrastructure/domains/shared/base-builder.js +112 -0
  108. package/infrastructure/domains/shared/base-resolver.js +186 -0
  109. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  110. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  111. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  112. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  113. package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
  114. package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
  115. package/infrastructure/domains/shared/environment-builder.js +119 -0
  116. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  117. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
  118. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
  119. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  120. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  121. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  122. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  123. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  124. package/infrastructure/domains/shared/resource-discovery.js +192 -0
  125. package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
  126. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  127. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  128. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  129. package/infrastructure/domains/shared/types/index.js +46 -0
  130. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  131. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  132. package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
  133. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  134. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  135. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  136. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  137. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  138. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
  139. package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
  140. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  141. package/infrastructure/esbuild.config.js +53 -0
  142. package/infrastructure/infrastructure-composer.js +87 -0
  143. package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
  144. package/infrastructure/scripts/build-prisma-layer.js +553 -0
  145. package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
  146. package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
  147. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  148. package/layers/prisma/nodejs/package.json +8 -0
  149. package/management-ui/server/utils/cliIntegration.js +1 -1
  150. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  151. package/package.json +11 -11
  152. package/frigg-cli/.eslintrc.js +0 -141
  153. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
  154. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
  155. package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
  156. package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
  157. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
  158. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
  159. package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
  160. package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
  161. package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
  162. package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
  163. package/frigg-cli/__tests__/utils/test-setup.js +0 -287
  164. package/frigg-cli/build-command/index.js +0 -65
  165. package/frigg-cli/db-setup-command/index.js +0 -193
  166. package/frigg-cli/deploy-command/index.js +0 -175
  167. package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
  168. package/frigg-cli/generate-command/azure-generator.js +0 -43
  169. package/frigg-cli/generate-command/gcp-generator.js +0 -47
  170. package/frigg-cli/generate-command/index.js +0 -332
  171. package/frigg-cli/generate-command/terraform-generator.js +0 -555
  172. package/frigg-cli/generate-iam-command.js +0 -118
  173. package/frigg-cli/index.js +0 -75
  174. package/frigg-cli/index.test.js +0 -158
  175. package/frigg-cli/init-command/backend-first-handler.js +0 -756
  176. package/frigg-cli/init-command/index.js +0 -93
  177. package/frigg-cli/init-command/template-handler.js +0 -143
  178. package/frigg-cli/install-command/backend-js.js +0 -33
  179. package/frigg-cli/install-command/commit-changes.js +0 -16
  180. package/frigg-cli/install-command/environment-variables.js +0 -127
  181. package/frigg-cli/install-command/environment-variables.test.js +0 -136
  182. package/frigg-cli/install-command/index.js +0 -54
  183. package/frigg-cli/install-command/install-package.js +0 -13
  184. package/frigg-cli/install-command/integration-file.js +0 -30
  185. package/frigg-cli/install-command/logger.js +0 -12
  186. package/frigg-cli/install-command/template.js +0 -90
  187. package/frigg-cli/install-command/validate-package.js +0 -75
  188. package/frigg-cli/jest.config.js +0 -124
  189. package/frigg-cli/package.json +0 -54
  190. package/frigg-cli/start-command/index.js +0 -149
  191. package/frigg-cli/start-command/start-command.test.js +0 -297
  192. package/frigg-cli/test/init-command.test.js +0 -180
  193. package/frigg-cli/test/npm-registry.test.js +0 -319
  194. package/frigg-cli/ui-command/index.js +0 -154
  195. package/frigg-cli/utils/app-resolver.js +0 -319
  196. package/frigg-cli/utils/backend-path.js +0 -25
  197. package/frigg-cli/utils/database-validator.js +0 -161
  198. package/frigg-cli/utils/error-messages.js +0 -257
  199. package/frigg-cli/utils/npm-registry.js +0 -167
  200. package/frigg-cli/utils/prisma-runner.js +0 -280
  201. package/frigg-cli/utils/process-manager.js +0 -199
  202. package/frigg-cli/utils/repo-detection.js +0 -405
  203. package/infrastructure/aws-discovery.js +0 -1176
  204. package/infrastructure/aws-discovery.test.js +0 -1220
  205. package/infrastructure/serverless-template.js +0 -2074
  206. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  207. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  208. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  209. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  210. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  211. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  212. /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
@@ -0,0 +1,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
+ });