package gateway import ( "strings" "testing" ) // TestUI_ImageRequestSendsJSONBody pins the bug that made the image page // unusable in production while every DOM assertion still passed. // // The page called api(path, {method, body: {...}}). api() is // `(p, o) => fetch(p, o)` — o is the NATIVE fetch options object, so an object // in `body` is coerced by fetch into the literal text "[object Object]". The // gateway's json.Unmarshal rejected it, the response was a bare 400 with // lat_ms 0, and the page showed an error toast with no image and no usable // message. // // What made this expensive: the pane rendered, the model list had all six // models, the button enabled itself, the console was clean. Only the WIRE // format was wrong, and nothing in the DOM shows a wire format. func TestUI_ImageRequestSendsJSONBody(t *testing.T) { body := sendImageSource(t) if !strings.Contains(body, "JSON.stringify") { t.Error("sendImage() does not JSON.stringify its body — fetch coerces " + "the object to the text \"[object Object]\" and the gateway answers " + "400 with lat_ms 0") } if !strings.Contains(body, `"Content-Type": "application/json"`) { t.Error("sendImage() does not set Content-Type: application/json; the " + "server sees text/plain and may refuse to parse the body") } // The literal defect, asserted so a later refactor reintroducing it fails // here rather than in a browser. if strings.Contains(body, "body: size ? {") || strings.Contains(body, "body: { model") { t.Error("sendImage() still passes an object literal as the fetch body") } } // TestUI_PostCallsSendJSONString guards every POST in the file, not just the // image one. The image page happened to be the only place that used api() with // an object body, so a check scoped to it would pass while another page had the // same defect. func TestUI_PostCallsSendJSONString(t *testing.T) { ui := uiSource(t) lines := strings.Split(ui, "\n") var bad []string for i, l := range lines { if !strings.Contains(l, `method: "POST"`) { continue } end := i + 8 if end > len(lines) { end = len(lines) } window := strings.Join(lines[i:end], "\n") if !strings.Contains(window, "body:") { continue // no body at all: legitimate (a bare trigger) } if strings.Contains(window, "JSON.stringify") || strings.Contains(window, "FormData") { continue } bad = append(bad, strings.TrimSpace(l)) } if len(bad) > 0 { t.Errorf("%d POST call(s) send a non-string body:", len(bad)) for _, b := range bad { if len(b) > 100 { b = b[:100] } t.Errorf(" %s", b) } } } // TestUI_ImagePageReportsServerErrors pins that a failed generation surfaces // something actionable. The production bug produced a bare "400 Bad Request" // toast; the requirement is that the gateway's own message reaches the page. func TestUI_ImagePageReportsServerErrors(t *testing.T) { body := sendImageSource(t) // api() throws Error(j.error.message) on !r.ok, so appending e.message is // what puts the explanation on screen. if !strings.Contains(body, "e && e.message") { t.Error("sendImage() does not surface the server's error message; a " + "failure would show a bare status with no cause") } // And a success carrying no image must not look like a success. if !strings.Contains(body, "imgNoData") { t.Error("sendImage() does not warn when the upstream returns no image") } } // sendImageSource returns the text of sendImage() only, so an assertion cannot // be satisfied by a string that happens to appear elsewhere in the file. func sendImageSource(t *testing.T) string { t.Helper() ui := uiSource(t) i := strings.Index(ui, "async function sendImage(") if i < 0 { t.Fatal("sendImage() not found") } body := ui[i:] if end := strings.Index(body[1:], "\n function "); end > 0 { body = body[:end+1] } return body }