init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

View File

@@ -0,0 +1,340 @@
package config
import (
"testing"
"time"
)
func TestLoadFileManagerUploadPolicyConfigFromEnv_Defaults(t *testing.T) {
t.Setenv("FILE_MANAGER_UPLOAD_MAX_FILE_SIZE_BYTES", "")
t.Setenv("FILE_MANAGER_UPLOAD_ALLOWED_MIME_TYPES", "")
t.Setenv("FILE_MANAGER_UPLOAD_ALLOWED_EXTENSIONS", "")
t.Setenv("FILE_MANAGER_UPLOAD_ALLOW_EMPTY_FILE", "")
t.Setenv("FILE_MANAGER_UPLOAD_MAX_FILENAME_LENGTH", "")
cfg := LoadFileManagerUploadPolicyConfigFromEnv()
if cfg.MaxFileSizeBytes != 4*1024*1024 {
t.Fatalf("expected default max file size 4194304, got %d", cfg.MaxFileSizeBytes)
}
if cfg.AllowEmptyFile {
t.Fatalf("expected default allow empty file false")
}
if cfg.MaxFilenameLength != 255 {
t.Fatalf("expected default max filename length 255, got %d", cfg.MaxFilenameLength)
}
if len(cfg.AllowedMimeTypes) != 0 {
t.Fatalf("expected empty default allowed mime types, got %v", cfg.AllowedMimeTypes)
}
if len(cfg.AllowedExtensions) != 0 {
t.Fatalf("expected empty default allowed extensions, got %v", cfg.AllowedExtensions)
}
}
func TestLoadFileManagerUploadPolicyConfigFromEnv_Normalize(t *testing.T) {
t.Setenv("FILE_MANAGER_UPLOAD_MAX_FILE_SIZE_BYTES", "1048576")
t.Setenv("FILE_MANAGER_UPLOAD_ALLOWED_MIME_TYPES", " image/PNG ; charset=utf-8,application/pdf,IMAGE/PNG")
t.Setenv("FILE_MANAGER_UPLOAD_ALLOWED_EXTENSIONS", " .PDF, png ,PNG")
t.Setenv("FILE_MANAGER_UPLOAD_ALLOW_EMPTY_FILE", "true")
t.Setenv("FILE_MANAGER_UPLOAD_MAX_FILENAME_LENGTH", "120")
cfg := LoadFileManagerUploadPolicyConfigFromEnv()
if cfg.MaxFileSizeBytes != 1048576 {
t.Fatalf("expected max file size 1048576, got %d", cfg.MaxFileSizeBytes)
}
if !cfg.AllowEmptyFile {
t.Fatalf("expected allow empty file true")
}
if cfg.MaxFilenameLength != 120 {
t.Fatalf("expected max filename length 120, got %d", cfg.MaxFilenameLength)
}
if len(cfg.AllowedMimeTypes) != 2 || cfg.AllowedMimeTypes[0] != "image/png" || cfg.AllowedMimeTypes[1] != "application/pdf" {
t.Fatalf("unexpected normalized mime types: %v", cfg.AllowedMimeTypes)
}
if len(cfg.AllowedExtensions) != 2 || cfg.AllowedExtensions[0] != "pdf" || cfg.AllowedExtensions[1] != "png" {
t.Fatalf("unexpected normalized extensions: %v", cfg.AllowedExtensions)
}
}
func TestLoadFileManagerTrashPolicyConfigFromEnv_Defaults(t *testing.T) {
t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_ENABLED", "")
t.Setenv("FILE_MANAGER_TRASH_RETENTION", "")
t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_INTERVAL", "")
t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_BATCH_SIZE", "")
cfg := LoadFileManagerTrashPolicyConfigFromEnv()
if !cfg.AutoPurgeEnabled {
t.Fatalf("expected default auto purge enabled")
}
if cfg.Retention != 30*24*time.Hour {
t.Fatalf("expected default retention 720h, got %s", cfg.Retention)
}
if cfg.AutoPurgeInterval != time.Hour {
t.Fatalf("expected default auto purge interval 1h, got %s", cfg.AutoPurgeInterval)
}
if cfg.AutoPurgeBatchSize != 200 {
t.Fatalf("expected default batch size 200, got %d", cfg.AutoPurgeBatchSize)
}
}
func TestLoadFileManagerTrashPolicyConfigFromEnv_Custom(t *testing.T) {
t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_ENABLED", "false")
t.Setenv("FILE_MANAGER_TRASH_RETENTION", "168h")
t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_INTERVAL", "15m")
t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_BATCH_SIZE", "50")
cfg := LoadFileManagerTrashPolicyConfigFromEnv()
if cfg.AutoPurgeEnabled {
t.Fatalf("expected auto purge disabled")
}
if cfg.Retention != 168*time.Hour {
t.Fatalf("expected retention 168h, got %s", cfg.Retention)
}
if cfg.AutoPurgeInterval != 15*time.Minute {
t.Fatalf("expected auto purge interval 15m, got %s", cfg.AutoPurgeInterval)
}
if cfg.AutoPurgeBatchSize != 50 {
t.Fatalf("expected batch size 50, got %d", cfg.AutoPurgeBatchSize)
}
}
func TestLoadFileManagerLifecycleConfigFromEnv_Defaults(t *testing.T) {
t.Setenv("ENABLE_S3_TAGGING", "")
t.Setenv("ENABLE_FILE_CLEANUP_JOB", "")
t.Setenv("TEMP_UPLOAD_GRACE_HOURS", "")
t.Setenv("ORPHAN_GRACE_PERIOD_DAYS", "")
t.Setenv("FILE_CLEANUP_BATCH_SIZE", "")
t.Setenv("FILE_CLEANUP_DRY_RUN", "")
cfg := LoadFileManagerLifecycleConfigFromEnv()
if !cfg.EnableS3Tagging {
t.Fatalf("expected s3 tagging enabled by default")
}
if cfg.EnableCleanupJob {
t.Fatalf("expected cleanup job disabled by default")
}
if cfg.TempUploadGraceHours != 2 {
t.Fatalf("expected temp upload grace 2 hours, got %d", cfg.TempUploadGraceHours)
}
if cfg.OrphanGraceDays != 30 {
t.Fatalf("expected orphan grace 30 days, got %d", cfg.OrphanGraceDays)
}
if cfg.CleanupBatchSize != 100 {
t.Fatalf("expected cleanup batch size 100, got %d", cfg.CleanupBatchSize)
}
if !cfg.CleanupDryRun {
t.Fatalf("expected cleanup dry run enabled by default")
}
}
func TestLoadFileManagerLifecycleConfigFromEnv_Custom(t *testing.T) {
t.Setenv("ENABLE_S3_TAGGING", "false")
t.Setenv("ENABLE_FILE_CLEANUP_JOB", "true")
t.Setenv("TEMP_UPLOAD_GRACE_HOURS", "6")
t.Setenv("ORPHAN_GRACE_PERIOD_DAYS", "45")
t.Setenv("FILE_CLEANUP_BATCH_SIZE", "250")
t.Setenv("FILE_CLEANUP_DRY_RUN", "false")
cfg := LoadFileManagerLifecycleConfigFromEnv()
if cfg.EnableS3Tagging {
t.Fatalf("expected s3 tagging disabled")
}
if !cfg.EnableCleanupJob {
t.Fatalf("expected cleanup job enabled")
}
if cfg.TempUploadGraceHours != 6 {
t.Fatalf("expected temp upload grace 6 hours, got %d", cfg.TempUploadGraceHours)
}
if cfg.OrphanGraceDays != 45 {
t.Fatalf("expected orphan grace 45 days, got %d", cfg.OrphanGraceDays)
}
if cfg.CleanupBatchSize != 250 {
t.Fatalf("expected cleanup batch size 250, got %d", cfg.CleanupBatchSize)
}
if cfg.CleanupDryRun {
t.Fatalf("expected cleanup dry run disabled")
}
}
func TestLoadFileQueueConfigFromEnv_Defaults(t *testing.T) {
t.Setenv("FILE_QUEUE_MESSAGE_SCHEMA_VERSION", "")
t.Setenv("FILE_QUEUE_IDEMPOTENCY_KEY_PREFIX", "")
t.Setenv("FILE_QUEUE_WORKER_MONITOR_ADDR", "")
t.Setenv("FILE_QUEUE_OUTBOX_ENABLED", "")
t.Setenv("FILE_QUEUE_OUTBOX_POLL_INTERVAL", "")
t.Setenv("FILE_QUEUE_OUTBOX_BATCH_SIZE", "")
t.Setenv("FILE_QUEUE_OUTBOX_DISPATCH_TIMEOUT", "")
t.Setenv("FILE_QUEUE_OUTBOX_LOCK_TTL", "")
t.Setenv("FILE_QUEUE_OUTBOX_MAX_ATTEMPTS", "")
cfg := LoadFileQueueConfigFromEnv()
if cfg.MessageSchemaVersion != "v1" {
t.Fatalf("expected default schema version v1, got %q", cfg.MessageSchemaVersion)
}
if cfg.IdempotencyKeyPrefix != "queue:idempotency:file:" {
t.Fatalf("unexpected default idempotency prefix: %q", cfg.IdempotencyKeyPrefix)
}
if cfg.WorkerMonitorAddr != "127.0.0.1:9091" {
t.Fatalf("unexpected default monitor addr: %q", cfg.WorkerMonitorAddr)
}
if !cfg.OutboxEnabled {
t.Fatalf("expected default file outbox enabled")
}
if cfg.OutboxPollInterval != time.Second {
t.Fatalf("expected default outbox poll interval 1s, got %s", cfg.OutboxPollInterval)
}
if cfg.OutboxBatchSize != 10 {
t.Fatalf("expected default outbox batch size 10, got %d", cfg.OutboxBatchSize)
}
if cfg.OutboxDispatchTimeout != 10*time.Second {
t.Fatalf("expected default outbox dispatch timeout 10s, got %s", cfg.OutboxDispatchTimeout)
}
if cfg.OutboxLockTTL != time.Minute {
t.Fatalf("expected default outbox lock ttl 1m, got %s", cfg.OutboxLockTTL)
}
if cfg.OutboxMaxAttempts != 20 {
t.Fatalf("expected default outbox max attempts 20, got %d", cfg.OutboxMaxAttempts)
}
}
func TestLoadFileSQSConfigFromEnv(t *testing.T) {
t.Setenv("AWS_REGION", "ap-southeast-1")
t.Setenv("SQS_ENDPOINT", "http://localhost:4566")
t.Setenv("SQS_ALLOW_INSECURE_ENDPOINT", "true")
t.Setenv("FILE_SQS_REGION", "")
t.Setenv("FILE_SQS_ENDPOINT", "")
t.Setenv("FILE_SQS_QUEUE_URL", "http://localhost:4566/000000000000/app-file-queue")
t.Setenv("FILE_SQS_QUEUE_TYPE", "standard")
t.Setenv("FILE_SQS_MESSAGE_GROUP_ID", "file")
t.Setenv("FILE_SQS_MAX_MESSAGES", "8")
t.Setenv("FILE_SQS_WAIT_TIME_SECONDS", "15")
t.Setenv("FILE_SQS_VISIBILITY_TIMEOUT", "90s")
t.Setenv("FILE_SQS_ALLOW_INSECURE_ENDPOINT", "")
cfg := LoadFileSQSConfigFromEnv()
if cfg.Region != "ap-southeast-1" {
t.Fatalf("unexpected region: %q", cfg.Region)
}
if cfg.Endpoint != "http://localhost:4566" {
t.Fatalf("unexpected endpoint: %q", cfg.Endpoint)
}
if cfg.QueueURL == "" {
t.Fatalf("expected queue url")
}
if cfg.MaxMessages != 8 {
t.Fatalf("unexpected max messages: %d", cfg.MaxMessages)
}
if cfg.WaitTimeSeconds != 15 {
t.Fatalf("unexpected wait time seconds: %d", cfg.WaitTimeSeconds)
}
if !cfg.AllowInsecureEndpoint {
t.Fatalf("expected insecure endpoint allowed from fallback")
}
}
func TestValidateDistinctQueueURLs(t *testing.T) {
t.Run("different queue urls", func(t *testing.T) {
err := ValidateDistinctQueueURLs(
SQSConfig{QueueURL: "http://localhost:4566/000000000000/app-queue"},
SQSConfig{QueueURL: "http://localhost:4566/000000000000/app-file-queue"},
)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
})
t.Run("same queue urls with different case and slash", func(t *testing.T) {
err := ValidateDistinctQueueURLs(
SQSConfig{QueueURL: "HTTP://LOCALHOST:4566/000000000000/app-queue"},
SQSConfig{QueueURL: "http://localhost:4566/000000000000/app-queue/"},
)
if err == nil {
t.Fatalf("expected error for same queue urls")
}
})
t.Run("skip when one queue url empty", func(t *testing.T) {
err := ValidateDistinctQueueURLs(
SQSConfig{QueueURL: "http://localhost:4566/000000000000/app-queue"},
SQSConfig{QueueURL: ""},
)
if err != nil {
t.Fatalf("expected no error when file queue url is empty, got %v", err)
}
})
}
func TestParseHTTPTimeoutOverrides(t *testing.T) {
t.Run("invalid format ignored", func(t *testing.T) {
parsed := parseHTTPTimeoutOverrides("GET /users/:id 100ms,foo")
if len(parsed) != 0 {
t.Fatalf("expected no parsed entries, got %v", parsed)
}
})
t.Run("invalid duration ignored", func(t *testing.T) {
parsed := parseHTTPTimeoutOverrides("GET /users/:id=not-a-duration")
if len(parsed) != 0 {
t.Fatalf("expected no parsed entries, got %v", parsed)
}
})
t.Run("zero and negative duration ignored", func(t *testing.T) {
parsed := parseHTTPTimeoutOverrides("GET /users/:id=0s,POST /users=-1s")
if len(parsed) != 0 {
t.Fatalf("expected no parsed entries, got %v", parsed)
}
})
t.Run("valid entries parsed", func(t *testing.T) {
parsed := parseHTTPTimeoutOverrides("GET /users/:id=120ms, POST /auth/login = 2s")
if len(parsed) != 2 {
t.Fatalf("expected 2 parsed entries, got %d", len(parsed))
}
if parsed["GET /users/:id"] != 120*time.Millisecond {
t.Fatalf("unexpected GET /users/:id timeout: %s", parsed["GET /users/:id"])
}
if parsed["POST /auth/login"] != 2*time.Second {
t.Fatalf("unexpected POST /auth/login timeout: %s", parsed["POST /auth/login"])
}
})
}
func TestParseHTTPEndpointRateLimitOverrides(t *testing.T) {
t.Run("invalid entries ignored", func(t *testing.T) {
parsed := parseHTTPEndpointRateLimitOverrides("POST /auth/login=abc,1m;foo;GET /x=1")
if len(parsed) != 0 {
t.Fatalf("expected no parsed entries, got %v", parsed)
}
})
t.Run("zero or negative values ignored", func(t *testing.T) {
parsed := parseHTTPEndpointRateLimitOverrides("POST /auth/login=0,1m;POST /auth/totp/verify=5,0s")
if len(parsed) != 0 {
t.Fatalf("expected no parsed entries, got %v", parsed)
}
})
t.Run("valid entries parsed", func(t *testing.T) {
parsed := parseHTTPEndpointRateLimitOverrides("POST /auth/login=5,1m; POST /auth/totp/verify = 3,30s")
if len(parsed) != 2 {
t.Fatalf("expected 2 parsed entries, got %d", len(parsed))
}
login, ok := parsed["POST /auth/login"]
if !ok {
t.Fatalf("expected POST /auth/login to be parsed")
}
if login.Max != 5 || login.Window != time.Minute {
t.Fatalf("unexpected login rule: %+v", login)
}
totp, ok := parsed["POST /auth/totp/verify"]
if !ok {
t.Fatalf("expected POST /auth/totp/verify to be parsed")
}
if totp.Max != 3 || totp.Window != 30*time.Second {
t.Fatalf("unexpected totp rule: %+v", totp)
}
})
}