Files
nofx/decision/prompt_reload_integration_test.go
2025-11-28 21:34:27 +08:00

244 lines
7.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package decision
import (
"os"
"path/filepath"
"strings"
"testing"
)
// TestPromptReloadEndToEnd 端到端测试:验证从文件修改到决策引擎使用的完整流程
func TestPromptReloadEndToEnd(t *testing.T) {
// 保存原始的 promptsDir
originalDir := promptsDir
defer func() {
promptsDir = originalDir
// 恢复原始模板
globalPromptManager.ReloadTemplates(originalDir)
}()
// 创建临时目录模拟 prompts/ 目录
tempDir := t.TempDir()
promptsDir = tempDir
// 步骤1: 创建初始 prompt 文件
initialContent := "# 初始交易策略\n你是一个保守的交易AI。"
if err := os.WriteFile(filepath.Join(tempDir, "test_strategy.txt"), []byte(initialContent), 0644); err != nil {
t.Fatalf("创建初始文件失败: %v", err)
}
// 步骤2: 首次加载(模拟系统启动)
if err := ReloadPromptTemplates(); err != nil {
t.Fatalf("首次加载失败: %v", err)
}
// 步骤3: 验证初始内容
template, err := GetPromptTemplate("test_strategy")
if err != nil {
t.Fatalf("获取初始模板失败: %v", err)
}
if template.Content != initialContent {
t.Errorf("初始内容不匹配\n期望: %s\n实际: %s", initialContent, template.Content)
}
// 步骤4: 使用 buildSystemPrompt 验证模板被正确使用
systemPrompt := buildSystemPrompt(10000.0, 10, 5, "test_strategy", "")
if !strings.Contains(systemPrompt, initialContent) {
t.Errorf("buildSystemPrompt 未包含模板内容\n生成的 prompt:\n%s", systemPrompt)
}
// 步骤5: 模拟用户修改文件(这是用户在硬盘上修改 prompt
updatedContent := "# 更新的交易策略\n你是一个激进的交易AI追求高风险高收益。"
if err := os.WriteFile(filepath.Join(tempDir, "test_strategy.txt"), []byte(updatedContent), 0644); err != nil {
t.Fatalf("更新文件失败: %v", err)
}
// 步骤6: 模拟交易员启动时调用 ReloadPromptTemplates()
t.Log("模拟交易员启动,调用 ReloadPromptTemplates()...")
if err := ReloadPromptTemplates(); err != nil {
t.Fatalf("重新加载失败: %v", err)
}
// 步骤7: 验证新内容已生效
reloadedTemplate, err := GetPromptTemplate("test_strategy")
if err != nil {
t.Fatalf("获取重新加载的模板失败: %v", err)
}
if reloadedTemplate.Content != updatedContent {
t.Errorf("重新加载后内容不匹配\n期望: %s\n实际: %s", updatedContent, reloadedTemplate.Content)
}
// 步骤8: 验证 buildSystemPrompt 使用了新内容
newSystemPrompt := buildSystemPrompt(10000.0, 10, 5, "test_strategy", "")
if !strings.Contains(newSystemPrompt, updatedContent) {
t.Errorf("buildSystemPrompt 未包含更新后的模板内容\n生成的 prompt:\n%s", newSystemPrompt)
}
// 步骤9: 验证旧内容不再存在
if strings.Contains(newSystemPrompt, "保守的交易AI") {
t.Errorf("buildSystemPrompt 仍包含旧的模板内容")
}
t.Log("✅ 端到端测试通过:文件修改 -> 重新加载 -> 决策引擎使用新内容")
}
// TestPromptReloadWithCustomPrompt 测试自定义 prompt 与模板重新加载的交互
func TestPromptReloadWithCustomPrompt(t *testing.T) {
// 保存原始的 promptsDir
originalDir := promptsDir
defer func() {
promptsDir = originalDir
globalPromptManager.ReloadTemplates(originalDir)
}()
// 创建临时目录
tempDir := t.TempDir()
promptsDir = tempDir
// 创建基础模板
baseContent := "基础策略:稳健交易"
if err := os.WriteFile(filepath.Join(tempDir, "base.txt"), []byte(baseContent), 0644); err != nil {
t.Fatalf("创建文件失败: %v", err)
}
// 加载模板
if err := ReloadPromptTemplates(); err != nil {
t.Fatalf("加载失败: %v", err)
}
// 测试1: 基础模板 + 自定义 prompt不覆盖
customPrompt := "个性化规则:只交易 BTC"
result := buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, false, "base", "")
if !strings.Contains(result, baseContent) {
t.Errorf("未包含基础模板内容")
}
if !strings.Contains(result, customPrompt) {
t.Errorf("未包含自定义 prompt")
}
// 测试2: 覆盖基础 prompt
result = buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, true, "base", "")
if strings.Contains(result, baseContent) {
t.Errorf("覆盖模式下仍包含基础模板内容")
}
if !strings.Contains(result, customPrompt) {
t.Errorf("覆盖模式下未包含自定义 prompt")
}
// 测试3: 重新加载后效果
updatedBase := "更新的基础策略:激进交易"
if err := os.WriteFile(filepath.Join(tempDir, "base.txt"), []byte(updatedBase), 0644); err != nil {
t.Fatalf("更新文件失败: %v", err)
}
if err := ReloadPromptTemplates(); err != nil {
t.Fatalf("重新加载失败: %v", err)
}
result = buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, false, "base", "")
if !strings.Contains(result, updatedBase) {
t.Errorf("重新加载后未包含更新的基础模板内容")
}
if strings.Contains(result, baseContent) {
t.Errorf("重新加载后仍包含旧的基础模板内容")
}
}
// TestPromptReloadFallback 测试模板不存在时的降级机制
func TestPromptReloadFallback(t *testing.T) {
// 保存原始的 promptsDir
originalDir := promptsDir
defer func() {
promptsDir = originalDir
globalPromptManager.ReloadTemplates(originalDir)
}()
// 创建临时目录
tempDir := t.TempDir()
promptsDir = tempDir
// 只创建 default 模板
defaultContent := "默认策略"
if err := os.WriteFile(filepath.Join(tempDir, "default.txt"), []byte(defaultContent), 0644); err != nil {
t.Fatalf("创建文件失败: %v", err)
}
if err := ReloadPromptTemplates(); err != nil {
t.Fatalf("加载失败: %v", err)
}
// 测试1: 请求不存在的模板,应该降级到 default
result := buildSystemPrompt(10000.0, 10, 5, "nonexistent", "")
if !strings.Contains(result, defaultContent) {
t.Errorf("请求不存在的模板时,未降级到 default")
}
// 测试2: 空模板名,应该使用 default
result = buildSystemPrompt(10000.0, 10, 5, "", "")
if !strings.Contains(result, defaultContent) {
t.Errorf("空模板名时,未使用 default")
}
}
// TestConcurrentPromptReload 测试并发场景下的 prompt 重新加载
func TestConcurrentPromptReload(t *testing.T) {
// 保存原始的 promptsDir
originalDir := promptsDir
defer func() {
promptsDir = originalDir
globalPromptManager.ReloadTemplates(originalDir)
}()
// 创建临时目录
tempDir := t.TempDir()
promptsDir = tempDir
// 创建测试文件
if err := os.WriteFile(filepath.Join(tempDir, "test.txt"), []byte("测试内容"), 0644); err != nil {
t.Fatalf("创建文件失败: %v", err)
}
if err := ReloadPromptTemplates(); err != nil {
t.Fatalf("初始加载失败: %v", err)
}
// 并发测试:同时读取和重新加载
done := make(chan bool)
// 启动多个读取 goroutine
for i := 0; i < 10; i++ {
go func() {
for j := 0; j < 100; j++ {
_, _ = GetPromptTemplate("test")
}
done <- true
}()
}
// 启动多个重新加载 goroutine
for i := 0; i < 3; i++ {
go func() {
for j := 0; j < 10; j++ {
_ = ReloadPromptTemplates()
}
done <- true
}()
}
// 等待所有 goroutine 完成
for i := 0; i < 13; i++ {
<-done
}
// 验证最终状态正确
template, err := GetPromptTemplate("test")
if err != nil {
t.Errorf("并发测试后获取模板失败: %v", err)
}
if template.Content != "测试内容" {
t.Errorf("并发测试后模板内容错误: %s", template.Content)
}
t.Log("✅ 并发测试通过:多个 goroutine 同时读取和重新加载模板,无数据竞争")
}