feat(photos): добавить эффекты и настройки панели редактора
- текстура, чёткость, дымка, виньетка и зерно в неразрушающем редакторе - живой WebGL2-предпросмотр эффектов и CPU-фолбэк - секции редактора свёрнуты по умолчанию, состояние в localStorage - вкладка «Редактирование» доступна только из просмотрщика
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user