222 lines
4.7 KiB
Go
222 lines
4.7 KiB
Go
package execute
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
func init() {
|
|
os.Setenv("OLLIE_TOOLS_PATH", "testdata/tools")
|
|
}
|
|
|
|
func TestOrderArgsBySchema(t *testing.T) {
|
|
script := `#!/usr/bin/env bash
|
|
# args_json: {"type":"object","properties":{"pattern":{"type":"string"},"path":{"type":"string"}},"required":["pattern"]}
|
|
# ollie:prompt
|
|
# ## test_tool
|
|
# ollie:end
|
|
`
|
|
// Run 100 times to ensure deterministic ordering
|
|
for i := 0; i < 100; i++ {
|
|
argMap := map[string]interface{}{
|
|
"pattern": "*.go",
|
|
"path": "/some/dir",
|
|
}
|
|
result := orderArgsBySchema(script, argMap)
|
|
if len(result) != 2 {
|
|
t.Fatalf("iteration %d: expected 2 args, got %d: %v", i, len(result), result)
|
|
}
|
|
if result[0] != "*.go" {
|
|
t.Fatalf("iteration %d: expected result[0]='*.go', got %q", i, result[0])
|
|
}
|
|
if result[1] != "/some/dir" {
|
|
t.Fatalf("iteration %d: expected result[1]='/some/dir', got %q", i, result[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOrderArgsBySchemaReversed(t *testing.T) {
|
|
script := `#!/usr/bin/env bash
|
|
# args_json: {"type":"object","properties":{"path":{"type":"string"},"pattern":{"type":"string"}},"required":["path"]}
|
|
# ollie:prompt
|
|
# ## test_tool
|
|
# ollie:end
|
|
`
|
|
for i := 0; i < 100; i++ {
|
|
argMap := map[string]interface{}{
|
|
"pattern": "*.go",
|
|
"path": "/some/dir",
|
|
}
|
|
result := orderArgsBySchema(script, argMap)
|
|
if len(result) != 2 {
|
|
t.Fatalf("iteration %d: expected 2 args, got %d: %v", i, len(result), result)
|
|
}
|
|
if result[0] != "/some/dir" {
|
|
t.Fatalf("iteration %d: expected result[0]='/some/dir', got %q", i, result[0])
|
|
}
|
|
if result[1] != "*.go" {
|
|
t.Fatalf("iteration %d: expected result[1]='*.go', got %q", i, result[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRegistryLoadUnload(t *testing.T) {
|
|
r, err := NewRegistry()
|
|
if err != nil {
|
|
t.Fatalf("NewRegistry: %v", err)
|
|
}
|
|
|
|
// Check global discovery
|
|
names := r.GlobalToolNames()
|
|
if len(names) == 0 {
|
|
t.Fatal("expected at least one tool")
|
|
}
|
|
|
|
// Load a tool
|
|
sid := "test-session-1"
|
|
err = r.Load(sid, names[0])
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
|
|
// Loaded should have 1 tool
|
|
loaded := r.Loaded(sid)
|
|
if len(loaded) != 1 {
|
|
t.Fatalf("expected 1 loaded tool, got %d", len(loaded))
|
|
}
|
|
if loaded[0].Name != names[0] {
|
|
t.Fatalf("expected %s, got %s", names[0], loaded[0].Name)
|
|
}
|
|
|
|
// Revision should be 1
|
|
rev := r.Revision(sid)
|
|
if rev != 1 {
|
|
t.Fatalf("expected revision 1, got %d", rev)
|
|
}
|
|
|
|
// Lookup should work
|
|
_, ok := r.Lookup(sid, names[0])
|
|
if !ok {
|
|
t.Fatal("Lookup should find promoted tool")
|
|
}
|
|
|
|
// Unload
|
|
err = r.Unload(sid, names[0])
|
|
if err != nil {
|
|
t.Fatalf("Unload: %v", err)
|
|
}
|
|
|
|
// Revision should be 2
|
|
rev = r.Revision(sid)
|
|
if rev != 2 {
|
|
t.Fatalf("expected revision 2, got %d", rev)
|
|
}
|
|
|
|
// Loaded should be empty
|
|
loaded = r.Loaded(sid)
|
|
if len(loaded) != 0 {
|
|
t.Fatalf("expected 0 loaded tools, got %d", len(loaded))
|
|
}
|
|
}
|
|
|
|
func TestRegistrySummaries(t *testing.T) {
|
|
r, err := NewRegistry()
|
|
if err != nil {
|
|
t.Fatalf("NewRegistry: %v", err)
|
|
}
|
|
|
|
summaries := r.Summaries()
|
|
if len(summaries) == 0 {
|
|
t.Fatal("expected at least one summary")
|
|
}
|
|
|
|
for _, s := range summaries {
|
|
if s.Name == "" {
|
|
t.Fatal("summary missing name")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRegistryIdempotentLoad(t *testing.T) {
|
|
r, err := NewRegistry()
|
|
if err != nil {
|
|
t.Fatalf("NewRegistry: %v", err)
|
|
}
|
|
|
|
names := r.GlobalToolNames()
|
|
if len(names) == 0 {
|
|
t.Fatal("expected at least one tool")
|
|
}
|
|
|
|
sid := "test-session-2"
|
|
|
|
// Load twice
|
|
err = r.Load(sid, names[0])
|
|
if err != nil {
|
|
t.Fatalf("first Load: %v", err)
|
|
}
|
|
rev1 := r.Revision(sid)
|
|
|
|
err = r.Load(sid, names[0])
|
|
if err != nil {
|
|
t.Fatalf("second Load: %v", err)
|
|
}
|
|
rev2 := r.Revision(sid)
|
|
|
|
if rev1 != rev2 {
|
|
t.Fatal("idempotent load should not bump revision")
|
|
}
|
|
}
|
|
|
|
func TestRegistryToolNotFound(t *testing.T) {
|
|
r, err := NewRegistry()
|
|
if err != nil {
|
|
t.Fatalf("NewRegistry: %v", err)
|
|
}
|
|
|
|
err = r.Load("test", "nonexistent-tool")
|
|
if err == nil {
|
|
t.Fatal("expected error for nonexistent tool")
|
|
}
|
|
}
|
|
|
|
func TestRegistryServerListTools(t *testing.T) {
|
|
r, err := NewRegistry()
|
|
if err != nil {
|
|
t.Fatalf("NewRegistry: %v", err)
|
|
}
|
|
|
|
names := r.GlobalToolNames()
|
|
if len(names) == 0 {
|
|
t.Fatal("expected at least one tool")
|
|
}
|
|
|
|
sid := "test-session-3"
|
|
r.Load(sid, names[0])
|
|
|
|
srv := &Server{}
|
|
srv.SetToolRegistry(r, sid)
|
|
|
|
tools, err := srv.ListTools()
|
|
if err != nil {
|
|
t.Fatalf("ListTools: %v", err)
|
|
}
|
|
if len(tools) < 1 {
|
|
t.Fatalf("expected at least 1 tool, got %d", len(tools))
|
|
}
|
|
// First tool is execute_code, promoted tools come after
|
|
found := false
|
|
for _, ti := range tools {
|
|
if ti.Name == names[0] {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("expected promoted tool %s in list", names[0])
|
|
}
|
|
if tools[0].InputSchema == nil {
|
|
t.Fatal("expected InputSchema to be non-nil")
|
|
}
|
|
}
|