package transcribe_test import ( "context" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "git.d-ma.be/mathias/tapir/internal/adapters/transcribe" "git.d-ma.be/mathias/tapir/internal/domain" ) type fakeBackend struct { name string text string err error calls int } func (f *fakeBackend) Name() string { return f.name } func (f *fakeBackend) Transcribe(_ context.Context, _ string) (domain.Transcription, error) { f.calls++ if f.err != nil { return domain.Transcription{}, f.err } return domain.Transcription{Text: f.text, Endpoint: f.name}, nil } func TestChain_PrimarySuccessNeverTouchesTheFallback(t *testing.T) { primary := &fakeBackend{name: "iguana/kb-whisper", text: "primary text"} fallback := &fakeBackend{name: "berget/kb-whisper", text: "fallback text"} got, err := transcribe.NewChain(primary, fallback).Transcribe(context.Background(), "/a.wav") require.NoError(t, err) assert.Equal(t, "primary text", got.Text) assert.Equal(t, "iguana/kb-whisper", got.Endpoint) assert.Zero(t, fallback.calls, "audio must not leave the homelab when the local endpoint worked") } func TestChain_AnyPrimaryFailureFallsBackAndRecordsTheFallbackEndpoint(t *testing.T) { tests := []struct { name string err error }{ {"server error", &transcribe.StatusError{Code: 500}}, {"gateway timeout", &transcribe.StatusError{Code: 504}}, {"client error", &transcribe.StatusError{Code: 400}}, {"transport failure", errors.New("dial tcp: connection refused")}, {"empty transcript", transcribe.ErrEmptyTranscript}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { primary := &fakeBackend{name: "iguana/kb-whisper", err: tt.err} fallback := &fakeBackend{name: "berget/kb-whisper", text: "fallback text"} got, err := transcribe.NewChain(primary, fallback).Transcribe(context.Background(), "/a.wav") require.NoError(t, err) assert.Equal(t, "fallback text", got.Text) assert.Equal(t, "berget/kb-whisper", got.Endpoint, "the row must say berget served it: that means the audio left the homelab") }) } } func TestChain_BothFailingReportsBothErrors(t *testing.T) { primary := &fakeBackend{name: "iguana/kb-whisper", err: errors.New("iguana down")} fallback := &fakeBackend{name: "berget/kb-whisper", err: errors.New("berget down")} _, err := transcribe.NewChain(primary, fallback).Transcribe(context.Background(), "/a.wav") require.Error(t, err) assert.Contains(t, err.Error(), "iguana down") assert.Contains(t, err.Error(), "berget down") } func TestChain_WithoutAFallbackAPrimaryFailureIsTheResult(t *testing.T) { primary := &fakeBackend{name: "iguana/kb-whisper", err: errors.New("iguana down")} _, err := transcribe.NewChain(primary, nil).Transcribe(context.Background(), "/a.wav") require.Error(t, err) assert.Contains(t, err.Error(), "iguana down") } func TestChain_ACancelledJobDoesNotFallBack(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() primary := &fakeBackend{name: "iguana/kb-whisper", err: context.Canceled} fallback := &fakeBackend{name: "berget/kb-whisper", text: "x"} _, err := transcribe.NewChain(primary, fallback).Transcribe(ctx, "/a.wav") assert.ErrorIs(t, err, context.Canceled) assert.Zero(t, fallback.calls, "a cancelled job must not ship the audio to a cloud endpoint") }