Files
ds-galo/backend/internal/app/photo_edit_effects_test.go
T
iu c953008a86 feat(photos): добавить эффекты и настройки панели редактора
- текстура, чёткость, дымка, виньетка и зерно в неразрушающем редакторе
- живой WebGL2-предпросмотр эффектов и CPU-фолбэк
- секции редактора свёрнуты по умолчанию, состояние в localStorage
- вкладка «Редактирование» доступна только из просмотрщика
2026-10-24 22:57:30 +03:00

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)
}
}