Files
majordomo/provider/llamaswap/video_test.go
T
steve 776ef6fda9
CI / Tidy (pull_request) Successful in 9m31s
CI / Build & Test (pull_request) Successful in 10m23s
fix: gadfly review — video-sized response cap, stale comment
maxVideoResponseBytes (512MB) replaces the shared 64MB JSON cap on the
/v1/videos/sync read path (doRaw now takes the cap per call) — 3/6
models flagged that a legitimate long/high-bitrate clip would be
discarded after minutes of GPU work. Plus a stale stable-diffusion
comment in initImageFilename and a test-handler early return.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
2026-07-12 09:57:53 -04:00

226 lines
7.1 KiB
Go

package llamaswap
import (
"context"
"encoding/base64"
"errors"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
"gitea.stevedudenhoeffer.com/steve/majordomo/videogen"
)
func TestVideoGenerate(t *testing.T) {
var gotPath, gotContentType string
var gotForm map[string]string
var gotFrame []byte
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
gotContentType = r.Header.Get("Content-Type")
if err := r.ParseMultipartForm(32 << 20); err != nil {
t.Errorf("parse form: %v", err)
return
}
gotForm = map[string]string{}
for k, v := range r.MultipartForm.Value {
gotForm[k] = v[0]
}
if f, _, err := r.FormFile("input_reference"); err == nil {
gotFrame, _ = io.ReadAll(f)
f.Close()
}
w.Header().Set("Content-Type", "video/mp4")
_, _ = w.Write([]byte("fake-mp4-bytes"))
}))
defer srv.Close()
p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client()))
vm, err := p.VideoModel("videogen-wan")
if err != nil {
t.Fatalf("VideoModel: %v", err)
}
frame, _ := base64.StdEncoding.DecodeString(onePixelPNG)
res, err := vm.Generate(context.Background(),
videogen.Request{Prompt: "a cat surfing", InitImage: &videogen.Image{MIME: "image/png", Data: frame}},
videogen.WithSize("1280x704"),
videogen.WithNumFrames(81),
videogen.WithFPS(16),
videogen.WithSteps(4),
videogen.WithGuidanceScale(1.0),
videogen.WithNegativePrompt("blurry"),
videogen.WithSeed(42),
)
if err != nil {
t.Fatalf("Generate: %v", err)
}
if string(res.Video.Data) != "fake-mp4-bytes" || res.Video.MIME != "video/mp4" {
t.Fatalf("video = %d bytes, MIME %q", len(res.Video.Data), res.Video.MIME)
}
if gotPath != "/v1/videos/sync" {
t.Errorf("path = %q", gotPath)
}
if !strings.HasPrefix(gotContentType, "multipart/form-data") {
t.Errorf("content-type = %q", gotContentType)
}
want := map[string]string{
"model": "videogen-wan",
"prompt": "a cat surfing",
"negative_prompt": "blurry",
"width": "1280",
"height": "704",
"num_frames": "81",
"fps": "16",
"num_inference_steps": "4",
"guidance_scale": "1",
"seed": "42",
}
for k, v := range want {
if gotForm[k] != v {
t.Errorf("form[%q] = %q, want %q", k, gotForm[k], v)
}
}
if string(gotFrame) != string(frame) {
t.Errorf("input_reference = %d bytes, want %d", len(gotFrame), len(frame))
}
}
func TestVideoGenerateOmitsUnsetOverrides(t *testing.T) {
var gotForm map[string][]string
var hadFrame bool
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = r.ParseMultipartForm(32 << 20)
gotForm = r.MultipartForm.Value
_, _, err := r.FormFile("input_reference")
hadFrame = err == nil
w.Header().Set("Content-Type", "video/mp4")
_, _ = w.Write([]byte("v"))
}))
defer srv.Close()
p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client()))
vm, _ := p.VideoModel("videogen-wan")
if _, err := vm.Generate(context.Background(), videogen.Request{Prompt: "x"}); err != nil {
t.Fatalf("Generate: %v", err)
}
for _, k := range []string{"negative_prompt", "width", "height", "num_frames", "fps", "num_inference_steps", "guidance_scale", "seed"} {
if _, ok := gotForm[k]; ok {
t.Errorf("form field %q sent, want omitted", k)
}
}
if hadFrame {
t.Error("input_reference sent, want omitted")
}
}
func TestVideoGenerateValidation(t *testing.T) {
p := New(WithBaseURL("http://unused.invalid"))
vm, _ := p.VideoModel("videogen-wan")
cases := []struct {
name string
req videogen.Request
}{
{"empty prompt", videogen.Request{}},
{"negative frames", videogen.Request{Prompt: "x", NumFrames: -1}},
{"negative fps", videogen.Request{Prompt: "x", FPS: -1}},
{"empty init image", videogen.Request{Prompt: "x", InitImage: &videogen.Image{}}},
{"bad size", videogen.Request{Prompt: "x", Size: "banana"}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := vm.Generate(context.Background(), tc.req)
if !errors.Is(err, llm.ErrUnsupported) {
t.Fatalf("err = %v, want ErrUnsupported", err)
}
})
}
}
func TestVideoGenerateUpstreamError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, `{"error":{"message":"boom"}}`, http.StatusInternalServerError)
}))
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", err)
}
}
func TestVideoGenerateEmptyResponse(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "video/mp4")
}))
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 empty body", err)
}
}
func TestVideoModelRequiresBaseURL(t *testing.T) {
p := New()
if _, err := p.VideoModel("videogen-wan"); err == nil {
t.Fatal("VideoModel with no base URL should error")
}
}
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
want string
}{
{"video/webm", []byte("x"), "video/webm"},
{"video/mp4; charset=binary", []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) = %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)
}
}