package sidekick import ( "os" "strings" "testing" ) func TestShellExecHandler(t *testing.T) { // Success got, err := shellExecHandler(map[string]interface{}{"command": "echo hello"}) if err != nil { t.Fatal(err) } if strings.TrimSpace(got) != "hello" { t.Errorf("expected hello, got %s", got) } // Failure got, err = shellExecHandler(map[string]interface{}{ "command": "ls /nonexistent-file-path-that-should-not-exist", }) if err != nil { t.Fatal(err) } if !strings.Contains(got, "Error:") { t.Errorf("expected error in output, got %s", got) } // Empty got, err = shellExecHandler(map[string]interface{}{"command": "true"}) if err != nil { t.Fatal(err) } if got != "(empty output)" { t.Errorf("expected empty output, got %s", got) } } func TestHashLine(t *testing.T) { line := "hello world" h1 := hashLine(line) h2 := hashLine(line) if h1 != h2 { t.Errorf("hashLine is not deterministic: %s != %s", h1, h2) } if len(h1) != 4 { t.Errorf("hashLine output length is not 4: %d", len(h1)) } h3 := hashLine("hello worle") if h1 == h3 { t.Errorf("hashLine collision for similar strings: %s == %s", h1, h3) } } func TestStripHashlines(t *testing.T) { input := "1:abcd:line 1\n2:ef01:line 2\nnot a hashline\n3:ghij:line 3" // ghij is not valid hex, so it should NOT be stripped if we follow the code strictly // Wait, my code checks for hex: (c >= 'a' && c <= 'f') // So 'ghij' should not be stripped. expected := "line 1\nline 2\nnot a hashline\n3:ghij:line 3" got := stripHashlines(input) if got != expected { t.Errorf("stripHashlines failed.\nGot:\n%s\nExpected:\n%s", got, expected) } input2 := "10:1234:content with : colons" expected2 := "content with : colons" got2 := stripHashlines(input2) if got2 != expected2 { t.Errorf("stripHashlines failed for content with colons.\nGot: %s\nExpected: %s", got2, expected2) } } func TestEditFileHandler_ExactHashlines(t *testing.T) { tmpFile := "test_edit_exact.txt" initial := "line one\nline two\nline three\nline four" os.WriteFile(tmpFile, []byte(initial), 0644) defer os.Remove(tmpFile) h2 := hashLine("line two") h3 := hashLine("line three") args := map[string]interface{}{ "path": tmpFile, "old_string": "2:" + h2 + ":line two\n3:" + h3 + ":line three", "new_string": "replaced two\nreplaced three", } _, err := editFileHandler(args) if err != nil { t.Fatal(err) } got, _ := os.ReadFile(tmpFile) expected := "line one\nreplaced two\nreplaced three\nline four" if string(got) != expected { t.Errorf("editFileHandler did not precisely replace using hashlines. Got: %s Expected: %s", string(got), expected) } } func TestGrepFileHandler_Hashlines(t *testing.T) { tmpFile := "test_read_hash.txt" content := "line one\nline two" os.WriteFile(tmpFile, []byte(content), 0644) defer os.Remove(tmpFile) args := map[string]interface{}{"path": tmpFile} got, err := readFileHandler(args) if err != nil { t.Fatal(err) } lines := strings.Split(got, "\n") if len(lines) != 2 { t.Fatalf("expected 2 lines, got %d", len(lines)) } if !strings.HasPrefix(lines[0], "1:") || !strings.HasSuffix(lines[0], ":line one") { t.Errorf("line 0 format wrong: %s", lines[0]) } if !strings.HasPrefix(lines[1], "2:") || !strings.HasSuffix(lines[1], ":line two") { t.Errorf("line 1 format wrong: %s", lines[1]) } } func TestWriteFileHandler_Strip(t *testing.T) { tmpFile := "test_write_strip.txt" defer os.Remove(tmpFile) content := "1:abcd:clean line\n2:1234:another line" args := map[string]interface{}{ "path": tmpFile, "content": content, } _, err := writeFileHandler(args) if err != nil { t.Fatal(err) } got, _ := os.ReadFile(tmpFile) expected := "clean line\nanother line" if string(got) != expected { t.Errorf("writeFileHandler did not strip hashlines.\nGot: %s\nExpected: %s", string(got), expected) } } func TestEditFileHandler_Strip(t *testing.T) { tmpFile := "test_edit_strip.txt" initial := "find me\nkeep me" os.WriteFile(tmpFile, []byte(initial), 0644) defer os.Remove(tmpFile) args := map[string]interface{}{ "path": tmpFile, "old_string": "1:abcd:find me", "new_string": "1:beef:replaced", } _, err := editFileHandler(args) if err != nil { t.Fatal(err) } got, _ := os.ReadFile(tmpFile) expected := "replaced\nkeep me" if string(got) != expected { t.Errorf("editFileHandler did not strip hashlines.\nGot: %s\nExpected: %s", string(got), expected) } } func TestGrepFileHandler_HashlineAnchors(t *testing.T) { tmpFile := "test_grep_anchors.txt" content := "first line\nsecond line\nthird needle\nfourth line\nfifth needle" os.WriteFile(tmpFile, []byte(content), 0644) defer os.Remove(tmpFile) args := map[string]interface{}{ "path": tmpFile, "pattern": "needle", "context_lines": 1.0, } got, err := grepFileHandler(args) if err != nil { t.Fatal(err) } // Lines in file: // 1: first line // 2: second line // 3: third needle (MATCH) // 4: fourth line // 5: fifth needle (MATCH) // Expected output structure should contain: // --- Match around line 3 --- // 2::second line // 3::third needle // 4::fourth line // --- Match around line 5 --- // 4::fourth line // 5::fifth needle lines := strings.Split(got, "\n") findLine := func(prefix string) bool { for _, l := range lines { if strings.HasPrefix(l, prefix) { return true } } return false } // Check anchors if !findLine("--- Match around line 3 ---") { t.Errorf("missing match anchor for line 3") } if !findLine("--- Match around line 5 ---") { t.Errorf("missing match anchor for line 5") } // Check specific hashline prefixes h2 := hashLine("second line") h3 := hashLine("third needle") h5 := hashLine("fifth needle") if !findLine("2:" + h2 + ":second line") { t.Errorf("missing correctly hashed context line 2") } if !findLine("3:" + h3 + ":third needle") { t.Errorf("missing correctly hashed match line 3") } if !findLine("5:" + h5 + ":fifth needle") { t.Errorf("missing correctly hashed match line 5") } }