Files
ragflow/internal/harness/core/tool_registry_test.go
Yingfeng 706e0d2d06 Refactor harness framework (#16271)
### What problem does this PR solve?

- Tools management
- Pregel engine wrapper for better usage
- UT race
- Coding style

### Type of change

- [x] Refactoring
2026-06-23 20:18:04 +08:00

218 lines
5.3 KiB
Go

package core
import (
"context"
"testing"
"ragflow/internal/harness/core/schema"
)
func TestToolRegistry_RegisterAndLookup(t *testing.T) {
r := NewToolRegistry()
tool := newTestTool("get_weather", "Get weather")
r.Register(tool)
found, ok := r.Lookup("get_weather")
if !ok {
t.Fatal("expected to find 'get_weather'")
}
if found.Name() != "get_weather" {
t.Errorf("expected 'get_weather', got %s", found.Name())
}
}
func TestToolRegistry_LookupNotFound(t *testing.T) {
r := NewToolRegistry()
_, ok := r.Lookup("nonexistent")
if ok {
t.Error("expected false for nonexistent tool")
}
}
func TestToolRegistry_WithAlias(t *testing.T) {
r := NewToolRegistry()
tool := newTestTool("web_search_v2", "Search web")
r.Register(tool, WithAlias("search"))
// Lookup by alias
found, ok := r.Lookup("search")
if !ok {
t.Fatal("expected to find via alias 'search'")
}
if found.Name() != "web_search_v2" {
t.Errorf("expected 'web_search_v2', got %s", found.Name())
}
// Lookup by canonical name still works
found, ok = r.Lookup("web_search_v2")
if !ok {
t.Fatal("expected to find via canonical name")
}
}
func TestToolRegistry_WithCategory(t *testing.T) {
r := NewToolRegistry()
r.Register(newTestTool("cat_tool1", ""), WithCategory("file", "read"))
r.Register(newTestTool("cat_tool2", ""), WithCategory("file"))
r.Register(newTestTool("other_tool", ""), WithCategory("network"))
fileTools := r.LookupByCategory("file")
if len(fileTools) != 2 {
t.Errorf("expected 2 file tools, got %d", len(fileTools))
}
readTools := r.LookupByCategory("read")
if len(readTools) != 1 {
t.Errorf("expected 1 read tool, got %d", len(readTools))
}
networkTools := r.LookupByCategory("network")
if len(networkTools) != 1 {
t.Errorf("expected 1 network tool, got %d", len(networkTools))
}
emptyTools := r.LookupByCategory("nonexistent")
if len(emptyTools) != 0 {
t.Errorf("expected 0 tools for nonexistent category, got %d", len(emptyTools))
}
}
func TestToolRegistry_AllTools(t *testing.T) {
r := NewToolRegistry()
r.Register(newTestTool("a", ""))
r.Register(newTestTool("b", ""))
all := r.AllTools()
if len(all) != 2 {
t.Errorf("expected 2 tools, got %d", len(all))
}
}
func TestToolRegistry_ToSlice(t *testing.T) {
r := NewToolRegistry()
r.Register(newTestTool("x", ""))
r.Register(newTestTool("y", ""))
slice := r.ToSlice()
if len(slice) != 2 {
t.Errorf("expected 2 tools, got %d", len(slice))
}
}
func TestToolRegistry_Filter(t *testing.T) {
r := NewToolRegistry()
r.Register(newTestTool("search_web", ""), WithCategory("web"))
r.Register(newTestTool("search_file", ""), WithCategory("file"))
r.Register(newTestTool("read_file", ""), WithCategory("file"))
filtered := r.Filter(func(tool Tool) bool {
return tool.Name() == "search_file"
})
if len(filtered.AllTools()) != 1 {
t.Errorf("expected 1 filtered tool, got %d", len(filtered.AllTools()))
}
}
func TestToolRegistry_Merge(t *testing.T) {
r1 := NewToolRegistry()
r1.Register(newTestTool("a", ""), WithAlias("alias_a"))
r2 := NewToolRegistry()
r2.Register(newTestTool("b", ""), WithAlias("alias_b"))
r1.Merge(r2)
if _, ok := r1.Lookup("a"); !ok {
t.Error("expected 'a' after merge")
}
if _, ok := r1.Lookup("b"); !ok {
t.Error("expected 'b' after merge")
}
if _, ok := r1.Lookup("alias_b"); !ok {
t.Error("expected 'alias_b' after merge")
}
}
func TestToolRegistry_Unregister(t *testing.T) {
r := NewToolRegistry()
r.Register(newTestTool("temp", ""), WithAlias("t"), WithCategory("test"))
r.Unregister("temp")
if _, ok := r.Lookup("temp"); ok {
t.Error("expected 'temp' to be removed")
}
if _, ok := r.Lookup("t"); ok {
t.Error("expected alias 't' to be removed")
}
if len(r.LookupByCategory("test")) != 0 {
t.Error("expected category 'test' to be empty")
}
}
func TestToolRegistry_MustLookup(t *testing.T) {
r := NewToolRegistry()
r.Register(newTestTool("safe", ""))
defer func() {
if r := recover(); r == nil {
t.Error("expected panic for missing tool")
}
}()
r.MustLookup("nonexistent")
}
func TestToolRegistry_ConcurrentAccess(t *testing.T) {
r := NewToolRegistry()
r.Register(newTestTool("concurrent_tool", ""))
done := make(chan struct{}, 2)
go func() {
for i := 0; i < 100; i++ {
r.Lookup("concurrent_tool")
}
done <- struct{}{}
}()
go func() {
for i := 0; i < 100; i++ {
r.Register(newTestTool("other", ""))
}
done <- struct{}{}
}()
<-done
<-done
}
func TestToolToSliceForReActConfig(t *testing.T) {
r := NewToolRegistry()
r.Register(newTestTool("t1", ""))
r.Register(newTestTool("t2", ""))
cfg := &ReActConfig[Message]{
Tools: r.ToSlice(),
}
if len(cfg.Tools) != 2 {
t.Errorf("expected 2 tools in config, got %d", len(cfg.Tools))
}
}
func TestToolRegistry_LookupPanicsForTool(t *testing.T) {
// Non-existent tool via MustLookup
r := NewToolRegistry()
r.Register(&testPanicTool{name: "good"})
// Should not panic
_ = r.MustLookup("good")
}
type testPanicTool struct{ name string }
func (t *testPanicTool) Name() string { return t.name }
func (t *testPanicTool) Description() string { return "" }
func (t *testPanicTool) Invoke(ctx context.Context, s string, opts ...ToolOption) (string, error) {
return "", nil
}
func (t *testPanicTool) Stream(ctx context.Context, s string, opts ...ToolOption) (*schema.StreamReader[string], error) {
return schema.StreamReaderFromArray([]string{""}), nil
}