summaryrefslogtreecommitdiff
path: root/internal/processor/processor_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-10 23:11:30 +0300
committerPaul Buetow <paul@buetow.org>2026-04-10 23:11:30 +0300
commit65430e9876a32988839deb7a70dc1e42da21cf64 (patch)
treee0de040cb53d5b90587908f2d55e86e49664fc35 /internal/processor/processor_test.go
parent4b4b3665230faafbd994f09894cb07c8f9d4b359 (diff)
Release 0.28.2v0.28.2
Diffstat (limited to 'internal/processor/processor_test.go')
-rw-r--r--internal/processor/processor_test.go148
1 files changed, 148 insertions, 0 deletions
diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go
index 649c092..64d6938 100644
--- a/internal/processor/processor_test.go
+++ b/internal/processor/processor_test.go
@@ -11,6 +11,7 @@ import (
"strings"
"testing"
+ "codeberg.org/snonux/totalrecall/internal"
"codeberg.org/snonux/totalrecall/internal/audio"
"codeberg.org/snonux/totalrecall/internal/cli"
"codeberg.org/snonux/totalrecall/internal/gui"
@@ -1333,6 +1334,153 @@ func TestProcessBatch_ValidFile(t *testing.T) {
}
}
+func TestRetryFailedAssets_RegeneratesMissingEnBgAssetsInOrder(t *testing.T) {
+ t.Setenv("GOOGLE_API_KEY", "test-google-key")
+
+ flags := cli.NewFlags()
+ flags.OutputDir = t.TempDir()
+ flags.ImageAPI = image.ImageProviderNanoBanana
+ flags.ImageAPISpecified = true
+ p := NewProcessor(flags, &Config{})
+
+ cardDir := p.findOrCreateWordDirectory("ябълка")
+ if err := os.WriteFile(filepath.Join(cardDir, "translation.txt"), []byte("ябълка = apple\n"), 0644); err != nil {
+ t.Fatalf("setup translation.txt: %v", err)
+ }
+
+ fakeProvider := &fakeAudioProvider{
+ generateFunc: func(_ string, outputFile string) error {
+ return os.WriteFile(outputFile, []byte("audio data"), 0644)
+ },
+ }
+ p.newAudioProvider = func(*audio.Config) (audio.Provider, error) {
+ return fakeProvider, nil
+ }
+ p.imageFactories.NewNanoBananaClient = func(*image.NanoBananaConfig) image.PromptAwareClient {
+ return &stubImageSearcher{}
+ }
+
+ output := captureStdout(t, func() {
+ if err := p.RetryFailedAssets(); err != nil {
+ t.Fatalf("RetryFailedAssets() unexpected error: %v", err)
+ }
+ })
+
+ if fakeProvider.generateCalls != 1 {
+ t.Fatalf("audio generate calls = %d, want 1", fakeProvider.generateCalls)
+ }
+ if !strings.Contains(output, "Regenerating audio") || !strings.Contains(output, "Regenerating image") {
+ t.Fatalf("stdout missing retry steps: %q", output)
+ }
+ if strings.Index(output, "Regenerating audio") > strings.Index(output, "Regenerating image") {
+ t.Fatalf("retry order is wrong, output = %q", output)
+ }
+ if _, err := os.Stat(filepath.Join(cardDir, "audio.mp3")); err != nil {
+ t.Fatalf("expected audio.mp3 to be created: %v", err)
+ }
+ if _, err := os.Stat(filepath.Join(cardDir, "image.png")); err != nil {
+ t.Fatalf("expected image.png to be created: %v", err)
+ }
+ promptData, err := os.ReadFile(filepath.Join(cardDir, "image_prompt.txt"))
+ if err != nil {
+ t.Fatalf("expected image_prompt.txt to be created: %v", err)
+ }
+ if strings.TrimSpace(string(promptData)) != "stub nanobanana prompt" {
+ t.Fatalf("image prompt = %q, want %q", strings.TrimSpace(string(promptData)), "stub nanobanana prompt")
+ }
+}
+
+func TestRetryFailedAssets_StopsOnFirstError(t *testing.T) {
+ flags := cli.NewFlags()
+ flags.OutputDir = t.TempDir()
+ flags.SkipImages = true
+ p := NewProcessor(flags, &Config{})
+
+ firstDir := p.findOrCreateWordDirectory("ябълка")
+ secondDir := p.findOrCreateWordDirectory("круша")
+ if err := os.WriteFile(filepath.Join(firstDir, "translation.txt"), []byte("ябълка = apple\n"), 0644); err != nil {
+ t.Fatalf("setup first translation.txt: %v", err)
+ }
+ if err := os.WriteFile(filepath.Join(secondDir, "translation.txt"), []byte("круша = pear\n"), 0644); err != nil {
+ t.Fatalf("setup second translation.txt: %v", err)
+ }
+
+ fakeProvider := &fakeAudioProvider{
+ generateFunc: func(_ string, _ string) error {
+ return errors.New("rate limit exceeded")
+ },
+ }
+ p.newAudioProvider = func(*audio.Config) (audio.Provider, error) {
+ return fakeProvider, nil
+ }
+
+ err := p.RetryFailedAssets()
+ if err == nil {
+ t.Fatal("expected RetryFailedAssets() to stop on first error")
+ }
+ if !strings.Contains(err.Error(), "rate limit exceeded") {
+ t.Fatalf("RetryFailedAssets() error = %v, want rate limit message", err)
+ }
+ if fakeProvider.generateCalls != 1 {
+ t.Fatalf("audio generate calls = %d, want 1", fakeProvider.generateCalls)
+ }
+ if _, statErr := os.Stat(filepath.Join(secondDir, "audio.mp3")); !os.IsNotExist(statErr) {
+ t.Fatalf("second card audio should not have been generated, stat err = %v", statErr)
+ }
+}
+
+func TestRetryFailedAssets_RegeneratesOnlyMissingBgBgBackAudio(t *testing.T) {
+ flags := cli.NewFlags()
+ flags.OutputDir = t.TempDir()
+ flags.SkipImages = true
+ p := NewProcessor(flags, &Config{})
+
+ cardDir := p.findOrCreateWordDirectory("ябълка")
+ if err := internal.SaveCardType(cardDir, internal.CardTypeBgBg); err != nil {
+ t.Fatalf("setup cardtype.txt: %v", err)
+ }
+ if err := os.WriteFile(filepath.Join(cardDir, "translation.txt"), []byte("ябълка = плод\n"), 0644); err != nil {
+ t.Fatalf("setup translation.txt: %v", err)
+ }
+
+ frontAudio := filepath.Join(cardDir, "audio_front.mp3")
+ if err := os.WriteFile(frontAudio, []byte("front audio"), 0644); err != nil {
+ t.Fatalf("setup audio_front.mp3: %v", err)
+ }
+ if err := os.WriteFile(audio.AttributionPath(frontAudio), []byte("front attribution"), 0644); err != nil {
+ t.Fatalf("setup front attribution: %v", err)
+ }
+ if err := os.WriteFile(filepath.Join(cardDir, "audio_metadata.txt"), []byte("format=mp3\naudio_file=audio_front.mp3\n"), 0644); err != nil {
+ t.Fatalf("setup audio_metadata.txt: %v", err)
+ }
+
+ fakeProvider := &fakeAudioProvider{
+ generateFunc: func(_ string, outputFile string) error {
+ return os.WriteFile(outputFile, []byte("back audio"), 0644)
+ },
+ }
+ p.newAudioProvider = func(*audio.Config) (audio.Provider, error) {
+ return fakeProvider, nil
+ }
+
+ if err := p.RetryFailedAssets(); err != nil {
+ t.Fatalf("RetryFailedAssets() unexpected error: %v", err)
+ }
+
+ if fakeProvider.generateCalls != 1 {
+ t.Fatalf("audio generate calls = %d, want 1", fakeProvider.generateCalls)
+ }
+ if !strings.HasSuffix(fakeProvider.lastOutputFile, "audio_back.mp3") {
+ t.Fatalf("last output file = %q, want audio_back.mp3", fakeProvider.lastOutputFile)
+ }
+ if _, err := os.Stat(filepath.Join(cardDir, "audio_back.mp3")); err != nil {
+ t.Fatalf("expected audio_back.mp3 to be created: %v", err)
+ }
+ if _, err := os.Stat(frontAudio); err != nil {
+ t.Fatalf("front audio should remain present: %v", err)
+ }
+}
+
func TestProcessWordWithTranslation_ProvidedTranslation(t *testing.T) {
flags := cli.NewFlags()
flags.OutputDir = t.TempDir()