// Copyright 2025 linux.do // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 package upload import ( "context" "io" "testing" "time" "github.com/Rain-kl/Wavelet/internal/db" "github.com/Rain-kl/Wavelet/internal/model" "github.com/Rain-kl/Wavelet/internal/storage" "github.com/Rain-kl/Wavelet/internal/task" "github.com/Rain-kl/Wavelet/internal/testhelper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCleanupUnusedUploadsHandler_Execute(t *testing.T) { _, _, cleanup := testhelper.SetupTestEnvironment(t) defer cleanup() // Mock S3 存储(让 DeleteObject 总是成功) storageMock := storage.MockStorage( func(ctx context.Context, key string, body io.Reader, size int64, contentType string) error { return nil }, func(ctx context.Context, key string) (*storage.ObjectInfo, error) { return nil, nil }, func(ctx context.Context, key string) error { return nil }, ) defer storageMock() ctx := context.Background() // 准备测试数据:创建一些上传记录 now := time.Now() twoHoursAgo := now.Add(-2 * time.Hour) records := []*model.Upload{ // 超过1小时且状态为 pending 的记录 —— 应被清理 { UserID: 1001, FileName: "old_file_1.jpg", FilePath: "uploads/old_1.jpg", FileSize: 1024, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash1", StorageDriver: "s3", Type: "attachment", Status: model.UploadStatusPending, CreatedAt: twoHoursAgo, }, { UserID: 1001, FileName: "old_file_2.png", FilePath: "uploads/old_2.png", FileSize: 2048, MimeType: "image/png", Extension: "png", Hash: "hash2", StorageDriver: "s3", Type: "attachment", Status: model.UploadStatusPending, CreatedAt: twoHoursAgo, }, // 状态为 used 的记录 —— 不应被清理 { UserID: 1001, FileName: "used_file.jpg", FilePath: "uploads/used.jpg", FileSize: 512, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash3", StorageDriver: "s3", Type: "attachment", Status: model.UploadStatusUsed, CreatedAt: twoHoursAgo, }, // 不到1小时的 pending 记录 —— 不应被清理 { UserID: 1001, FileName: "recent_file.jpg", FilePath: "uploads/recent.jpg", FileSize: 256, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash4", StorageDriver: "s3", Type: "attachment", Status: model.UploadStatusPending, CreatedAt: now.Add(-10 * time.Minute), }, } for _, r := range records { err := db.DB(ctx).Create(r).Error require.NoError(t, err) } // 执行 handler handler := &CleanupUnusedUploadsHandler{} result, err := handler.Execute(ctx, nil) // 验证结果 require.NoError(t, err) require.NotNil(t, result) assert.Contains(t, result.Message, "共处理 2 个文件,成功删除 2 个") // 验证数据库状态:pending 且超过1小时的应被标记为 deleted var pendingCount int64 db.DB(ctx).Model(&model.Upload{}).Where("status = ?", model.UploadStatusPending).Count(&pendingCount) assert.Equal(t, int64(1), pendingCount, "应只剩1条 pending 记录(最近的文件)") var deletedCount int64 db.DB(ctx).Model(&model.Upload{}).Where("status = ?", model.UploadStatusDeleted).Count(&deletedCount) assert.Equal(t, int64(2), deletedCount, "应有2条被标记为 deleted") var usedCount int64 db.DB(ctx).Model(&model.Upload{}).Where("status = ?", model.UploadStatusUsed).Count(&usedCount) assert.Equal(t, int64(1), usedCount, "used 状态的文件不应受影响") } func TestCleanupUnusedUploadsHandler_ExecuteNoFiles(t *testing.T) { _, _, cleanup := testhelper.SetupTestEnvironment(t) defer cleanup() // Mock S3 存储 storageMock := storage.MockStorage( func(ctx context.Context, key string, body io.Reader, size int64, contentType string) error { return nil }, func(ctx context.Context, key string) (*storage.ObjectInfo, error) { return nil, nil }, func(ctx context.Context, key string) error { return nil }, ) defer storageMock() ctx := context.Background() // 没有任何上传记录 handler := &CleanupUnusedUploadsHandler{} result, err := handler.Execute(ctx, nil) require.NoError(t, err) require.NotNil(t, result) assert.Contains(t, result.Message, "共处理 0 个文件,成功删除 0 个") } func TestCleanupUnusedUploadsHandler_ImplementsTaskHandler(t *testing.T) { // 编译期验证 CleanupUnusedUploadsHandler 实现了 TaskHandler 接口 var _ task.TaskHandler = (*CleanupUnusedUploadsHandler)(nil) }