first commit
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user