test(12-05): bring the Phase 12 framework packages to full unit coverage
- lagoon: Go-typed request values (typed slices and maps, sized integers, floats, file pointers, typed path lookups), regex delimiters, mimes sniffing (jpg/jpeg, SVG, PHP names, unreadable content), UploadedFileFromHeader, the rule builders, custom catalog lines with :input/:index/:position, and exists: against Postgres (text compare, inferred and NULL columns, arrays, unsafe identifiers, missing tables) - lagoon/attach: thumbnails in every mode from PNG, GIF and JPEG originals, bucket URL normalisation, OpenBucket/Publish refusals, URL helpers and static prefix stripping - tide: part validation and encoding edges, symlinks out of the fixture directory, Content-Type handling, every response mask and the coverage report helpers Coverage: lagoon 84.4%, lagoon/attach 87.7%, tide 81.4%, beachcomber 84.3%, beachcomber/typesense 95.9%.
This commit is contained in:
@@ -6,12 +6,16 @@ import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
"gocloud.dev/blob"
|
||||
"gocloud.dev/blob/memblob"
|
||||
@@ -195,3 +199,166 @@ func TestThumbBrokenSourceServesPlaceholder(t *testing.T) {
|
||||
t.Fatal("an out-of-range size must stay an error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestThumbModesAndFormats generates crop, exact, auto and fit thumbnails
|
||||
// from PNG, GIF and JPEG originals and checks the stored bytes decode to
|
||||
// the requested box in the original's format.
|
||||
func TestThumbModesAndFormats(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
t.Cleanup(func() { _ = bucket.Close() })
|
||||
src := image.NewRGBA(image.Rect(0, 0, 120, 60))
|
||||
encoders := map[string]func(*bytes.Buffer) error{
|
||||
"png": func(b *bytes.Buffer) error { return png.Encode(b, src) },
|
||||
"gif": func(b *bytes.Buffer) error { return gif.Encode(b, src, nil) },
|
||||
"jpg": func(b *bytes.Buffer) error { return jpeg.Encode(b, src, nil) },
|
||||
}
|
||||
id := uint(500)
|
||||
for ext, enc := range encoders {
|
||||
var buf bytes.Buffer
|
||||
if err := enc(&buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, mode := range []string{"crop", "exact", "auto", "fit", "CROP"} {
|
||||
id++
|
||||
f := &File{ID: id, DiskName: fmt.Sprintf("m%02dmodes%03d.%s", id%100, id, ext)}
|
||||
if err := bucket.WriteAll(ctx, BlobKey(f.DiskName), buf.Bytes(), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
url, err := f.Thumb(ctx, bucket, 40, 40, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", ext, mode, err)
|
||||
}
|
||||
key := strings.TrimPrefix(url, PublicPathPrefix()+"/")
|
||||
raw, err := bucket.ReadAll(ctx, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg, format, err := image.DecodeConfig(bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantFormat := map[string]string{"png": "png", "gif": "gif", "jpg": "jpeg"}[ext]
|
||||
if format != wantFormat {
|
||||
t.Errorf("%s %s: thumbnail format %s", ext, mode, format)
|
||||
}
|
||||
switch strings.ToLower(mode) {
|
||||
case "crop", "exact":
|
||||
if cfg.Width != 40 || cfg.Height != 40 {
|
||||
t.Errorf("%s %s: %dx%d, want 40x40", ext, mode, cfg.Width, cfg.Height)
|
||||
}
|
||||
default:
|
||||
if cfg.Width != 40 || cfg.Height != 20 {
|
||||
t.Errorf("%s %s: %dx%d, want 40x20 (fit)", ext, mode, cfg.Width, cfg.Height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
f := &File{ID: 1, DiskName: "noextension"}
|
||||
if got := fileExt(f.DiskName); got != "jpg" {
|
||||
t.Fatalf("fileExt without extension = %q", got)
|
||||
}
|
||||
if got := fileExt("A.PNG"); got != "png" {
|
||||
t.Fatalf("fileExt upper case = %q", got)
|
||||
}
|
||||
if _, err := (*File)(nil).Thumb(ctx, bucket, 10, 10, "crop"); err == nil {
|
||||
t.Fatal("nil file")
|
||||
}
|
||||
if _, err := f.Thumb(ctx, nil, 10, 10, "crop"); err == nil {
|
||||
t.Fatal("nil bucket")
|
||||
}
|
||||
if _, err := (&File{ID: 2, DiskName: "abcdefghi.p-ng"}).Thumb(ctx, bucket, 10, 10, "crop"); err == nil {
|
||||
t.Fatal("unsafe extension")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBucketAndURLEdges covers bucket URL normalisation, OpenBucket and
|
||||
// Publish refusals, partition and URL helpers and static prefix stripping.
|
||||
func TestBucketAndURLEdges(t *testing.T) {
|
||||
for raw, want := range map[string]string{
|
||||
"mem://": "mem://",
|
||||
"memory://anything": "mem://",
|
||||
"fileblob:///tmp/x": "file:///tmp/x?create_dir=true",
|
||||
"file:///tmp/y": "file:///tmp/y?create_dir=true",
|
||||
"file:///tmp/z?create_dir=false": "file:///tmp/z?create_dir=false",
|
||||
"s3://bucket?region=eu-central-1": "s3://bucket?region=eu-central-1",
|
||||
} {
|
||||
got, err := normalizeBucketURL(raw)
|
||||
if err != nil || got != want {
|
||||
t.Errorf("normalizeBucketURL(%q) = %q %v, want %q", raw, got, err, want)
|
||||
}
|
||||
}
|
||||
if _, err := normalizeBucketURL("%zz"); err == nil {
|
||||
t.Error("an unparsable bucket URL must be an error")
|
||||
}
|
||||
if _, err := OpenBucket(t.Context(), nil); err == nil {
|
||||
t.Error("nil config")
|
||||
}
|
||||
open := func(body string) error {
|
||||
dir := t.TempDir()
|
||||
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)
|
||||
}
|
||||
b, err := OpenBucket(t.Context(), cfg)
|
||||
if err == nil {
|
||||
_ = b.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := open("uploads:\n bucket_url: \"\"\n"); err == nil {
|
||||
t.Error("an empty bucket_url must fail")
|
||||
}
|
||||
if err := open("uploads:\n bucket_url: \"nope://x\"\n"); err == nil {
|
||||
t.Error("an unknown scheme must fail")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
if err := open("uploads:\n bucket_url: \"file://" + dir + "/up\"\n"); err != nil {
|
||||
t.Fatalf("file bucket: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { setPublicPathPrefix(defaultPublicPathPrefix) })
|
||||
if got := PublicPathPrefix(); got != defaultPublicPathPrefix {
|
||||
t.Fatalf("default prefix = %q", got)
|
||||
}
|
||||
if err := Publish(nil, memblob.OpenBucket(nil)); err == nil {
|
||||
t.Error("Publish with a nil app")
|
||||
}
|
||||
if err := Publish(backpack.New(nil), nil); err == nil {
|
||||
t.Error("Publish with a nil bucket")
|
||||
}
|
||||
if got := PartitionDirectory("ab"); got != "ab/" {
|
||||
t.Errorf("short partition = %q", got)
|
||||
}
|
||||
if got := PartitionDirectory("abcdefghijkl"); got != "abc/def/ghi/" {
|
||||
t.Errorf("partition = %q", got)
|
||||
}
|
||||
setPublicPathPrefix("")
|
||||
if got := PublicURL("/a/b.png"); got != "/a/b.png" {
|
||||
t.Errorf("PublicURL without prefix = %q", got)
|
||||
}
|
||||
setPublicPathPrefix("/files/")
|
||||
if got := PublicURL("a/b.png"); got != "/files/a/b.png" {
|
||||
t.Errorf("PublicURL with a trailing slash prefix = %q", got)
|
||||
}
|
||||
setPublicPathPrefix(defaultPublicPathPrefix)
|
||||
if (*File)(nil).URL() != "" {
|
||||
t.Error("nil File URL")
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
path, prefix, key string
|
||||
ok bool
|
||||
}{
|
||||
{"/abc/def.png", "", "abc/def.png", true},
|
||||
{"/storage", "/storage", "", false},
|
||||
{"/storage/a.png", "/storage", "a.png", true},
|
||||
{"/storagex/a.png", "/storage", "", false},
|
||||
} {
|
||||
key, ok := stripStaticPrefix(tc.path, tc.prefix)
|
||||
if key != tc.key || ok != tc.ok {
|
||||
t.Errorf("stripStaticPrefix(%q, %q) = %q %v", tc.path, tc.prefix, key, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user