(require 'ert) (require 'llm-tools-hl) (ert-deftest llm-tools--hl-file-read-test () "Test that 'llm-tools--hl-file-read' produces correct hashline format." (let* ((content "\ ;; greeting.el (defconst greeting-title \"Mr\") (defun greet (name) (concat greeting-title (or (string-trim name) \"guest\")) )") (file (make-temp-file nil nil nil content)) (actual (llm-tools--hl-file-read file))) (unwind-protect (progn (should (= (length (string-lines actual)) 7)) (should (string-match-p "\\`1gi|;; greeting" actual)) (should (string-match-p "7ce|)\\'" actual))) (delete-file file)))) (ert-deftest llm-tools--hl-anchor-to-line-num-test () (should (= (llm-tools--hl-anchor-to-line-num "5ff") 5)) (should (= (llm-tools--hl-anchor-to-line-num "123ab") 123)) (should (= (llm-tools--hl-anchor-to-line-num "1vx") 1))) (ert-deftest llm-tools--hl-parse-section-single-replace () (let ((section "= 1vx..1vx\n~(defconst greeting-title \"Mrs\")")) (should (= (length (llm-tools--hl-parse-section section)) 1)) (let ((op (car (llm-tools--hl-parse-section section)))) (should (eq (hl-op-type op) 'replace)) (should (equal (hl-op-range op) '("1vx" "1vx"))) (should (equal (hl-op-payload op) '("(defconst greeting-title \"Mrs\")")))))) (ert-deftest llm-tools--hl-parse-section-multi-line-payload () (let ((section "= 3gv..6be\n~ (concat\n~ greeting-title\n~ (or (string-trim name) \"guest\"))\n~ )")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (= (length (hl-op-payload op)) 4))))) (ert-deftest llm-tools--hl-parse-section-blank-replace () "Replace with no payload should produce nil payload, not empty list." (let ((section "= 5ff..5ff")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (eq (hl-op-type op) 'replace)) (should (null (hl-op-payload op)))))) (ert-deftest llm-tools--hl-parse-section-insert-after () (let ((section "+ 4ei\n~ greeting-title")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (eq (hl-op-type op) 'insert-after)) (should (string= (hl-op-anchor op) "4ei")) (should (equal (hl-op-payload op) '(" greeting-title")))))) (ert-deftest llm-tools--hl-parse-section-insert-before () (let ((section "< 5ff\n~ greeting-title")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (eq (hl-op-type op) 'insert-before)) (should (string= (hl-op-anchor op) "5ff"))))) (ert-deftest llm-tools--hl-parse-section-delete () (let ((section "- 5ff..5ff")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (eq (hl-op-type op) 'delete)) (should (equal (hl-op-range op) '("5ff" "5ff")))))) (ert-deftest llm-tools--hl-parse-section-skip-comments-and-blanks () (let ((section "# comment\n\n+ 4ei\n~line")) (should (= (length (llm-tools--hl-parse-section section)) 1)))) (ert-deftest llm-tools--hl-parse-section-multiple-ops () "Two separate insert ops in one section." (let ((section "+ 4ei\n~first\n< 5ff\n~second")) (let ((ops (llm-tools--hl-parse-section section))) (should (= (length ops) 2)) (should (eq (hl-op-type (car ops)) 'insert-after)) (should (eq (hl-op-type (cadr ops)) 'insert-before))))) (ert-deftest llm-tools--hl-apply-replace-single-line () (let* ((content "line1\nline2\nline3\n") (file (make-temp-file nil nil nil content)) (anchor (llm-tools--hl-file-line-hash file 2)) (patch (format "@@ %s\n= %s..%s\n~REPLACED" file anchor anchor))) (unwind-protect (progn (llm-tools--hl-apply patch) (should (string= (llm-tools--hl-file-read file) (string-join (list (format "1%s|line1" (llm-tools--hl-hash "line1")) (format "2%s|REPLACED" (llm-tools--hl-hash "REPLACED")) (format "3%s|line3" (llm-tools--hl-hash "line3"))) "\n")))) (delete-file file)))) (ert-deftest llm-tools--hl-apply-insert-after () (let* ((content "A\nB\nC\n") (file (make-temp-file nil nil nil content)) (anchor (llm-tools--hl-file-line-hash file 1)) (patch (format "@@ %s\n+ %s\n~NEW" file anchor))) (unwind-protect (progn (llm-tools--hl-apply patch) (should (equal (llm-tools--hl-file-lines file) '("A" "NEW" "B" "C")))) (delete-file file)))) (ert-deftest llm-tools--hl-apply-insert-before () (let* ((content "A\nB\nC\n") (file (make-temp-file nil nil nil content)) (anchor (llm-tools--hl-file-line-hash file 3)) (patch (format "@@ %s\n< %s\n~NEW" file anchor))) (unwind-protect (progn (llm-tools--hl-apply patch) (should (equal (llm-tools--hl-file-lines file) '("A" "B" "NEW" "C")))) (delete-file file)))) (ert-deftest llm-tools--hl-apply-append-to-eof () (let* ((content "A\nB\n") (file (make-temp-file nil nil nil content)) (patch (format "@@ %s\n+ EOF\n~APPENDED" file))) (unwind-protect (progn (llm-tools--hl-apply patch) (should (equal (llm-tools--hl-file-lines file) '("A" "B" "APPENDED")))) (delete-file file)))) (ert-deftest llm-tools--hl-apply-delete () (let* ((content "A\nB\nC\n") (file (make-temp-file nil nil nil content)) (anchor (llm-tools--hl-file-line-hash file 2)) (patch (format "@@ %s\n- %s..%s" file anchor anchor))) (unwind-protect (progn (llm-tools--hl-apply patch) (should (equal (llm-tools--hl-file-lines file) '("A" "C")))) (delete-file file)))) (ert-deftest llm-tools--hl-apply-blank-line () "Replace with no payload produces a blank line." (let* ((content "A\nB\nC\n") (file (make-temp-file nil nil nil content)) (anchor (llm-tools--hl-file-line-hash file 2)) (patch (format "@@ %s\n= %s..%s" file anchor anchor))) (unwind-protect (progn (llm-tools--hl-apply patch) (should (equal (llm-tools--hl-file-lines file) '("A" "" "C")))) (delete-file file)))) (ert-deftest llm-tools--hl-apply-offset-tracking () "Multiple ops in one file: verify offset adjusts subsequent line numbers." (let* ((content "A\nB\nC\nD\nE\n") (file (make-temp-file nil nil nil content)) (a1 (llm-tools--hl-file-line-hash file 1)) (a3 (llm-tools--hl-file-line-hash file 3)) (patch (format "@@ %s\n+ %s\n~X\n~Y\n- %s..%s" file a1 a3 a3))) (unwind-protect (progn (llm-tools--hl-apply patch) (let ((lines (llm-tools--hl-file-lines file))) (should (equal lines '("A" "X" "Y" "B" "D" "E"))))) (delete-file file)))) (ert-deftest llm-tools--hl-apply-multi-file () "Two files in one patch both get modified." (let* ((file1 (make-temp-file nil nil nil "A\nB\n")) (file2 (make-temp-file nil nil nil "X\nY\n")) (a1 (llm-tools--hl-file-line-hash file1 1)) (a2 (llm-tools--hl-file-line-hash file2 2)) (patch (format "@@ %s\n+ %s\n~NEW1\n@@ %s\n= %s..%s\n~NEW2" file1 a1 file2 a2 a2))) (unwind-protect (progn (llm-tools--hl-apply patch) (should (equal (llm-tools--hl-file-lines file1) '("A" "NEW1" "B"))) (should (equal (llm-tools--hl-file-lines file2) '("X" "NEW2")))) (delete-file file1) (delete-file file2)))) (ert-deftest llm-tools--hl-apply-multi-line-payload () "Replace with many payload lines." (let* ((content "A\nB\n") (file (make-temp-file nil nil nil content)) (a1 (llm-tools--hl-file-line-hash file 1)) (patch (format "@@ %s\n= %s..%s\n~ONE\n~TWO\n~THREE" file a1 a1))) (unwind-protect (progn (llm-tools--hl-apply patch) (let ((lines (llm-tools--hl-file-lines file))) (should (equal lines '("ONE" "TWO" "THREE" "B"))))) (delete-file file)))) ;; --- llm-tools--hl-hash --- (ert-deftest llm-tools--hl-hash-test () "Test that hashing produces consistent, valid bigram output." ;; Deterministic: same input -> same output (should (string= (llm-tools--hl-hash "hello") (llm-tools--hl-hash "hello"))) ;; Different input -> likely different output (hash collision unlikely) (should (not (string= (llm-tools--hl-hash "hello") (llm-tools--hl-hash "world")))) ;; Output is always a valid bigram from the table (let ((result (llm-tools--hl-hash "test line"))) (should (= (length result) 2)) (should (member result llm-tools--hl-bigrams)))) ;; --- llm-tools--hl-file-lines --- (ert-deftest llm-tools--hl-file-lines-test () "Test reading file contents as a list of strings." (let* ((content "alpha\nbeta\ngamma\n") (file (make-temp-file nil nil nil content))) (unwind-protect (progn (should (equal (llm-tools--hl-file-lines file) '("alpha" "beta" "gamma"))) ;; With beg/end range (inclusive, 1-based) (should (equal (llm-tools--hl-file-lines file 1 2) '("alpha" "beta"))) (should (equal (llm-tools--hl-file-lines file 2 3) '("beta" "gamma"))) (should (equal (llm-tools--hl-file-lines file 2 2) '("beta")))) (delete-file file)))) (ert-deftest llm-tools--hl-file-lines-empty-file-test () "Test reading an empty file." (let ((file (make-temp-file nil nil ""))) (unwind-protect (should (equal (llm-tools--hl-file-lines file) '(""))) (delete-file file)))) ;; --- llm-tools--hl-format-lines --- (ert-deftest llm-tools--hl-format-lines-test () "Test formatting lines in hashline format." (let ((lines '("hello" "world"))) ;; Without content (let ((no-content (llm-tools--hl-format-lines lines nil t))) (should (= (length (string-lines no-content)) 2)) (should (string-match-p "\\`1.." no-content))) ;; With content (let ((with-content (llm-tools--hl-format-lines lines))) (should (string-match-p "\\`1..|hello" with-content)) (should (string-match-p "2..|world\\'" with-content))) ;; With custom start number (let ((custom-start (llm-tools--hl-format-lines lines 5 t))) (should (string-match-p "\\`5.." custom-start)) (should (string-match-p "6..\\'" custom-start))))) ;; --- llm-tools--hl-file-read-range --- (ert-deftest llm-tools--hl-file-read-range-test () "Test reading a range of lines in hashline format." (let* ((content "line1\nline2\nline3\nline4\n") (file (make-temp-file nil nil nil content))) (unwind-protect (let ((result (llm-tools--hl-file-read file 2 3))) (should (= (length (string-lines result)) 2)) (should (string-match-p "\\`2.." result)) (should (string-match-p "line2" result)) (should (string-match-p "line3" result))) (delete-file file)))) ;; --- llm-tools--hl-file-read-hashes --- (ert-deftest llm-tools--hl-file-read-hashes-test () "Test reading file as list of hash strings without content." (let* ((content "foo\nbar\n") (file (make-temp-file nil nil nil content))) (unwind-protect (let ((hashes (llm-tools--hl-file-read file nil nil t t))) (should (= (length hashes) 2)) (should (string-match-p "\\`1.." (elt hashes 0))) (should (string-match-p "\\`2.." (elt hashes 1))) ;; Each hash is exactly 3 chars (1+2 for bigram, variable digit count) (dolist (h hashes) (should (>= (length h) 3)))) (delete-file file)))) ;; --- llm-tools--hl-file-line-hash --- (ert-deftest llm-tools--hl-file-line-hash-test () "Test getting the hash of a specific line in a file." (let* ((content "first\nsecond\nthird\n") (file (make-temp-file nil nil nil content))) (unwind-protect (progn (should (string= (llm-tools--hl-file-line-hash file 1) (concat "1" (llm-tools--hl-hash "first")))) (should (string= (llm-tools--hl-file-line-hash file 2) (concat "2" (llm-tools--hl-hash "second")))) (should (string= (llm-tools--hl-file-line-hash file 3) (concat "3" (llm-tools--hl-hash "third"))))) (delete-file file)))) ;; --- llm-tools--split-patch-sections --- (ert-deftest llm-tools--split-patch-sections-single-file-test () "Test splitting a patch with one file section." (let ((patch "@@ foo.el\n= 1vx..1vx\n~hello")) (let ((sections (llm-tools--split-patch-sections patch))) (should (= (length sections) 1)) (should (string= (car (car sections)) "foo.el")) (should (string-match-p "= 1vx" (cdr (car sections))))))) (ert-deftest llm-tools--split-patch-sections-multi-file-test () "Test splitting a patch with multiple file sections." (let ((patch "@@ a.el\n+ 1vx\n~X\n@@ b.el\n- 2in..2in")) (let ((sections (llm-tools--split-patch-sections patch))) (should (= (length sections) 2)) (should (string= (car (car sections)) "a.el")) (should (string= (car (cadr sections)) "b.el"))))) (ert-deftest llm-tools--split-patch-sections-empty-test () "Test splitting an empty or invalid patch." (should (null (llm-tools--split-patch-sections ""))) (should (null (llm-tools--split-patch-sections "no header here")))) ;; --- llm-tools--hl-get-anchors --- (ert-deftest llm-tools--hl-get-anchors-test () "Test extracting anchors from a patch." (let ((patch "@@ test.el\n+ 4ei\n~X\n= 3gv..6be\n~Y\n- 5ff..5ff")) (let ((anchors (llm-tools--hl-get-anchors patch))) (should (= (length anchors) 1)) (let ((file-anchors (cdr (car anchors)))) (should (member "4ei" file-anchors)) (should (member "5ff" file-anchors)) (should (member "3gv" file-anchors)) (should (member "6be" file-anchors)))))) (ert-deftest llm-tools--hl-get-anchors-eof-bof-test () "Test that EOF/BOF anchors are extracted correctly." (let ((patch "@@ test.el\n+ EOF\n~X\n< BOF\n~Y")) (let ((anchors (llm-tools--hl-get-anchors patch))) (let ((file-anchors (cdr (car anchors)))) (should (member "EOF" file-anchors)) (should (member "BOF" file-anchors)))))) ;; --- llm-tools--hl-parse-op-line --- (ert-deftest llm-tools--hl-parse-op-line-insert-after-test () (let ((op (llm-tools--hl-parse-op-line "+ 4ei"))) (should (eq (hl-op-type op) 'insert-after)) (should (string= (hl-op-anchor op) "4ei")))) (ert-deftest llm-tools--hl-parse-op-line-insert-before-test () (let ((op (llm-tools--hl-parse-op-line "< 5ff"))) (should (eq (hl-op-type op) 'insert-before)) (should (string= (hl-op-anchor op) "5ff")))) (ert-deftest llm-tools--hl-parse-op-line-delete-test () (let ((op (llm-tools--hl-parse-op-line "- 3gv..6be"))) (should (eq (hl-op-type op) 'delete)) (should (equal (hl-op-range op) '("3gv" "6be"))))) (ert-deftest llm-tools--hl-parse-op-line-replace-test () (let ((op (llm-tools--hl-parse-op-line "= 1vx..2in"))) (should (eq (hl-op-type op) 'replace)) (should (equal (hl-op-range op) '("1vx" "2in"))))) (ert-deftest llm-tools--hl-parse-op-line-eof-anchor-test () (let ((op (llm-tools--hl-parse-op-line "+ EOF"))) (should (eq (hl-op-type op) 'insert-after)) (should (string= (hl-op-anchor op) "EOF")))) (ert-deftest llm-tools--hl-parse-op-line-bof-anchor-test () (let ((op (llm-tools--hl-parse-op-line "< BOF"))) (should (eq (hl-op-type op) 'insert-before)) (should (string= (hl-op-anchor op) "BOF")))) ;; --- llm-tools--hl-group-by-file --- (ert-deftest llm-tools--hl-group-by-file-test () "Test grouping sections by file path." (let* ((sec1 (cons "a.el" "body1")) (sec2 (cons "b.el" "body2")) (sec3 (cons "a.el" "body3")) (sections (list sec1 sec2 sec3))) (let ((grouped (llm-tools--hl-group-by-file sections))) (let ((total 0)) (dolist (g grouped) (cl-incf total (length (cdr g)))) (should (= total 3))) (let ((a-group (assoc "a.el" grouped))) (should a-group) (should (= (length (cdr a-group)) 2))) (let ((b-group (assoc "b.el" grouped))) (should b-group) (should (= (length (cdr b-group)) 1)))))) ;; --- llm-tools--hl-validate-no-payload-before-op --- (ert-deftest llm-tools--hl-validate-no-payload-before-op-test () "Test that payload before first op is detected." ;; This is an edge case: payload lines at the very start with no preceding op ;; The parser won't actually produce this, but test the validator directly (let ((op (make-hl-verify-op :type 'insert-after :anchor "4ei" :payload '("should not be here")))) (let ((errors (llm-tools--hl-validate-no-payload-before-op (list op)))) (should (= (length errors) 1)))) ;; Clean case (let ((op (make-hl-verify-op :type 'insert-after :anchor "4ei" :payload nil))) (let ((errors (llm-tools--hl-validate-no-payload-before-op (list op)))) (should (= (length errors) 0))))) ;; --- llm-tools--hl-validate-insert-has-payload --- (ert-deftest llm-tools--hl-validate-insert-has-payload-test () "Test that inserts without payload are flagged." (let ((op (make-hl-verify-op :type 'insert-after :anchor "4ei" :payload nil))) (let ((errors (llm-tools--hl-validate-insert-has-payload (list op)))) (should (= (length errors) 1)))) ;; Insert with payload is fine (let ((op (make-hl-verify-op :type 'insert-after :anchor "4ei" :payload '("line")))) (should (= (length (llm-tools--hl-validate-insert-has-payload (list op))) 0))) ;; Insert-before without payload (let ((op (make-hl-verify-op :type 'insert-before :anchor "4ei" :payload nil))) (let ((errors (llm-tools--hl-validate-insert-has-payload (list op)))) (should (= (length errors) 1))))) ;; --- llm-tools--hl-validate-delete-no-payload --- (ert-deftest llm-tools--hl-validate-delete-no-payload-test () "Test that deletes with payload are flagged." (let ((op (make-hl-verify-op :type 'delete :anchor "1vx..2in" :payload '("bad")))) (let ((errors (llm-tools--hl-validate-delete-no-payload (list op)))) (should (= (length errors) 1)))) ;; Delete without payload is fine (let ((op (make-hl-verify-op :type 'delete :anchor "1vx..2in" :payload nil))) (should (= (length (llm-tools--hl-validate-delete-no-payload (list op))) 0)))) ;; --- llm-tools--hl-validate-anchors --- (ert-deftest llm-tools--hl-validate-anchors-insert-valid-test () "Test valid insert anchors pass validation." (let ((op (make-hl-verify-op :type 'insert-after :anchor "4ei" :payload '("x")))) (should (= (length (llm-tools--hl-validate-anchors (list op))) 0)))) (ert-deftest llm-tools--hl-validate-anchors-insert-eof-test () "Test that EOF is accepted as a valid insert anchor." (let ((op (make-hl-verify-op :type 'insert-after :anchor "EOF" :payload '("x")))) (should (= (length (llm-tools--hl-validate-anchors (list op))) 0)))) (ert-deftest llm-tools--hl-validate-anchors-insert-bof-test () "Test that BOF is accepted as a valid insert anchor." (let ((op (make-hl-verify-op :type 'insert-before :anchor "BOF" :payload '("x")))) (should (= (length (llm-tools--hl-validate-anchors (list op))) 0)))) (ert-deftest llm-tools--hl-validate-anchors-insert-invalid-test () "Test that malformed insert anchors are flagged." (let ((op (make-hl-verify-op :type 'insert-after :anchor "bad" :payload '("x")))) (let ((errors (llm-tools--hl-validate-anchors (list op)))) (should (> (length errors) 0)))) (let ((op (make-hl-verify-op :type 'insert-after :anchor "" :payload '("x")))) (let ((errors (llm-tools--hl-validate-anchors (list op)))) (should (> (length errors) 0))))) (ert-deftest llm-tools--hl-validate-anchors-range-valid-test () "Test valid range anchors pass validation." (let ((op (make-hl-verify-op :type 'delete :anchor "3gv..6be" :payload nil))) (should (= (length (llm-tools--hl-validate-anchors (list op))) 0)))) (ert-deftest llm-tools--hl-validate-anchors-range-missing-dotdot-test () "Test that ranges without '..' are flagged." (let ((op (make-hl-verify-op :type 'delete :anchor "3gv6be" :payload nil))) (let ((errors (llm-tools--hl-validate-anchors (list op)))) (should (> (length errors) 0))))) (ert-deftest llm-tools--hl-validate-anchors-range-non-numeric-test () "Test that range parts starting with non-numeric chars are flagged." (let ((op (make-hl-verify-op :type 'replace :anchor "abc..def" :payload nil))) (let ((errors (llm-tools--hl-validate-anchors (list op)))) (should (> (length errors) 0))))) ;; --- llm-tools--hl-verify-section --- (ert-deftest llm-tools--hl-verify-section-clean-test () "Test that a well-formed section produces no errors." (let ((section (cons "test.el" "\n+ 4ei\n~payload"))) (should (= (length (llm-tools--hl-verify-section section)) 0)))) (ert-deftest llm-tools--hl-verify-section-missing-payload-test () "Test that an insert without payload produces an error." (let ((section (cons "test.el" "\n+ 4ei"))) (should (> (length (llm-tools--hl-verify-section section)) 0)))) ;; --- llm-tools--hl-verify-structure --- (ert-deftest llm-tools--hl-verify-structure-clean-test () "Test that a structurally valid patch produces no errors." (let ((patch "@@ test.el\n+ 4ei\n~payload")) (should (= (length (llm-tools--hl-verify-structure patch)) 0)))) (ert-deftest llm-tools--hl-verify-structure-errors-test () "Test that structural violations are reported." (let* ((content "A\n") (file (make-temp-file nil nil nil content)) (patch (format "@@ %s\n+ 4ei" file))) (unwind-protect (should (> (length (llm-tools--hl-verify-structure patch)) 0)) (delete-file file)))) ;; --- llm-tools--hl-verify-anchors --- (ert-deftest llm-tools--hl-verify-anchors-matching-test () "Test that anchors matching file content return t." (let* ((content "hello\nworld\n") (file (make-temp-file nil nil nil content)) (anchor (llm-tools--hl-file-line-hash file 1)) (patch (format "@@ %s\n+ %s\n~new" file anchor))) (unwind-protect (let ((results (llm-tools--hl-verify-anchors patch))) (should (= (length results) 1)) (let ((entry (car results))) (should (string= (car entry) file)) (should (cdr entry)))) (delete-file file)))) (ert-deftest llm-tools--hl-verify-anchors-stale-test () "Test that stale anchors (file changed) return nil." (let* ((content "hello\nworld\n") (file (make-temp-file nil nil nil content)) (patch (format "@@ %s\n= 1zzz..1zzz\n~new" file))) ;; 1zzz is unlikely to match the actual hash (unwind-protect (let ((results (llm-tools--hl-verify-anchors patch))) (should (= (length results) 1)) (should-not (cdr (car results)))) (delete-file file)))) (ert-deftest llm-tools--hl-verify-anchors-eof-bof-ignored-test () "Test that EOF and BOF are excluded from anchor verification." (let* ((content "hello\n") (file (make-temp-file nil nil nil content))) (unwind-protect (progn ;; EOF anchor should not cause anchor mismatch (let ((patch (format "@@ %s\n+ EOF\n~appended" file))) (let ((results (llm-tools--hl-verify-anchors patch))) (should (cdr (car results))))) ;; BOF anchor should not cause anchor mismatch (let ((patch (format "@@ %s\n< BOF\n~prepended" file))) (let ((results (llm-tools--hl-verify-anchors patch))) (should (cdr (car results)))))) (delete-file file)))) ;; --- llm-tools--hl-verify --- (ert-deftest llm-tools--hl-verify-clean-test () "Test that a valid patch returns empty string." (let* ((content "hello\nworld\n") (file (make-temp-file nil nil nil content)) (anchor (llm-tools--hl-file-line-hash file 1)) (patch (format "@@ %s\n+ %s\n~new" file anchor))) (unwind-protect (should (string= (llm-tools--hl-verify patch) "")) (delete-file file)))) (ert-deftest llm-tools--hl-verify-structural-error-test () "Test that structural errors are returned as a string." (let* ((file (make-temp-file nil nil nil ""))) (unwind-protect (let ((patch (format "@@ %s\n+ 4ei" file))) (should-not (string= (llm-tools--hl-verify patch) ""))) (delete-file file)))) (ert-deftest llm-tools--hl-verify-anchor-error-test () "Test that anchor mismatches produce a descriptive error." (let* ((content "hello\n") (file (make-temp-file nil nil nil content))) (unwind-protect (let ((patch (format "@@ %s\n= 1zzz..1zzz\n~new" file))) (let ((msg (llm-tools--hl-verify patch))) (should-not (string= msg "")) (should (string-match "incorrect anchors" msg)))) (delete-file file)))) ;; --- llm-tools--hl-edit --- (ert-deftest llm-tools--hl-edit-success-test () "Test that a valid patch is applied successfully via llm-tools--hl-edit." (let* ((content "hello\nworld\n") (file (make-temp-file nil nil nil content)) (anchor (llm-tools--hl-file-line-hash file 1)) (patch (format "@@ %s\n+ %s\n~new" file anchor))) (unwind-protect (let ((result (llm-tools--hl-edit patch))) (should (string-match "Finished" result)) (should (equal (llm-tools--hl-file-lines file) '("hello" "new" "world")))) (delete-file file)))) (ert-deftest llm-tools--hl-edit-failure-test () "Test that an invalid patch is rejected via llm-tools--hl-edit." (let* ((content "hello\n") (file (make-temp-file nil nil nil content))) (unwind-protect (let ((patch (format "@@ %s\n= 1zzz..1zzz\n~new" file))) (let ((result (llm-tools--hl-edit patch))) (should-not (string-match "Finished" result)))) (delete-file file)))) ;; --- llm-tools--hl-apply-one-op edge cases --- (ert-deftest llm-tools--hl-apply-one-op-insert-at-eof-test () "Test inserting at EOF anchor." (let* ((content '("A" "B")) (op (make-hl-op :type 'insert-after :anchor "EOF" :payload '("C")))) (let ((result (llm-tools--hl-apply-one-op op content 0))) (should (equal (car result) '("A" "B" "C")))))) (ert-deftest llm-tools--hl-apply-one-op-insert-at-bof-test () "Test inserting at BOF anchor." (let* ((content '("A" "B")) (op (make-hl-op :type 'insert-before :anchor "BOF" :payload '("C")))) (let ((result (llm-tools--hl-apply-one-op op content 0))) (should (equal (car result) '("C" "A" "B")))))) (ert-deftest llm-tools--hl-apply-one-op-replace-blank-payload-test () "Test that replace with nil payload produces a blank line." (let* ((content '("A" "B" "C")) (op (make-hl-op :type 'replace :range '("2in" "2in") :payload nil))) (let ((result (llm-tools--hl-apply-one-op op content 0))) (should (equal (car result) '("A" "" "C")))))) (ert-deftest llm-tools--hl-apply-one-op-replace-multi-line-test () "Test replace spanning multiple lines." (let* ((content '("A" "B" "C" "D")) (op (make-hl-op :type 'replace :range '("2in" "3cc") :payload '("X" "Y")))) (let ((result (llm-tools--hl-apply-one-op op content 0))) (should (equal (car result) '("A" "X" "Y" "D")))))) (ert-deftest llm-tools--hl-apply-one-op-delete-range-test () "Test deleting a range of lines." (let* ((content '("A" "B" "C" "D" "E")) (op (make-hl-op :type 'delete :range '("2in" "4ei")))) (let ((result (llm-tools--hl-apply-one-op op content 0))) (should (equal (car result) '("A" "E")))))) (ert-deftest llm-tools--hl-apply-one-op-offset-tracking-test () "Test that offset is updated correctly after each op." (let* ((content '("A" "B" "C")) (op (make-hl-op :type 'insert-after :anchor "1vx" :payload '("X" "Y")))) (let ((result (llm-tools--hl-apply-one-op op content 0))) (should (= (cdr result) 2))))) ;; --- llm-tools--hl-parse-section edge cases --- (ert-deftest llm-tools--hl-parse-section-empty-payload-for-replace-test () "Test that replace with no ~ lines produces nil payload." (let ((section "= 1vx..1vx")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (null (hl-op-payload op)))))) (ert-deftest llm-tools--hl-parse-section-payload-with-tabs-test () "Test that payload lines preserve leading tabs." (let ((section "+ 4ei\n~\tindented")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (string= (car (hl-op-payload op)) "\tindented"))))) (ert-deftest llm-tools--hl-parse-section-payload-with-spaces-test () "Test that payload lines preserve leading spaces." (let ((section "+ 4ei\n~ two spaces")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (string= (car (hl-op-payload op)) " two spaces"))))) (ert-deftest llm-tools--hl-parse-section-blank-payload-line-test () "Test that a ~ line with no content produces an empty string in payload." (let ((section "= 1vx..1vx\n~")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (equal (hl-op-payload op) '("")))))) (ert-deftest llm-tools--hl-parse-section-eof-anchor-test () "Test parsing an insert with EOF anchor." (let ((section "+ EOF\n~appended")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (eq (hl-op-type op) 'insert-after)) (should (string= (hl-op-anchor op) "EOF"))))) (ert-deftest llm-tools--hl-parse-section-bof-anchor-test () "Test parsing an insert with BOF anchor." (let ((section "< BOF\n~prepended")) (let ((op (car (llm-tools--hl-parse-section section)))) (should (eq (hl-op-type op) 'insert-before)) (should (string= (hl-op-anchor op) "BOF")))))