diff --git a/internal/config/uploads_test.go b/internal/config/uploads_test.go new file mode 100644 index 0000000..c55d77f --- /dev/null +++ b/internal/config/uploads_test.go @@ -0,0 +1,123 @@ +package config + +import ( + "os" + "testing" + "time" +) + +// clearUploadEnv unsets every upload knob so a test observes the default, not the +// host environment. +func clearUploadEnv(t *testing.T) { + t.Helper() + for _, k := range []string{ + "TAPIR_UPLOADS_ENABLED", "TAPIR_UPLOAD_MAX_MB", "TAPIR_UPLOAD_MAX_MINUTES", + "TAPIR_TRANSCRIBE_MODEL", "TAPIR_TRANSCRIBE_FALLBACK_MODEL", "TAPIR_TRANSCRIBE_TIMEOUT", + "TAPIR_UPLOAD_TMP_DIR", "TAPIR_FFMPEG_BIN", "TAPIR_FFPROBE_BIN", + "TAPIR_MAX_UPLOADED_TRANSCRIPT_CHARS", + } { + unset(t, k) + } +} + +func TestLoad_UploadDefaults(t *testing.T) { + clearUploadEnv(t) + + c, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + + if c.UploadsEnabled { + t.Error("uploads must be OFF by default: the runtime image needs ffmpeg first (ADR-033)") + } + if c.UploadMaxBytes != 200<<20 { + t.Errorf("UploadMaxBytes = %d, want 200 MB", c.UploadMaxBytes) + } + if c.UploadMaxDuration != 90*time.Minute { + t.Errorf("UploadMaxDuration = %v, want 90m (the longest length measured, not a found ceiling)", c.UploadMaxDuration) + } + if c.TranscribeModel != "iguana/kb-whisper" { + t.Errorf("TranscribeModel = %q", c.TranscribeModel) + } + if c.TranscribeFallbackModel != "berget/kb-whisper" { + t.Errorf("TranscribeFallbackModel = %q", c.TranscribeFallbackModel) + } + if c.TranscribeTimeout != 30*time.Minute { + t.Errorf("TranscribeTimeout = %v, want 30m (a 90 min recording took ~7.5 min)", c.TranscribeTimeout) + } + if c.UploadTmpDir != os.TempDir() { + t.Errorf("UploadTmpDir = %q, want os.TempDir()", c.UploadTmpDir) + } + if c.FFmpegBin != "ffmpeg" || c.FFprobeBin != "ffprobe" { + t.Errorf("bins = %q, %q", c.FFmpegBin, c.FFprobeBin) + } + if c.MaxUploadedTranscriptChars != 120000 { + t.Errorf("MaxUploadedTranscriptChars = %d, want 120000", c.MaxUploadedTranscriptChars) + } +} + +func TestLoad_UploadValuesAreParsed(t *testing.T) { + clearUploadEnv(t) + setEnv(t, map[string]string{ + "TAPIR_UPLOADS_ENABLED": "true", + "TAPIR_UPLOAD_MAX_MB": "50", + "TAPIR_UPLOAD_MAX_MINUTES": "30", + "TAPIR_TRANSCRIBE_MODEL": "koala/whisper", + "TAPIR_TRANSCRIBE_TIMEOUT": "10m", + "TAPIR_UPLOAD_TMP_DIR": "/var/spool/tapir", + "TAPIR_FFMPEG_BIN": "/opt/ffmpeg", + "TAPIR_FFPROBE_BIN": "/opt/ffprobe", + "TAPIR_MAX_UPLOADED_TRANSCRIPT_CHARS": "60000", + }) + + c, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + + if !c.UploadsEnabled || c.UploadMaxBytes != 50<<20 || c.UploadMaxDuration != 30*time.Minute || + c.TranscribeModel != "koala/whisper" || c.TranscribeTimeout != 10*time.Minute || + c.UploadTmpDir != "/var/spool/tapir" || c.FFmpegBin != "/opt/ffmpeg" || + c.FFprobeBin != "/opt/ffprobe" || c.MaxUploadedTranscriptChars != 60000 { + t.Errorf("values not applied: %+v", c) + } +} + +func TestLoad_EmptyTranscribeFallbackDisablesIt(t *testing.T) { + clearUploadEnv(t) + t.Setenv("TAPIR_TRANSCRIBE_FALLBACK_MODEL", "") + + c, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + + if c.TranscribeFallbackModel != "" { + t.Errorf("TranscribeFallbackModel = %q: an explicit empty value must disable the cloud fallback, "+ + "so private audio can be kept on the homelab", c.TranscribeFallbackModel) + } +} + +func TestLoad_UploadRejectsInvalidValues(t *testing.T) { + tests := []struct{ key, val string }{ + {"TAPIR_UPLOADS_ENABLED", "maybe"}, + {"TAPIR_UPLOAD_MAX_MB", "lots"}, + {"TAPIR_UPLOAD_MAX_MB", "0"}, + {"TAPIR_UPLOAD_MAX_MB", "-5"}, + {"TAPIR_UPLOAD_MAX_MINUTES", "0"}, + {"TAPIR_UPLOAD_MAX_MINUTES", "forever"}, + {"TAPIR_TRANSCRIBE_TIMEOUT", "soon"}, + {"TAPIR_TRANSCRIBE_TIMEOUT", "0s"}, + } + for _, tt := range tests { + t.Run(tt.key+"="+tt.val, func(t *testing.T) { + clearUploadEnv(t) + t.Setenv(tt.key, tt.val) + + if _, err := Load(); err == nil { + t.Errorf("Load accepted %s=%q; a zero or negative limit would reject every upload", tt.key, tt.val) + } + }) + } +}