feat(media): 素材删除 - 安全清理未引用的存储资源
删除素材前检查项目、画布、任务、其他素材、工作流、风格和声音引用;仅清理未复用的本地或 OSS/COS 对象。
This commit is contained in:
1 parent
9f182a2902
commit
c7965e7934
8 files changed
+731
-29
No files matched your search
@@ -0,0 +1,80 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user