test(05-04): add failing tests for attach File, Thumb, and bucket
- ThumbFilename/PartitionDirectory/File.Thumb behavior tests - OpenBucket fail-loud and memblob tests - Migrate empty plugin list must create system_files
This commit is contained in:
10
lagoon/attach/app_test.go
Normal file
10
lagoon/attach/app_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package attach
|
||||
|
||||
import (
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/compass"
|
||||
)
|
||||
|
||||
func backpackApp(cfg *compass.Config) *backpack.App {
|
||||
return backpack.New(cfg)
|
||||
}
|
||||
22
lagoon/attach/bucket.go
Normal file
22
lagoon/attach/bucket.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package attach
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/compass"
|
||||
"gocloud.dev/blob"
|
||||
_ "gocloud.dev/blob/fileblob"
|
||||
_ "gocloud.dev/blob/memblob"
|
||||
)
|
||||
|
||||
func OpenBucket(ctx context.Context, cfg *compass.Config) (*blob.Bucket, error) {
|
||||
return nil, fmt.Errorf("attach: bucket not implemented")
|
||||
}
|
||||
|
||||
func Publish(app *backpack.App, bucket *blob.Bucket) error {
|
||||
return fmt.Errorf("attach: publish not implemented")
|
||||
}
|
||||
|
||||
func PublicPathPrefix() string { return "" }
|
||||
91
lagoon/attach/bucket_test.go
Normal file
91
lagoon/attach/bucket_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package attach
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/summercms/compass"
|
||||
"gocloud.dev/blob"
|
||||
"gocloud.dev/blob/memblob"
|
||||
)
|
||||
|
||||
func TestOpenBucketRequiresURL(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "storage.yaml"), []byte("uploads:\n bucket_url: \"\"\n"), 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)
|
||||
}
|
||||
_, err = OpenBucket(t.Context(), cfg)
|
||||
if err == nil || !strings.Contains(err.Error(), "bucket_url") {
|
||||
t.Fatalf("got %v, want bucket_url error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenBucketOpensMem(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
body := "uploads:\n bucket_url: \"mem://\"\n public_path_prefix: \"/storage/uploads\"\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() })
|
||||
if PublicPathPrefix() != "/storage/uploads" {
|
||||
t.Fatalf("prefix = %q", PublicPathPrefix())
|
||||
}
|
||||
ctx := t.Context()
|
||||
if err := bucket.WriteAll(ctx, "abc/123/xyz/probe.jpg", []byte("hi"), &blob.WriterOptions{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := bucket.ReadAll(ctx, "abc/123/xyz/probe.jpg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != "hi" {
|
||||
t.Fatalf("read = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublishStoresBucket(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("name: attach-test\n"), 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)
|
||||
}
|
||||
app := backpackApp(cfg)
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
t.Cleanup(func() { _ = bucket.Close() })
|
||||
if err := Publish(app, bucket); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, ok := app.Lookup[*blob.Bucket]()
|
||||
if !ok || got != bucket {
|
||||
t.Fatal("Publish must store the same *blob.Bucket")
|
||||
}
|
||||
}
|
||||
48
lagoon/attach/file.go
Normal file
48
lagoon/attach/file.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package attach
|
||||
|
||||
import "time"
|
||||
|
||||
// Owner is implemented by models that own system_files rows. MorphName
|
||||
// must return the PHP class string so cutover-copied attachment_type
|
||||
// values keep matching.
|
||||
type Owner interface {
|
||||
MorphName() string
|
||||
}
|
||||
|
||||
// File is Winter's system_files row. AttachmentID is a string because
|
||||
// Winter stores the morph FK as a string, never an integer.
|
||||
type File struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
DiskName string `gorm:"column:disk_name"`
|
||||
FileName string `gorm:"column:file_name"`
|
||||
FileSize int64 `gorm:"column:file_size"`
|
||||
ContentType string `gorm:"column:content_type"`
|
||||
Title *string `gorm:"column:title"`
|
||||
Description *string `gorm:"column:description"`
|
||||
Field string `gorm:"column:field"`
|
||||
AttachmentID string `gorm:"column:attachment_id"`
|
||||
AttachmentType string `gorm:"column:attachment_type"`
|
||||
IsPublic bool `gorm:"column:is_public"`
|
||||
SortOrder int `gorm:"column:sort_order"`
|
||||
Metadata *string `gorm:"column:metadata"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
func (File) TableName() string { return "system_files" }
|
||||
|
||||
var all []any
|
||||
|
||||
// Register appends models so schema tooling can see File without a plugin registry.
|
||||
func Register(models ...any) {
|
||||
all = append(all, models...)
|
||||
}
|
||||
|
||||
// All returns every model registered in this package.
|
||||
func All() []any {
|
||||
return all
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(&File{})
|
||||
}
|
||||
19
lagoon/attach/file_test.go
Normal file
19
lagoon/attach/file_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package attach
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFileTableName(t *testing.T) {
|
||||
if got := (File{}).TableName(); got != "system_files" {
|
||||
t.Fatalf("TableName = %q, want system_files", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSelfRegisters(t *testing.T) {
|
||||
for _, m := range All() {
|
||||
switch m.(type) {
|
||||
case *File, File:
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatal("File must self-register via attach.Register")
|
||||
}
|
||||
7
lagoon/attach/migrations.go
Normal file
7
lagoon/attach/migrations.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package attach
|
||||
|
||||
import "github.com/go-gormigrate/gormigrate/v2"
|
||||
|
||||
// Migrations is the framework-owned system_files set. lagoon.Migrate
|
||||
// runs it before every plugin set (D-14).
|
||||
var Migrations = []*gormigrate.Migration{}
|
||||
25
lagoon/attach/thumb.go
Normal file
25
lagoon/attach/thumb.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package attach
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"gocloud.dev/blob"
|
||||
)
|
||||
|
||||
func ThumbFilename(id uint, w, h int, offsetX, offsetY int, mode, ext string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func PartitionDirectory(diskName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *File) Thumb(ctx context.Context, bucket *blob.Bucket, w, h int, mode string) (string, error) {
|
||||
return "", fmt.Errorf("attach: thumb not implemented")
|
||||
}
|
||||
|
||||
var resizeImage = func(src image.Image, w, h int, mode string) image.Image {
|
||||
return src
|
||||
}
|
||||
84
lagoon/attach/thumb_test.go
Normal file
84
lagoon/attach/thumb_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package attach
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"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 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 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