- attach.PublicURL and (*File).URL build Winter File::getPath() URLs; the thumbnailer decodes webp via golang.org/x/image v0.46.0 and checks the image size from the header before decoding - tide requests carry multipart parts (files beside the fixture pinned by sha256) encoded with the fixed MultipartBoundary, so PHP and Go receive byte-identical bodies - tide masks the random partition, disk name and file id of url/thumb_url upload URLs while still diffing prefix, size, mode and extension, and NormalizePublications masks Carbon dates in the published album
108 lines
3.6 KiB
Go
108 lines
3.6 KiB
Go
package attach
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
"image/jpeg"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
"gocloud.dev/blob"
|
|
)
|
|
|
|
// winterLayoutBucket opens a mem:// bucket with the WinterCMS public prefix
|
|
// and restores the framework default afterwards.
|
|
func winterLayoutBucket(t *testing.T) *blob.Bucket {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
body := "uploads:\n bucket_url: \"mem://\"\n public_path_prefix: \"/storage/app/uploads/public\"\n"
|
|
if err := os.WriteFile(filepath.Join(dir, "storage.yaml"), []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := compass.Open(compass.Options{Dir: dir, Env: "development", Environ: []string{"SUMMER_ENV=development"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bucket, err := OpenBucket(t.Context(), cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = bucket.Close()
|
|
setPublicPathPrefix(defaultPublicPathPrefix)
|
|
})
|
|
return bucket
|
|
}
|
|
|
|
func TestFileURLWinterLayout(t *testing.T) {
|
|
bucket := winterLayoutBucket(t)
|
|
// A WinterCMS disk name: uniqid('', true) without the dot, plus the
|
|
// extension.
|
|
f := &File{ID: 12, DiskName: "651a2b3c4d5e61234567.png"}
|
|
if got, want := f.URL(), "/storage/app/uploads/public/651/a2b/3c4/651a2b3c4d5e61234567.png"; got != want {
|
|
t.Fatalf("URL = %q, want %q", got, want)
|
|
}
|
|
if got, want := PublicURL("/651/a2b/3c4/x.png"), "/storage/app/uploads/public/651/a2b/3c4/x.png"; got != want {
|
|
t.Fatalf("PublicURL = %q, want %q", got, want)
|
|
}
|
|
if err := bucket.WriteAll(t.Context(), BlobKey(f.DiskName), testJPEG(t), &blob.WriterOptions{ContentType: "image/png"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
thumb, err := f.Thumb(t.Context(), bucket, 200, 200, "crop")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Winter getThumbFilename: implode('_', [thumb, id, w, h, ox, oy, mode.ext]).
|
|
if want := "/storage/app/uploads/public/651/a2b/3c4/thumb_12_200_200_0_0_crop.png"; thumb != want {
|
|
t.Fatalf("thumb = %q, want %q", thumb, want)
|
|
}
|
|
var nilFile *File
|
|
if nilFile.URL() != "" {
|
|
t.Fatal("nil file URL must be empty")
|
|
}
|
|
}
|
|
|
|
// webpFixture is a 16x12 lossless WebP: blue left half, red right half.
|
|
var webpFixture = []byte{
|
|
0x52, 0x49, 0x46, 0x46, 0x2a, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x4c,
|
|
0x1d, 0x00, 0x00, 0x00, 0x2f, 0x0f, 0xc0, 0x02, 0x00, 0x0f, 0x70, 0x14, 0xfb, 0x53, 0xd0, 0x5e,
|
|
0x88, 0x7b, 0xfe, 0x83, 0x07, 0x62, 0xc1, 0x64, 0xfe, 0xd2, 0xbd, 0x21, 0x44, 0xf4, 0x3f, 0x74,
|
|
0x01, 0x00,
|
|
}
|
|
|
|
func TestThumbWebP(t *testing.T) {
|
|
cfg, format, err := image.DecodeConfig(bytes.NewReader(webpFixture))
|
|
if err != nil {
|
|
t.Fatalf("DecodeConfig: %v", err)
|
|
}
|
|
if format != "webp" || cfg.Width != 16 || cfg.Height != 12 {
|
|
t.Fatalf("config = %s %dx%d", format, cfg.Width, cfg.Height)
|
|
}
|
|
bucket := winterLayoutBucket(t)
|
|
f := &File{ID: 5, DiskName: "abcdef123456789012345.webp"}
|
|
if err := bucket.WriteAll(t.Context(), BlobKey(f.DiskName), webpFixture, &blob.WriterOptions{ContentType: "image/webp"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
url, err := f.Thumb(t.Context(), bucket, 200, 200, "crop")
|
|
if err != nil {
|
|
t.Fatalf("Thumb: %v", err)
|
|
}
|
|
if want := "/storage/app/uploads/public/abc/def/123/thumb_5_200_200_0_0_crop.webp"; url != want {
|
|
t.Fatalf("thumb url = %q, want %q", url, want)
|
|
}
|
|
raw, err := bucket.ReadAll(t.Context(), "abc/def/123/thumb_5_200_200_0_0_crop.webp")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// imaging cannot encode webp: the thumb is JPEG bytes under the .webp name.
|
|
img, err := jpeg.Decode(bytes.NewReader(raw))
|
|
if err != nil {
|
|
t.Fatalf("thumb is not JPEG: %v", err)
|
|
}
|
|
if b := img.Bounds(); b.Dx() != 200 || b.Dy() != 200 {
|
|
t.Fatalf("thumb size = %v", b)
|
|
}
|
|
}
|