161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
package agent_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/smallnest/pigo/agent"
|
|
)
|
|
|
|
// hermetic points provider/skill/plugin discovery at throwaway dirs and supplies
|
|
// a dummy key so New resolves fully without ever contacting a network. None of
|
|
// these tests call Prompt/Stream, so no real request is made.
|
|
func hermetic(t *testing.T) {
|
|
t.Helper()
|
|
t.Setenv("OPENROUTER_API_KEY", "test-key")
|
|
t.Setenv("PIGO_HOME", t.TempDir())
|
|
}
|
|
|
|
func contains(set []string, name string) bool {
|
|
for _, n := range set {
|
|
if n == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// TestNewDefaults is the zero-config path: the full built-in tool set is
|
|
// advertised and the model id resolves to the openrouter provider.
|
|
func TestNewDefaults(t *testing.T) {
|
|
hermetic(t)
|
|
sess, err := agent.New(agent.WithModel("openrouter/free"))
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
defer sess.Close()
|
|
|
|
if got := sess.Model(); got != "openrouter/free" {
|
|
t.Errorf("Model() = %q, want %q", got, "openrouter/free")
|
|
}
|
|
if got := sess.Provider(); got != "openrouter" {
|
|
t.Errorf("Provider() = %q, want %q", got, "openrouter")
|
|
}
|
|
for _, want := range []string{"read", "write", "edit", "grep", "find", "bash", "task"} {
|
|
if !contains(sess.ToolNames(), want) {
|
|
t.Errorf("default tool set missing %q: %q", want, sess.ToolNames())
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestWithToolsAllowlist confirms an allowlist narrows the set to exactly the
|
|
// named tools, in order.
|
|
func TestWithToolsAllowlist(t *testing.T) {
|
|
hermetic(t)
|
|
sess, err := agent.New(
|
|
agent.WithModel("openrouter/free"),
|
|
agent.WithTools("read", "grep"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
defer sess.Close()
|
|
|
|
if got := strings.Join(sess.ToolNames(), ","); got != "read,grep" {
|
|
t.Errorf("ToolNames() = %q, want %q", got, "read,grep")
|
|
}
|
|
}
|
|
|
|
// TestDenyWinsOverAllow is the fail-closed guarantee at the SDK layer: a tool on
|
|
// both lists is removed.
|
|
func TestDenyWinsOverAllow(t *testing.T) {
|
|
hermetic(t)
|
|
sess, err := agent.New(
|
|
agent.WithModel("openrouter/free"),
|
|
agent.WithTools("read", "bash"),
|
|
agent.WithDisallowedTools("bash"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
defer sess.Close()
|
|
|
|
if contains(sess.ToolNames(), "bash") {
|
|
t.Errorf("bash was on both lists and must be removed: %q", sess.ToolNames())
|
|
}
|
|
if !contains(sess.ToolNames(), "read") {
|
|
t.Errorf("read was allowed and not denied, so must survive: %q", sess.ToolNames())
|
|
}
|
|
}
|
|
|
|
// TestWithoutTools yields an empty set — a pure text completion.
|
|
func TestWithoutTools(t *testing.T) {
|
|
hermetic(t)
|
|
sess, err := agent.New(
|
|
agent.WithModel("openrouter/free"),
|
|
agent.WithoutTools(),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
defer sess.Close()
|
|
|
|
if len(sess.ToolNames()) != 0 {
|
|
t.Errorf("WithoutTools must leave no tools, got %q", sess.ToolNames())
|
|
}
|
|
}
|
|
|
|
// TestUnknownToolIsError confirms a misspelled tool name fails construction
|
|
// rather than silently dropping the boundary.
|
|
func TestUnknownToolIsError(t *testing.T) {
|
|
hermetic(t)
|
|
_, err := agent.New(
|
|
agent.WithModel("openrouter/free"),
|
|
agent.WithTools("raed"),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("New = nil error, want a failure for the misspelled tool name")
|
|
}
|
|
}
|
|
|
|
// TestInvalidThinkingLevelIsError confirms the level is validated up front.
|
|
func TestInvalidThinkingLevelIsError(t *testing.T) {
|
|
hermetic(t)
|
|
_, err := agent.New(
|
|
agent.WithModel("openrouter/free"),
|
|
agent.WithThinkingLevel("supersonic"),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("New = nil error, want a failure for the invalid thinking level")
|
|
}
|
|
}
|
|
|
|
// TestValidThinkingLevels accepts every documented level.
|
|
func TestValidThinkingLevels(t *testing.T) {
|
|
hermetic(t)
|
|
for _, level := range []string{"off", "minimal", "low", "medium", "high", "xhigh", "max"} {
|
|
sess, err := agent.New(
|
|
agent.WithModel("openrouter/free"),
|
|
agent.WithThinkingLevel(level),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("WithThinkingLevel(%q): %v", level, err)
|
|
continue
|
|
}
|
|
sess.Close()
|
|
}
|
|
}
|
|
|
|
// TestCloseHermetic confirms Close is a no-op (nil) when the session holds no
|
|
// plugin manager or memory store, and is safe to call.
|
|
func TestCloseHermetic(t *testing.T) {
|
|
hermetic(t)
|
|
sess, err := agent.New(agent.WithModel("openrouter/free"), agent.WithoutTools())
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
if err := sess.Close(); err != nil {
|
|
t.Errorf("Close() = %v, want nil", err)
|
|
}
|
|
}
|