Add video as a media kind in photos: streaming upload with per-kind limits, ffprobe metadata, poster frame reusing the image variant pipeline, and a playback object (remux when web-compatible, else h264/yuv420p/aac transcode). Video jobs run on a separate pool with a long lease. Frontend gains a video viewer, grid badge/duration and video upload.
235 lines
6.0 KiB
Go
235 lines
6.0 KiB
Go
package config
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func setRequiredEnv(t *testing.T) {
|
|
t.Helper()
|
|
t.Setenv("JWT_SECRET", "test-secret")
|
|
t.Setenv("PG_PASSWORD", "test-pass")
|
|
}
|
|
|
|
func TestLoadStorageBackend(t *testing.T) {
|
|
t.Run("defaults to fs", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("STORAGE_BACKEND", "")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Storage.Backend != "fs" || cfg.Storage.FSRoot != "./data" {
|
|
t.Fatalf("storage = %+v", cfg.Storage)
|
|
}
|
|
})
|
|
|
|
t.Run("fs with root", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("STORAGE_BACKEND", "fs")
|
|
t.Setenv("FS_ROOT", "/var/lib/galo/objects")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Storage.Backend != "fs" || cfg.Storage.FSRoot != "/var/lib/galo/objects" {
|
|
t.Fatalf("storage = %+v", cfg.Storage)
|
|
}
|
|
})
|
|
|
|
t.Run("fs empty root falls back to default", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("STORAGE_BACKEND", "fs")
|
|
t.Setenv("FS_ROOT", "")
|
|
t.Setenv("URL_SECRET", "")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Storage.FSRoot != "./data" {
|
|
t.Fatalf("FSRoot = %q, want ./data", cfg.Storage.FSRoot)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid backend", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("STORAGE_BACKEND", "gcs")
|
|
if _, err := Load(); err == nil {
|
|
t.Fatal("expected error for invalid backend")
|
|
}
|
|
})
|
|
|
|
t.Run("object url ttl", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("STORAGE_BACKEND", "")
|
|
t.Setenv("OBJECT_URL_TTL", "30m")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Storage.URLTTL != 30*time.Minute {
|
|
t.Fatalf("URLTTL = %v, want 30m", cfg.Storage.URLTTL)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLoadDatabaseBackend(t *testing.T) {
|
|
t.Run("defaults to sqlite", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("DB_BACKEND", "")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Database.Backend != "sqlite" || cfg.Database.SQLitePath != "./galo.db" {
|
|
t.Fatalf("database = %+v", cfg.Database)
|
|
}
|
|
})
|
|
|
|
t.Run("postgres requires password", func(t *testing.T) {
|
|
t.Setenv("JWT_SECRET", "s")
|
|
t.Setenv("PG_PASSWORD", "")
|
|
t.Setenv("DB_BACKEND", "postgres")
|
|
if _, err := Load(); err == nil {
|
|
t.Fatal("expected error for postgres without PG_PASSWORD")
|
|
}
|
|
})
|
|
|
|
t.Run("sqlite with path", func(t *testing.T) {
|
|
t.Setenv("JWT_SECRET", "s")
|
|
t.Setenv("PG_PASSWORD", "")
|
|
t.Setenv("DB_BACKEND", "sqlite")
|
|
t.Setenv("SQLITE_PATH", "/data/galo.db")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Database.Backend != "sqlite" || cfg.Database.SQLitePath != "/data/galo.db" {
|
|
t.Fatalf("database = %+v", cfg.Database)
|
|
}
|
|
})
|
|
|
|
t.Run("sqlite empty path falls back to default", func(t *testing.T) {
|
|
t.Setenv("JWT_SECRET", "s")
|
|
t.Setenv("PG_PASSWORD", "")
|
|
t.Setenv("DB_BACKEND", "sqlite")
|
|
t.Setenv("SQLITE_PATH", "")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Database.SQLitePath != "./galo.db" {
|
|
t.Fatalf("SQLitePath = %q, want ./galo.db", cfg.Database.SQLitePath)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid backend", func(t *testing.T) {
|
|
t.Setenv("JWT_SECRET", "s")
|
|
t.Setenv("PG_PASSWORD", "p")
|
|
t.Setenv("DB_BACKEND", "mysql")
|
|
if _, err := Load(); err == nil {
|
|
t.Fatal("expected error for invalid backend")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLoadLog(t *testing.T) {
|
|
t.Run("defaults", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("LOG_LEVEL", "")
|
|
t.Setenv("LOG_FORMAT", "")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Log.Level != "info" || cfg.Log.Format != "json" {
|
|
t.Fatalf("log = %+v, want info/json", cfg.Log)
|
|
}
|
|
})
|
|
|
|
t.Run("custom values", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("LOG_LEVEL", "debug")
|
|
t.Setenv("LOG_FORMAT", "text")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Log.Level != "debug" || cfg.Log.Format != "text" {
|
|
t.Fatalf("log = %+v, want debug/text", cfg.Log)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid level", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("LOG_LEVEL", "trace")
|
|
if _, err := Load(); err == nil {
|
|
t.Fatal("expected error for invalid LOG_LEVEL")
|
|
}
|
|
})
|
|
|
|
t.Run("invalid format", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("LOG_FORMAT", "xml")
|
|
if _, err := Load(); err == nil {
|
|
t.Fatal("expected error for invalid LOG_FORMAT")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLoadUploadAndVideo(t *testing.T) {
|
|
t.Run("defaults", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Upload.ImageMaxBytes != 100<<20 {
|
|
t.Fatalf("image max = %d, want 100MiB", cfg.Upload.ImageMaxBytes)
|
|
}
|
|
if cfg.Upload.VideoMaxBytes != 2<<30 {
|
|
t.Fatalf("video max = %d, want 2GiB", cfg.Upload.VideoMaxBytes)
|
|
}
|
|
if cfg.Video.FFmpegPath != "ffmpeg" || cfg.Video.FFprobePath != "ffprobe" {
|
|
t.Fatalf("tool paths = %+v", cfg.Video)
|
|
}
|
|
if cfg.Video.TranscodeConcurrency != 1 {
|
|
t.Fatalf("transcode concurrency = %d, want 1", cfg.Video.TranscodeConcurrency)
|
|
}
|
|
})
|
|
|
|
t.Run("custom values", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("IMAGE_MAX_UPLOAD_SIZE", "50MB")
|
|
t.Setenv("VIDEO_MAX_UPLOAD_SIZE", "3GB")
|
|
t.Setenv("FFMPEG_PATH", "/usr/bin/ffmpeg")
|
|
t.Setenv("FFPROBE_PATH", "/usr/bin/ffprobe")
|
|
t.Setenv("VIDEO_TRANSCODE_CONCURRENCY", "2")
|
|
t.Setenv("VIDEO_TEMP_DIR", "/var/tmp/galo")
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if cfg.Upload.ImageMaxBytes != 50<<20 {
|
|
t.Fatalf("image max = %d, want 50MiB", cfg.Upload.ImageMaxBytes)
|
|
}
|
|
if cfg.Upload.VideoMaxBytes != 3<<30 {
|
|
t.Fatalf("video max = %d, want 3GiB", cfg.Upload.VideoMaxBytes)
|
|
}
|
|
if cfg.Video.FFmpegPath != "/usr/bin/ffmpeg" || cfg.Video.TranscodeConcurrency != 2 {
|
|
t.Fatalf("video = %+v", cfg.Video)
|
|
}
|
|
if cfg.Video.TempDir != "/var/tmp/galo" {
|
|
t.Fatalf("temp dir = %q", cfg.Video.TempDir)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid size", func(t *testing.T) {
|
|
setRequiredEnv(t)
|
|
t.Setenv("VIDEO_MAX_UPLOAD_SIZE", "lots")
|
|
if _, err := Load(); err == nil {
|
|
t.Fatal("expected error for invalid VIDEO_MAX_UPLOAD_SIZE")
|
|
}
|
|
})
|
|
}
|