Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions cmd/app/internal/database/factory.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package database

import (
"os"

"github.com/hackmajoris/glad/pkg/config"
"github.com/hackmajoris/glad/pkg/logger"
)
Expand All @@ -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
}
202 changes: 202 additions & 0 deletions cmd/app/internal/database/factory_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
6 changes: 5 additions & 1 deletion deployments/app/cdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down
Loading