diff --git a/cmd/app/internal/database/factory.go b/cmd/app/internal/database/factory.go index 5b75362..132de7f 100644 --- a/cmd/app/internal/database/factory.go +++ b/cmd/app/internal/database/factory.go @@ -1,6 +1,8 @@ package database import ( + "os" + "github.com/hackmajoris/glad/pkg/config" "github.com/hackmajoris/glad/pkg/logger" ) @@ -15,11 +17,38 @@ type Repository interface { func NewRepository(cfg *config.Config) Repository { log := logger.WithComponent("database") - if cfg.LocalServer.Environment == "development" || cfg.LocalServer.Environment == "test" { + // Determine if we should use mock or real DynamoDB + if shouldUseMockRepository(cfg) { log.Info("Creating Mock repository for development/testing") return NewMockRepository() } - log.Info("Creating DynamoDB repository for production") + log.Info("Creating DynamoDB repository for production/Lambda") return NewDynamoDBRepository() } + +// shouldUseMockRepository determines if we should use mock repository +func shouldUseMockRepository(cfg *config.Config) bool { + // 1. If AWS_LAMBDA_FUNCTION_NAME exists, we're definitely in Lambda - use DynamoDB + if os.Getenv("AWS_LAMBDA_FUNCTION_NAME") != "" { + return false + } + + // 2. If ENVIRONMENT is explicitly set to production, use DynamoDB + if os.Getenv("ENVIRONMENT") == "production" { + return false + } + + // 3. If LocalServer environment is development or test, use mock + if cfg.IsDevelopment() { + return true + } + + // 4. If DB_MOCK is explicitly set to true, use mock (useful for testing) + if os.Getenv("DB_MOCK") == "true" { + return true + } + + // 5. Default to DynamoDB for production + return false +} diff --git a/cmd/app/internal/database/factory_test.go b/cmd/app/internal/database/factory_test.go new file mode 100644 index 0000000..eb03ba3 --- /dev/null +++ b/cmd/app/internal/database/factory_test.go @@ -0,0 +1,202 @@ +package database + +import ( + "os" + "testing" + "time" + + "github.com/hackmajoris/glad/pkg/config" +) + +func TestNewRepository_EnvironmentDetection(t *testing.T) { + tests := []struct { + name string + setupEnv func() + cleanupEnv func() + configEnv string + expectMock bool + description string + }{ + { + name: "Lambda environment should use DynamoDB", + setupEnv: func() { + err := os.Setenv("AWS_LAMBDA_FUNCTION_NAME", "glad-api") + if err != nil { + return + } + }, + cleanupEnv: func() { + err := os.Unsetenv("AWS_LAMBDA_FUNCTION_NAME") + if err != nil { + return + } + }, + configEnv: "development", + expectMock: false, + description: "When AWS_LAMBDA_FUNCTION_NAME is set, should use DynamoDB regardless of config", + }, + { + name: "Explicit production environment should use DynamoDB", + setupEnv: func() { + err := os.Setenv("ENVIRONMENT", "production") + if err != nil { + return + } + }, + cleanupEnv: func() { + err := os.Unsetenv("ENVIRONMENT") + if err != nil { + return + } + }, + configEnv: "development", + expectMock: false, + description: "When ENVIRONMENT=production, should use DynamoDB", + }, + { + name: "Development config should use Mock", + setupEnv: func() { + // No special env vars set + }, + cleanupEnv: func() { + // Nothing to clean + }, + configEnv: "development", + expectMock: true, + description: "When LocalServer.Environment=development, should use Mock", + }, + { + name: "Explicit DB_MOCK=true should use Mock", + setupEnv: func() { + err := os.Setenv("DB_MOCK", "true") + if err != nil { + return + } + }, + cleanupEnv: func() { + err := os.Unsetenv("DB_MOCK") + if err != nil { + return + } + }, + configEnv: "production", + expectMock: true, + description: "When DB_MOCK=true, should use Mock even in production config", + }, + { + name: "Default production should use DynamoDB", + setupEnv: func() { + // No special env vars set + }, + cleanupEnv: func() { + // Nothing to clean + }, + configEnv: "production", + expectMock: false, + description: "When LocalServer.Environment=production and no overrides, should use DynamoDB", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup environment + tt.setupEnv() + defer tt.cleanupEnv() + + // Create config with specified environment + cfg := &config.Config{ + JWT: config.JWTConfig{ + Secret: "test-secret", + Expiry: 24 * time.Hour, + }, + LocalServer: config.ServerConfig{ + Environment: tt.configEnv, + Port: 8080, + }, + } + + // Test the factory + repo := NewRepository(cfg) + + // Check if we got the expected repository type + _, isMock := repo.(*MockRepository) + _, isDynamoDB := repo.(*DynamoDBRepository) + + if tt.expectMock { + if !isMock { + t.Errorf("Expected MockRepository, but got %T. %s", repo, tt.description) + } + if isDynamoDB { + t.Errorf("Got DynamoDBRepository when expecting MockRepository. %s", tt.description) + } + } else { + if isMock { + t.Errorf("Got MockRepository when expecting DynamoDBRepository. %s", tt.description) + } + if !isDynamoDB { + t.Errorf("Expected DynamoDBRepository, but got %T. %s", repo, tt.description) + } + } + }) + } +} + +func TestShouldUseMockRepository(t *testing.T) { + tests := []struct { + name string + setupEnv func() + cleanupEnv func() + configEnv string + expected bool + description string + }{ + { + name: "AWS Lambda function name present", + setupEnv: func() { + err := os.Setenv("AWS_LAMBDA_FUNCTION_NAME", "my-function") + if err != nil { + return + } + }, + cleanupEnv: func() { + err := os.Unsetenv("AWS_LAMBDA_FUNCTION_NAME") + if err != nil { + return + } + }, + configEnv: "development", + expected: false, + description: "Should return false when AWS_LAMBDA_FUNCTION_NAME is set", + }, + { + name: "Development config", + setupEnv: func() {}, + cleanupEnv: func() {}, + configEnv: "development", + expected: true, + description: "Should return true for development config", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup + tt.setupEnv() + defer tt.cleanupEnv() + + cfg := &config.Config{ + LocalServer: config.ServerConfig{ + Environment: tt.configEnv, + }, + } + + // Test + result := shouldUseMockRepository(cfg) + + // Verify + if result != tt.expected { + t.Errorf("Expected %v, got %v. %s", tt.expected, result, tt.description) + } + }) + } +} diff --git a/deployments/app/cdk.go b/deployments/app/cdk.go index 61f23c8..eebdbe3 100644 --- a/deployments/app/cdk.go +++ b/deployments/app/cdk.go @@ -24,8 +24,10 @@ func NewCdkStack(scope constructs.Construct, id string, props *CdkStackProps) aw } stack := awscdk.NewStack(scope, &id, &sprops) + ENVIRONMENT := "production" // todo: will be parametrised + // Add environment tag - awscdk.Tags_Of(stack).Add(jsii.String("Environment"), jsii.String("production"), nil) + awscdk.Tags_Of(stack).Add(jsii.String("Environment"), jsii.String(ENVIRONMENT), nil) // The code that defines your stack goes here @@ -111,6 +113,8 @@ func NewCdkStack(scope constructs.Construct, id string, props *CdkStackProps) aw Handler: jsii.String("main"), }) + myFunc.AddEnvironment(jsii.String("environment"), jsii.String(ENVIRONMENT), nil) + // Grant Lambda read/write access to DynamoDB table entitiesTable.GrantReadWriteData(myFunc)