aboutsummaryrefslogtreecommitdiff
path: root/tests/test-update-text-file.el
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-update-text-file.el')
-rw-r--r--tests/test-update-text-file.el473
1 files changed, 0 insertions, 473 deletions
diff --git a/tests/test-update-text-file.el b/tests/test-update-text-file.el
deleted file mode 100644
index fc4f8c36a..000000000
--- a/tests/test-update-text-file.el
+++ /dev/null
@@ -1,473 +0,0 @@
-;;; test-update-text-file.el --- Tests for update_text_file gptel tool -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Normal / Boundary / Error tests for each operation in
-;; gptel-tools/update_text_file.el, plus file-level wrapper tests.
-;; The pure-string helpers carry most of the coverage; the wrapper
-;; only adds the I/O surface (backup, write, validation).
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-
-(eval-and-compile
- (add-to-list 'load-path (expand-file-name "tests" user-emacs-directory))
- (add-to-list 'load-path (expand-file-name "gptel-tools" user-emacs-directory))
- (setq load-prefer-newer t)
- ;; Stub gptel so the tool file can be loaded without the real package.
- (unless (featurep 'gptel)
- (defvar gptel-tools nil)
- (defun gptel-make-tool (&rest _args) nil)
- (defun gptel-get-tool (&rest _args) nil)
- (provide 'gptel)))
-
-(require 'update_text_file)
-
-;; ----------------------------------------------------- helpers
-
-(defun test-update-text-file--with-temp (content fn)
- "Write CONTENT to a temp file, call FN with its path, then delete."
- (let ((path (make-temp-file "test-update-text-file-")))
- (unwind-protect
- (progn
- (with-temp-file path (insert content))
- (funcall fn path))
- (when (file-exists-p path) (delete-file path)))))
-
-;; ----------------------------------------------------- replace
-
-(ert-deftest test-update-text-file-replace-normal ()
- "Normal: replace all occurrences of the literal pattern."
- (should (equal (cj/update-text-file--replace "foo bar foo" "foo" "BAZ")
- "BAZ bar BAZ")))
-
-(ert-deftest test-update-text-file-replace-boundary-no-match ()
- "Boundary: pattern absent returns content unchanged."
- (should (equal (cj/update-text-file--replace "abc" "xyz" "QQ") "abc")))
-
-(ert-deftest test-update-text-file-replace-boundary-special-chars ()
- "Boundary: regex metacharacters in pattern are treated as literals."
- (should (equal (cj/update-text-file--replace "a.b.c" "." "-") "a-b-c"))
- (should (equal (cj/update-text-file--replace "(x)(y)" "(x)" "_") "_(y)"))
- (should (equal (cj/update-text-file--replace "a$b" "$" "S") "aSb")))
-
-(ert-deftest test-update-text-file-replace-boundary-unicode ()
- "Boundary: unicode in both pattern and replacement."
- (should (equal (cj/update-text-file--replace "café résumé" "café" "thé")
- "thé résumé")))
-
-(ert-deftest test-update-text-file-replace-boundary-replacement-with-backref-like ()
- "Boundary: replacement strings with \\1 etc. are literal, not back-refs."
- (should (equal (cj/update-text-file--replace "foo" "foo" "\\1bar")
- "\\1bar")))
-
-(ert-deftest test-update-text-file-replace-error-empty-pattern ()
- "Error: empty pattern signals."
- (should-error (cj/update-text-file--replace "abc" "" "x")))
-
-(ert-deftest test-update-text-file-replace-error-nil-pattern ()
- "Error: nil pattern signals."
- (should-error (cj/update-text-file--replace "abc" nil "x")))
-
-(ert-deftest test-update-text-file-replace-error-nil-replacement ()
- "Error: nil replacement signals."
- (should-error (cj/update-text-file--replace "abc" "a" nil)))
-
-;; ----------------------------------------------------- append
-
-(ert-deftest test-update-text-file-append-normal ()
- "Normal: append adds text plus a trailing newline."
- (should (equal (cj/update-text-file--append "line1\n" "line2")
- "line1\nline2\n")))
-
-(ert-deftest test-update-text-file-append-boundary-no-trailing-newline ()
- "Boundary: appends still produce a newline when content has none."
- (should (equal (cj/update-text-file--append "abc" "def")
- "abc\ndef\n")))
-
-(ert-deftest test-update-text-file-append-boundary-empty-content ()
- "Boundary: appending to empty content yields just the new text + newline."
- (should (equal (cj/update-text-file--append "" "hello") "hello\n")))
-
-(ert-deftest test-update-text-file-append-boundary-text-with-trailing-newline ()
- "Boundary: text that already ends in newline isn't duplicated."
- (should (equal (cj/update-text-file--append "a\n" "b\n") "a\nb\n")))
-
-(ert-deftest test-update-text-file-append-error-empty-text ()
- "Error: empty text signals."
- (should-error (cj/update-text-file--append "foo" "")))
-
-(ert-deftest test-update-text-file-append-error-nil-text ()
- "Error: nil text signals."
- (should-error (cj/update-text-file--append "foo" nil)))
-
-;; ----------------------------------------------------- prepend
-
-(ert-deftest test-update-text-file-prepend-normal ()
- "Normal: prepend adds text plus a separator newline."
- (should (equal (cj/update-text-file--prepend "line1\n" "line0")
- "line0\nline1\n")))
-
-(ert-deftest test-update-text-file-prepend-boundary-empty-content ()
- "Boundary: prepending to empty content keeps just the new text + sep."
- (should (equal (cj/update-text-file--prepend "" "hello") "hello\n")))
-
-(ert-deftest test-update-text-file-prepend-boundary-text-with-trailing-newline ()
- "Boundary: text already terminated by newline is not double-broken."
- (should (equal (cj/update-text-file--prepend "rest" "first\n")
- "first\nrest")))
-
-(ert-deftest test-update-text-file-prepend-error-empty-text ()
- "Error: empty text signals."
- (should-error (cj/update-text-file--prepend "foo" "")))
-
-(ert-deftest test-update-text-file-prepend-error-nil-text ()
- "Error: nil text signals."
- (should-error (cj/update-text-file--prepend "foo" nil)))
-
-;; ----------------------------------------------------- insert-at-line
-
-(ert-deftest test-update-text-file-insert-at-line-normal ()
- "Normal: insert before line 2 of a 3-line file."
- (should (equal (cj/update-text-file--insert-at-line "a\nb\nc\n" 2 "X")
- "a\nX\nb\nc\n")))
-
-(ert-deftest test-update-text-file-insert-at-line-boundary-first-line ()
- "Boundary: inserting at line 1 prepends."
- (should (equal (cj/update-text-file--insert-at-line "a\nb\n" 1 "X")
- "X\na\nb\n")))
-
-(ert-deftest test-update-text-file-insert-at-line-boundary-one-past-end ()
- "Boundary: inserting one past the last line appends."
- (should (equal (cj/update-text-file--insert-at-line "a\nb\n" 3 "X")
- "a\nb\nX\n")))
-
-(ert-deftest test-update-text-file-insert-at-line-boundary-no-trailing-newline ()
- "Boundary: works on content without a trailing newline."
- (should (equal (cj/update-text-file--insert-at-line "a\nb" 2 "X")
- "a\nX\nb")))
-
-(ert-deftest test-update-text-file-insert-at-line-boundary-text-with-trailing-newline ()
- "Boundary: inserted text that ends in newline is not double-terminated."
- (should (equal (cj/update-text-file--insert-at-line "a\nb\n" 2 "X\n")
- "a\nX\nb\n")))
-
-(ert-deftest test-update-text-file-insert-at-line-boundary-multiline-text ()
- "Boundary: multi-line inserted text is inserted as a block."
- (should (equal (cj/update-text-file--insert-at-line "a\nb\n" 2 "X\nY")
- "a\nX\nY\nb\n")))
-
-(ert-deftest test-update-text-file-insert-at-line-boundary-empty-file-line-1 ()
- "Boundary: inserting at line 1 in an empty file works."
- (should (equal (cj/update-text-file--insert-at-line "" 1 "X")
- "X\n")))
-
-(ert-deftest test-update-text-file-insert-at-line-error-empty-file-line-2 ()
- "Error: line 2 is out of range for an empty file."
- (should-error (cj/update-text-file--insert-at-line "" 2 "X")))
-
-(ert-deftest test-update-text-file-insert-at-line-error-out-of-range ()
- "Error: line number beyond file length signals."
- (should-error (cj/update-text-file--insert-at-line "a\nb\n" 5 "X")))
-
-(ert-deftest test-update-text-file-insert-at-line-error-zero ()
- "Error: line number 0 signals."
- (should-error (cj/update-text-file--insert-at-line "a\n" 0 "X")))
-
-(ert-deftest test-update-text-file-insert-at-line-error-negative ()
- "Error: negative line number signals."
- (should-error (cj/update-text-file--insert-at-line "a\n" -1 "X")))
-
-(ert-deftest test-update-text-file-insert-at-line-error-empty-text ()
- "Error: empty text signals."
- (should-error (cj/update-text-file--insert-at-line "a\n" 1 "")))
-
-;; ----------------------------------------------------- delete-lines
-
-(ert-deftest test-update-text-file-delete-lines-normal ()
- "Normal: removes lines containing the literal pattern."
- (should (equal (cj/update-text-file--delete-lines "keep\nkill me\nkeep\n" "kill")
- "keep\nkeep\n")))
-
-(ert-deftest test-update-text-file-delete-lines-boundary-no-match ()
- "Boundary: pattern matches nothing returns content unchanged."
- (should (equal (cj/update-text-file--delete-lines "a\nb\nc\n" "z")
- "a\nb\nc\n")))
-
-(ert-deftest test-update-text-file-delete-lines-boundary-all-lines-match ()
- "Boundary: every line removed yields the empty string."
- (should (equal (cj/update-text-file--delete-lines "x\nx\nx\n" "x") "")))
-
-(ert-deftest test-update-text-file-delete-lines-boundary-special-chars-literal ()
- "Boundary: regex metacharacters in pattern are treated as literals."
- (should (equal (cj/update-text-file--delete-lines "a.b\naxb\n" ".")
- "axb\n")))
-
-(ert-deftest test-update-text-file-delete-lines-boundary-no-trailing-newline ()
- "Boundary: content without trailing newline keeps that shape."
- (should (equal (cj/update-text-file--delete-lines "keep\ndrop" "drop")
- "keep")))
-
-(ert-deftest test-update-text-file-delete-lines-boundary-empty-file ()
- "Boundary: deleting from an empty file returns the empty string."
- (should (equal (cj/update-text-file--delete-lines "" "anything") "")))
-
-(ert-deftest test-update-text-file-delete-lines-boundary-backslash-literal ()
- "Boundary: backslashes in the pattern are literal."
- (should (equal (cj/update-text-file--delete-lines "keep\npath\\name\n" "\\")
- "keep\n")))
-
-(ert-deftest test-update-text-file-delete-lines-error-empty-pattern ()
- "Error: empty pattern signals."
- (should-error (cj/update-text-file--delete-lines "a\nb\n" "")))
-
-(ert-deftest test-update-text-file-delete-lines-error-nil-pattern ()
- "Error: nil pattern signals."
- (should-error (cj/update-text-file--delete-lines "a\nb\n" nil)))
-
-;; ----------------------------------------------------- apply-operation
-
-(ert-deftest test-update-text-file-apply-operation-dispatch ()
- "Each operation name dispatches to its transform."
- (should (equal (cj/update-text-file--apply-operation "abc" "replace" "b" "B" nil)
- "aBc"))
- (should (equal (cj/update-text-file--apply-operation "a" "append" "b" nil nil)
- "a\nb\n"))
- (should (equal (cj/update-text-file--apply-operation "a" "prepend" "b" nil nil)
- "b\na"))
- (should (equal (cj/update-text-file--apply-operation "a\nb\n" "insert-at-line" "X" nil 2)
- "a\nX\nb\n"))
- (should (equal (cj/update-text-file--apply-operation "a\nb\n" "delete-lines" "a" nil nil)
- "b\n")))
-
-(ert-deftest test-update-text-file-apply-operation-error-unknown ()
- "Unknown operation signals."
- (should-error (cj/update-text-file--apply-operation "x" "frobnicate" nil nil nil)))
-
-;; ----------------------------------------------------- validate-path
-
-(ert-deftest test-update-text-file-validate-path-normal ()
- "Normal: an existing readable+writable file under HOME passes."
- (let* ((file (make-temp-file "test-update-text-file-")))
- (unwind-protect
- (progn
- ;; make-temp-file may land in /tmp; rebase to HOME for the test.
- (let* ((home-file (expand-file-name
- (concat ".test-update-text-file-" (format-time-string "%s") ".tmp")
- "~")))
- (unwind-protect
- (progn
- (copy-file file home-file t)
- (should (equal (cj/update-text-file--validate-path home-file)
- (file-truename home-file))))
- (when (file-exists-p home-file) (delete-file home-file)))))
- (when (file-exists-p file) (delete-file file)))))
-
-(ert-deftest test-update-text-file-validate-path-error-missing ()
- "Error: a missing file under HOME signals."
- (let ((path (expand-file-name
- (concat ".test-update-text-file-missing-"
- (format-time-string "%s") ".tmp")
- "~")))
- (when (file-exists-p path) (delete-file path))
- (should-error (cj/update-text-file--validate-path path))))
-
-(ert-deftest test-update-text-file-validate-path-error-outside-home ()
- "Error: a path outside HOME signals."
- (should-error (cj/update-text-file--validate-path "/etc/hostname")))
-
-(ert-deftest test-update-text-file-validate-path-error-directory ()
- "Error: a directory signals."
- (should-error (cj/update-text-file--validate-path "~")))
-
-(ert-deftest test-update-text-file-validate-path-error-unreadable ()
- "Error: an unreadable file signals."
- (test-update-text-file--in-home
- "unreadable" "secret\n"
- (lambda (path)
- (cl-letf (((symbol-function 'file-readable-p) (lambda (_) nil)))
- (should-error (cj/update-text-file--validate-path path))))))
-
-(ert-deftest test-update-text-file-validate-path-error-unwritable ()
- "Error: an unwritable file signals."
- (test-update-text-file--in-home
- "unwritable" "locked\n"
- (lambda (path)
- (cl-letf (((symbol-function 'file-writable-p) (lambda (_) nil)))
- (should-error (cj/update-text-file--validate-path path))))))
-
-(ert-deftest test-update-text-file-validate-path-boundary-relative-home-path ()
- "Boundary: a relative path resolves under HOME."
- (test-update-text-file--in-home
- "relative" "ok\n"
- (lambda (path)
- (let ((relative (file-relative-name path (expand-file-name "~"))))
- (should (equal (cj/update-text-file--validate-path relative)
- (file-truename path)))))))
-
-(ert-deftest test-update-text-file-validate-path-boundary-symlink-inside-home ()
- "Boundary: a symlink inside HOME resolving inside HOME is accepted."
- (test-update-text-file--in-home
- "symlink-target" "ok\n"
- (lambda (target)
- (let ((link (expand-file-name
- (format ".test-update-text-file-link-%s.tmp"
- (format-time-string "%s%N"))
- "~")))
- (unwind-protect
- (progn
- (make-symbolic-link target link t)
- (should (equal (cj/update-text-file--validate-path link)
- (file-truename target))))
- (when (file-symlink-p link) (delete-file link)))))))
-
-(ert-deftest test-update-text-file-validate-path-error-symlink-outside-home ()
- "Error: a symlink inside HOME pointing outside HOME is rejected."
- (let ((outside (make-temp-file "test-update-text-file-outside-"))
- (link (expand-file-name
- (format ".test-update-text-file-outside-link-%s.tmp"
- (format-time-string "%s%N"))
- "~")))
- (unwind-protect
- (progn
- (make-symbolic-link outside link t)
- (should-error (cj/update-text-file--validate-path link)))
- (when (file-exists-p outside) (delete-file outside))
- (when (file-symlink-p link) (delete-file link)))))
-
-;; ----------------------------------------------------- backup-name
-
-(ert-deftest test-update-text-file-backup-name-shape ()
- "Backup names append a timestamped .bak suffix."
- (let ((name (cj/update-text-file--backup-name "/home/user/foo.txt")))
- (should (string-prefix-p "/home/user/foo.txt-" name))
- (should (string-suffix-p ".bak" name))
- ;; Format is YYYY-MM-DD-HHMMSS.
- (should (string-match-p "-[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}-[0-9]\\{6\\}\\.bak\\'"
- name))))
-
-;; ----------------------------------------------------- file-level wrapper
-
-(defun test-update-text-file--in-home (suffix content fn)
- "Write CONTENT to a temp file under HOME with SUFFIX, call FN, then delete.
-Backups (path-TS.bak) are cleaned up after FN returns."
- (let* ((name (format ".test-update-text-file-%s-%s.tmp"
- suffix (format-time-string "%s%N")))
- (path (expand-file-name name "~")))
- (unwind-protect
- (progn
- (with-temp-file path (insert content))
- (funcall fn path))
- (when (file-exists-p path) (delete-file path))
- (dolist (b (file-expand-wildcards (concat path "-*.bak")))
- (when (file-exists-p b) (delete-file b))))))
-
-(ert-deftest test-update-text-file-run-replace-normal ()
- "Wrapper: replace operation rewrites the file and creates a backup."
- (test-update-text-file--in-home
- "replace" "alpha bravo alpha\n"
- (lambda (path)
- (let ((result (cj/update-text-file--run path "replace" "alpha" "GAMMA" nil)))
- (should (string-match-p "Updated" result))
- (should (string-match-p "backup:" result))
- (with-temp-buffer
- (insert-file-contents path)
- (should (equal (buffer-string) "GAMMA bravo GAMMA\n")))
- (let ((backup (car (file-expand-wildcards (concat path "-*.bak")))))
- (should backup)
- (with-temp-buffer
- (insert-file-contents backup)
- (should (equal (buffer-string) "alpha bravo alpha\n"))))))))
-
-(ert-deftest test-update-text-file-run-no-change-no-backup ()
- "Wrapper: no-op operation leaves the file untouched and creates no backup."
- (test-update-text-file--in-home
- "noop" "abc\n"
- (lambda (path)
- (let ((result (cj/update-text-file--run path "replace" "zzz" "QQ" nil)))
- (should (string-match-p "No changes" result))
- (with-temp-buffer
- (insert-file-contents path)
- (should (equal (buffer-string) "abc\n")))
- (should-not (file-expand-wildcards (concat path "-*.bak")))))))
-
-(ert-deftest test-update-text-file-run-append-normal ()
- "Wrapper: append operation adds a line to the file."
- (test-update-text-file--in-home
- "append" "first\n"
- (lambda (path)
- (cj/update-text-file--run path "append" "second" nil nil)
- (with-temp-buffer
- (insert-file-contents path)
- (should (equal (buffer-string) "first\nsecond\n"))))))
-
-(ert-deftest test-update-text-file-run-insert-at-line-normal ()
- "Wrapper: insert-at-line inserts and rewrites the file."
- (test-update-text-file--in-home
- "insert" "a\nb\nc\n"
- (lambda (path)
- (cj/update-text-file--run path "insert-at-line" "X" nil 2)
- (with-temp-buffer
- (insert-file-contents path)
- (should (equal (buffer-string) "a\nX\nb\nc\n"))))))
-
-(ert-deftest test-update-text-file-run-delete-lines-normal ()
- "Wrapper: delete-lines removes matching lines."
- (test-update-text-file--in-home
- "delete" "keep1\nkill\nkeep2\nkill\n"
- (lambda (path)
- (cj/update-text-file--run path "delete-lines" "kill" nil nil)
- (with-temp-buffer
- (insert-file-contents path)
- (should (equal (buffer-string) "keep1\nkeep2\n"))))))
-
-(ert-deftest test-update-text-file-run-error-transform-leaves-file-unchanged ()
- "Wrapper: transform errors create no backup and leave the file unchanged."
- (test-update-text-file--in-home
- "transform-error" "abc\n"
- (lambda (path)
- (should-error (cj/update-text-file--run path "replace" "" "x" nil))
- (with-temp-buffer
- (insert-file-contents path)
- (should (equal (buffer-string) "abc\n")))
- (should-not (file-expand-wildcards (concat path "-*.bak"))))))
-
-(ert-deftest test-update-text-file-run-error-unknown-operation-leaves-file-unchanged ()
- "Wrapper: unknown operations create no backup and leave the file unchanged."
- (test-update-text-file--in-home
- "unknown-operation" "abc\n"
- (lambda (path)
- (should-error (cj/update-text-file--run path "frobnicate" "x" nil nil))
- (with-temp-buffer
- (insert-file-contents path)
- (should (equal (buffer-string) "abc\n")))
- (should-not (file-expand-wildcards (concat path "-*.bak"))))))
-
-(ert-deftest test-update-text-file-run-error-too-large-leaves-file-unchanged ()
- "Wrapper: the size guard errors before backup/write."
- (test-update-text-file--in-home
- "too-large" "abcdef\n"
- (lambda (path)
- (let ((cj/update-text-file--size-limit 3))
- (should-error (cj/update-text-file--run path "append" "x" nil nil)))
- (with-temp-buffer
- (insert-file-contents path)
- (should (equal (buffer-string) "abcdef\n")))
- (should-not (file-expand-wildcards (concat path "-*.bak"))))))
-
-(ert-deftest test-update-text-file-run-error-missing-file ()
- "Wrapper: missing file signals."
- (let ((path (expand-file-name
- (concat ".test-update-text-file-absent-"
- (format-time-string "%s") ".tmp")
- "~")))
- (when (file-exists-p path) (delete-file path))
- (should-error (cj/update-text-file--run path "append" "x" nil nil))))
-
-(ert-deftest test-update-text-file-run-error-outside-home ()
- "Wrapper: path outside home signals."
- (should-error (cj/update-text-file--run "/etc/hostname" "append" "x" nil nil)))
-
-(provide 'test-update-text-file)
-;;; test-update-text-file.el ends here