blob: 77f886277bcc4b3ad93bab5dce35b118045c1062 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
|
;;; test-gptel-tools-move-to-trash.el --- Tests for move_to_trash gptel tool -*- lexical-binding: t; -*-
;;; Commentary:
;; Tests for the helpers in move_to_trash.el.
;;; 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)
(unless (featurep 'gptel)
(defvar gptel-tools nil)
(defun gptel-make-tool (&rest _args) nil)
(defun gptel-get-tool (&rest _args) nil)
(provide 'gptel)))
(require 'move_to_trash)
;; -------------------------- helpers
(defun test-gptel-tools-trash--with-tmp-tree (fn)
"Create a temp source dir and trash dir; run FN with both; clean up."
(let* ((src (make-temp-file "test-gptel-tools-trash-src-" t))
(trash (make-temp-file "test-gptel-tools-trash-dst-" t)))
(unwind-protect
(funcall fn src trash)
(when (file-exists-p src) (delete-directory src t))
(when (file-exists-p trash) (delete-directory trash t)))))
;; -------------------------- generate-unique-name
(ert-deftest test-gptel-tools-trash-generate-unique-name-no-conflict ()
"No conflict: returns the plain base name in trash."
(test-gptel-tools-trash--with-tmp-tree
(lambda (_src trash)
(let ((out (gptel--move-to-trash-generate-unique-name
"/anywhere/foo.txt" trash)))
(should (equal (file-name-nondirectory out) "foo.txt"))))))
(ert-deftest test-gptel-tools-trash-generate-unique-name-conflict-timestamps ()
"Name conflict: returns a name with a timestamp suffix."
(test-gptel-tools-trash--with-tmp-tree
(lambda (_src trash)
(with-temp-file (expand-file-name "foo.txt" trash) (insert ""))
(let* ((out (gptel--move-to-trash-generate-unique-name
"/anywhere/foo.txt" trash))
(name (file-name-nondirectory out)))
(should-not (equal name "foo.txt"))
(should (string-match-p "\\`foo-[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}-[0-9]\\{2\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\.txt\\'"
name))))))
(ert-deftest test-gptel-tools-trash-generate-unique-name-no-extension ()
"Conflict on a name without extension: timestamp appended to the bare name."
(test-gptel-tools-trash--with-tmp-tree
(lambda (_src trash)
(with-temp-file (expand-file-name "noext" trash) (insert ""))
(let* ((out (gptel--move-to-trash-generate-unique-name
"/anywhere/noext" trash))
(name (file-name-nondirectory out)))
(should-not (equal name "noext"))
(should (string-match-p "\\`noext-[0-9]" name))))))
;; -------------------------- validate-path
(ert-deftest test-gptel-tools-trash-validate-path-normal-home ()
"Normal: an existing path under HOME validates."
(let ((path (expand-file-name
(format ".test-gptel-tools-trash-home-%s.tmp"
(format-time-string "%s%N"))
"~")))
(unwind-protect
(progn
(with-temp-file path (insert ""))
(should (equal (gptel--move-to-trash-validate-path path)
(expand-file-name path))))
(when (file-exists-p path) (delete-file path)))))
(ert-deftest test-gptel-tools-trash-validate-path-normal-tmp ()
"Normal: an existing path under /tmp validates."
(let ((path (make-temp-file "test-gptel-tools-trash-tmpvalidate-")))
(unwind-protect
(should (equal (gptel--move-to-trash-validate-path path)
(expand-file-name path)))
(when (file-exists-p path) (delete-file path)))))
(ert-deftest test-gptel-tools-trash-validate-path-error-outside-allowed ()
"Error: a path outside HOME or /tmp signals."
(should-error (gptel--move-to-trash-validate-path "/etc/hostname")))
(ert-deftest test-gptel-tools-trash-validate-path-error-tmp-prefix-trick ()
"Error: paths that merely start with /tmp are not treated as /tmp children."
(should-error (gptel--move-to-trash-validate-path "/tmpnotreally/file")))
(ert-deftest test-gptel-tools-trash-validate-path-error-critical-dir ()
"Error: critical directories (home root, .emacs.d, .config, /tmp) signal."
(should-error (gptel--move-to-trash-validate-path "~"))
(should-error (gptel--move-to-trash-validate-path "~/.emacs.d"))
(should-error (gptel--move-to-trash-validate-path "~/.config"))
(should-error (gptel--move-to-trash-validate-path "/tmp")))
(ert-deftest test-gptel-tools-trash-validate-path-error-missing ()
"Error: missing path signals."
(let ((path (expand-file-name
(format ".test-gptel-tools-trash-missing-%s.tmp"
(format-time-string "%s%N"))
"~")))
(when (file-exists-p path) (delete-file path))
(should-error (gptel--move-to-trash-validate-path path))))
(ert-deftest test-gptel-tools-trash-validate-path-error-symlink-outside-allowed ()
"Error: allowed-location symlinks resolving outside allowed roots are rejected."
(let ((link (expand-file-name
(format ".test-gptel-tools-trash-outside-link-%s.tmp"
(format-time-string "%s%N"))
"~")))
(unwind-protect
(progn
(make-symbolic-link "/etc/hostname" link t)
(should-error (gptel--move-to-trash-validate-path link)))
(when (file-symlink-p link) (delete-file link)))))
;; -------------------------- perform
(ert-deftest test-gptel-tools-trash-perform-moves-file ()
"Perform: moves the file out of the source dir into the trash dir."
(test-gptel-tools-trash--with-tmp-tree
(lambda (src trash)
(let ((file (expand-file-name "doomed.txt" src)))
(with-temp-file file (insert "trash me"))
(let ((status (gptel--move-to-trash-perform file trash)))
(should (string-match-p "moved to trash" status))
(should-not (file-exists-p file))
(should (file-exists-p (expand-file-name "doomed.txt" trash))))))))
(ert-deftest test-gptel-tools-trash-perform-handles-directory ()
"Perform: moves a directory as a unit."
(test-gptel-tools-trash--with-tmp-tree
(lambda (src trash)
(let ((dir (expand-file-name "subdir" src)))
(make-directory dir)
(with-temp-file (expand-file-name "inside.txt" dir) (insert "x"))
(let ((status (gptel--move-to-trash-perform dir trash)))
(should (string-match-p "Directory moved to trash" status))
(should-not (file-exists-p dir))
(should (file-exists-p (expand-file-name "subdir/inside.txt" trash))))))))
(ert-deftest test-gptel-tools-trash-perform-handles-symlink ()
"Perform: moving a symlink moves the link, not its target."
(test-gptel-tools-trash--with-tmp-tree
(lambda (src trash)
(let ((target (expand-file-name "target.txt" src))
(link (expand-file-name "link.txt" src)))
(with-temp-file target (insert "target"))
(make-symbolic-link target link t)
(let ((status (gptel--move-to-trash-perform link trash)))
(should (string-match-p "Symlink moved to trash" status))
(should (file-exists-p target))
(should-not (file-symlink-p link))
(should (file-symlink-p (expand-file-name "link.txt" trash))))))))
(ert-deftest test-gptel-tools-trash-perform-error-rename-failure ()
"Error: rename failures are reported with context."
(test-gptel-tools-trash--with-tmp-tree
(lambda (src trash)
(let ((file (expand-file-name "doomed.txt" src)))
(with-temp-file file (insert "trash me"))
(cl-letf (((symbol-function 'rename-file)
(lambda (&rest _args) (error "rename failed"))))
(should-error (gptel--move-to-trash-perform file trash)))
(should (file-exists-p file))))))
(ert-deftest test-gptel-tools-trash-perform-error-permission-denied ()
"Error: permission-denied rename failures get a specific message."
(test-gptel-tools-trash--with-tmp-tree
(lambda (src trash)
(let ((file (expand-file-name "denied.txt" src)))
(with-temp-file file (insert "trash me"))
(cl-letf (((symbol-function 'rename-file)
(lambda (&rest _args)
(signal 'permission-denied '("denied")))))
(should-error (gptel--move-to-trash-perform file trash)
:type 'error))
(should (file-exists-p file))))))
(ert-deftest test-gptel-tools-trash-perform-error-original-still-exists ()
"Error: post-move verification catches a source path that remains."
(test-gptel-tools-trash--with-tmp-tree
(lambda (src trash)
(let ((file (expand-file-name "still-there.txt" src)))
(with-temp-file file (insert "trash me"))
(cl-letf (((symbol-function 'rename-file)
(lambda (&rest _args) nil)))
(should-error (gptel--move-to-trash-perform file trash)))
(should (file-exists-p file))))))
(ert-deftest test-gptel-tools-trash-perform-error-trash-missing-after-move ()
"Error: post-move verification catches a missing trash target."
(test-gptel-tools-trash--with-tmp-tree
(lambda (src trash)
(let ((file (expand-file-name "missing-trash.txt" src))
(real-file-exists-p (symbol-function 'file-exists-p)))
(with-temp-file file (insert "trash me"))
(cl-letf (((symbol-function 'rename-file)
(lambda (&rest _args) nil))
((symbol-function 'file-exists-p)
(lambda (path)
(cond
((equal path file) nil)
((string-prefix-p trash path) nil)
(t (funcall real-file-exists-p path))))))
(should-error (gptel--move-to-trash-perform file trash)))
(should (funcall real-file-exists-p file))))))
(provide 'test-gptel-tools-move-to-trash)
;;; test-gptel-tools-move-to-trash.el ends here
|