| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- package tools
- import (
- "context"
- "os"
- "path/filepath"
- "testing"
- "github.com/charmbracelet/crush/internal/history"
- "github.com/charmbracelet/crush/internal/permission"
- "github.com/charmbracelet/crush/internal/pubsub"
- "github.com/stretchr/testify/require"
- )
- type mockPermissionService struct {
- *pubsub.Broker[permission.PermissionRequest]
- }
- func (m *mockPermissionService) Request(ctx context.Context, req permission.CreatePermissionRequest) (bool, error) {
- return true, nil
- }
- func (m *mockPermissionService) Grant(req permission.PermissionRequest) {}
- func (m *mockPermissionService) Deny(req permission.PermissionRequest) {}
- func (m *mockPermissionService) GrantPersistent(req permission.PermissionRequest) {}
- func (m *mockPermissionService) AutoApproveSession(sessionID string) {}
- func (m *mockPermissionService) SetSkipRequests(skip bool) {}
- func (m *mockPermissionService) SkipRequests() bool {
- return false
- }
- func (m *mockPermissionService) SubscribeNotifications(ctx context.Context) <-chan pubsub.Event[permission.PermissionNotification] {
- return make(<-chan pubsub.Event[permission.PermissionNotification])
- }
- type mockHistoryService struct {
- *pubsub.Broker[history.File]
- }
- func (m *mockHistoryService) Create(ctx context.Context, sessionID, path, content string) (history.File, error) {
- return history.File{Path: path, Content: content}, nil
- }
- func (m *mockHistoryService) CreateVersion(ctx context.Context, sessionID, path, content string) (history.File, error) {
- return history.File{}, nil
- }
- func (m *mockHistoryService) GetByPathAndSession(ctx context.Context, path, sessionID string) (history.File, error) {
- return history.File{Path: path, Content: ""}, nil
- }
- func (m *mockHistoryService) Get(ctx context.Context, id string) (history.File, error) {
- return history.File{}, nil
- }
- func (m *mockHistoryService) ListBySession(ctx context.Context, sessionID string) ([]history.File, error) {
- return nil, nil
- }
- func (m *mockHistoryService) ListLatestSessionFiles(ctx context.Context, sessionID string) ([]history.File, error) {
- return nil, nil
- }
- func (m *mockHistoryService) Delete(ctx context.Context, id string) error {
- return nil
- }
- func (m *mockHistoryService) DeleteSessionFiles(ctx context.Context, sessionID string) error {
- return nil
- }
- func TestApplyEditToContentPartialSuccess(t *testing.T) {
- t.Parallel()
- content := "line 1\nline 2\nline 3\n"
- // Test successful edit.
- newContent, err := applyEditToContent(content, MultiEditOperation{
- OldString: "line 1",
- NewString: "LINE 1",
- })
- require.NoError(t, err)
- require.Contains(t, newContent, "LINE 1")
- require.Contains(t, newContent, "line 2")
- // Test failed edit (string not found).
- _, err = applyEditToContent(content, MultiEditOperation{
- OldString: "line 99",
- NewString: "LINE 99",
- })
- require.Error(t, err)
- require.Contains(t, err.Error(), "not found")
- }
- func TestMultiEditSequentialApplication(t *testing.T) {
- t.Parallel()
- tmpDir := t.TempDir()
- testFile := filepath.Join(tmpDir, "test.txt")
- // Create test file.
- content := "line 1\nline 2\nline 3\nline 4\n"
- err := os.WriteFile(testFile, []byte(content), 0o644)
- require.NoError(t, err)
- // Manually test the sequential application logic.
- currentContent := content
- // Apply edits sequentially, tracking failures.
- edits := []MultiEditOperation{
- {OldString: "line 1", NewString: "LINE 1"}, // Should succeed
- {OldString: "line 99", NewString: "LINE 99"}, // Should fail - doesn't exist
- {OldString: "line 3", NewString: "LINE 3"}, // Should succeed
- {OldString: "line 2", NewString: "LINE 2"}, // Should succeed - still exists
- }
- var failedEdits []FailedEdit
- successCount := 0
- for i, edit := range edits {
- newContent, err := applyEditToContent(currentContent, edit)
- if err != nil {
- failedEdits = append(failedEdits, FailedEdit{
- Index: i + 1,
- Error: err.Error(),
- Edit: edit,
- })
- continue
- }
- currentContent = newContent
- successCount++
- }
- // Verify results.
- require.Equal(t, 3, successCount, "Expected 3 successful edits")
- require.Len(t, failedEdits, 1, "Expected 1 failed edit")
- // Check failed edit details.
- require.Equal(t, 2, failedEdits[0].Index)
- require.Contains(t, failedEdits[0].Error, "not found")
- // Verify content changes.
- require.Contains(t, currentContent, "LINE 1")
- require.Contains(t, currentContent, "LINE 2")
- require.Contains(t, currentContent, "LINE 3")
- require.Contains(t, currentContent, "line 4") // Original unchanged
- require.NotContains(t, currentContent, "LINE 99")
- }
- func TestMultiEditAllEditsSucceed(t *testing.T) {
- t.Parallel()
- content := "line 1\nline 2\nline 3\n"
- edits := []MultiEditOperation{
- {OldString: "line 1", NewString: "LINE 1"},
- {OldString: "line 2", NewString: "LINE 2"},
- {OldString: "line 3", NewString: "LINE 3"},
- }
- currentContent := content
- successCount := 0
- for _, edit := range edits {
- newContent, err := applyEditToContent(currentContent, edit)
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- currentContent = newContent
- successCount++
- }
- require.Equal(t, 3, successCount)
- require.Contains(t, currentContent, "LINE 1")
- require.Contains(t, currentContent, "LINE 2")
- require.Contains(t, currentContent, "LINE 3")
- }
- func TestMultiEditAllEditsFail(t *testing.T) {
- t.Parallel()
- content := "line 1\nline 2\n"
- edits := []MultiEditOperation{
- {OldString: "line 99", NewString: "LINE 99"},
- {OldString: "line 100", NewString: "LINE 100"},
- }
- currentContent := content
- var failedEdits []FailedEdit
- for i, edit := range edits {
- newContent, err := applyEditToContent(currentContent, edit)
- if err != nil {
- failedEdits = append(failedEdits, FailedEdit{
- Index: i + 1,
- Error: err.Error(),
- Edit: edit,
- })
- continue
- }
- currentContent = newContent
- }
- require.Len(t, failedEdits, 2)
- require.Equal(t, content, currentContent, "Content should be unchanged")
- }
|