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) } }