Files
ttstd-infinite-canvas/backend/internal/service/resource_delete_test.go
MasterChiefCN c646487dfb feat(media): 素材删除 - 安全清理未引用的存储资源
删除素材前检查项目、画布、任务、其他素材、工作流、风格和声音引用;仅清理未复用的本地或 OSS/COS 对象。
2026-08-16 13:11:30 +08:00

81 lines
2.8 KiB
Go

package service
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestCollectOwnedAssetDocumentReferences(t *testing.T) {
resources := map[string]struct{}{}
tasks := map[string]struct{}{}
raw := `{
"data":{"storageKey":"resource:resource-1","url":"/api/resources/resource-2/file"},
"metadata":{"taskId":"task-1","referenceResourceIds":["resource-3"],"errorDetails":"failed near /api/resources/not-a-reference/file"}
}`
if err := collectOwnedAssetDocumentReferences(raw, resources, tasks); err != nil {
t.Fatal(err)
}
for _, resourceID := range []string{"resource-1", "resource-2", "resource-3"} {
if _, exists := resources[resourceID]; !exists {
t.Fatalf("resource %q was not collected: %#v", resourceID, resources)
}
}
if _, exists := tasks["task-1"]; !exists {
t.Fatalf("origin task was not collected: %#v", tasks)
}
if _, exists := resources["not-a-reference"]; exists {
t.Fatalf("diagnostic text must not be treated as an actual resource reference: %#v", resources)
}
}
func TestDocumentReferencesResourcesUsesExactResourceID(t *testing.T) {
candidates := map[string]struct{}{"resource-1": {}}
if documentReferencesResources(`{"storageKey":"resource:resource-10"}`, candidates) {
t.Fatal("resource-1 must not match resource-10")
}
if !documentReferencesResources(`{"storageKey":"resource:resource-1"}`, candidates) {
t.Fatal("exact resource ID should match")
}
}
func TestResourceOccupiedMessageNamesBusinessRecord(t *testing.T) {
message := resourceOccupiedMessage([]resourceUsage{
{Kind: "画布", ID: "canvas-1", Title: "广告分镜"},
{Kind: "画布", ID: "canvas-1", Title: "广告分镜"},
})
if !strings.Contains(message, "画布「广告分镜」") || !strings.Contains(message, "解除引用") {
t.Fatalf("message = %q", message)
}
}
func TestDeleteLocalResourceObjectRemovesOnlyResourceDirectoryFile(t *testing.T) {
dataDir := t.TempDir()
resourcePath := filepath.Join(dataDir, "resources", "users", "user-1", "image", "asset.png")
if err := os.MkdirAll(filepath.Dir(resourcePath), 0o750); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(resourcePath, []byte("image"), 0o640); err != nil {
t.Fatal(err)
}
service := &Service{dataDir: dataDir}
if err := service.deleteLocalResourceObject("users/user-1/image/asset.png"); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(resourcePath); !os.IsNotExist(err) {
t.Fatalf("resource file still exists or stat failed unexpectedly: %v", err)
}
outsidePath := filepath.Join(dataDir, "outside.txt")
if err := os.WriteFile(outsidePath, []byte("keep"), 0o640); err != nil {
t.Fatal(err)
}
if err := service.deleteLocalResourceObject("../outside.txt"); err == nil {
t.Fatal("path traversal should be rejected")
}
if _, err := os.Stat(outsidePath); err != nil {
t.Fatalf("outside file was changed: %v", err)
}
}