multiedit_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package tools
  2. import (
  3. "context"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "github.com/charmbracelet/crush/internal/history"
  8. "github.com/charmbracelet/crush/internal/permission"
  9. "github.com/charmbracelet/crush/internal/pubsub"
  10. "github.com/stretchr/testify/require"
  11. )
  12. type mockPermissionService struct {
  13. *pubsub.Broker[permission.PermissionRequest]
  14. }
  15. func (m *mockPermissionService) Request(ctx context.Context, req permission.CreatePermissionRequest) (bool, error) {
  16. return true, nil
  17. }
  18. func (m *mockPermissionService) Grant(req permission.PermissionRequest) {}
  19. func (m *mockPermissionService) Deny(req permission.PermissionRequest) {}
  20. func (m *mockPermissionService) GrantPersistent(req permission.PermissionRequest) {}
  21. func (m *mockPermissionService) AutoApproveSession(sessionID string) {}
  22. func (m *mockPermissionService) SetSkipRequests(skip bool) {}
  23. func (m *mockPermissionService) SkipRequests() bool {
  24. return false
  25. }
  26. func (m *mockPermissionService) SubscribeNotifications(ctx context.Context) <-chan pubsub.Event[permission.PermissionNotification] {
  27. return make(<-chan pubsub.Event[permission.PermissionNotification])
  28. }
  29. type mockHistoryService struct {
  30. *pubsub.Broker[history.File]
  31. }
  32. func (m *mockHistoryService) Create(ctx context.Context, sessionID, path, content string) (history.File, error) {
  33. return history.File{Path: path, Content: content}, nil
  34. }
  35. func (m *mockHistoryService) CreateVersion(ctx context.Context, sessionID, path, content string) (history.File, error) {
  36. return history.File{}, nil
  37. }
  38. func (m *mockHistoryService) GetByPathAndSession(ctx context.Context, path, sessionID string) (history.File, error) {
  39. return history.File{Path: path, Content: ""}, nil
  40. }
  41. func (m *mockHistoryService) Get(ctx context.Context, id string) (history.File, error) {
  42. return history.File{}, nil
  43. }
  44. func (m *mockHistoryService) ListBySession(ctx context.Context, sessionID string) ([]history.File, error) {
  45. return nil, nil
  46. }
  47. func (m *mockHistoryService) ListLatestSessionFiles(ctx context.Context, sessionID string) ([]history.File, error) {
  48. return nil, nil
  49. }
  50. func (m *mockHistoryService) Delete(ctx context.Context, id string) error {
  51. return nil
  52. }
  53. func (m *mockHistoryService) DeleteSessionFiles(ctx context.Context, sessionID string) error {
  54. return nil
  55. }
  56. func TestApplyEditToContentPartialSuccess(t *testing.T) {
  57. t.Parallel()
  58. content := "line 1\nline 2\nline 3\n"
  59. // Test successful edit.
  60. newContent, err := applyEditToContent(content, MultiEditOperation{
  61. OldString: "line 1",
  62. NewString: "LINE 1",
  63. })
  64. require.NoError(t, err)
  65. require.Contains(t, newContent, "LINE 1")
  66. require.Contains(t, newContent, "line 2")
  67. // Test failed edit (string not found).
  68. _, err = applyEditToContent(content, MultiEditOperation{
  69. OldString: "line 99",
  70. NewString: "LINE 99",
  71. })
  72. require.Error(t, err)
  73. require.Contains(t, err.Error(), "not found")
  74. }
  75. func TestMultiEditSequentialApplication(t *testing.T) {
  76. t.Parallel()
  77. tmpDir := t.TempDir()
  78. testFile := filepath.Join(tmpDir, "test.txt")
  79. // Create test file.
  80. content := "line 1\nline 2\nline 3\nline 4\n"
  81. err := os.WriteFile(testFile, []byte(content), 0o644)
  82. require.NoError(t, err)
  83. // Manually test the sequential application logic.
  84. currentContent := content
  85. // Apply edits sequentially, tracking failures.
  86. edits := []MultiEditOperation{
  87. {OldString: "line 1", NewString: "LINE 1"}, // Should succeed
  88. {OldString: "line 99", NewString: "LINE 99"}, // Should fail - doesn't exist
  89. {OldString: "line 3", NewString: "LINE 3"}, // Should succeed
  90. {OldString: "line 2", NewString: "LINE 2"}, // Should succeed - still exists
  91. }
  92. var failedEdits []FailedEdit
  93. successCount := 0
  94. for i, edit := range edits {
  95. newContent, err := applyEditToContent(currentContent, edit)
  96. if err != nil {
  97. failedEdits = append(failedEdits, FailedEdit{
  98. Index: i + 1,
  99. Error: err.Error(),
  100. Edit: edit,
  101. })
  102. continue
  103. }
  104. currentContent = newContent
  105. successCount++
  106. }
  107. // Verify results.
  108. require.Equal(t, 3, successCount, "Expected 3 successful edits")
  109. require.Len(t, failedEdits, 1, "Expected 1 failed edit")
  110. // Check failed edit details.
  111. require.Equal(t, 2, failedEdits[0].Index)
  112. require.Contains(t, failedEdits[0].Error, "not found")
  113. // Verify content changes.
  114. require.Contains(t, currentContent, "LINE 1")
  115. require.Contains(t, currentContent, "LINE 2")
  116. require.Contains(t, currentContent, "LINE 3")
  117. require.Contains(t, currentContent, "line 4") // Original unchanged
  118. require.NotContains(t, currentContent, "LINE 99")
  119. }
  120. func TestMultiEditAllEditsSucceed(t *testing.T) {
  121. t.Parallel()
  122. content := "line 1\nline 2\nline 3\n"
  123. edits := []MultiEditOperation{
  124. {OldString: "line 1", NewString: "LINE 1"},
  125. {OldString: "line 2", NewString: "LINE 2"},
  126. {OldString: "line 3", NewString: "LINE 3"},
  127. }
  128. currentContent := content
  129. successCount := 0
  130. for _, edit := range edits {
  131. newContent, err := applyEditToContent(currentContent, edit)
  132. if err != nil {
  133. t.Fatalf("Unexpected error: %v", err)
  134. }
  135. currentContent = newContent
  136. successCount++
  137. }
  138. require.Equal(t, 3, successCount)
  139. require.Contains(t, currentContent, "LINE 1")
  140. require.Contains(t, currentContent, "LINE 2")
  141. require.Contains(t, currentContent, "LINE 3")
  142. }
  143. func TestMultiEditAllEditsFail(t *testing.T) {
  144. t.Parallel()
  145. content := "line 1\nline 2\n"
  146. edits := []MultiEditOperation{
  147. {OldString: "line 99", NewString: "LINE 99"},
  148. {OldString: "line 100", NewString: "LINE 100"},
  149. }
  150. currentContent := content
  151. var failedEdits []FailedEdit
  152. for i, edit := range edits {
  153. newContent, err := applyEditToContent(currentContent, edit)
  154. if err != nil {
  155. failedEdits = append(failedEdits, FailedEdit{
  156. Index: i + 1,
  157. Error: err.Error(),
  158. Edit: edit,
  159. })
  160. continue
  161. }
  162. currentContent = newContent
  163. }
  164. require.Len(t, failedEdits, 2)
  165. require.Equal(t, content, currentContent, "Content should be unchanged")
  166. }