Form field (Swedish preselected, Other = auto) read before the file part and refused before anything is spooled if outside the closed set; stored on the row (migration 021, CHECK constraint, default sv for history); carried on the Job and the worker's context; shown on the status page. Feature scenarios and the coverage map are updated, and ADR-033/034 amended. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
150 lines
5.0 KiB
Go
150 lines
5.0 KiB
Go
package web_test
|
|
|
|
// The language of a recording is chosen on the upload form (tapir#61). These drive the real
|
|
// Router, Intake and Postgres store; the submitter is the process boundary and records the
|
|
// job the worker would receive.
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"git.d-ma.be/mathias/tapir/internal/domain"
|
|
)
|
|
|
|
// langReq builds an upload whose parts come in the order given: "title", "language" and
|
|
// "file" (the file part always carries the same synthetic bytes).
|
|
func langReq(t *testing.T, order []string, language string) *http.Request {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
mw := multipart.NewWriter(&buf)
|
|
for _, part := range order {
|
|
switch part {
|
|
case "title":
|
|
require.NoError(t, mw.WriteField("title", "m"))
|
|
case "language":
|
|
require.NoError(t, mw.WriteField("language", language))
|
|
case "file":
|
|
fw, err := mw.CreateFormFile("file", "m.wav")
|
|
require.NoError(t, err)
|
|
_, err = fw.Write([]byte("RIFF....WAVE"))
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
require.NoError(t, mw.Close())
|
|
req := httptest.NewRequest(http.MethodPost, "/uploads", &buf)
|
|
req.Header.Set("Content-Type", mw.FormDataContentType())
|
|
return req
|
|
}
|
|
|
|
func rowLanguage(t *testing.T, location string) domain.Language {
|
|
t.Helper()
|
|
id := strings.TrimPrefix(location, "/uploads/")
|
|
got, err := newStore(t).GetUpload(context.Background(), userID, id)
|
|
require.NoError(t, err)
|
|
return got.Language
|
|
}
|
|
|
|
func TestUploadPost_TheLanguageFieldReachesTheJobAndTheRow(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
name string
|
|
language string
|
|
want domain.Language
|
|
}{
|
|
{"other", "auto", domain.LangAuto},
|
|
{"swedish", "sv", domain.LangSwedish},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
app, sub := newUploadApp(t, audioOK, 200<<20)
|
|
|
|
rec := do(t, app, langReq(t, []string{"title", "language", "file"}, tt.language))
|
|
|
|
require.Equal(t, http.StatusSeeOther, rec.Code)
|
|
require.Len(t, sub.jobs, 1)
|
|
assert.Equal(t, tt.want, sub.jobs[0].Language)
|
|
assert.Equal(t, tt.want, rowLanguage(t, rec.Header().Get("Location")))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUploadPost_NoLanguageFieldIsSwedishAsBefore(t *testing.T) {
|
|
app, sub := newUploadApp(t, audioOK, 200<<20)
|
|
|
|
rec := do(t, app, langReq(t, []string{"title", "file"}, ""))
|
|
|
|
require.Equal(t, http.StatusSeeOther, rec.Code)
|
|
require.Len(t, sub.jobs, 1)
|
|
assert.Equal(t, domain.LangSwedish, sub.jobs[0].Language)
|
|
assert.Equal(t, domain.LangSwedish, rowLanguage(t, rec.Header().Get("Location")))
|
|
}
|
|
|
|
// A part after the file is never read (the file part ends the stream), so a client that
|
|
// orders its fields wrongly gets Swedish, the old behaviour, not a guess.
|
|
func TestUploadPost_ALanguageFieldAfterTheFileIsNotReadAndTheUploadIsSwedish(t *testing.T) {
|
|
app, sub := newUploadApp(t, audioOK, 200<<20)
|
|
|
|
rec := do(t, app, langReq(t, []string{"title", "file", "language"}, "auto"))
|
|
|
|
require.Equal(t, http.StatusSeeOther, rec.Code)
|
|
require.Len(t, sub.jobs, 1)
|
|
assert.Equal(t, domain.LangSwedish, sub.jobs[0].Language)
|
|
}
|
|
|
|
func TestUploadPost_ALanguageOutsideTheClosedSetIsRefusedBeforeAnythingIsSpooledOrStored(t *testing.T) {
|
|
for _, bad := range []string{"en", "SV", " sv", "sv,auto", "../auto", strings.Repeat("a", 5000)} {
|
|
t.Run(bad[:min(len(bad), 12)], func(t *testing.T) {
|
|
app, sub := newUploadApp(t, audioOK, 200<<20)
|
|
|
|
rec := do(t, app, langReq(t, []string{"title", "language", "file"}, bad))
|
|
|
|
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
|
assert.Empty(t, sub.jobs, "a refused post must not enqueue transcription")
|
|
rows, err := newStore(t).ListUploads(context.Background(), userID)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, rows, "and must leave no row")
|
|
assert.Contains(t, body(t, rec), "language")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUploadPage_TheFormOffersSwedishPreselectedAndOtherBeforeTheFilePart(t *testing.T) {
|
|
app, _ := newUploadApp(t, audioOK, 200<<20)
|
|
|
|
b := body(t, do(t, app, get("/uploads")))
|
|
|
|
sel := strings.Index(b, `name="language"`)
|
|
file := strings.Index(b, `name="file"`)
|
|
require.True(t, sel >= 0, "the form has a language field")
|
|
assert.True(t, sel < file, "the field must come before the file part: the stream ends at the file")
|
|
assert.Regexp(t, `<option value="sv" selected`, b, "Swedish is preselected: today's behaviour")
|
|
assert.Contains(t, b, `<option value="auto"`)
|
|
}
|
|
|
|
func TestUploadStatus_NamesTheLanguageTheRecordingWasTranscribedAs(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
language domain.Language
|
|
want string
|
|
}{
|
|
{domain.LangSwedish, "Swedish"},
|
|
{domain.LangAuto, "Other (detected by the engine)"},
|
|
} {
|
|
t.Run(string(tt.language), func(t *testing.T) {
|
|
app, _ := newUploadApp(t, audioOK, 200<<20)
|
|
id := seedUpload(t, userID, "m", "done", "Synthetic transcript text.")
|
|
_, err := rawPool(t).Exec(context.Background(), `UPDATE uploaded_transcripts SET language = $2 WHERE id = $1`, id, string(tt.language))
|
|
require.NoError(t, err)
|
|
|
|
b := body(t, do(t, app, get("/uploads/"+id+"/status")))
|
|
|
|
assert.Contains(t, b, "Language: "+tt.want)
|
|
})
|
|
}
|
|
}
|