package main import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // The wiring of the per-engine deadline: a gateway whose primary model never answers // must still reach the fallback model inside the job's own deadline (adversarial review // F4). The fake gateway answers per model from the multipart `model` field. func TestBuildTranscriber_AWedgedPrimaryReachesTheFallbackBeforeTheJobDeadline(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _ = r.ParseMultipartForm(1 << 20) if r.FormValue("model") == "iguana/kb-whisper" { <-r.Context().Done() // wedged: never answers return } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"text":"hej","duration":1,"segments":[{"start":0,"end":1,"text":"hej"}]}`)) })) defer srv.Close() cfg := uploadCfg(t, true) cfg.GatewayURL = srv.URL cfg.TranscribeModel = "iguana/kb-whisper" cfg.TranscribeFallbackModel = "berget/kb-whisper" cfg.TranscribeEngineTimeout = 150 * time.Millisecond wav := filepath.Join(t.TempDir(), "a.wav") require.NoError(t, os.WriteFile(wav, []byte("RIFF....WAVEfmt "), 0o600)) job, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() got, err := buildTranscriber(cfg).Transcribe(job, wav) require.NoError(t, err, "the wedged primary used the whole job and the fallback never ran") assert.Equal(t, "berget/kb-whisper", got.Endpoint) } func TestBuildTranscriber_AWedgedBaseEngineIsASoftFailureInTheEnsemble(t *testing.T) { // The same deadline must guard the ensemble's BASE engine, not only the chain's primary // (second adversarial review: the wiring was tested for the chain alone). srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _ = r.ParseMultipartForm(1 << 20) if r.FormValue("model") == "iguana/pianissimo" { <-r.Context().Done() return } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"text":"hej","duration":1,"segments":[{"start":0,"end":1,"text":"hej"}]}`)) })) defer srv.Close() cfg := uploadCfg(t, true) cfg.GatewayURL = srv.URL cfg.TranscribeModel = "iguana/kb-whisper" cfg.ReconcileBaseModel = "iguana/pianissimo" cfg.ReconcileModel = "iguana/gemma4-31b" cfg.TranscribeEngineTimeout = 150 * time.Millisecond wav := filepath.Join(t.TempDir(), "a.wav") require.NoError(t, os.WriteFile(wav, []byte("RIFF....WAVEfmt "), 0o600)) job, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() got, err := buildTranscriber(cfg).Transcribe(job, wav) require.NoError(t, err, "a wedged base engine must not use up the job") assert.Equal(t, "hej", got.Text) require.NotNil(t, got.Reconciliation) assert.Equal(t, "single_engine", string(got.Reconciliation.Status)) assert.Contains(t, got.Reconciliation.Reason, "timeout") }