feat: harden multi-user download concurrency

This commit is contained in:
OpenClaw Agent
2026-09-18 09:06:59 +08:00
parent 0735cb2e09
commit b116303abe
11 changed files with 1003 additions and 202 deletions
+79
View File
@@ -0,0 +1,79 @@
package main
import (
"os"
"path/filepath"
"testing"
)
func TestCleanupLocalFileKeepsSharedAssetForPendingTask(t *testing.T) {
db := openMigrationTestDB(t)
if err := migrateDatabase(db); err != nil {
t.Fatal(err)
}
path := filepath.Join(t.TempDir(), "shared.flac")
if err := os.WriteFile(path, []byte("audio"), 0644); err != nil {
t.Fatal(err)
}
asset := MediaAsset{AssetKey: assetKey("kw", "shared", "lossless"), Source: "kw", RID: "shared", Quality: "lossless", LocalPath: path, Format: "flac", Status: "done", Size: 5}
if err := db.Create(&asset).Error; err != nil {
t.Fatal(err)
}
first := Task{UserID: 1, AssetID: asset.ID, Source: "kw", RID: "shared", Quality: "lossless", Status: "done", LocalPath: path, RemotePath: "Music/shared.flac"}
second := Task{UserID: 2, AssetID: asset.ID, Source: "kw", RID: "shared", Quality: "lossless", Status: "upload_pending", LocalPath: path}
if err := db.Create(&first).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&second).Error; err != nil {
t.Fatal(err)
}
a := &App{db: db, cfg: Config{LocalFilePolicy: "delete_after_upload"}}
a.cleanupLocalFile(&first)
if _, err := os.Stat(path); err != nil {
t.Fatalf("shared file deleted while needed: %v", err)
}
if err := db.Model(&second).Updates(map[string]any{"status": "done", "remote_path": "Music/shared.flac"}).Error; err != nil {
t.Fatal(err)
}
a.cleanupLocalFile(&first)
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("shared file still exists: %v", err)
}
var gotAsset MediaAsset
if err := db.First(&gotAsset, asset.ID).Error; err != nil {
t.Fatal(err)
}
if gotAsset.Status != "evicted" || gotAsset.LocalPath != "" {
t.Fatalf("asset not evicted: %+v", gotAsset)
}
}
func TestRecoverMissingLocalUploadRequeuesTask(t *testing.T) {
db := openMigrationTestDB(t)
if err := migrateDatabase(db); err != nil {
t.Fatal(err)
}
path := filepath.Join(t.TempDir(), "missing.flac")
asset := MediaAsset{AssetKey: assetKey("kw", "missing", "lossless"), Source: "kw", RID: "missing", Quality: "lossless", LocalPath: path, Format: "flac", Status: "done", Size: 10}
if err := db.Create(&asset).Error; err != nil {
t.Fatal(err)
}
task := Task{UserID: 1, AssetID: asset.ID, Source: "kw", RID: "missing", Quality: "lossless", Status: "upload_pending", LocalPath: path}
if err := db.Create(&task).Error; err != nil {
t.Fatal(err)
}
a := &App{db: db, queue: make(chan uint, 1)}
if !a.recoverMissingLocalUpload(&task) {
t.Fatal("missing local file was not requeued")
}
var got Task
if err := db.First(&got, task.ID).Error; err != nil {
t.Fatal(err)
}
if got.Status != "pending" {
t.Fatalf("task status = %q, want pending", got.Status)
}
if <-a.queue != task.ID {
t.Fatal("queued task id mismatch")
}
}