fix: address review — fail loud on non-video bodies, dedupe form plumbing, doc parity
CI / Tidy (pull_request) Successful in 9m28s
CI / Build & Test (pull_request) Successful in 10m0s

- videoMIME no longer hard-falls-back to video/mp4: a 2xx body that is
  neither declared nor sniffable as video (JSON job envelope, HTML error
  page) is now an APIError instead of a 'successful' corrupt clip.
- Resolution rides the wire as width/height AND the OpenAI-style size
  string, so either upstream convention honors an explicit request.
- writeFormFields + mimeFromContentType shared helpers replace the
  copied multipart loop (audio.go/video.go) and Content-Type branch.
- ADR-0019 indexed in docs/adr/README.md; README gains the videogen
  section + support-matrix mention (docs-parity rule).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
This commit is contained in:
2026-07-12 09:53:34 -04:00
co-authored by Claude Fable 5
parent 4629c6af0f
commit 89210346b8
6 changed files with 114 additions and 38 deletions
+30 -3
View File
@@ -179,6 +179,9 @@ func TestVideoModelRequiresBaseURL(t *testing.T) {
}
func TestVideoMIME(t *testing.T) {
// A minimal ISO-BMFF prefix http.DetectContentType sniffs as video/mp4
// (the "mp4" brand prefix must appear inside the declared ftyp box).
mp4Magic := append([]byte{0, 0, 0, 20}, []byte("ftypmp42\x00\x00\x00\x00mp42")...)
cases := []struct {
contentType string
data []byte
@@ -186,12 +189,36 @@ func TestVideoMIME(t *testing.T) {
}{
{"video/webm", []byte("x"), "video/webm"},
{"video/mp4; charset=binary", []byte("x"), "video/mp4"},
{"application/octet-stream", []byte("x"), "video/mp4"},
{"", []byte("x"), "video/mp4"},
{"application/octet-stream", mp4Magic, "video/mp4"},
// Neither declared nor sniffable as video → "" (Generate errors).
{"application/octet-stream", []byte(`{"id":"job-1"}`), ""},
{"", []byte("x"), ""},
}
for _, tc := range cases {
if got := videoMIME(tc.contentType, tc.data); got != tc.want {
t.Errorf("videoMIME(%q) = %q, want %q", tc.contentType, got, tc.want)
t.Errorf("videoMIME(%q, %q) = %q, want %q", tc.contentType, tc.data, got, tc.want)
}
}
}
func TestVideoGenerateNonVideoBodyErrors(t *testing.T) {
// A stock async /v1/videos handler mounted at the sync path (or an HTML
// error page behind a proxy) answers 200 with a non-video body — that
// must be an error, never a "successful" garbage clip.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"job-1","status":"queued"}`))
}))
defer srv.Close()
p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client()))
vm, _ := p.VideoModel("videogen-wan")
_, err := vm.Generate(context.Background(), videogen.Request{Prompt: "x"})
var apiErr *llm.APIError
if !errors.As(err, &apiErr) {
t.Fatalf("err = %v, want *llm.APIError for non-video 2xx body", err)
}
if !strings.Contains(apiErr.Message, "not a video") {
t.Errorf("message = %q, want mention of non-video body", apiErr.Message)
}
}