package config import ( "strings" "testing" ) func TestValidateDurationsRequirePositiveValues(t *testing.T) { tests := []struct { name string set func(*PipelineConfig, string) want string }{ {name: "whisperx timeout", set: func(p *PipelineConfig, value string) { p.WhisperX.Timeout = value }, want: "pipeline.whisperx.timeout"}, {name: "whisperx retry delay", set: func(p *PipelineConfig, value string) { p.WhisperX.RetryDelay = value }, want: "pipeline.whisperx.retry_delay"}, {name: "seriatim timeout", set: func(p *PipelineConfig, value string) { p.Seriatim.Timeout = value }, want: "pipeline.seriatim.timeout"}, {name: "audita timeout", set: func(p *PipelineConfig, value string) { p.Audita.Timeout = value }, want: "pipeline.audita.timeout"}, {name: "scriptorium timeout", set: func(p *PipelineConfig, value string) { p.Scriptorium.Timeout = value }, want: "pipeline.scriptorium.timeout"}, {name: "scriptorium artifact timeout", set: func(p *PipelineConfig, value string) { if p.Scriptorium.Artifacts == nil { p.Scriptorium.Artifacts = map[string]ScriptoriumArtifactConfig{} } p.Scriptorium.Artifacts["session_recap"] = ScriptoriumArtifactConfig{Timeout: value} }, want: "pipeline.scriptorium.artifacts.session_recap.timeout"}, {name: "trim bounds timeout", set: func(p *PipelineConfig, value string) { p.Trim.Bounds.Timeout = value }, want: "pipeline.trim.bounds.timeout"}, } for _, value := range []string{"0s", "-1ms"} { for _, tt := range tests { t.Run(tt.name+"/"+value, func(t *testing.T) { cfg := loadedValidConfig(t) tt.set(cfg.Pipeline, value) err := Validate(cfg) if err == nil || !strings.Contains(err.Error(), tt.want+" must be positive") { t.Fatalf("Validate() error = %v, want positive-value error for %s", err, tt.want) } }) } } } func TestValidateWhisperXRequiresAbsoluteHTTPSEndpoint(t *testing.T) { retries := 0 concurrency := 1 base := WhisperXConfig{ Language: "en", Timeout: "1s", Retries: &retries, RetryDelay: "1ms", Concurrency: &concurrency, } for _, endpoint := range []string{"ftp://example.com/transcribe", "file:///tmp/transcribe", "//example.com/transcribe", "https:/missing-host"} { cfg := base cfg.TranscribeURL = endpoint if err := validateWhisperX(cfg); err == nil || !strings.Contains(err.Error(), "absolute http or https URL") { t.Errorf("validateWhisperX(%q) error = %v, want HTTP(S) endpoint validation", endpoint, err) } } for _, endpoint := range []string{"http://example.com/transcribe", "https://example.com/transcribe"} { cfg := base cfg.TranscribeURL = endpoint if err := validateWhisperX(cfg); err != nil { t.Errorf("validateWhisperX(%q) error = %v", endpoint, err) } } } func TestValidateNotariusTimeoutRequiresPositiveValue(t *testing.T) { for _, value := range []string{"0s", "-1ms"} { t.Run(value, func(t *testing.T) { cfg := loadedValidConfig(t) cfg.Pipeline.Notarius = &NotariusConfig{ Enabled: true, Binary: "notarius", ConfigPath: "/tmp/notarius.yml", PipelineID: "session", Timeout: value, WorkingDirectory: "/tmp", Outputs: map[string]NotariusOutputConfig{ "npc_registry": { LaneID: "npc-registry", MediaType: "application/json", SchemaID: "notarius.dnd.npc_registry", SchemaVersion: "v1", }, }, } err := Validate(cfg) if err == nil || !strings.Contains(err.Error(), "pipeline.notarius.timeout must be positive") { t.Fatalf("Validate() error = %v, want positive Notarius timeout error", err) } }) } } func TestValidateDurationRejectsOverflowAndAcceptsPositiveSubsecondValues(t *testing.T) { cfg := loadedValidConfig(t) cfg.Pipeline.WhisperX.Timeout = "999999999999999999999h" if err := Validate(cfg); err == nil || !strings.Contains(err.Error(), "pipeline.whisperx.timeout must be a valid duration") { t.Fatalf("Validate() overflow error = %v, want duration parse error", err) } cfg = loadedValidConfig(t) cfg.Pipeline.WhisperX.Timeout = "1ms" cfg.Pipeline.WhisperX.RetryDelay = "1ms" cfg.Pipeline.Seriatim.Timeout = "1ms" cfg.Pipeline.Audita.Timeout = "1ms" cfg.Pipeline.Scriptorium.Timeout = "1ms" cfg.Pipeline.Trim.Bounds.Timeout = "1ms" if cfg.Pipeline.Scriptorium.Artifacts == nil { cfg.Pipeline.Scriptorium.Artifacts = map[string]ScriptoriumArtifactConfig{} } cfg.Pipeline.Scriptorium.Artifacts["session_recap"] = ScriptoriumArtifactConfig{Timeout: "1ms"} if err := Validate(cfg); err != nil { t.Fatalf("Validate() positive subsecond durations error = %v", err) } } func TestValidateAllowsEmptyArtifactTimeoutFallback(t *testing.T) { cfg := loadedValidConfig(t) if cfg.Pipeline.Scriptorium.Artifacts == nil { cfg.Pipeline.Scriptorium.Artifacts = map[string]ScriptoriumArtifactConfig{} } cfg.Pipeline.Scriptorium.Artifacts["session_recap"] = ScriptoriumArtifactConfig{Timeout: ""} if err := Validate(cfg); err != nil { t.Fatalf("Validate() empty artifact timeout error = %v", err) } } func loadedValidConfig(t *testing.T) *Config { t.Helper() pipelinePath, sessionPath := writeConfigFiles(t, testPipelineBaseYAML, testSessionBaseYAML) cfg, err := Load(pipelinePath, sessionPath) if err != nil { t.Fatalf("Load() error = %v", err) } return cfg }