Add Scriptorium configuration contract
This commit is contained in:
203
internal/config/scriptorium_test.go
Normal file
203
internal/config/scriptorium_test.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScriptoriumLoadAndValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
scriptoriumYAML string
|
||||
wantLoadErr string
|
||||
wantValidateErr string
|
||||
assert func(t *testing.T, cfg *Config)
|
||||
}{
|
||||
{
|
||||
name: "valid minimal scriptorium config",
|
||||
scriptoriumYAML: `scriptorium:
|
||||
binary: scriptorium
|
||||
artifacts:
|
||||
session_recap:
|
||||
enabled: true
|
||||
prompt_id: dnd.session_recap
|
||||
output_path: artifacts/session_recap.md
|
||||
inputs:
|
||||
transcript:
|
||||
source: processed_transcript
|
||||
required: true
|
||||
vars:
|
||||
session_id: true
|
||||
output_kind: session_recap
|
||||
`,
|
||||
assert: func(t *testing.T, cfg *Config) {
|
||||
t.Helper()
|
||||
if cfg.Pipeline.Scriptorium == nil {
|
||||
t.Fatal("scriptorium config should be present")
|
||||
}
|
||||
if cfg.Pipeline.Scriptorium.Timeout != "10m" {
|
||||
t.Fatalf("scriptorium.timeout default = %q, want 10m", cfg.Pipeline.Scriptorium.Timeout)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown scriptorium field fails",
|
||||
scriptoriumYAML: `scriptorium:
|
||||
binary: scriptorium
|
||||
bogus: true
|
||||
`,
|
||||
wantLoadErr: "strict decode failed",
|
||||
},
|
||||
{
|
||||
name: "missing binary fails when section present",
|
||||
scriptoriumYAML: `scriptorium:
|
||||
timeout: 10m
|
||||
`,
|
||||
wantValidateErr: "pipeline.scriptorium.binary is required",
|
||||
},
|
||||
{
|
||||
name: "enabled artifact missing prompt id fails",
|
||||
scriptoriumYAML: `scriptorium:
|
||||
binary: scriptorium
|
||||
artifacts:
|
||||
session_recap:
|
||||
enabled: true
|
||||
output_path: artifacts/session_recap.md
|
||||
`,
|
||||
wantValidateErr: "pipeline.scriptorium.artifacts.session_recap.prompt_id is required when enabled",
|
||||
},
|
||||
{
|
||||
name: "enabled artifact missing output path fails",
|
||||
scriptoriumYAML: `scriptorium:
|
||||
binary: scriptorium
|
||||
artifacts:
|
||||
session_recap:
|
||||
enabled: true
|
||||
prompt_id: dnd.session_recap
|
||||
`,
|
||||
wantValidateErr: "pipeline.scriptorium.artifacts.session_recap.output_path is required when enabled",
|
||||
},
|
||||
{
|
||||
name: "invalid timeout fails",
|
||||
scriptoriumYAML: `scriptorium:
|
||||
binary: scriptorium
|
||||
timeout: definitely-not-a-duration
|
||||
`,
|
||||
wantValidateErr: "pipeline.scriptorium.timeout must be a valid duration",
|
||||
},
|
||||
{
|
||||
name: "optional previous recap input is accepted",
|
||||
scriptoriumYAML: `scriptorium:
|
||||
binary: scriptorium
|
||||
artifacts:
|
||||
session_recap:
|
||||
enabled: true
|
||||
prompt_id: dnd.session_recap
|
||||
output_path: artifacts/session_recap.md
|
||||
inputs:
|
||||
transcript:
|
||||
source: processed_transcript
|
||||
required: true
|
||||
previous_recap:
|
||||
source: previous_session_artifact
|
||||
artifact: session_recap
|
||||
path: ""
|
||||
required: false
|
||||
vars:
|
||||
session_id: true
|
||||
output_kind: session_recap
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "multiple artifact definitions can be decoded",
|
||||
scriptoriumYAML: `scriptorium:
|
||||
binary: scriptorium
|
||||
artifacts:
|
||||
session_recap:
|
||||
enabled: true
|
||||
prompt_id: dnd.session_recap
|
||||
output_path: artifacts/session_recap.md
|
||||
inputs:
|
||||
transcript:
|
||||
source: processed_transcript
|
||||
required: true
|
||||
player_summary:
|
||||
enabled: true
|
||||
prompt_id: dnd.player_summary
|
||||
profile_id: local-fast
|
||||
output_path: artifacts/player_summary.md
|
||||
timeout: 3m
|
||||
inputs:
|
||||
transcript:
|
||||
source: processed_transcript
|
||||
required: true
|
||||
`,
|
||||
assert: func(t *testing.T, cfg *Config) {
|
||||
t.Helper()
|
||||
if cfg.Pipeline.Scriptorium == nil {
|
||||
t.Fatal("scriptorium config should be present")
|
||||
}
|
||||
if len(cfg.Pipeline.Scriptorium.Artifacts) != 2 {
|
||||
t.Fatalf("scriptorium.artifacts count = %d, want 2", len(cfg.Pipeline.Scriptorium.Artifacts))
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pipelineYAML := testPipelineBaseYAML + "\n" + tt.scriptoriumYAML
|
||||
pipelinePath, sessionPath := writeConfigFiles(t, pipelineYAML, testSessionBaseYAML)
|
||||
|
||||
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)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
|
||||
if tt.assert != nil {
|
||||
tt.assert(t, cfg)
|
||||
}
|
||||
|
||||
err = Validate(cfg)
|
||||
if tt.wantValidateErr != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("expected validation error containing %q, got nil", tt.wantValidateErr)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantValidateErr) {
|
||||
t.Fatalf("validation error = %q, want to contain %q", err.Error(), tt.wantValidateErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Validate() error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const testPipelineBaseYAML = `workspace:
|
||||
root: /tmp/narratio
|
||||
whisperx:
|
||||
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||
seriatim:
|
||||
binary: seriatim
|
||||
audita:
|
||||
binary: audita
|
||||
`
|
||||
|
||||
const testSessionBaseYAML = `session_id: 2026-05-03
|
||||
inputs:
|
||||
audio_dir: ./audio
|
||||
speakers_file: ./speakers.yml
|
||||
autocorrect_file: ./autocorrect.yml
|
||||
glossary_file: ./glossary.yml
|
||||
`
|
||||
Reference in New Issue
Block a user