Files
silo-server/internal/api/handlers/collection_artwork_test.go
T
Silo Server Migration 9c3848d205 Port collection templates to bundled poster storage
- Store bundled template posters in public S3 when available
- Presign imported user collection posters in API responses
- Thread frontend assets into router and collection handlers
2026-05-23 20:26:05 -04:00

176 lines
4.3 KiB
Go

package handlers
import (
"bytes"
"context"
"image"
"image/color"
"image/jpeg"
"io"
"net/http"
"net/http/httptest"
"sync"
"testing"
"testing/fstest"
"github.com/Silo-Server/silo-server/internal/s3client"
)
type collectionArtworkS3Recorder struct {
server *httptest.Server
mu sync.Mutex
puts []string
}
func newCollectionArtworkS3Recorder(t *testing.T) *collectionArtworkS3Recorder {
t.Helper()
recorder := &collectionArtworkS3Recorder{}
recorder.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.Copy(io.Discard, r.Body)
_ = r.Body.Close()
if r.Method == http.MethodPut {
recorder.mu.Lock()
recorder.puts = append(recorder.puts, r.URL.Path)
recorder.mu.Unlock()
}
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(recorder.server.Close)
return recorder
}
func (r *collectionArtworkS3Recorder) client() *s3client.Client {
return s3client.NewClient(s3client.BucketConfig{
Endpoint: r.server.URL,
Region: "us-east-1",
Bucket: "public-assets",
AccessKey: "test",
SecretKey: "test",
PathStyle: true,
})
}
func (r *collectionArtworkS3Recorder) putPaths() []string {
r.mu.Lock()
defer r.mu.Unlock()
out := make([]string, len(r.puts))
copy(out, r.puts)
return out
}
func TestStoreBundledCollectionPosterIfS3Configured_NoS3KeepsPath(t *testing.T) {
path := "/images/collection-templates/template.jpg"
gotPath, gotThumbhash, stored, err := storeBundledCollectionPosterIfS3Configured(
context.Background(),
nil,
fstest.MapFS{},
"collection-1",
adminCollectionImagePrefix,
path,
)
if err != nil {
t.Fatalf("storeBundledCollectionPosterIfS3Configured: %v", err)
}
if stored {
t.Fatal("stored = true, want false")
}
if gotPath != path {
t.Fatalf("path = %q, want %q", gotPath, path)
}
if gotThumbhash != "" {
t.Fatalf("thumbhash = %q, want empty", gotThumbhash)
}
}
func TestStoreBundledCollectionPosterIfS3Configured_IgnoresNonTemplatePath(t *testing.T) {
recorder := newCollectionArtworkS3Recorder(t)
path := "collection-images/existing/poster/original.webp"
gotPath, gotThumbhash, stored, err := storeBundledCollectionPosterIfS3Configured(
context.Background(),
recorder.client(),
fstest.MapFS{},
"collection-1",
adminCollectionImagePrefix,
path,
)
if err != nil {
t.Fatalf("storeBundledCollectionPosterIfS3Configured: %v", err)
}
if stored {
t.Fatal("stored = true, want false")
}
if gotPath != path {
t.Fatalf("path = %q, want %q", gotPath, path)
}
if gotThumbhash != "" {
t.Fatalf("thumbhash = %q, want empty", gotThumbhash)
}
if puts := recorder.putPaths(); len(puts) != 0 {
t.Fatalf("PUT paths = %#v, want none", puts)
}
}
func TestStoreBundledCollectionPosterIfS3Configured_UploadsTemplatePoster(t *testing.T) {
recorder := newCollectionArtworkS3Recorder(t)
frontendFS := fstest.MapFS{
"images/collection-templates/template.jpg": {
Data: testCollectionPosterJPEG(t),
},
}
gotPath, gotThumbhash, stored, err := storeBundledCollectionPosterIfS3Configured(
context.Background(),
recorder.client(),
frontendFS,
"collection-1",
adminCollectionImagePrefix,
"/images/collection-templates/template.jpg",
)
if err != nil {
t.Fatalf("storeBundledCollectionPosterIfS3Configured: %v", err)
}
if !stored {
t.Fatal("stored = false, want true")
}
if gotPath != "collection-images/collection-1/poster/original.webp" {
t.Fatalf("path = %q", gotPath)
}
if gotThumbhash == "" {
t.Fatal("thumbhash is empty")
}
want := map[string]bool{
"/public-assets/collection-images/collection-1/poster/original.webp": true,
"/public-assets/collection-images/collection-1/poster/w500.webp": true,
"/public-assets/collection-images/collection-1/poster/w300.webp": true,
}
puts := recorder.putPaths()
if len(puts) != len(want) {
t.Fatalf("PUT paths = %#v", puts)
}
for _, path := range puts {
if !want[path] {
t.Fatalf("unexpected PUT path %q in %#v", path, puts)
}
}
}
func testCollectionPosterJPEG(t *testing.T) []byte {
t.Helper()
img := image.NewRGBA(image.Rect(0, 0, 32, 48))
for y := 0; y < 48; y++ {
for x := 0; x < 32; x++ {
img.Set(x, y, color.RGBA{R: uint8(x * 6), G: uint8(y * 4), B: 120, A: 255})
}
}
var buf bytes.Buffer
if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 90}); err != nil {
t.Fatalf("encode jpeg: %v", err)
}
return buf.Bytes()
}