From d6ba11d288ad2d38f6545d1fe65dfbda03904e34 Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 1 Oct 2026 23:23:00 +0200 Subject: [PATCH] test(web): the page says what reconciliation did, plainly, and never claims more (red) Reconciled: who the two engines and the deciding model were, and the counts (differences, settled automatically, decided by the model, uncertain). The contested spots are listed with a minute mark, both readings and which was used, uncertain ones marked, readings escaped, and a very long list shortened with its true count. A single-engine transcript makes no claim of a second opinion; one-engine and model-unavailable cases say why. Red: the page renders none of this yet. Co-Authored-By: Claude Sonnet 5.5 --- internal/web/uploads_handler_test.go | 105 +++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/internal/web/uploads_handler_test.go b/internal/web/uploads_handler_test.go index a94400a..158eafd 100644 --- a/internal/web/uploads_handler_test.go +++ b/internal/web/uploads_handler_test.go @@ -3,6 +3,7 @@ package web_test import ( "bytes" "context" + "fmt" "log/slog" "mime/multipart" "net/http" @@ -355,3 +356,107 @@ func TestUploadPost_ARejectionIsLoggedWithItsReasonAndNeverTheFilenameOrTitle(t assert.NotContains(t, logged, "Secret project notes", "the title is private content") assert.NotContains(t, logged, "client-acme-roadmap", "the filename is private content") } + +func setReconciliation(t *testing.T, id, json string) { + t.Helper() + _, err := rawPool(t).Exec(context.Background(), `UPDATE uploaded_transcripts SET reconciliation = $2::jsonb WHERE id = $1`, id, json) + require.NoError(t, err) +} + +const reconciledJSON = `{"status":"reconciled","base":"iguana/pianissimo","alt":"iguana/kb-whisper","reconciler":"iguana/gemma4-26b", + "disputes":380,"auto_resolved":90,"adjudicated":285,"changed":120,"unsure":5,"errors":0, + "contested":[ + {"seconds":2210.5,"base":"SEB","alt":"SCB","used":"alt","unsure":false}, + {"seconds":3400,"base":"T+1","alt":"en T","used":"base","unsure":true}]}` + +func TestUploadStatus_ReconciledSaysWhatWasDoneInPlainWords(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript.") + setReconciliation(t, id, reconciledJSON) + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.Contains(t, b, "Reconciled from two transcripts") + assert.Contains(t, b, "iguana/pianissimo") + assert.Contains(t, b, "iguana/kb-whisper") + assert.Contains(t, b, "iguana/gemma4-26b", "who decided is part of the account") + assert.Contains(t, b, "380 differences") + assert.Contains(t, b, "90 settled automatically") + assert.Contains(t, b, "285 decided by the model") + assert.Contains(t, b, "5 uncertain") +} + +func TestUploadStatus_ListsTheContestedSpotsWithAMinuteMarkBothReadingsAndWhichWasUsed(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript.") + setReconciliation(t, id, reconciledJSON) + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.Contains(t, b, "~37 min", "2210.5 s") + assert.Contains(t, b, "SEB") + assert.Contains(t, b, "SCB") + assert.Contains(t, b, "~57 min", "3400 s") + assert.Contains(t, b, "T+1") + assert.Contains(t, b, "en T") + assert.Contains(t, b, "uncertain", "an unsure spot is marked so the reader knows to check it") +} + +func TestUploadStatus_ASingleEngineTranscriptDoesNotClaimASecondOpinion(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript.") + + assert.NotContains(t, body(t, do(t, app, get("/uploads/"+id+"/status"))), "Reconciled", "no reconciliation, no claim") +} + +func TestUploadStatus_WhenOnlyOneEngineWorkedItSaysWhyTheSecondOpinionIsMissing(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript.") + setReconciliation(t, id, `{"status":"single_engine","reason":"iguana/pianissimo failed: wedged","base":"iguana/pianissimo","alt":"iguana/kb-whisper","reconciler":"iguana/gemma4-26b"}`) + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.Contains(t, b, "Only one engine") + assert.Contains(t, b, "iguana/pianissimo failed") + assert.NotContains(t, b, "Reconciled from two") +} + +func TestUploadStatus_WhenTheModelWasUnavailableItSaysTheTextIsUnreconciled(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript.") + setReconciliation(t, id, `{"status":"skipped","reason":"the model failed 3 times in a row: gateway down","base":"iguana/pianissimo","alt":"iguana/kb-whisper","reconciler":"iguana/gemma4-26b","disputes":380,"errors":3}`) + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.Contains(t, b, "could not be reconciled") + assert.Contains(t, b, "iguana/pianissimo", "which engine's text is being shown") + assert.Contains(t, b, "gateway down") +} + +func TestUploadStatus_ContestedReadingsAreEscaped(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript.") + setReconciliation(t, id, `{"status":"reconciled","base":"a","alt":"b","reconciler":"c","disputes":1,"unsure":1, + "contested":[{"seconds":60,"base":"","alt":"ok","used":"base","unsure":true}]}`) + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.NotContains(t, b, "", "a reading is words from a recording, never markup") + assert.Contains(t, b, "<script>") +} + +func TestUploadStatus_AVeryLongListOfContestedSpotsIsShortenedWithItsTrueCount(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript.") + var spots []string + for i := 0; i < 120; i++ { + spots = append(spots, fmt.Sprintf(`{"seconds":%d,"base":"b%d","alt":"a%d","used":"base","unsure":true}`, i*10, i, i)) + } + setReconciliation(t, id, `{"status":"reconciled","base":"a","alt":"b","reconciler":"c","disputes":400,"unsure":120,"contested":[`+strings.Join(spots, ",")+`]}`) + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.Contains(t, b, "b0", "the first spots are shown") + assert.NotContains(t, b, ">b119<", "the page does not render every one") + assert.Contains(t, b, "70 more", "120 stored, 50 shown") +}