2026-05-02 10:43:45 -05:00
package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestLoadAndValidate ( t * testing . T ) {
tests := [ ] struct {
name string
pipelineYAML string
sessionYAML string
wantLoadErr string
wantValidate string
2026-05-03 15:23:47 -05:00
checkDefault bool
2026-05-02 10:43:45 -05:00
} {
{
name : "valid minimal config" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
2026-05-03 15:23:47 -05:00
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
2026-05-02 10:43:45 -05:00
seriatim :
2026-05-03 19:13:50 -05:00
binary : seriatim
2026-05-02 10:43:45 -05:00
audita :
2026-05-04 07:48:59 -05:00
binary : audita
2026-05-02 10:43:45 -05:00
analyzer :
timeout : 20 m
notification :
timeout : 15 s
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
2026-05-03 15:23:47 -05:00
checkDefault : true ,
2026-05-02 10:43:45 -05:00
} ,
{
name : "unknown pipeline field fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
bogus : true
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
2026-05-02 11:44:15 -05:00
wantLoadErr : "pipeline file" ,
2026-05-02 10:43:45 -05:00
} ,
2026-05-03 15:23:47 -05:00
{
name : "unknown whisperx field fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
bogus : true
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantLoadErr : "strict decode failed" ,
} ,
2026-05-02 10:43:45 -05:00
{
name : "unknown session field fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
unknown_field : true
` ,
2026-05-02 11:44:15 -05:00
wantLoadErr : "session file" ,
2026-05-02 10:43:45 -05:00
} ,
{
name : "missing required field fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
2026-05-03 15:23:47 -05:00
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
2026-05-03 19:13:50 -05:00
seriatim :
binary : seriatim
2026-05-02 10:43:45 -05:00
` ,
sessionYAML : ` session_id : ""
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
2026-05-02 11:44:15 -05:00
wantValidate : "session config \"session.yml\" invalid: session.session_id is required" ,
2026-05-02 10:43:45 -05:00
} ,
2026-05-03 15:23:47 -05:00
{
name : "missing transcribe_url fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
2026-05-03 19:13:50 -05:00
seriatim :
binary : seriatim
2026-05-03 15:23:47 -05:00
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.whisperx.transcribe_url is required" ,
} ,
{
name : "invalid transcribe_url fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : : //
2026-05-03 19:13:50 -05:00
seriatim :
binary : seriatim
2026-05-03 15:23:47 -05:00
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.whisperx.transcribe_url must be a valid URL" ,
} ,
2026-05-02 10:43:45 -05:00
{
name : "invalid timeout fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
2026-05-03 15:23:47 -05:00
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
2026-05-02 10:43:45 -05:00
timeout : definitely - not - a - duration
2026-05-03 19:13:50 -05:00
seriatim :
binary : seriatim
2026-05-02 10:43:45 -05:00
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
2026-05-02 11:44:15 -05:00
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.whisperx.timeout must be a valid duration" ,
2026-05-02 10:43:45 -05:00
} ,
2026-05-03 15:23:47 -05:00
{
name : "invalid retry_delay fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
retry_delay : not - a - duration
2026-05-03 19:13:50 -05:00
seriatim :
binary : seriatim
2026-05-03 15:23:47 -05:00
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.whisperx.retry_delay must be a valid duration" ,
} ,
{
name : "negative retries fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
retries : - 1
2026-05-03 19:13:50 -05:00
seriatim :
binary : seriatim
2026-05-03 15:23:47 -05:00
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.whisperx.retries must be >= 0" ,
} ,
{
name : "invalid concurrency fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
concurrency : 0
2026-05-03 19:13:50 -05:00
seriatim :
binary : seriatim
2026-05-03 15:23:47 -05:00
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.whisperx.concurrency must be > 0" ,
} ,
2026-05-03 19:13:50 -05:00
{
name : "unknown seriatim field fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
bogus : true
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantLoadErr : "strict decode failed" ,
} ,
{
name : "unknown seriatim env field fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
env :
unknown_knob : 1.0
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantLoadErr : "strict decode failed" ,
} ,
{
name : "missing seriatim binary fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
timeout : 10 m
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.seriatim.binary is required" ,
} ,
{
name : "invalid seriatim timeout fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
timeout : not - a - duration
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.seriatim.timeout must be a valid duration" ,
} ,
{
name : "invalid seriatim output schema fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
output_schema : invalid - schema
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.seriatim.output_schema must be one of: seriatim-minimal, seriatim-intermediate, seriatim-full" ,
} ,
{
name : "negative seriatim coalesce gap fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
coalesce_gap : - 0.1
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.seriatim.coalesce_gap must be >= 0" ,
} ,
{
name : "invalid seriatim env float fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
env :
overlap_word_run_gap : 0
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.seriatim.env.overlap_word_run_gap must be > 0 when provided" ,
} ,
2026-05-04 07:48:59 -05:00
{
name : "unknown audita field fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
binary : audita
bogus : true
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantLoadErr : "strict decode failed" ,
} ,
{
name : "missing audita binary fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
timeout : 3 h
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.audita.binary is required" ,
} ,
{
name : "invalid audita timeout fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
binary : audita
timeout : bad - timeout
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.audita.timeout must be a valid duration" ,
} ,
{
name : "empty audita modules fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
binary : audita
modules : [ ]
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.audita.modules must include at least one module" ,
} ,
{
name : "empty audita module item fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
binary : audita
modules :
- glossary
- ""
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.audita.modules[1] must be non-empty" ,
} ,
{
name : "invalid audita module item fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
binary : audita
modules :
- glossary
- bad_module
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.audita.modules[1] must be one of: glossary, homophones, spoken_word, grammar" ,
} ,
{
name : "invalid audita base_url fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
binary : audita
base_url : : //
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.audita.base_url must be a valid URL" ,
} ,
{
name : "invalid audita llm_concurrency fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
binary : audita
llm_concurrency : 0
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.audita.llm_concurrency must be > 0" ,
} ,
{
name : "invalid audita validation_llm_concurrency fails" ,
pipelineYAML : ` workspace :
root : / tmp / narratio
whisperx :
transcribe_url : https : //transcription.ai.rakestrawhome.com/transcribe
seriatim :
binary : seriatim
audita :
binary : audita
validation_llm_concurrency : 0
` ,
sessionYAML : ` session_id : 2026 - 05 - 03
inputs :
audio_dir : . / audio
speakers_file : . / speakers . yml
autocorrect_file : . / autocorrect . yml
glossary_file : . / glossary . yml
` ,
wantValidate : "pipeline config \"pipeline.yml\" invalid: pipeline.audita.validation_llm_concurrency must be > 0" ,
} ,
2026-05-02 10:43:45 -05:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
pipelinePath , sessionPath := writeConfigFiles ( t , tt . pipelineYAML , tt . sessionYAML )
cfg , err := Load ( pipelinePath , sessionPath )
if tt . wantLoadErr != "" {
if err == nil {
t . Fatalf ( "expected load error containing %q, got nil" , tt . wantLoadErr )
}
if ! strings . Contains ( err . Error ( ) , tt . wantLoadErr ) {
t . Fatalf ( "load error = %q, want to contain %q" , err . Error ( ) , tt . wantLoadErr )
}
2026-05-02 11:44:15 -05:00
if ! strings . Contains ( err . Error ( ) , "strict decode failed" ) {
t . Fatalf ( "load error = %q, want strict decode context" , err . Error ( ) )
}
2026-05-02 10:43:45 -05:00
return
}
if err != nil {
t . Fatalf ( "Load() error = %v" , err )
}
if cfg . PipelinePath != pipelinePath {
t . Fatalf ( "PipelinePath = %q, want %q" , cfg . PipelinePath , pipelinePath )
}
if cfg . SessionPath != sessionPath {
t . Fatalf ( "SessionPath = %q, want %q" , cfg . SessionPath , sessionPath )
}
2026-05-03 15:23:47 -05:00
if tt . checkDefault {
if cfg . Pipeline . WhisperX . Language != "en" {
t . Fatalf ( "whisperx.language = %q, want %q" , cfg . Pipeline . WhisperX . Language , "en" )
}
if cfg . Pipeline . WhisperX . Timeout != "30m" {
t . Fatalf ( "whisperx.timeout = %q, want %q" , cfg . Pipeline . WhisperX . Timeout , "30m" )
}
if cfg . Pipeline . WhisperX . RetryDelay != "2s" {
t . Fatalf ( "whisperx.retry_delay = %q, want %q" , cfg . Pipeline . WhisperX . RetryDelay , "2s" )
}
if cfg . Pipeline . WhisperX . Retries == nil || * cfg . Pipeline . WhisperX . Retries != 3 {
t . Fatalf ( "whisperx.retries = %v, want 3" , cfg . Pipeline . WhisperX . Retries )
}
if cfg . Pipeline . WhisperX . Concurrency == nil || * cfg . Pipeline . WhisperX . Concurrency != 2 {
t . Fatalf ( "whisperx.concurrency = %v, want 2" , cfg . Pipeline . WhisperX . Concurrency )
}
2026-05-03 19:13:50 -05:00
if cfg . Pipeline . Seriatim . Timeout != "10m" {
t . Fatalf ( "seriatim.timeout = %q, want %q" , cfg . Pipeline . Seriatim . Timeout , "10m" )
}
if cfg . Pipeline . Seriatim . OutputSchema != "seriatim-intermediate" {
t . Fatalf ( "seriatim.output_schema = %q, want %q" , cfg . Pipeline . Seriatim . OutputSchema , "seriatim-intermediate" )
}
if cfg . Pipeline . Seriatim . CoalesceGap == nil || * cfg . Pipeline . Seriatim . CoalesceGap != 3.0 {
t . Fatalf ( "seriatim.coalesce_gap = %v, want 3.0" , cfg . Pipeline . Seriatim . CoalesceGap )
}
if cfg . Pipeline . Seriatim . Report == nil || * cfg . Pipeline . Seriatim . Report != true {
t . Fatalf ( "seriatim.report = %v, want true" , cfg . Pipeline . Seriatim . Report )
}
2026-05-04 07:48:59 -05:00
if cfg . Pipeline . Audita . Timeout != "3h" {
t . Fatalf ( "audita.timeout = %q, want %q" , cfg . Pipeline . Audita . Timeout , "3h" )
}
2026-05-04 11:46:57 -05:00
if cfg . Pipeline . Audita . LLMAPIKeyEnv != "" {
t . Fatalf ( "audita.llm_api_key_env = %q, want empty by default" , cfg . Pipeline . Audita . LLMAPIKeyEnv )
2026-05-04 07:48:59 -05:00
}
if got := strings . Join ( cfg . Pipeline . Audita . Modules , "," ) ; got != "glossary,homophones,glossary,spoken_word,grammar,homophones,glossary" {
t . Fatalf ( "audita.modules = %q, want default sequence" , got )
}
if cfg . Pipeline . Audita . BaseURL != "https://openrouter.ai/api/v1" {
t . Fatalf ( "audita.base_url = %q, want %q" , cfg . Pipeline . Audita . BaseURL , "https://openrouter.ai/api/v1" )
}
if cfg . Pipeline . Audita . Model != "openrouter/google/gemma-4-31b-it" {
t . Fatalf ( "audita.model = %q, want %q" , cfg . Pipeline . Audita . Model , "openrouter/google/gemma-4-31b-it" )
}
if cfg . Pipeline . Audita . LLMConcurrency == nil || * cfg . Pipeline . Audita . LLMConcurrency != 1 {
t . Fatalf ( "audita.llm_concurrency = %v, want 1" , cfg . Pipeline . Audita . LLMConcurrency )
}
if cfg . Pipeline . Audita . ValidationModel != "" {
t . Fatalf ( "audita.validation_model = %q, want empty default" , cfg . Pipeline . Audita . ValidationModel )
}
if cfg . Pipeline . Audita . ValidationLLMConcurrency == nil || * cfg . Pipeline . Audita . ValidationLLMConcurrency != 1 {
t . Fatalf ( "audita.validation_llm_concurrency = %v, want 1" , cfg . Pipeline . Audita . ValidationLLMConcurrency )
}
if cfg . Pipeline . Audita . Report == nil || * cfg . Pipeline . Audita . Report != true {
t . Fatalf ( "audita.report = %v, want true" , cfg . Pipeline . Audita . Report )
}
2026-05-03 15:23:47 -05:00
}
2026-05-02 10:43:45 -05:00
err = Validate ( cfg )
if tt . wantValidate != "" {
if err == nil {
t . Fatalf ( "expected validation error containing %q, got nil" , tt . wantValidate )
}
if ! strings . Contains ( err . Error ( ) , tt . wantValidate ) {
t . Fatalf ( "validation error = %q, want to contain %q" , err . Error ( ) , tt . wantValidate )
}
return
}
if err != nil {
t . Fatalf ( "Validate() error = %v" , err )
}
} )
}
}
func TestValidateMissingAudioSource ( t * testing . T ) {
2026-05-03 15:23:47 -05:00
retries := 3
concurrency := 2
2026-05-02 10:43:45 -05:00
cfg := & Config {
2026-05-03 15:23:47 -05:00
Pipeline : & PipelineConfig {
Workspace : WorkspaceConfig { Root : "/tmp/narratio" } ,
WhisperX : WhisperXConfig {
TranscribeURL : "https://transcription.ai.rakestrawhome.com/transcribe" ,
Language : "en" ,
Timeout : "30m" ,
Retries : & retries ,
RetryDelay : "2s" ,
Concurrency : & concurrency ,
} ,
2026-05-03 19:13:50 -05:00
Seriatim : SeriatimConfig {
Binary : "seriatim" ,
Timeout : "10m" ,
OutputSchema : "seriatim-intermediate" ,
CoalesceGap : float64Ptr ( 3.0 ) ,
Report : boolPtr ( true ) ,
} ,
2026-05-04 07:48:59 -05:00
Audita : AuditaConfig {
Binary : "audita" ,
Timeout : "3h" ,
LLMAPIKeyEnv : "AUDITA_LLM_API_KEY" ,
Modules : [ ] string { "glossary" , "homophones" } ,
BaseURL : "https://openrouter.ai/api/v1" ,
Model : "openrouter/google/gemma-4-31b-it" ,
LLMConcurrency : intPtr ( 1 ) ,
ValidationModel : "" ,
ValidationLLMConcurrency : intPtr ( 1 ) ,
Report : boolPtr ( true ) ,
} ,
2026-05-03 15:23:47 -05:00
} ,
2026-05-02 10:43:45 -05:00
Session : & SessionConfig {
SessionID : "2026-05-03" ,
Inputs : SessionInputsConfig {
SpeakersFile : "speakers.yml" ,
AutocorrectFile : "autocorrect.yml" ,
GlossaryFile : "glossary.yml" ,
} ,
} ,
}
err := Validate ( cfg )
if err == nil {
t . Fatal ( "expected validation error, got nil" )
}
if ! strings . Contains ( err . Error ( ) , "audio_dir or at least one audio_files" ) {
t . Fatalf ( "error = %q, want audio source guidance" , err . Error ( ) )
}
2026-05-02 11:44:15 -05:00
if ! strings . Contains ( err . Error ( ) , "session config" ) {
t . Fatalf ( "error = %q, want session config context" , err . Error ( ) )
}
}
func TestExamplesLoadAndValidate ( t * testing . T ) {
pipelinePath := filepath . Join ( ".." , ".." , "examples" , "pipeline.minimal.yml" )
sessionPath := filepath . Join ( ".." , ".." , "examples" , "session.minimal.yml" )
cfg , err := Load ( pipelinePath , sessionPath )
if err != nil {
t . Fatalf ( "Load(examples) error = %v" , err )
}
if err := Validate ( cfg ) ; err != nil {
t . Fatalf ( "Validate(examples) error = %v" , err )
}
2026-05-02 10:43:45 -05:00
}
func writeConfigFiles ( t * testing . T , pipelineYAML , sessionYAML string ) ( string , string ) {
t . Helper ( )
2026-05-04 07:48:59 -05:00
if ! strings . Contains ( pipelineYAML , "\naudita:" ) && ! strings . HasPrefix ( pipelineYAML , "audita:" ) {
if ! strings . HasSuffix ( pipelineYAML , "\n" ) {
pipelineYAML += "\n"
}
pipelineYAML += "audita:\n binary: audita\n"
}
2026-05-02 10:43:45 -05:00
dir := t . TempDir ( )
pipelinePath := filepath . Join ( dir , "pipeline.yml" )
sessionPath := filepath . Join ( dir , "session.yml" )
if err := os . WriteFile ( pipelinePath , [ ] byte ( pipelineYAML ) , 0 o644 ) ; err != nil {
t . Fatalf ( "write pipeline.yml: %v" , err )
}
if err := os . WriteFile ( sessionPath , [ ] byte ( sessionYAML ) , 0 o644 ) ; err != nil {
t . Fatalf ( "write session.yml: %v" , err )
}
return pipelinePath , sessionPath
}