Files
ttstd-infinite-canvas/backend/internal/app/resource_batch_delete_test.go

90 lines
3.9 KiB
Go

package app
import (
"testing"
"time"
"yingce/backend/internal/model"
)
func TestPurgeAssetsBatchSharedResourcesAndHistory(t *testing.T) {
svc, db, _ := newResourceDeletionTestService(t)
for _, id := range []string{"batch-shared", "keep-shared", "same-object", "foreign-alias"} {
owner, objectKey := "user-1", id+".png"
if id == "foreign-alias" {
owner, objectKey = "user-2", "same-object.png"
}
if err := db.Create(&model.Resource{ID: id, UserID: owner, Provider: "unsupported-test-provider", ObjectKey: objectKey, Status: model.ResourceStatusReady}).Error; err != nil {
t.Fatal(err)
}
}
for _, record := range []any{
&model.Asset{ID: "first", UserID: "user-1", Status: model.AssetVersionStatusConfirmed, PayloadJSON: `{"resourceIds":["batch-shared","keep-shared","same-object"]}`},
&model.Asset{ID: "second", UserID: "user-1", Status: model.AssetVersionStatusArchived, PayloadJSON: `{}`},
&model.AssetVersion{ID: "second-version", AssetID: "second", DefinitionJSON: `{"resourceId":"batch-shared"}`},
&model.AssetRepresentation{ID: "second-representation", AssetVersionID: "second-version", Role: "image", ResourceID: "batch-shared", MetadataJSON: `{}`},
&model.Asset{ID: "unselected", UserID: "user-1", PayloadJSON: `{}`},
&model.AssetVersion{ID: "unselected-version", AssetID: "unselected", DefinitionJSON: `{}`},
&model.AssetRepresentation{ID: "unselected-representation", AssetVersionID: "unselected-version", Role: "video", MetadataJSON: `{"resourceId":"keep-shared"}`},
&model.Task{ID: "batch-task", UserID: "user-1", Status: model.TaskStatusRunning, InputJSON: `{"resourceId":"batch-shared"}`},
&model.CanvasProject{ID: "batch-canvas", UserID: "user-1", PayloadJSON: `{"resourceId":"batch-shared"}`},
&model.CanvasSnapshot{ID: "batch-snapshot", CanvasID: "batch-canvas", UserID: "user-1", Revision: 1, PayloadJSON: `{}`, CreatedAt: time.Now()},
&model.CanvasSnapshotResource{SnapshotID: "batch-snapshot", ResourceID: "batch-shared"},
} {
if err := db.Create(record).Error; err != nil {
t.Fatal(err)
}
}
assertCount := func(record any, want int64) {
t.Helper()
var count int64
if err := db.Model(record).Count(&count).Error; err != nil || count != want {
t.Fatalf("%T count=%d want=%d err=%v", record, count, want, err)
}
}
// 彻底删除不受任务、画布与画布历史引用拦截(产品约定);
// 但事务后段失败时,素材、版本、表现、历史索引与 Outbox 必须整批回滚。
if err := db.Exec("CREATE TRIGGER fail_batch_resource_delete BEFORE DELETE ON resources BEGIN SELECT RAISE(ABORT, 'forced failure'); END;").Error; err != nil {
t.Fatal(err)
}
if err := svc.PurgeUserAssets("user-1", []string{"first", "second"}); err == nil {
t.Fatal("expected rollback")
}
assertCount(&model.Asset{}, 3)
assertCount(&model.Resource{}, 4)
assertCount(&model.ResourceDeletionJob{}, 0)
assertCount(&model.CanvasSnapshotResource{}, 1)
assertCount(&model.AssetVersion{}, 2)
assertCount(&model.AssetRepresentation{}, 2)
if err := db.Exec("DROP TRIGGER fail_batch_resource_delete").Error; err != nil {
t.Fatal(err)
}
if err := svc.PurgeUserAssets("user-1", []string{"first", " second ", "first"}); err != nil {
t.Fatal(err)
}
assertCount(&model.Asset{}, 1)
assertCount(&model.Resource{}, 2)
assertCount(&model.ResourceDeletionJob{}, 1)
assertCount(&model.CanvasSnapshotResource{}, 0)
assertCount(&model.CanvasSnapshot{}, 1)
assertCount(&model.Task{}, 1)
assertCount(&model.CanvasProject{}, 1)
assertCount(&model.AssetVersion{}, 1)
assertCount(&model.AssetRepresentation{}, 1)
var job model.ResourceDeletionJob
if err := db.First(&job).Error; err != nil {
t.Fatal(err)
}
if job.ResourceID != "batch-shared" {
t.Fatalf("wrong physical object queued: %s", job.ResourceID)
}
for _, id := range []string{"keep-shared", "foreign-alias"} {
var resource model.Resource
if err := db.First(&resource, "id = ?", id).Error; err != nil {
t.Fatalf("shared resource %s lost: %v", id, err)
}
}
}