bash_test.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package tools
  2. import (
  3. "context"
  4. "encoding/json"
  5. "testing"
  6. "charm.land/fantasy"
  7. "github.com/charmbracelet/crush/internal/config"
  8. "github.com/charmbracelet/crush/internal/permission"
  9. "github.com/charmbracelet/crush/internal/pubsub"
  10. "github.com/charmbracelet/crush/internal/shell"
  11. "github.com/stretchr/testify/require"
  12. )
  13. type mockBashPermissionService struct {
  14. *pubsub.Broker[permission.PermissionRequest]
  15. }
  16. func (m *mockBashPermissionService) Request(ctx context.Context, req permission.CreatePermissionRequest) (bool, error) {
  17. return true, nil
  18. }
  19. func (m *mockBashPermissionService) Grant(req permission.PermissionRequest) {}
  20. func (m *mockBashPermissionService) Deny(req permission.PermissionRequest) {}
  21. func (m *mockBashPermissionService) GrantPersistent(req permission.PermissionRequest) {}
  22. func (m *mockBashPermissionService) AutoApproveSession(sessionID string) {}
  23. func (m *mockBashPermissionService) SetSkipRequests(skip bool) {}
  24. func (m *mockBashPermissionService) SkipRequests() bool {
  25. return false
  26. }
  27. func (m *mockBashPermissionService) SubscribeNotifications(ctx context.Context) <-chan pubsub.Event[permission.PermissionNotification] {
  28. return make(<-chan pubsub.Event[permission.PermissionNotification])
  29. }
  30. func TestBashTool_DefaultAutoBackgroundThreshold(t *testing.T) {
  31. workingDir := t.TempDir()
  32. tool := newBashToolForTest(workingDir)
  33. ctx := context.WithValue(context.Background(), SessionIDContextKey, "test-session")
  34. resp := runBashTool(t, tool, ctx, BashParams{
  35. Description: "default threshold",
  36. Command: "echo done",
  37. })
  38. require.False(t, resp.IsError)
  39. var meta BashResponseMetadata
  40. require.NoError(t, json.Unmarshal([]byte(resp.Metadata), &meta))
  41. require.False(t, meta.Background)
  42. require.Empty(t, meta.ShellID)
  43. require.Contains(t, meta.Output, "done")
  44. }
  45. func TestBashTool_CustomAutoBackgroundThreshold(t *testing.T) {
  46. workingDir := t.TempDir()
  47. tool := newBashToolForTest(workingDir)
  48. ctx := context.WithValue(context.Background(), SessionIDContextKey, "test-session")
  49. resp := runBashTool(t, tool, ctx, BashParams{
  50. Description: "custom threshold",
  51. Command: "sleep 1.5 && echo done",
  52. AutoBackgroundAfter: 1,
  53. })
  54. require.False(t, resp.IsError)
  55. var meta BashResponseMetadata
  56. require.NoError(t, json.Unmarshal([]byte(resp.Metadata), &meta))
  57. require.True(t, meta.Background)
  58. require.NotEmpty(t, meta.ShellID)
  59. require.Contains(t, resp.Content, "moved to background")
  60. bgManager := shell.GetBackgroundShellManager()
  61. require.NoError(t, bgManager.Kill(meta.ShellID))
  62. }
  63. func newBashToolForTest(workingDir string) fantasy.AgentTool {
  64. permissions := &mockBashPermissionService{Broker: pubsub.NewBroker[permission.PermissionRequest]()}
  65. attribution := &config.Attribution{TrailerStyle: config.TrailerStyleNone}
  66. return NewBashTool(permissions, workingDir, attribution, "test-model")
  67. }
  68. func runBashTool(t *testing.T, tool fantasy.AgentTool, ctx context.Context, params BashParams) fantasy.ToolResponse {
  69. t.Helper()
  70. input, err := json.Marshal(params)
  71. require.NoError(t, err)
  72. call := fantasy.ToolCall{
  73. ID: "test-call",
  74. Name: BashToolName,
  75. Input: string(input),
  76. }
  77. resp, err := tool.Run(ctx, call)
  78. require.NoError(t, err)
  79. return resp
  80. }