fix: address review — fail loud on non-video bodies, dedupe form plumbing, doc parity
- 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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user