diff --git a/internal/adapters/transcribe/gateway_test.go b/internal/adapters/transcribe/gateway_test.go index eb3b677..6a8c221 100644 --- a/internal/adapters/transcribe/gateway_test.go +++ b/internal/adapters/transcribe/gateway_test.go @@ -121,7 +121,7 @@ func TestGateway_NormalisesTheVerboseJSONResponse(t *testing.T) { got, err := g.Transcribe(context.Background(), wavFile(t, "x")) require.NoError(t, err) - assert.Equal(t, "God morgon allihopa.\n Vi går igenom läget.", got.Text) + assert.Equal(t, "God morgon allihopa. Vi går igenom läget.", got.Text) assert.Equal(t, "iguana/kb-whisper", got.Endpoint, "the serving endpoint is part of the result") assert.InDelta(t, 4.25, got.AudioSeconds, 1e-9) assert.Equal(t, []domain.Segment{ @@ -172,3 +172,61 @@ func TestGateway_MissingAudioFileIsAnError(t *testing.T) { assert.Error(t, err) } + +// whisper.cpp wraps its output at ~60 characters, and a wrap can land INSIDE a word: +// the continuation segment has no leading space, the `text` field joins segments +// with newlines, and a segment can be a lone ".". Measured on real gateway output +// 2026-10-01: 7% of a one-hour recording's segments were such fragments. Joining by +// newline hands the chat model broken words ("nyt\nt"), so Text is rebuilt from the +// segments: a segment that begins with a space starts a new word, one that does not +// continues the previous one. +func TestGateway_RejoinsWordsWhisperCppSplitAcrossSegments(t *testing.T) { + reply := `{ + "text": " Vi ligger just nu 12 procent över den ursprungliga kalkylen\n.\n Det kräver ett nyt\nt godkännande från beställaren.\n Framförallt på arbetspl\natsen.\n", + "language": "swedish", "duration": 20, + "segments": [ + {"start": 0, "end": 4, "text": " Vi ligger just nu 12 procent över den ursprungliga kalkylen"}, + {"start": 4, "end": 5, "text": "."}, + {"start": 5, "end": 8, "text": " Det kräver ett nyt"}, + {"start": 8, "end": 11, "text": "t godkännande från beställaren."}, + {"start": 11, "end": 14, "text": " Framförallt på arbetspl"}, + {"start": 14, "end": 16, "text": "atsen."} + ]}` + srv := serve(t, 200, reply, nil) + g := transcribe.NewGateway(srv.URL+"/v1", "k", "iguana/kb-whisper", srv.Client()) + + got, err := g.Transcribe(context.Background(), wavFile(t, "x")) + + require.NoError(t, err) + assert.Equal(t, "Vi ligger just nu 12 procent över den ursprungliga kalkylen. "+ + "Det kräver ett nytt godkännande från beställaren. Framförallt på arbetsplatsen.", got.Text) + assert.Len(t, got.Segments, 6, "the timed segments are kept as the backend gave them (trimmed)") + assert.Equal(t, ".", got.Segments[1].Text) +} + +func TestGateway_SentenceLevelSegmentsWithNoLeadingSpaceAreJoinedWithASpace(t *testing.T) { + // berget/kb-whisper returns whole sentences with no leading space; rebuilding by + // concatenation would glue them ("allihopa!Vi"). + reply := `{"text": "God morgon allihopa!\nVi går igenom läget.", "language": "sv", "duration": 5, + "segments": [ + {"start": 0.05, "end": 1.2, "text": "God morgon allihopa!"}, + {"start": 1.6, "end": 4.2, "text": "Vi går igenom läget."} + ]}` + srv := serve(t, 200, reply, nil) + g := transcribe.NewGateway(srv.URL+"/v1", "k", "berget/kb-whisper", srv.Client()) + + got, err := g.Transcribe(context.Background(), wavFile(t, "x")) + + require.NoError(t, err) + assert.Equal(t, "God morgon allihopa! Vi går igenom läget.", got.Text) +} + +func TestGateway_WithNoSegmentsTheTextFieldIsUsedWithWhitespaceCollapsed(t *testing.T) { + srv := serve(t, 200, `{"text": " Hej allihopa.\n Vi börjar. ", "duration": 3, "segments": []}`, nil) + g := transcribe.NewGateway(srv.URL+"/v1", "k", "m", srv.Client()) + + got, err := g.Transcribe(context.Background(), wavFile(t, "x")) + + require.NoError(t, err) + assert.Equal(t, "Hej allihopa. Vi börjar.", got.Text) +}