test: Phase 6 全功能测试 — 19个测试全部通过 + 开发路线文档
- host: 沙箱执行/命令拦截/超时/文件读写/系统信息/路径验证 (6 tests) - rag: 文本分块/余弦相似度/关键词匹配/文档索引+搜索 (4 tests) - tools: host_exec/host_file/host_system/knowledge_search/knowledge_ingest (5 tests) - vision: 图片编码/错误处理/定义验证/执行流程 (4 tests) - Embedder 重构为接口,支持 API 和 Simple 两种实现 - 添加 ROADMAP.md 未来开发路线 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/host"
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/rag"
|
||||
)
|
||||
|
||||
func TestHostExecToolDefinition(t *testing.T) {
|
||||
cfg := host.DefaultSandboxConfig()
|
||||
cfg.AllowedDirs = []string{os.TempDir()}
|
||||
sandbox := host.NewSandbox(cfg)
|
||||
mgr := host.NewManager(sandbox)
|
||||
|
||||
tool := NewHostExecTool(mgr)
|
||||
def := tool.Definition()
|
||||
if def.Name != "host_exec" {
|
||||
t.Fatalf("unexpected name: %s", def.Name)
|
||||
}
|
||||
t.Logf("host_exec definition OK")
|
||||
|
||||
// Test execute with echo
|
||||
result, err := tool.Execute(context.Background(), map[string]interface{}{
|
||||
"command": "echo test-ok",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("execute error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("execute failed: %s", result.Error)
|
||||
}
|
||||
t.Logf("host_exec execute OK: data=%s", result.Data[:50])
|
||||
}
|
||||
|
||||
func TestHostFileToolDefinition(t *testing.T) {
|
||||
cfg := host.DefaultSandboxConfig()
|
||||
tmpDir := os.TempDir()
|
||||
cfg.AllowedDirs = []string{tmpDir}
|
||||
sandbox := host.NewSandbox(cfg)
|
||||
mgr := host.NewManager(sandbox)
|
||||
mgr.SetAllowedDirs([]string{tmpDir})
|
||||
|
||||
tool := NewHostFileTool(mgr)
|
||||
def := tool.Definition()
|
||||
if def.Name != "host_file" {
|
||||
t.Fatalf("unexpected name: %s", def.Name)
|
||||
}
|
||||
t.Logf("host_file definition OK")
|
||||
|
||||
// Test list
|
||||
result, err := tool.Execute(context.Background(), map[string]interface{}{
|
||||
"action": "list",
|
||||
"path": tmpDir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list execute error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("list failed: %s", result.Error)
|
||||
}
|
||||
t.Logf("host_file list OK: data len=%d", len(result.Data))
|
||||
}
|
||||
|
||||
func TestHostSystemToolDefinition(t *testing.T) {
|
||||
cfg := host.DefaultSandboxConfig()
|
||||
sandbox := host.NewSandbox(cfg)
|
||||
mgr := host.NewManager(sandbox)
|
||||
|
||||
tool := NewHostSystemTool(mgr)
|
||||
def := tool.Definition()
|
||||
if def.Name != "host_system" {
|
||||
t.Fatalf("unexpected name: %s", def.Name)
|
||||
}
|
||||
t.Logf("host_system definition OK")
|
||||
|
||||
result, err := tool.Execute(context.Background(), map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatalf("execute error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("execute failed: %s", result.Error)
|
||||
}
|
||||
t.Logf("host_system execute OK: data len=%d", len(result.Data))
|
||||
}
|
||||
|
||||
type testEmbedder struct{}
|
||||
|
||||
func (e *testEmbedder) Embed(ctx context.Context, text string) ([]float64, error) {
|
||||
n := float64(len([]rune(text)))
|
||||
v := make([]float64, 128)
|
||||
for _, r := range text {
|
||||
v[int(r)%128] += 1.0 / n
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (e *testEmbedder) EmbedBatch(ctx context.Context, texts []string) ([]float64, error) {
|
||||
combined := ""
|
||||
for _, t := range texts {
|
||||
combined += t
|
||||
}
|
||||
return e.Embed(ctx, combined)
|
||||
}
|
||||
|
||||
func (e *testEmbedder) IsAvailable() bool { return true }
|
||||
|
||||
func TestKnowledgeSearchToolDefinition(t *testing.T) {
|
||||
store := rag.NewKnowledgeStore(&testEmbedder{}, os.TempDir())
|
||||
retriever := rag.NewRetriever(store)
|
||||
|
||||
tool := NewKnowledgeSearchTool(retriever)
|
||||
def := tool.Definition()
|
||||
if def.Name != "knowledge_search" {
|
||||
t.Fatalf("unexpected name: %s", def.Name)
|
||||
}
|
||||
t.Logf("knowledge_search definition OK")
|
||||
|
||||
result, err := tool.Execute(context.Background(), map[string]interface{}{
|
||||
"query": "test query",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("execute error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("execute failed: %s", result.Error)
|
||||
}
|
||||
t.Logf("knowledge_search execute OK: data=%s", result.Data[:80])
|
||||
}
|
||||
|
||||
func TestKnowledgeIngestToolDefinition(t *testing.T) {
|
||||
store := rag.NewKnowledgeStore(&testEmbedder{}, os.TempDir())
|
||||
|
||||
tool := NewKnowledgeIngestTool(store)
|
||||
def := tool.Definition()
|
||||
if def.Name != "knowledge_ingest" {
|
||||
t.Fatalf("unexpected name: %s", def.Name)
|
||||
}
|
||||
t.Logf("knowledge_ingest definition OK")
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncodeImageToDataURL(t *testing.T) {
|
||||
// Create a minimal 1x1 PNG
|
||||
pngBytes, _ := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==")
|
||||
tmpPath := filepath.Join(os.TempDir(), "cyrene-test-vision.png")
|
||||
if err := os.WriteFile(tmpPath, pngBytes, 0644); err != nil {
|
||||
t.Fatalf("write test image: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
dataURL, mimeType, err := encodeImageToDataURL(tmpPath)
|
||||
if err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(dataURL, "data:image/png;base64,") {
|
||||
t.Fatalf("unexpected data URL: %s...", dataURL[:50])
|
||||
}
|
||||
if mimeType != "image/png" {
|
||||
t.Fatalf("unexpected mime type: %s", mimeType)
|
||||
}
|
||||
t.Logf("encode OK: mime=%s, len=%d", mimeType, len(dataURL))
|
||||
}
|
||||
|
||||
func TestEncodeImageToDataURL_InvalidPath(t *testing.T) {
|
||||
_, _, err := encodeImageToDataURL("/nonexistent/image.png")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nonexistent file")
|
||||
}
|
||||
t.Logf("error handling OK: %v", err)
|
||||
}
|
||||
|
||||
func TestVisionToolDefinition(t *testing.T) {
|
||||
tool := NewVisionTool()
|
||||
def := tool.Definition()
|
||||
if def.Name != "vision_analyze" {
|
||||
t.Fatalf("unexpected tool name: %s", def.Name)
|
||||
}
|
||||
params := def.Parameters
|
||||
props, ok := params["properties"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("missing properties")
|
||||
}
|
||||
if props["image_path"] == nil {
|
||||
t.Fatal("missing image_path parameter")
|
||||
}
|
||||
if props["task"] == nil {
|
||||
t.Fatal("missing task parameter")
|
||||
}
|
||||
t.Logf("definition OK: name=%s, params=%v", def.Name, def.Parameters)
|
||||
}
|
||||
|
||||
func TestVisionToolExecute(t *testing.T) {
|
||||
// Create test image
|
||||
pngBytes, _ := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==")
|
||||
tmpPath := filepath.Join(os.TempDir(), "cyrene-test-vision-exec.png")
|
||||
if err := os.WriteFile(tmpPath, pngBytes, 0644); err != nil {
|
||||
t.Fatalf("write test image: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
tool := NewVisionTool()
|
||||
ctx := context.Background()
|
||||
result, err := tool.Execute(ctx, map[string]interface{}{
|
||||
"image_path": tmpPath,
|
||||
"task": "ocr",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("execute: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("execute failed: %s", result.Error)
|
||||
}
|
||||
if !strings.Contains(result.Data, "data:image/png;base64,") {
|
||||
t.Fatal("result missing data URL")
|
||||
}
|
||||
if !strings.Contains(result.Data, "ocr") {
|
||||
t.Fatal("result missing task info")
|
||||
}
|
||||
t.Logf("execute OK: data len=%d", len(result.Data))
|
||||
}
|
||||
Reference in New Issue
Block a user