Files
backend_v2/internal/service/ingest_service_test.go
2026-09-20 01:06:13 +08:00

87 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 验证「清理S4孤儿图」任务:引用计数为 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)
}
}
}