- текстура, чёткость, дымка, виньетка и зерно в неразрушающем редакторе - живой WebGL2-предпросмотр эффектов и CPU-фолбэк - секции редактора свёрнуты по умолчанию, состояние в localStorage - вкладка «Редактирование» доступна только из просмотрщика
97 lines
2.9 KiB
Go
97 lines
2.9 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"galo/internal/auth"
|
|
"galo/internal/models"
|
|
)
|
|
|
|
// The PATCH /photos/:id/edit endpoint must accept the whole edit payload,
|
|
// including the effects block: the HTTP layer has its own request struct, so a
|
|
// new parameter group can silently disappear if it is not added there.
|
|
func TestEditEndpointAcceptsEffects(t *testing.T) {
|
|
ctx := context.Background()
|
|
r, token, uid, repos := newAvatarServer(t)
|
|
|
|
album := &models.Album{ID: auth.NewID(), OwnerID: uid, Title: "Effects"}
|
|
if err := repos.Albums.Create(ctx, album); err != nil {
|
|
t.Fatalf("create album: %v", err)
|
|
}
|
|
if err := repos.Members.Add(ctx, album.ID, uid, "editor", nil); err != nil {
|
|
t.Fatalf("add owner member: %v", err)
|
|
}
|
|
|
|
body, contentType := multipartFile(t, "file", "effects.png", makeTestPNG(t))
|
|
rec := doAuthed(r, http.MethodPost, "/api/albums/"+album.ID+"/photos", body, contentType, token)
|
|
if rec.Code != http.StatusCreated {
|
|
t.Fatalf("upload = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var uploaded struct {
|
|
Photos []models.Photo `json:"photos"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &uploaded); err != nil {
|
|
t.Fatalf("decode upload: %v", err)
|
|
}
|
|
if len(uploaded.Photos) != 1 {
|
|
t.Fatalf("uploaded photos = %d", len(uploaded.Photos))
|
|
}
|
|
photoID := uploaded.Photos[0].ID
|
|
|
|
// Explicit zero midpoint/feather/size/roughness must survive the round trip,
|
|
// and omitted strengths must stay zero.
|
|
payload := []byte(`{
|
|
"effects": {
|
|
"texture": 40,
|
|
"clarity": -20,
|
|
"dehaze": 10,
|
|
"vignette": {
|
|
"mode": "color",
|
|
"amount": 50,
|
|
"midpoint": 0,
|
|
"feather": 0,
|
|
"roundness": 10,
|
|
"highlights": 5,
|
|
"color": "#ff0000"
|
|
},
|
|
"grain": {"amount": 30, "size": 0, "roughness": 0}
|
|
}
|
|
}`)
|
|
rec = doAuthed(r, http.MethodPatch, "/api/photos/"+photoID+"/edit", bytes.NewBuffer(payload), "application/json", token)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("edit = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var edited struct {
|
|
Photo models.Photo `json:"photo"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &edited); err != nil {
|
|
t.Fatalf("decode edit: %v", err)
|
|
}
|
|
effects, ok := edited.Photo.EditParams["effects"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("effects missing from edit_params: %+v", edited.Photo.EditParams)
|
|
}
|
|
if got := effects["texture"]; got != float64(40) {
|
|
t.Fatalf("texture = %v, want 40", got)
|
|
}
|
|
vignette, ok := effects["vignette"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("vignette missing from effects: %+v", effects)
|
|
}
|
|
if vignette["mode"] != "color" || vignette["midpoint"] != float64(0) ||
|
|
vignette["feather"] != float64(0) || vignette["color"] != "#ff0000" {
|
|
t.Fatalf("vignette params not stored as sent: %+v", vignette)
|
|
}
|
|
grain, ok := effects["grain"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("grain missing from effects: %+v", effects)
|
|
}
|
|
if grain["size"] != float64(0) || grain["roughness"] != float64(0) {
|
|
t.Fatalf("grain params not stored as sent: %+v", grain)
|
|
}
|
|
}
|