Changeset 0.7.2 (#40)

This commit is contained in:
2026-02-21 19:03:19 +00:00
parent 494b1aa981
commit 416e5439ea
28 changed files with 3813 additions and 138 deletions

194
server/tools/tools_test.go Normal file
View File

@@ -0,0 +1,194 @@
package tools
import (
"context"
"encoding/json"
"testing"
)
// ── Registry Tests ──────────────────────────
func TestRegistryHasTools(t *testing.T) {
// init() in notes.go registers 4 tools
if !HasTools() {
t.Fatal("Expected HasTools() == true after init registration")
}
}
func TestRegistryAllDefinitions(t *testing.T) {
defs := AllDefinitions()
if len(defs) < 4 {
t.Errorf("Expected at least 4 tool definitions, got %d", len(defs))
}
expected := map[string]bool{
"note_create": false,
"note_search": false,
"note_update": false,
"note_list": false,
}
for _, d := range defs {
if _, ok := expected[d.Name]; ok {
expected[d.Name] = true
}
// Every tool must have a description and parameters
if d.Description == "" {
t.Errorf("Tool %q has empty description", d.Name)
}
if len(d.Parameters) == 0 {
t.Errorf("Tool %q has empty parameters", d.Name)
}
}
for name, found := range expected {
if !found {
t.Errorf("Expected tool %q not found in registry", name)
}
}
}
func TestRegistryGetKnown(t *testing.T) {
tool := Get("note_create")
if tool == nil {
t.Fatal("Get(note_create) returned nil")
}
def := tool.Definition()
if def.Name != "note_create" {
t.Errorf("Expected name 'note_create', got %q", def.Name)
}
}
func TestRegistryGetUnknown(t *testing.T) {
tool := Get("nonexistent_tool")
if tool != nil {
t.Error("Get(nonexistent_tool) should return nil")
}
}
func TestExecuteCallUnknownTool(t *testing.T) {
ctx := context.Background()
execCtx := ExecutionContext{UserID: "test", ChannelID: "test"}
call := ToolCall{ID: "call_1", Name: "nonexistent", Arguments: "{}"}
result := ExecuteCall(ctx, execCtx, call)
if !result.IsError {
t.Error("Expected is_error=true for unknown tool")
}
if result.ToolCallID != "call_1" {
t.Errorf("Expected tool_call_id='call_1', got %q", result.ToolCallID)
}
}
func TestExecuteAll(t *testing.T) {
ctx := context.Background()
execCtx := ExecutionContext{UserID: "test", ChannelID: "test"}
calls := []ToolCall{
{ID: "call_1", Name: "nonexistent_a", Arguments: "{}"},
{ID: "call_2", Name: "nonexistent_b", Arguments: "{}"},
}
results := ExecuteAll(ctx, execCtx, calls)
if len(results) != 2 {
t.Fatalf("Expected 2 results, got %d", len(results))
}
for i, r := range results {
if !r.IsError {
t.Errorf("Result %d: expected is_error=true", i)
}
}
}
// ── Tool Definition Schema Tests ────────────
func TestToolDefinitionsHaveValidJSON(t *testing.T) {
for _, def := range AllDefinitions() {
var schema map[string]interface{}
if err := json.Unmarshal(def.Parameters, &schema); err != nil {
t.Errorf("Tool %q has invalid JSON schema: %v", def.Name, err)
}
// Should be a JSON Schema object with "type" and "properties"
if schema["type"] != "object" {
t.Errorf("Tool %q schema type should be 'object', got %v", def.Name, schema["type"])
}
if _, ok := schema["properties"]; !ok {
t.Errorf("Tool %q schema missing 'properties'", def.Name)
}
}
}
// ── JSON Schema Helper Tests ────────────────
func TestPropString(t *testing.T) {
p := Prop("string", "a description")
if p["type"] != "string" {
t.Errorf("Expected type=string, got %v", p["type"])
}
if p["description"] != "a description" {
t.Errorf("Expected description, got %v", p["description"])
}
}
func TestPropEnum(t *testing.T) {
p := PropEnum("content mode", "replace", "append", "prepend")
if p["type"] != "string" {
t.Error("Expected type=string")
}
enums := p["enum"].([]string)
if len(enums) != 3 {
t.Errorf("Expected 3 enum values, got %d", len(enums))
}
}
func TestPropArray(t *testing.T) {
p := PropArray("list of tags")
if p["type"] != "array" {
t.Error("Expected type=array")
}
items := p["items"].(map[string]string)
if items["type"] != "string" {
t.Error("Expected items.type=string")
}
}
func TestJSONSchema(t *testing.T) {
props := map[string]interface{}{
"title": Prop("string", "note title"),
"content": Prop("string", "note body"),
}
schema := JSONSchema(props, []string{"title"})
var m map[string]interface{}
if err := json.Unmarshal(schema, &m); err != nil {
t.Fatalf("Invalid JSON: %v", err)
}
if m["type"] != "object" {
t.Error("Expected type=object")
}
required := m["required"].([]interface{})
if len(required) != 1 || required[0] != "title" {
t.Errorf("Expected required=[title], got %v", required)
}
mProps := m["properties"].(map[string]interface{})
if _, ok := mProps["title"]; !ok {
t.Error("Missing property 'title'")
}
if _, ok := mProps["content"]; !ok {
t.Error("Missing property 'content'")
}
}
func TestMustJSON(t *testing.T) {
result := MustJSON(map[string]string{"key": "value"})
var m map[string]string
if err := json.Unmarshal(result, &m); err != nil {
t.Fatalf("MustJSON produced invalid JSON: %v", err)
}
if m["key"] != "value" {
t.Errorf("Expected value, got %q", m["key"])
}
}