This commit is contained in:
toom1996
2026-09-07 00:04:01 +08:00
parent 6a5a4378ab
commit 10d8a96e8c
29 changed files with 5349 additions and 0 deletions

View File

@ -0,0 +1,86 @@
package service
import (
"context"
"testing"
"fashionapi/internal/model"
"fashionapi/internal/pkg/storage"
"fashionapi/internal/repository"
)
// fakeMediaRepo 计数固定返回 0:即所有 key 都是孤儿,应被真删(不误删的前提是调用方已软删引用)。
type fakeMediaRepo struct{ repository.MediaRepository }
func (fakeMediaRepo) CountImageKeyRefs(ctx context.Context, key string) (int, error) { return 0, nil }
// fakeIngestRepo 仅实现 processMediaCleanup 需要的 MarkDone/MarkFailed(其余由内嵌 nil 接口兜底,不会被调用)。
type fakeIngestRepo struct{ repository.IngestRepository }
func (fakeIngestRepo) MarkDone(ctx context.Context, id uint32) error { return nil }
func (fakeIngestRepo) MarkFailed(ctx context.Context, id uint32, errMsg string) error {
return nil
}
// countingDeleter 包装 LocalUploader,记录被删除的 key,便于断言。
type countingDeleter struct {
*storage.LocalUploader
deleted []string
}
func (c *countingDeleter) Delete(ctx context.Context, key string) error {
c.deleted = append(c.deleted, key)
return c.LocalUploader.Delete(ctx, key)
}
// TestProcessMediaCleanup 验证「清理七牛孤儿图」任务:引用计数为 0 的 key 会被真删。
func TestProcessMediaCleanup(t *testing.T) {
dir := t.TempDir()
up := storage.NewLocalUploader(dir, "/uploads")
del := &countingDeleter{LocalUploader: up}
// 预置一个"孤儿"文件,确认它确实存在。
if _, err := up.Upload(context.Background(), "runway/abc.jpg", []byte("x"), "image/jpeg"); err != nil {
t.Fatalf("预置文件失败: %v", err)
}
svc := NewIngestService(fakeIngestRepo{}, nil, fakeMediaRepo{}, up, del, up)
job := model.IngestJob{Kind: model.IngestKindMediaCleanup, Payload: `{"keys":["runway/abc.jpg"]}`}
svc.processMediaCleanup(context.Background(), job)
if len(del.deleted) != 1 || del.deleted[0] != "runway/abc.jpg" {
t.Fatalf("期望删除 1 个孤儿 key,实际 %v", del.deleted)
}
}
// TestProcessMediaCleanupSkipsReferenced 引用计数 > 0 的 key 必须保留(不误删共享 key)。
func TestProcessMediaCleanupSkipsReferenced(t *testing.T) {
dir := t.TempDir()
up := storage.NewLocalUploader(dir, "/uploads")
del := &countingDeleter{LocalUploader: up}
// 引用计数恒为 1(仍被其他图集使用),不应真删。
svc := NewIngestService(fakeIngestRepo{}, nil, referencedMediaRepo{}, up, del, up)
job := model.IngestJob{Kind: model.IngestKindMediaCleanup, Payload: `{"keys":["runway/shared.jpg"]}`}
svc.processMediaCleanup(context.Background(), job)
if len(del.deleted) != 0 {
t.Fatalf("被引用的 key 不应被删除,实际 %v", del.deleted)
}
}
type referencedMediaRepo struct{ repository.MediaRepository }
func (referencedMediaRepo) CountImageKeyRefs(ctx context.Context, key string) (int, error) {
return 1, nil
}
// TestIngestRetryBackoff 验证指数退避:30s / 60s / 120s 且封顶 600s。
func TestIngestRetryBackoff(t *testing.T) {
cases := map[int]int{1: 30, 2: 60, 3: 120, 10: 600}
for attempts, want := range cases {
if got := model.IngestRetryBackoff(attempts); got != want {
t.Fatalf("IngestRetryBackoff(%d)=%d, want %d", attempts, got, want)
}
}
}