refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
186
modules/lagoon/attach/thumb_test.go
Normal file
186
modules/lagoon/attach/thumb_test.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package attach
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gocloud.dev/blob"
|
||||
"gocloud.dev/blob/memblob"
|
||||
)
|
||||
|
||||
func TestThumbFilename(t *testing.T) {
|
||||
got := ThumbFilename(42, 200, 200, 0, 0, "crop", "jpg")
|
||||
const want = "thumb_42_200_200_0_0_crop.jpg"
|
||||
if got != want {
|
||||
t.Fatalf("ThumbFilename = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThumbFilenameRejectsUnsafeTokens(t *testing.T) {
|
||||
for _, tc := range []struct{ mode, ext string }{
|
||||
{"../../secret", "jpg"},
|
||||
{"auto", "../jpg"},
|
||||
{"a/b", "jpg"},
|
||||
{"auto", "j.pg"},
|
||||
{"", ""},
|
||||
{"Crop", "JPG"},
|
||||
} {
|
||||
got := ThumbFilename(42, 200, 200, 0, 0, tc.mode, tc.ext)
|
||||
if strings.ContainsAny(got, `/\`) || strings.Contains(got, "..") {
|
||||
t.Fatalf("mode=%q ext=%q produced unsafe name %q", tc.mode, tc.ext, got)
|
||||
}
|
||||
if !strings.HasPrefix(got, "thumb_42_200_200_0_0_") {
|
||||
t.Fatalf("mode=%q ext=%q name %q lost its prefix", tc.mode, tc.ext, got)
|
||||
}
|
||||
}
|
||||
if got := ThumbFilename(42, 200, 200, 0, 0, "../../secret", "jpg"); got != "thumb_42_200_200_0_0_auto.jpg" {
|
||||
t.Fatalf("unsafe mode coerced to %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileThumbRejectsOutOfRangeSize(t *testing.T) {
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
defer bucket.Close()
|
||||
f := &File{ID: 1, DiskName: "abc123xyz.jpg"}
|
||||
for _, tc := range []struct{ w, h int }{
|
||||
{0, 200},
|
||||
{200, 0},
|
||||
{-1, -1},
|
||||
{maxThumbEdge + 1, 200},
|
||||
{200, maxThumbEdge + 1},
|
||||
{100000, 100000},
|
||||
} {
|
||||
if _, err := f.Thumb(t.Context(), bucket, tc.w, tc.h, "crop"); err == nil {
|
||||
t.Fatalf("size %dx%d must be rejected", tc.w, tc.h)
|
||||
}
|
||||
}
|
||||
iter := bucket.List(nil)
|
||||
if obj, err := iter.Next(t.Context()); err != io.EOF {
|
||||
t.Fatalf("rejected sizes must not touch the bucket, found %v (err %v)", obj, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileThumbRejectsTraversalMode(t *testing.T) {
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
defer bucket.Close()
|
||||
f := &File{ID: 42, DiskName: "abc123xyz789.jpg"}
|
||||
for _, mode := range []string{"../../secret", "a/b", "auto.jpg", "cr op"} {
|
||||
if _, err := f.Thumb(t.Context(), bucket, 200, 200, mode); err == nil {
|
||||
t.Fatalf("mode %q must be rejected", mode)
|
||||
}
|
||||
}
|
||||
iter := bucket.List(nil)
|
||||
if obj, err := iter.Next(t.Context()); err != io.EOF {
|
||||
t.Fatalf("rejected modes must write nothing, found %v (err %v)", obj, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartitionDirectory(t *testing.T) {
|
||||
got := PartitionDirectory("abc123xyz.jpg")
|
||||
const want = "abc/123/xyz/"
|
||||
if got != want {
|
||||
t.Fatalf("PartitionDirectory = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileThumbResizesOnce(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
t.Cleanup(func() { _ = bucket.Close() })
|
||||
|
||||
f := &File{ID: 42, DiskName: "abc123xyz.jpg", FileName: "cover.jpg"}
|
||||
origKey := PartitionDirectory(f.DiskName) + f.DiskName
|
||||
if origKey == "abc123xyz.jpg" || origKey == "" {
|
||||
// PartitionDirectory still stubbed — write under the Winter key the
|
||||
// GREEN implementation will look up so this test stays the behavior spec.
|
||||
origKey = "abc/123/xyz/" + f.DiskName
|
||||
}
|
||||
if err := bucket.WriteAll(ctx, origKey, testJPEG(t), &blob.WriterOptions{ContentType: "image/jpeg"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var n int
|
||||
orig := resizeImage
|
||||
resizeImage = func(src image.Image, w, h int, mode string) image.Image {
|
||||
n++
|
||||
return orig(src, w, h, mode)
|
||||
}
|
||||
t.Cleanup(func() { resizeImage = orig })
|
||||
|
||||
url1, err := f.Thumb(ctx, bucket, 200, 200, "crop")
|
||||
if err != nil {
|
||||
t.Fatalf("first Thumb: %v", err)
|
||||
}
|
||||
url2, err := f.Thumb(ctx, bucket, 200, 200, "crop")
|
||||
if err != nil {
|
||||
t.Fatalf("second Thumb: %v", err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Fatalf("resize calls = %d, want 1", n)
|
||||
}
|
||||
want := "/storage/uploads/abc/123/xyz/thumb_42_200_200_0_0_crop.jpg"
|
||||
if url1 != want || url2 != want {
|
||||
t.Fatalf("urls = %q %q, want %q", url1, url2, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileThumbEncodeFailureDoesNotCache(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
t.Cleanup(func() { _ = bucket.Close() })
|
||||
|
||||
f := &File{ID: 7, DiskName: "abc123xyz.jpg"}
|
||||
if err := bucket.WriteAll(ctx, BlobKey(f.DiskName), testJPEG(t), &blob.WriterOptions{ContentType: "image/jpeg"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
orig := encodeImage
|
||||
encodeImage = func(w io.Writer, img image.Image, ext string) error {
|
||||
_, _ = w.Write([]byte("partial"))
|
||||
return errors.New("encode boom")
|
||||
}
|
||||
t.Cleanup(func() { encodeImage = orig })
|
||||
|
||||
if _, err := f.Thumb(ctx, bucket, 200, 200, "crop"); err == nil {
|
||||
t.Fatal("encode failure must surface")
|
||||
}
|
||||
thumbKey := PartitionDirectory(f.DiskName) + ThumbFilename(f.ID, 200, 200, 0, 0, "crop", "jpg")
|
||||
exists, err := bucket.Exists(ctx, thumbKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exists {
|
||||
t.Fatal("failed encode must not leave a cached thumb blob")
|
||||
}
|
||||
|
||||
encodeImage = orig
|
||||
url, err := f.Thumb(ctx, bucket, 200, 200, "crop")
|
||||
if err != nil {
|
||||
t.Fatalf("retry after failed encode: %v", err)
|
||||
}
|
||||
want := "/storage/uploads/abc/123/xyz/thumb_7_200_200_0_0_crop.jpg"
|
||||
if url != want {
|
||||
t.Fatalf("retry url = %q, want %q", url, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testJPEG(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
img := image.NewRGBA(image.Rect(0, 0, 8, 8))
|
||||
for y := 0; y < 8; y++ {
|
||||
for x := 0; x < 8; x++ {
|
||||
img.Set(x, y, color.RGBA{R: 200, A: 255})
|
||||
}
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 90}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
Reference in New Issue
Block a user