From c918ee3682f462daf0514738472b1977d37beaa9 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 2 Apr 2026 08:52:16 +0300 Subject: Align processor attribution with provider semantics --- internal/processor/processor_test.go | 93 ++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 10 deletions(-) (limited to 'internal/processor/processor_test.go') diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go index 4d9f3ad..d6326f6 100644 --- a/internal/processor/processor_test.go +++ b/internal/processor/processor_test.go @@ -520,7 +520,7 @@ func TestGenerateAudioUsesConfiguredGeminiVoiceAndModel(t *testing.T) { flags.AudioFormat = "mp3" p := NewProcessor(flags) - if err := p.generateAudio("ябълка"); err != nil { + if err := p.generateAudio("ябълка!?"); err != nil { t.Fatalf("generateAudio() unexpected error: %v", err) } @@ -546,7 +546,7 @@ func TestGenerateAudioUsesConfiguredGeminiVoiceAndModel(t *testing.T) { t.Fatalf("GenerateAudio() output file = %q, want wav output", fakeProvider.lastOutputFile) } - wordDir := p.findCardDirectory("ябълка") + wordDir := p.findCardDirectory("ябълка!?") if wordDir == "" { t.Fatal("expected generated word directory") } @@ -575,9 +575,15 @@ func TestGenerateAudioUsesConfiguredGeminiVoiceAndModel(t *testing.T) { t.Fatalf("expected attribution file %q: %v", attrPath, err) } attribution := string(attributionData) - if !strings.Contains(attribution, "Processed text sent to TTS: ябълка") { + if !strings.Contains(attribution, "Processed text sent to TTS: ябълка!?") { t.Fatalf("gemini attribution missing exact processed text: %q", attribution) } + if !strings.Contains(attribution, "Speak at a natural pace.") { + t.Fatalf("gemini attribution missing speed hint semantics: %q", attribution) + } + if !strings.Contains(attribution, "voice named Kore") { + t.Fatalf("gemini attribution missing voice semantics: %q", attribution) + } } func TestGenerateAudioUsesGeminiModelDefaultWhenVoiceNotSet(t *testing.T) { @@ -608,7 +614,7 @@ func TestGenerateAudioUsesGeminiModelDefaultWhenVoiceNotSet(t *testing.T) { flags.AudioProvider = "gemini" p := NewProcessor(flags) - if err := p.generateAudio("ябълка"); err != nil { + if err := p.generateAudio("ябълка!?"); err != nil { t.Fatalf("generateAudio() unexpected error: %v", err) } @@ -625,7 +631,7 @@ func TestGenerateAudioUsesGeminiModelDefaultWhenVoiceNotSet(t *testing.T) { t.Fatalf("GenerateAudio() output file = %q, want wav output", fakeProvider.lastOutputFile) } - wordDir := p.findCardDirectory("ябълка") + wordDir := p.findCardDirectory("ябълка!?") if wordDir == "" { t.Fatal("expected generated word directory") } @@ -651,9 +657,12 @@ func TestGenerateAudioUsesGeminiModelDefaultWhenVoiceNotSet(t *testing.T) { t.Fatalf("expected attribution file %q: %v", attrPath, err) } attribution := string(attributionData) - if !strings.Contains(attribution, "Processed text sent to TTS: ябълка") { + if !strings.Contains(attribution, "Processed text sent to TTS: ябълка!?") { t.Fatalf("gemini attribution missing exact processed text: %q", attribution) } + if !strings.Contains(attribution, "Speak at a natural pace.") { + t.Fatalf("gemini attribution missing speed hint semantics: %q", attribution) + } } func TestGenerateAudioBgBgUsesGeminiModelDefaultWhenVoiceNotSet(t *testing.T) { @@ -684,7 +693,7 @@ func TestGenerateAudioBgBgUsesGeminiModelDefaultWhenVoiceNotSet(t *testing.T) { flags.AudioProvider = "gemini" p := NewProcessor(flags) - if err := p.generateAudioBgBg("ябълка", "круша"); err != nil { + if err := p.generateAudioBgBg("ябълка!?", "круша."); err != nil { t.Fatalf("generateAudioBgBg() unexpected error: %v", err) } @@ -706,10 +715,13 @@ func TestGenerateAudioBgBgUsesGeminiModelDefaultWhenVoiceNotSet(t *testing.T) { t.Fatalf("expected attribution file %q: %v", attrPath, err) } attribution := string(attributionData) - wantText := []string{"ябълка", "круша"}[i] + wantText := []string{"ябълка!?", "круша."}[i] if !strings.Contains(attribution, "Processed text sent to TTS: "+wantText) { t.Fatalf("bg-bg attribution missing exact processed text %q: %q", wantText, attribution) } + if !strings.Contains(attribution, "Speak at a natural pace.") { + t.Fatalf("bg-bg attribution missing speed hint semantics: %q", attribution) + } } } @@ -742,8 +754,8 @@ func TestGenerateAudioUsesConfiguredAudioFormatWhenOpenAIConfigIsSetOnly(t *test flags.AudioFormat = "wav" p := NewProcessor(flags) - wordDir := p.findOrCreateWordDirectory("ябълка") - if err := p.generateAudioWithVoiceAndFilenameInDir("ябълка", "alloy", "audio", wordDir); err != nil { + wordDir := p.findOrCreateWordDirectory("ябълка!?") + if err := p.generateAudioWithVoiceAndFilenameInDir("ябълка!?", "alloy", "audio", wordDir); err != nil { t.Fatalf("generateAudioWithVoiceAndFilenameInDir() unexpected error: %v", err) } @@ -786,6 +798,9 @@ func TestGenerateAudioUsesConfiguredAudioFormatWhenOpenAIConfigIsSetOnly(t *test if !strings.Contains(attribution, "Processed text sent to TTS: ябълка") { t.Fatalf("openai attribution missing exact processed text: %q", attribution) } + if strings.Contains(attribution, "Voice instructions:") { + t.Fatalf("openai attribution unexpectedly recorded instructions: %q", attribution) + } } func TestGenerateAudioUsesConfiguredOpenAIVoiceFromConfig(t *testing.T) { @@ -863,6 +878,64 @@ func TestGenerateAudioUsesConfiguredOpenAIVoiceFromConfig(t *testing.T) { } } +func TestGenerateAudioOmitsOpenAIInstructionsForUnsupportedModel(t *testing.T) { + originalFactory := newAudioProvider + t.Cleanup(func() { + newAudioProvider = originalFactory + }) + + fakeProvider := &fakeAudioProvider{} + var capturedConfig *audio.Config + newAudioProvider = func(config *audio.Config) (audio.Provider, error) { + copyConfig := *config + capturedConfig = ©Config + return fakeProvider, nil + } + + originalConfig := viper.New() + *originalConfig = *viper.GetViper() + defer func() { + *viper.GetViper() = *originalConfig + }() + viper.Reset() + viper.Set("audio.provider", "openai") + viper.Set("audio.openai_instruction", "Speak clearly.") + + flags := cli.NewFlags() + flags.OutputDir = t.TempDir() + flags.AudioProvider = "openai" + flags.AudioFormat = "mp3" + flags.OpenAIModel = "tts-1" + + p := NewProcessor(flags) + if err := p.generateAudio("ябълка!?"); err != nil { + t.Fatalf("generateAudio() unexpected error: %v", err) + } + + if capturedConfig == nil { + t.Fatal("expected audio provider config to be captured") + } + if capturedConfig.OpenAIModel != "tts-1" { + t.Fatalf("captured OpenAIModel = %q, want %q", capturedConfig.OpenAIModel, "tts-1") + } + if capturedConfig.OpenAIInstruction != "Speak clearly." { + t.Fatalf("captured OpenAIInstruction = %q, want %q", capturedConfig.OpenAIInstruction, "Speak clearly.") + } + + attrPath := audio.AttributionPath(fakeProvider.lastOutputFile) + attributionData, err := os.ReadFile(attrPath) + if err != nil { + t.Fatalf("expected attribution file %q: %v", attrPath, err) + } + attribution := string(attributionData) + if strings.Contains(attribution, "Voice instructions:") { + t.Fatalf("openai attribution unexpectedly recorded unsupported instructions: %q", attribution) + } + if !strings.Contains(attribution, "Processed text sent to TTS: ябълка") { + t.Fatalf("openai attribution missing cleaned processed text: %q", attribution) + } +} + func TestGenerateAnkiFileUsesEffectiveAudioFormatForGemini(t *testing.T) { originalConfig := viper.New() *originalConfig = *viper.GetViper() -- cgit v1.2.3