单测包不经 main,缺驱动空导入导致 go test ./workflow/dao/flow/ 报 "cannot find database driver for specified database type pgsql"。 新增 TestFlowSegmentResultDaoResurrect:验证 DeleteByExecution 软删后, 对同一 (execution_id, node_id, segment_index) 重新 Save 能否清除 deleted_at 使 ListByNode 再次可见。实测 upsert 的 DO UPDATE SET 子句 不含 deleted_at,段2 不复活 —— 留作失败证据供控制器裁决。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
114 lines
4.4 KiB
Go
114 lines
4.4 KiB
Go
package flow
|
||
|
||
import (
|
||
"context"
|
||
"os"
|
||
"testing"
|
||
"time"
|
||
|
||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||
|
||
// 空导入:pgsql 驱动。main.go 里已有,但 go test ./workflow/dao/flow/ 单测包不经 main,
|
||
// 不加则报 "cannot find database driver for specified database type pgsql"
|
||
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||
)
|
||
|
||
// TestFlowSegmentResultDao 段结果 DAO 集成测试:Save / ListByNode / DeleteByExecution。
|
||
// 依赖本地 black_deacon + 用户信息注入;默认跳过,设置 AI_AGENT_TEST_DB=1 时运行。
|
||
func TestFlowSegmentResultDao(t *testing.T) {
|
||
if os.Getenv("AI_AGENT_TEST_DB") == "" {
|
||
t.Skip("设置 AI_AGENT_TEST_DB=1 且本地 black_deacon 可用时运行")
|
||
}
|
||
// gfdb Insert/Select 钩子从 ctx 读用户信息补 tenant_id/creator 并追加租户过滤
|
||
ctx := context.WithValue(context.Background(), "user", &beans.User{UserName: "unit_test", TenantId: 999999})
|
||
execId := time.Now().UnixMilli()
|
||
nodeId := "seg_test_node"
|
||
|
||
dao := FlowSegmentResultDao
|
||
if err := dao.Save(ctx, execId, nodeId, 1, "video_url", "http://host/u1.mp4"); err != nil {
|
||
t.Fatalf("Save 段1失败: %v", err)
|
||
}
|
||
if err := dao.Save(ctx, execId, nodeId, 2, "video_url", "http://host/u2.mp4"); err != nil {
|
||
t.Fatalf("Save 段2失败: %v", err)
|
||
}
|
||
|
||
// 唯一键冲突 → 更新不报错
|
||
if err := dao.Save(ctx, execId, nodeId, 2, "video_url", "http://host/u2-new.mp4"); err != nil {
|
||
t.Fatalf("Save 段2(冲突更新)失败: %v", err)
|
||
}
|
||
|
||
m, err := dao.ListByNode(ctx, execId, nodeId)
|
||
if err != nil {
|
||
t.Fatalf("ListByNode失败: %v", err)
|
||
}
|
||
if len(m) != 2 {
|
||
t.Fatalf("期望 2 段,实际 %d", len(m))
|
||
}
|
||
if m[1].URL != "http://host/u1.mp4" || m[2].URL != "http://host/u2-new.mp4" {
|
||
t.Fatalf("段内容不符: %+v", m)
|
||
}
|
||
if m[2].Key != "video_url" {
|
||
t.Fatalf("段2 key 不符: %s", m[2].Key)
|
||
}
|
||
|
||
if err := dao.DeleteByExecution(ctx, execId); err != nil {
|
||
t.Fatalf("DeleteByExecution失败: %v", err)
|
||
}
|
||
m, err = dao.ListByNode(ctx, execId, nodeId)
|
||
if err != nil {
|
||
t.Fatalf("删除后 ListByNode失败: %v", err)
|
||
}
|
||
if len(m) != 0 {
|
||
t.Fatalf("删除后应无段结果,实际 %d", len(m))
|
||
}
|
||
}
|
||
|
||
// TestFlowSegmentResultDaoResurrect 软删→重存的"复活"路径验证。
|
||
// 实体嵌入 beans.SQLBaseDO(含 deleted_at),GoFrame 将其识别为软删除列:
|
||
// DeleteByExecution 实际执行的是 UPDATE ... SET deleted_at=now(),而非物理 DELETE。
|
||
// 本测试验证随后对同一 (execution_id, node_id, segment_index) 的 Save(命中
|
||
// ON CONFLICT ... DO UPDATE)是否会清除 deleted_at,使 ListByNode 再次可见该段。
|
||
// 若不可见,说明 upsert 的 SET 子句不含 deleted_at,复活路径被破坏 → 续跑复用静默失效。
|
||
func TestFlowSegmentResultDaoResurrect(t *testing.T) {
|
||
if os.Getenv("AI_AGENT_TEST_DB") == "" {
|
||
t.Skip("设置 AI_AGENT_TEST_DB=1 且本地 black_deacon 可用时运行")
|
||
}
|
||
ctx := context.WithValue(context.Background(), "user", &beans.User{UserName: "unit_test", TenantId: 999999})
|
||
execId := time.Now().UnixMilli()
|
||
nodeId := "seg_test_resurrect"
|
||
dao := FlowSegmentResultDao
|
||
|
||
// 软删即可,物理残留由外部硬清理(docker exec psql DELETE FROM)负责
|
||
t.Cleanup(func() { _ = dao.DeleteByExecution(ctx, execId) })
|
||
|
||
if err := dao.Save(ctx, execId, nodeId, 1, "video_url", "http://host/u1-resurrect.mp4"); err != nil {
|
||
t.Fatalf("Save 段1失败: %v", err)
|
||
}
|
||
if err := dao.Save(ctx, execId, nodeId, 2, "video_url", "http://host/u2-resurrect.mp4"); err != nil {
|
||
t.Fatalf("Save 段2失败: %v", err)
|
||
}
|
||
|
||
// 软删除整条执行
|
||
if err := dao.DeleteByExecution(ctx, execId); err != nil {
|
||
t.Fatalf("DeleteByExecution失败: %v", err)
|
||
}
|
||
|
||
// 重新保存段2(同一 exec/node/idx,不同 URL):
|
||
// 若 ON CONFLICT DO UPDATE 的 SET 子句不含 deleted_at,段2 在 ListByNode 中仍不可见
|
||
if err := dao.Save(ctx, execId, nodeId, 2, "video_url", "http://host/u2-resurrect.mp4"); err != nil {
|
||
t.Fatalf("Save 段2(软删后重存)失败: %v", err)
|
||
}
|
||
|
||
m, err := dao.ListByNode(ctx, execId, nodeId)
|
||
if err != nil {
|
||
t.Fatalf("ListByNode失败: %v", err)
|
||
}
|
||
// 期望:段2 复活(deleted_at 被 upsert 清除),len(m)==2 且 m[2] 为更新后的 URL
|
||
if len(m) != 2 {
|
||
t.Fatalf("软删后重存应复活段2(len=2),实际 len=%d: %+v", len(m), m)
|
||
}
|
||
if m[2].URL != "http://host/u2-resurrect.mp4" {
|
||
t.Fatalf("段2 应恢复为更新后的 URL,实际: %+v", m[2])
|
||
}
|
||
}
|