Files
summercms/modules/lagoon/deferred_test.go
Jakub Zych 19f4cf8232 feat(12.2-01): add deferred bindings, guarded upload store and purge
- deferred_bindings migration set under summercms.deferred with backend_user_id
- lagoon.DeferredBind/Unbind/Bindings/Forget/Slaves scoped by DeferredKey
- lagoon.PurgeDeferred with SKIP LOCKED batches and after-commit blob deletes
- attach.Store with the ported image guard, extension and MIME limits
- attach.Relation, attach.HasRelations, attach.BlobKeys, File.ThumbKey
- lagoon README and attachments docs
2026-10-02 17:36:43 +02:00

117 lines
3.7 KiB
Go

package lagoon
import (
"bytes"
"context"
"image"
"image/png"
"strconv"
"testing"
"time"
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
"gocloud.dev/blob/memblob"
"gorm.io/gorm"
)
// TestDeferredUploadPurgeTracer runs the 12.2 storage path end to end: a
// guarded upload held by one admin's deferred binding, then purged with its
// row and, after commit, its blob.
func TestDeferredUploadPurgeTracer(t *testing.T) {
db, _ := dedicatedDB(t, "lagoon_deferred_tracer")
gdb, err := Use(t.Context(), db)
if err != nil {
t.Fatal(err)
}
if err := Migrate(gdb, nil); err != nil {
t.Fatal(err)
}
ctx := t.Context()
bucket := memblob.OpenBucket(nil)
t.Cleanup(func() { _ = bucket.Close() })
var img bytes.Buffer
if err := png.Encode(&img, image.NewRGBA(image.Rect(0, 0, 2, 2))); err != nil {
t.Fatal(err)
}
f, err := attach.Store(ctx, gdb, bucket, attach.Upload{FileName: "cover.png", Body: &img, Public: true}, attach.Limits{Image: true})
if err != nil {
t.Fatal(err)
}
key := DeferredKey{SessionKey: "tracer-session-key-0123456789abcdef", AdminID: 7, MasterType: "acme_posts"}
slaveID := strconv.FormatUint(uint64(f.ID), 10)
if err := Transaction(ctx, gdb, func(ctx context.Context, tx *gorm.DB) error {
if err := DeferredBind(ctx, tx, key, "cover", DeferredFileType, slaveID, nil); err != nil {
return err
}
// A repeated bind writes nothing.
return DeferredBind(ctx, tx, key, "cover", DeferredFileType, slaveID, nil)
}); err != nil {
t.Fatal(err)
}
var rows []DeferredBinding
if err := gdb.Find(&rows).Error; err != nil {
t.Fatal(err)
}
if len(rows) != 1 || !rows[0].IsBind || rows[0].BackendUserID != 7 || rows[0].MasterType != "acme_posts" {
t.Fatalf("bindings %+v", rows)
}
// Another admin's key sees nothing.
other := key
other.AdminID = 8
var seen []DeferredBinding
if err := Transaction(ctx, gdb, func(ctx context.Context, tx *gorm.DB) error {
var err error
seen, err = DeferredBindings(ctx, tx, other, []string{"cover"})
return err
}); err != nil || len(seen) != 0 {
t.Fatalf("foreign admin saw %v (%v)", seen, err)
}
if err := gdb.Exec(`UPDATE deferred_bindings SET created_at = NOW() - INTERVAL '6 days'`).Error; err != nil {
t.Fatal(err)
}
res, err := PurgeDeferred(ctx, gdb, bucket, PurgeOptions{Before: time.Now().Add(-5 * 24 * time.Hour)})
if err != nil {
t.Fatal(err)
}
if res.Bindings != 1 || res.Files != 1 || res.Skipped != 0 {
t.Fatalf("purge result %+v", res)
}
var count int64
if err := gdb.Model(&DeferredBinding{}).Count(&count).Error; err != nil || count != 0 {
t.Fatalf("bindings left %d (%v)", count, err)
}
if err := gdb.Model(&attach.File{}).Where("id = ?", f.ID).Count(&count).Error; err != nil || count != 0 {
t.Fatalf("file rows left %d (%v)", count, err)
}
exists, err := bucket.Exists(ctx, attach.BlobKey(f.DiskName))
if err != nil || exists {
t.Fatalf("blob still exists=%v (%v)", exists, err)
}
// A bind followed by an unbind of the same slave cancels the pair and
// hands the cancelled bind back.
var cancelled *DeferredBinding
if err := Transaction(ctx, gdb, func(ctx context.Context, tx *gorm.DB) error {
env := &DeferredEnvelope{Created: true}
if err := DeferredBind(ctx, tx, key, "comments", "acme_comments", "41", env); err != nil {
return err
}
var err error
cancelled, err = DeferredUnbind(ctx, tx, key, "comments", "acme_comments", "41")
return err
}); err != nil {
t.Fatal(err)
}
if cancelled == nil || !cancelled.IsBind {
t.Fatalf("cancelled bind %+v", cancelled)
}
if env, err := cancelled.Envelope(); err != nil || !env.Created {
t.Fatalf("envelope %+v (%v)", env, err)
}
if err := gdb.Model(&DeferredBinding{}).Count(&count).Error; err != nil || count != 0 {
t.Fatalf("bindings after cancel %d (%v)", count, err)
}
}