Two editor features that were in flight alongside the sound work: - FontSize TipTap extension (rides on textStyle) with Small/Normal/Large/ Title presets in the toolbar; StatusBar + CSS support - Image handling: internal/images handler, upload route + config, client API, EditorCore wiring, and md/html/docx export support for images Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package images
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// a 1x1 transparent PNG.
|
|
var pngBytes = []byte{
|
|
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
|
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4,
|
|
0x89, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x00, 0x01, 0x00, 0x00,
|
|
0x05, 0x00, 0x01, 0x0d, 0x0a, 0x2d, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
|
|
0x42, 0x60, 0x82,
|
|
}
|
|
|
|
func uploadReq(t *testing.T, field string, data []byte) *http.Request {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
mw := multipart.NewWriter(&buf)
|
|
fw, err := mw.CreateFormFile(field, "x.png")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fw.Write(data)
|
|
mw.Close()
|
|
req := httptest.NewRequest(http.MethodPost, "/", &buf)
|
|
req.Header.Set("Content-Type", mw.FormDataContentType())
|
|
return req
|
|
}
|
|
|
|
func TestUploadAndServe(t *testing.T) {
|
|
h, err := New(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := h.Routes()
|
|
|
|
// Upload a PNG → expect a JSON url under /api/images/.
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, uploadReq(t, "image", pngBytes))
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("upload code=%d body=%s", rec.Code, rec.Body)
|
|
}
|
|
var resp struct{ URL string }
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(resp.URL, "/api/images/") || !strings.HasSuffix(resp.URL, ".png") {
|
|
t.Fatalf("unexpected url %q", resp.URL)
|
|
}
|
|
|
|
// The same content uploaded again dedupes to the same URL.
|
|
rec2 := httptest.NewRecorder()
|
|
r.ServeHTTP(rec2, uploadReq(t, "image", pngBytes))
|
|
var resp2 struct{ URL string }
|
|
json.Unmarshal(rec2.Body.Bytes(), &resp2)
|
|
if resp2.URL != resp.URL {
|
|
t.Fatalf("expected dedup to same url, got %q vs %q", resp2.URL, resp.URL)
|
|
}
|
|
|
|
// Fetch it back.
|
|
name := strings.TrimPrefix(resp.URL, "/api/images/")
|
|
rec3 := httptest.NewRecorder()
|
|
r.ServeHTTP(rec3, httptest.NewRequest(http.MethodGet, "/"+name, nil))
|
|
if rec3.Code != http.StatusOK {
|
|
t.Fatalf("serve code=%d", rec3.Code)
|
|
}
|
|
if !bytes.Equal(rec3.Body.Bytes(), pngBytes) {
|
|
t.Fatal("served bytes differ from uploaded")
|
|
}
|
|
}
|
|
|
|
func TestUploadRejectsNonImage(t *testing.T) {
|
|
h, _ := New(t.TempDir())
|
|
r := h.Routes()
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, uploadReq(t, "image", []byte("this is plainly not an image at all")))
|
|
if rec.Code != http.StatusUnsupportedMediaType {
|
|
t.Fatalf("expected 415, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestServeMissing(t *testing.T) {
|
|
h, _ := New(t.TempDir())
|
|
r := h.Routes()
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/deadbeef.png", nil))
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d", rec.Code)
|
|
}
|
|
}
|