mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 05:56:38 +08:00
fix(admin): propagate cache errors in FlushTaskExecutionLog
缓存故障(非 ErrCacheMiss)不再被静默吞掉:上抛包装错误,避免缓冲任务日志丢失并误报持久化成功; ErrCacheMiss 仍视为无日志的正常路径。附 3 个回归测试(故障/未命中/持久化+清理)。 lint_issues 34→33 (nilerr 清零)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/pkg/cache/ram"
|
||||
"Wavelet/pkg/idgen"
|
||||
"Wavelet/pkg/util"
|
||||
@@ -502,7 +503,15 @@ func FlushTaskExecutionLog(ctx context.Context, taskID string) error {
|
||||
|
||||
key := taskExecutionLogRedisKey(taskID)
|
||||
var logText string
|
||||
if err := cacheSvc.Get(ctx, key, &logText); err != nil || logText == "" {
|
||||
if err := cacheSvc.Get(ctx, key, &logText); err != nil {
|
||||
// 缓存未命中属于正常情况(任务无输出),其余错误必须上抛,
|
||||
// 否则缓冲日志会被静默丢弃并误报持久化成功。
|
||||
if !errors.Is(err, contracts.ErrCacheMiss) {
|
||||
return fmt.Errorf("load buffered task execution log: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if logText == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/redis/go-redis/v9/maintnotifications"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"Wavelet/core"
|
||||
"Wavelet/core/contracts"
|
||||
cacheplugin "Wavelet/plugins/infra/cache"
|
||||
)
|
||||
|
||||
// stubDBService 用内存 SQLite 满足 DBService 契约,隔离外部依赖。
|
||||
type stubDBService struct{ db *gorm.DB }
|
||||
|
||||
func (s stubDBService) GORM() *gorm.DB { return s.db }
|
||||
|
||||
func (s stubDBService) DB(context.Context) *gorm.DB { return s.db }
|
||||
|
||||
func (s stubDBService) Named(string) *gorm.DB { return s.db }
|
||||
|
||||
// newFlushLogTestCache 构建真实多层缓存服务并注入 admin 插件上下文。
|
||||
func newFlushLogTestCache(t *testing.T) (contracts.CacheService, *miniredis.Miniredis, func()) {
|
||||
t.Helper()
|
||||
mr, err := miniredis.Run()
|
||||
require.NoError(t, err)
|
||||
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr(), MaintNotificationsConfig: &maintnotifications.Config{Mode: maintnotifications.ModeDisabled}})
|
||||
|
||||
p := cacheplugin.New(cacheplugin.WithRedis(rdb), cacheplugin.WithRAMCapacity(64))
|
||||
ctx := core.NewContext(context.Background())
|
||||
require.NoError(t, p.Apply(ctx))
|
||||
svc, err := core.Inject[contracts.CacheService](ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
prev := cacheService
|
||||
SetCacheService(svc)
|
||||
cleanup := func() {
|
||||
SetCacheService(prev)
|
||||
_ = rdb.Close()
|
||||
mr.Close()
|
||||
}
|
||||
return svc, mr, cleanup
|
||||
}
|
||||
|
||||
// TestFlushTaskExecutionLogPropagatesCacheError 回归:缓存读取失败(非未命中)时,
|
||||
// FlushTaskExecutionLog 必须返回错误而不是静默吞掉日志并误报成功(nilerr 修复)。
|
||||
func TestFlushTaskExecutionLogPropagatesCacheError(t *testing.T) {
|
||||
_, mr, cleanup := newFlushLogTestCache(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
const taskID = "flush-err-task"
|
||||
|
||||
// 先缓冲一行日志
|
||||
require.NoError(t, AppendTaskExecutionLog(ctx, taskID, "step-1 ok"))
|
||||
|
||||
// 关闭 miniredis 模拟缓存基础设施故障(读取出错而非未命中)
|
||||
mr.Close()
|
||||
|
||||
err := FlushTaskExecutionLog(ctx, taskID)
|
||||
assert.Error(t, err, "缓存故障时必须返回错误,防止缓冲日志被静默丢弃")
|
||||
}
|
||||
|
||||
// TestFlushTaskExecutionLogCacheMissIsNoop 回归:任务无缓冲日志(未命中)时应为空操作成功。
|
||||
func TestFlushTaskExecutionLogCacheMissIsNoop(t *testing.T) {
|
||||
_, _, cleanup := newFlushLogTestCache(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
assert.NoError(t, FlushTaskExecutionLog(ctx, "missing-task"))
|
||||
}
|
||||
|
||||
// TestFlushTaskExecutionLogPersistsAndClears 验证正常路径:缓冲日志写入执行记录后清理缓存。
|
||||
func TestFlushTaskExecutionLogPersistsAndClears(t *testing.T) {
|
||||
svc, _, cleanup := newFlushLogTestCache(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
const taskID = "flush-ok-task"
|
||||
require.NoError(t, AppendTaskExecutionLog(ctx, taskID, "done"))
|
||||
|
||||
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&TaskExecution{}))
|
||||
SetDBService(stubDBService{db: sqliteDB})
|
||||
defer SetDBService(nil)
|
||||
gormDB := sqliteDB
|
||||
exec := &TaskExecution{TaskID: taskID, TaskType: "upload:test", TaskName: "t", Status: TaskExecutionStatusSucceeded}
|
||||
require.NoError(t, gormDB.Create(exec).Error)
|
||||
|
||||
require.NoError(t, FlushTaskExecutionLog(ctx, taskID))
|
||||
|
||||
var got TaskExecution
|
||||
require.NoError(t, gormDB.First(&got, exec.ID).Error)
|
||||
assert.Contains(t, got.Log, "done")
|
||||
|
||||
// 缓存中的缓冲日志应已被清理
|
||||
var buf string
|
||||
err = svc.Get(ctx, taskExecutionLogRedisKey(taskID), &buf)
|
||||
assert.True(t, errors.Is(err, contracts.ErrCacheMiss), "flush 后缓存应清空, got %v", err)
|
||||
}
|
||||
Reference in New Issue
Block a user