mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-12 00:06:37 +08:00
117 lines
4.1 KiB
Go
117 lines
4.1 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// 长期运行时各类缓存必须有上限或清理,否则会慢慢吃满磁盘。
|
|
// 这里覆盖新增的「书源文件缓存 + 孤儿临时文件」清理任务。
|
|
|
|
func writeAgedFile(t *testing.T, path string, age time.Duration) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stamp := time.Now().Add(-age)
|
|
if err := os.Chtimes(path, stamp, stamp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestJobCleanReaderFilesPrunesByRetention(t *testing.T) {
|
|
cacheDir := t.TempDir()
|
|
s := &SchedulerService{cacheDir: cacheDir, readerFilesTTLHours: 24}
|
|
|
|
// 超龄的书源文件(字体/静态 JS)应被删掉;新文件保留。
|
|
oldFile := filepath.Join(cacheDir, "reader", "files", "abc.woff")
|
|
newFile := filepath.Join(cacheDir, "reader", "files", "nested", "fresh.js")
|
|
writeAgedFile(t, oldFile, 48*time.Hour)
|
|
writeAgedFile(t, newFile, time.Hour)
|
|
|
|
if err := s.jobCleanReaderFiles(context.Background()); err != nil {
|
|
t.Fatalf("清理失败: %v", err)
|
|
}
|
|
if _, err := os.Stat(oldFile); !os.IsNotExist(err) {
|
|
t.Fatalf("超龄书源文件应被删除,err=%v", err)
|
|
}
|
|
if _, err := os.Stat(newFile); err != nil {
|
|
t.Fatalf("未超龄文件不应被删除: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestJobCleanReaderFilesRemovesOrphanTempFiles(t *testing.T) {
|
|
cacheDir := t.TempDir()
|
|
s := &SchedulerService{cacheDir: cacheDir, readerFilesTTLHours: 24}
|
|
ctx := context.Background()
|
|
|
|
// 中断的转码产物 .mp3.part 与正文缓存的 .content-* 临时文件。
|
|
oldPart := filepath.Join(cacheDir, "reader-audio", "deadbeef.mp3.part")
|
|
freshPart := filepath.Join(cacheDir, "reader-audio", "running.mp3.part")
|
|
oldContentTmp := filepath.Join(cacheDir, "reader-content", "aa", "bb", ".content-123")
|
|
// 成品与正常缓存文件不能被误删。
|
|
finished := filepath.Join(cacheDir, "reader-audio", "done.mp3")
|
|
contentJSON := filepath.Join(cacheDir, "reader-content", "aa", "bb", "chapter.json")
|
|
|
|
writeAgedFile(t, oldPart, 48*time.Hour)
|
|
writeAgedFile(t, freshPart, time.Minute)
|
|
writeAgedFile(t, oldContentTmp, 48*time.Hour)
|
|
writeAgedFile(t, finished, 48*time.Hour)
|
|
writeAgedFile(t, contentJSON, 48*time.Hour)
|
|
|
|
if err := s.jobCleanReaderFiles(ctx); err != nil {
|
|
t.Fatalf("清理失败: %v", err)
|
|
}
|
|
if _, err := os.Stat(oldPart); !os.IsNotExist(err) {
|
|
t.Fatalf("孤儿 .part 应被删除,err=%v", err)
|
|
}
|
|
if _, err := os.Stat(oldContentTmp); !os.IsNotExist(err) {
|
|
t.Fatalf("孤儿 .content-* 应被删除,err=%v", err)
|
|
}
|
|
if _, err := os.Stat(freshPart); err != nil {
|
|
t.Fatalf("正在写入的 .part 不应被删除: %v", err)
|
|
}
|
|
if _, err := os.Stat(finished); err != nil {
|
|
t.Fatalf("转码成品不应被删除: %v", err)
|
|
}
|
|
if _, err := os.Stat(contentJSON); err != nil {
|
|
t.Fatalf("正文缓存文件不应被删除: %v", err)
|
|
}
|
|
}
|
|
|
|
// 未配置 TTL 时用兜底保留时长,而不是「不清理」。
|
|
func TestJobCleanReaderFilesFallsBackToDefaultRetention(t *testing.T) {
|
|
cacheDir := t.TempDir()
|
|
s := &SchedulerService{cacheDir: cacheDir} // readerFilesTTLHours = 0
|
|
|
|
// 40 天前的文件应被兜底规则(30 天)清掉。
|
|
veryOld := filepath.Join(cacheDir, "reader", "files", "ancient.js")
|
|
writeAgedFile(t, veryOld, 40*24*time.Hour)
|
|
|
|
if err := s.jobCleanReaderFiles(context.Background()); err != nil {
|
|
t.Fatalf("清理失败: %v", err)
|
|
}
|
|
if _, err := os.Stat(veryOld); !os.IsNotExist(err) {
|
|
t.Fatalf("未配置 TTL 也应有兜底清理,err=%v", err)
|
|
}
|
|
}
|
|
|
|
// 目录不存在时不应报错(缓存未启用或尚未产生文件)。
|
|
func TestJobCleanReaderFilesMissingDirs(t *testing.T) {
|
|
s := &SchedulerService{cacheDir: filepath.Join(t.TempDir(), "nope")}
|
|
if err := s.jobCleanReaderFiles(context.Background()); err != nil {
|
|
t.Fatalf("目录不存在应静默通过: %v", err)
|
|
}
|
|
// cacheDir 为空(未配置)同样直接返回。
|
|
empty := &SchedulerService{}
|
|
if err := empty.jobCleanReaderFiles(context.Background()); err != nil {
|
|
t.Fatalf("未配置缓存目录应静默通过: %v", err)
|
|
}
|
|
}
|