mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
299ac30ee4
- Purify core micro-kernel by removing context hardcoded helpers and reverse dependencies - Eliminate init() side effects in infra plugins with reversible lifecycle disposal - Completely isolate plugins by removing cross-plugin imports and using core/contracts - Introduce TaskService and RiskControlService contracts for unified cross-plugin APIs - Regenerate Swagger documentation and update developer guide matrix - Achieve 0 violations in check_cordis_architecture.sh and 100% test pass
273 lines
7.5 KiB
Go
273 lines
7.5 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package task
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"Wavelet/plugins/domain/upload/filesrv"
|
|
"Wavelet/plugins/domain/upload/models"
|
|
"Wavelet/plugins/domain/upload/shared"
|
|
)
|
|
|
|
func TestSystemCleanupHandler_Execute(t *testing.T) {
|
|
_, cleanup := shared.SetupTestEnv(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
db := shared.GetDB(ctx)
|
|
|
|
// 准备测试数据:创建一些上传记录
|
|
now := time.Now()
|
|
twoHoursAgo := now.Add(-2 * time.Hour)
|
|
|
|
records := []*models.Upload{
|
|
// 超过1小时且状态为 pending 的记录 —— 应被清理
|
|
{
|
|
UserID: 1001, FileName: "old_file_1.jpg", FilePath: "uploads/old_1.jpg",
|
|
FileSize: 1024, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash1",
|
|
Type: "attachment", Status: models.UploadStatusPending,
|
|
CreatedAt: twoHoursAgo,
|
|
},
|
|
{
|
|
UserID: 1001, FileName: "old_file_2.png", FilePath: "uploads/old_2.png",
|
|
FileSize: 2048, MimeType: "image/png", Extension: "png", Hash: "hash2",
|
|
Type: "attachment", Status: models.UploadStatusPending,
|
|
CreatedAt: twoHoursAgo,
|
|
},
|
|
// 状态为 used 的记录 —— 不应被清理
|
|
{
|
|
UserID: 1001, FileName: "used_file.jpg", FilePath: "uploads/used.jpg",
|
|
FileSize: 512, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash3",
|
|
Type: "attachment", Status: models.UploadStatusUsed,
|
|
CreatedAt: twoHoursAgo,
|
|
},
|
|
// 不到1小时的 pending 记录 —— 不应被清理
|
|
{
|
|
UserID: 1001, FileName: "recent_file.jpg", FilePath: "uploads/recent.jpg",
|
|
FileSize: 256, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash4",
|
|
Type: "attachment", Status: models.UploadStatusPending,
|
|
CreatedAt: now.Add(-10 * time.Minute),
|
|
},
|
|
}
|
|
for _, r := range records {
|
|
err := db.Create(r).Error
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// 执行 handler
|
|
handler := &SystemCleanupHandler{}
|
|
result, err := handler.Execute(ctx, nil)
|
|
|
|
// 验证结果
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
assert.Contains(t, result.Message, "系统垃圾清理完成")
|
|
|
|
// 验证数据库状态:pending 且超过1小时的已被清理
|
|
var pendingCount int64
|
|
db.Model(&models.Upload{}).Where("status = ?", models.UploadStatusPending).Count(&pendingCount)
|
|
assert.Equal(t, int64(1), pendingCount, "应只剩1条 pending 记录(最近的文件)")
|
|
|
|
var usedCount int64
|
|
db.Model(&models.Upload{}).Where("status = ?", models.UploadStatusUsed).Count(&usedCount)
|
|
assert.Equal(t, int64(1), usedCount, "used 状态的文件不应受影响")
|
|
}
|
|
|
|
func TestSystemCleanupHandler_ExecuteNoFiles(t *testing.T) {
|
|
_, cleanup := shared.SetupTestEnv(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
|
|
// 没有任何上传记录
|
|
handler := &SystemCleanupHandler{}
|
|
result, err := handler.Execute(ctx, nil)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
assert.Contains(t, result.Message, "系统垃圾清理完成")
|
|
}
|
|
|
|
func TestWarmImageCacheHandlerValidatePayload(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
payload []byte
|
|
wantQuality string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "normalizes quality",
|
|
payload: []byte(`{"quality":" HIGH "}`),
|
|
wantQuality: shared.ImageQualityHigh,
|
|
},
|
|
{
|
|
name: "empty payload",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid json",
|
|
payload: []byte(`{`),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "origin is not a compressed quality",
|
|
payload: []byte(`{"quality":"origin"}`),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "unsupported quality",
|
|
payload: []byte(`{"quality":"maximum"}`),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
handler := &WarmImageCacheHandler{}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotPayload, err := handler.ValidatePayload(tt.payload)
|
|
if gotErr := err != nil; gotErr != tt.wantErr {
|
|
t.Fatalf("ValidatePayload(%s) error = %v, want error presence = %t", tt.payload, err, tt.wantErr)
|
|
}
|
|
if tt.wantErr {
|
|
return
|
|
}
|
|
|
|
var got WarmImageCachePayload
|
|
if err := json.Unmarshal(gotPayload, &got); err != nil {
|
|
t.Fatalf("json.Unmarshal(%s) returned error: %v", gotPayload, err)
|
|
}
|
|
if got.Quality != tt.wantQuality {
|
|
t.Errorf("ValidatePayload(%s).Quality = %q, want %q", tt.payload, got.Quality, tt.wantQuality)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWarmImageCacheHandlerExecute(t *testing.T) {
|
|
dbConn, cleanup := shared.SetupTestEnv(t)
|
|
defer cleanup()
|
|
|
|
testDir := t.TempDir()
|
|
firstPath := filepath.Join(testDir, "first.png")
|
|
secondPath := filepath.Join(testDir, "second.jpg")
|
|
writeTaskTestPNG(t, firstPath, color.RGBA{R: 255, A: 255})
|
|
writeTaskTestPNG(t, secondPath, color.RGBA{G: 255, A: 255})
|
|
|
|
records := []models.Upload{
|
|
{
|
|
ID: 4101,
|
|
UserID: 1001,
|
|
FileName: "first.png",
|
|
FilePath: firstPath,
|
|
MimeType: "image/png",
|
|
Extension: "png",
|
|
Status: models.UploadStatusUsed,
|
|
},
|
|
{
|
|
ID: 4102,
|
|
UserID: 1001,
|
|
FileName: "second.jpg",
|
|
FilePath: secondPath,
|
|
MimeType: "application/octet-stream",
|
|
Extension: "jpg",
|
|
Status: models.UploadStatusPending,
|
|
},
|
|
{
|
|
ID: 4103,
|
|
UserID: 1001,
|
|
FileName: "notes.txt",
|
|
FilePath: filepath.Join(testDir, "notes.txt"),
|
|
MimeType: "text/plain",
|
|
Extension: "txt",
|
|
Status: models.UploadStatusUsed,
|
|
},
|
|
{
|
|
ID: 4104,
|
|
UserID: 1001,
|
|
FileName: "deleted.png",
|
|
FilePath: firstPath,
|
|
MimeType: "image/png",
|
|
Extension: "png",
|
|
Status: models.UploadStatusDeleted,
|
|
},
|
|
}
|
|
for i := range records {
|
|
if info, err := os.Stat(records[i].FilePath); err == nil {
|
|
records[i].FileSize = info.Size()
|
|
}
|
|
if err := dbConn.Create(&records[i]).Error; err != nil {
|
|
t.Fatalf("failed to create upload %d: %v", records[i].ID, err)
|
|
}
|
|
}
|
|
|
|
handler := &WarmImageCacheHandler{}
|
|
payload := []byte(`{"quality":"low"}`)
|
|
|
|
result, err := handler.Execute(context.Background(), payload)
|
|
if err != nil {
|
|
t.Fatalf("Execute(%s) returned error: %v", payload, err)
|
|
}
|
|
if result == nil {
|
|
t.Fatal("Execute() result = nil, want non-nil")
|
|
}
|
|
if result.Message != "图片缓存预热完成,共处理 2 张,生成 2 张,命中 0 张,失败 0 张" {
|
|
t.Errorf("Execute() message = %q, want generated summary", result.Message)
|
|
}
|
|
|
|
for i := range records[:2] {
|
|
key := filesrv.ImageCompressionCacheKey(&records[i], shared.ImageQualityLow)
|
|
got, hit, err := filesrv.EnsureCompressedImageCache(context.Background(), &records[i], shared.ImageQualityLow)
|
|
if err != nil {
|
|
t.Errorf("EnsureCompressedImageCache(%q) returned error: %v", key, err)
|
|
continue
|
|
}
|
|
if !hit {
|
|
t.Errorf("expected cache hit for %q", key)
|
|
}
|
|
if len(got) == 0 {
|
|
t.Errorf("EnsureCompressedImageCache(%q) returned empty WebP data", key)
|
|
}
|
|
}
|
|
|
|
secondResult, err := handler.Execute(context.Background(), payload)
|
|
if err != nil {
|
|
t.Fatalf("second Execute(%s) returned error: %v", payload, err)
|
|
}
|
|
if secondResult.Message != "图片缓存预热完成,共处理 2 张,生成 0 张,命中 2 张,失败 0 张" {
|
|
t.Errorf("second Execute() message = %q, want cache-hit summary", secondResult.Message)
|
|
}
|
|
}
|
|
|
|
func writeTaskTestPNG(t *testing.T, path string, fill color.RGBA) {
|
|
t.Helper()
|
|
|
|
img := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
|
for y := 0; y < 2; y++ {
|
|
for x := 0; x < 2; x++ {
|
|
img.Set(x, y, fill)
|
|
}
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err != nil {
|
|
t.Fatalf("png.Encode() returned error: %v", err)
|
|
}
|
|
if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
|
|
t.Fatalf("os.WriteFile(%q) returned error: %v", path, err)
|
|
}
|
|
}
|