package aitools import "testing" func TestConfirmBindHashStableAndSensitive(t *testing.T) { a := ConfirmBindHash("c1", "INSERT INTO t VALUES (1)") b := ConfirmBindHash("c1", " INSERT INTO t VALUES (1) ") if a != b { t.Fatalf("normalize mismatch %s vs %s", a, b) } c := ConfirmBindHash("c1", "INSERT INTO t VALUES (2)") if a == c { t.Fatal("different sql should differ") } if err := VerifyConfirmBind("c1", "INSERT INTO t VALUES (1)", a); err != nil { t.Fatal(err) } if err := VerifyConfirmBind("c1", "INSERT INTO t VALUES (2)", a); err == nil { t.Fatal("want mismatch") } if err := VerifyConfirmBind("c1", "INSERT INTO t VALUES (1)", ""); err == nil { t.Fatal("want empty bind error") } } func TestAgentConfirmBindIncludesDatabase(t *testing.T) { bind := ConfirmAgentBindHash("c1", "demo", "DELETE FROM users WHERE id=1") if err := VerifyAgentBind("c1", "demo", "DELETE FROM users WHERE id=1", bind); err != nil { t.Fatal(err) } if err := VerifyAgentBind("c1", "other", "DELETE FROM users WHERE id=1", bind); err == nil { t.Fatal("database switch must invalidate confirmation") } if err := VerifyAgentBind("c2", "demo", "DELETE FROM users WHERE id=1", bind); err == nil { t.Fatal("connection switch must invalidate confirmation") } } func TestNeedConfirmIncludesBind(t *testing.T) { rt := NewRuntime(Policy{ Mode: ModeReadWrite, AllowedConnectionIDs: []string{"c1"}, DefaultLimit: 100, MaxLimit: 500, }) rt.RejectCallerConfirm = true rt.FindConn = func(id string) (*SavedConnection, error) { return &SavedConnection{ID: id, Driver: "mysql", Host: "127.0.0.1"}, nil } rt.Audit = func(AuditEntry) {} out, err := rt.Call(nil, ToolExecuteQuery, map[string]any{ "connection_id": "c1", "sql": "DELETE FROM users WHERE id=1", }) if err != nil { t.Fatal(err) } m, ok := out.(map[string]any) if !ok || m["needConfirm"] != true { t.Fatalf("%v", out) } bind, _ := m["confirmBind"].(string) if bind == "" { t.Fatal("missing confirmBind") } want := ConfirmBindHash("c1", "DELETE FROM users WHERE id=1") if bind != want { t.Fatalf("bind %s want %s", bind, want) } } func TestAgentWriteProposalBindsCurrentDatabase(t *testing.T) { rt := NewRuntime(Policy{Mode: ModeReadWrite, AllowedConnectionIDs: []string{"c1"}}) rt.BoundConnectionID = "c1" rt.BoundDatabase = "demo" rt.RejectCallerConfirm = true rt.Audit = func(AuditEntry) {} statement := "DELETE FROM users WHERE id=1" out, err := rt.Call(nil, ToolExecuteQuery, map[string]any{"connection_id": "c1", "sql": statement, "database": "demo"}) if err != nil { t.Fatal(err) } bind := out.(map[string]any)["confirmBind"].(string) if err := VerifyAgentBind("c1", "demo", statement, bind); err != nil { t.Fatal(err) } if err := VerifyAgentBind("c1", "other", statement, bind); err == nil { t.Fatal("confirmation must be scoped to demo") } }