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
|
;;; test-telega-config--docker-pin.el --- Tests for the telega docker image pin -*- lexical-binding: t; -*-
;;; Commentary:
;; Tests for pinning the telega-server container image.
;;
;; telega infers its image from `telega-tdlib-min-version' and only pins to a
;; version tag when min and max versions are equal and the version ends in
;; ".0". This config has min "1.8.66" and max nil, so the inference always
;; falls through to "zevlg/telega-server:latest" -- a moving tag that can
;; swap the server out from under a fixed elisp version without notice.
;;
;; Since 2026-08-25 the pin names an image built locally from
;; docker/telega-server/Dockerfile (upstream's image is missing a shared
;; library, zevlg/telega.el#596). Three files have to agree on that image:
;; the defcustom default, the Makefile's build tag, and the Dockerfile's
;; digest-pinned base. The tests below hold them together.
;;; Code:
(require 'ert)
(require 'cl-lib)
(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
(require 'telega-config)
(defun test-telega-config--file-string (relative)
"Return the contents of RELATIVE under `user-emacs-directory'."
(with-temp-buffer
(insert-file-contents (expand-file-name relative user-emacs-directory))
(buffer-string)))
(defun test-telega-config--pin-default ()
"Return the defcustom's shipped default, not the live value.
Customizing the pin (including to nil) is not a test failure; only
changing the shipped default is."
(eval (car (get 'cj/telega-docker-image 'standard-value)) t))
;; -- cj/--telega-docker-pinned-image -----------------------------------------
(ert-deftest test-telega-config-pin-returns-configured-reference ()
"Normal: a configured pin is returned verbatim."
(let ((cj/telega-docker-image "zevlg/telega-server@sha256:abc123"))
(should (equal (cj/--telega-docker-pinned-image)
"zevlg/telega-server@sha256:abc123"))))
(ert-deftest test-telega-config-pin-nil-when-unset ()
"Boundary: no pin returns nil so telega's own inference stays in charge."
(let ((cj/telega-docker-image nil))
(should-not (cj/--telega-docker-pinned-image))))
(ert-deftest test-telega-config-pin-nil-for-empty-string ()
"Boundary: an empty or whitespace pin is not a reference.
An empty string would otherwise reach the docker command line as a blank
image argument, which fails in a way that looks unrelated to this setting."
(let ((cj/telega-docker-image ""))
(should-not (cj/--telega-docker-pinned-image)))
(let ((cj/telega-docker-image " "))
(should-not (cj/--telega-docker-pinned-image))))
(ert-deftest test-telega-config-pin-nil-for-non-string ()
"Error: a non-string pin is ignored rather than passed to the shell."
(let ((cj/telega-docker-image 'latest))
(should-not (cj/--telega-docker-pinned-image)))
(let ((cj/telega-docker-image 42))
(should-not (cj/--telega-docker-pinned-image))))
(ert-deftest test-telega-config-pin-trims-surrounding-whitespace ()
"Boundary: a pin with stray whitespace is trimmed, not rejected.
A trailing newline is easy to introduce when pasting a digest from docker."
(let ((cj/telega-docker-image " zevlg/telega-server@sha256:abc123\n"))
(should (equal (cj/--telega-docker-pinned-image)
"zevlg/telega-server@sha256:abc123"))))
;; -- cj/--telega-docker-image-name (the advice) ------------------------------
(ert-deftest test-telega-config-image-advice-prefers-the-pin ()
"Normal: with a pin set, the advice returns it instead of calling telega."
(let ((cj/telega-docker-image "zevlg/telega-server@sha256:abc123")
(called nil))
(should (equal (cj/--telega-docker-image-name
(lambda () (setq called t) "zevlg/telega-server:latest"))
"zevlg/telega-server@sha256:abc123"))
(should-not called)))
(ert-deftest test-telega-config-image-advice-delegates-without-a-pin ()
"Boundary: with no pin, telega's own inference is used unchanged.
Removing the pin must restore stock behavior rather than break the image
name, so this stays a reversible setting."
(let ((cj/telega-docker-image nil))
(should (equal (cj/--telega-docker-image-name
(lambda () "zevlg/telega-server:latest"))
"zevlg/telega-server:latest"))))
(ert-deftest test-telega-config-image-advice-is-named-and-removable ()
"Normal: the advice is a named function so it can be removed by reference."
(should (fboundp 'cj/--telega-docker-image-name)))
;; -- the shipped default and the files it depends on -------------------------
(ert-deftest test-telega-config-pin-default-matches-makefile-build-tag ()
"Normal: the default pin is exactly the tag `make telega-image' builds.
The image is built locally, so the pin is a tag rather than a registry
digest. The Makefile owns the tag; the defcustom must name the same one
or a fresh machine builds an image telega never looks for."
(let ((makefile (test-telega-config--file-string "Makefile")))
(should (string-match "^TELEGA_IMAGE[ \t]*[?:]?=[ \t]*\\([^ \t\n]+\\)" makefile))
(should (equal (test-telega-config--pin-default)
(match-string 1 makefile)))))
(ert-deftest test-telega-config-pin-default-is-a-local-tag-not-a-digest ()
"Boundary: the default is a plain tag, with no registry digest suffix.
A locally built image has no RepoDigest, so a digest reference here could
never resolve."
(let ((default (test-telega-config--pin-default)))
(should (stringp default))
(should (string-match-p "\\`[a-z0-9./-]+:[A-Za-z0-9._-]+\\'" default))
(should-not (string-match-p "@sha256:" default))))
(ert-deftest test-telega-config-dockerfile-pins-base-image-by-digest ()
"Normal: the Dockerfile's base is an immutable upstream digest.
This is where the digest guarantee the old pin gave now lives. A tag in
the FROM line would let upstream swap the base under a rebuild."
(let ((dockerfile (test-telega-config--file-string "docker/telega-server/Dockerfile")))
(should (string-match-p
"^FROM zevlg/telega-server@sha256:[0-9a-f]\\{64\\}[ \t]*$"
dockerfile))))
(ert-deftest test-telega-config-dockerfile-adds-the-missing-library ()
"Normal: the Dockerfile installs libglycin, the whole reason it exists.
Upstream's image fails to start without it (zevlg/telega.el#596)."
(let ((dockerfile (test-telega-config--file-string "docker/telega-server/Dockerfile")))
(should (string-match-p "^RUN apk add .*libglycin" dockerfile))))
;; -- cj/--telega-docker-image-present-p (the docker boundary) ----------------
;; Exercised against a fake `docker' executable on a private exec-path rather
;; than by mocking `call-process' (a subr; see the native-comp mocking gotcha).
(defun test-telega-config--with-fake-docker (exit-code thunk)
"Call THUNK with a fake `docker' on `exec-path' that exits EXIT-CODE."
(let* ((dir (make-temp-file "fake-docker-" t))
(script (expand-file-name "docker" dir)))
(unwind-protect
(progn
(with-temp-file script
(insert (format "#!/bin/sh\nexit %d\n" exit-code)))
(set-file-modes script #o700)
(let ((exec-path (list dir)))
(funcall thunk)))
(delete-directory dir t))))
(ert-deftest test-telega-config-image-present-p-true-when-inspect-succeeds ()
"Normal: `docker image inspect' exiting 0 means the image is present."
(test-telega-config--with-fake-docker 0
(lambda () (should (cj/--telega-docker-image-present-p "cj/telega-server:x")))))
(ert-deftest test-telega-config-image-present-p-nil-when-inspect-fails ()
"Boundary: a non-zero exit (no such image) reads as not present."
(test-telega-config--with-fake-docker 1
(lambda () (should-not (cj/--telega-docker-image-present-p "cj/telega-server:x")))))
(ert-deftest test-telega-config-image-present-p-nil-without-docker ()
"Error: with no docker on `exec-path', the helper returns nil instead of
signalling, so the launcher can still route the user to the make target."
(let ((exec-path nil))
(should-not (cj/--telega-docker-image-present-p "cj/telega-server:x"))))
;; -- cj/telega refuses to launch against a missing local image ---------------
(ert-deftest test-telega-config-missing-image-message-names-image-and-target ()
"Normal: the message names the missing image and the make target that builds it."
(let ((msg (cj/--telega-missing-image-message "cj/telega-server:x")))
(should (string-match-p "cj/telega-server:x" msg))
(should (string-match-p "make telega-image" msg))))
(ert-deftest test-telega-config-launcher-errors-when-pinned-image-is-absent ()
"Error: with a pin set and no such image, `cj/telega' stops with the make hint.
Without this, docker fails to pull a local-only tag and the error names a
registry the image was never meant to come from."
(let ((cj/telega-docker-image "cj/telega-server:x")
(launched nil))
(cl-letf (((symbol-function 'locate-library) (lambda (&rest _) "telega.el"))
((symbol-function 'cj/--telega-docker-image-present-p) (lambda (_) nil))
((symbol-function 'telega) (lambda (&rest _) (setq launched t))))
(let ((err (should-error (cj/telega) :type 'user-error)))
(should (string-match-p "make telega-image" (cadr err))))
(should-not launched))))
(ert-deftest test-telega-config-launcher-runs-when-pinned-image-is-present ()
"Normal: with the pinned image present, `cj/telega' launches telega."
(let ((cj/telega-docker-image "cj/telega-server:x")
(launched nil))
(cl-letf (((symbol-function 'locate-library) (lambda (&rest _) "telega.el"))
((symbol-function 'cj/--telega-docker-image-present-p) (lambda (_) t))
((symbol-function 'telega) (lambda (&rest _) (setq launched t))))
(cj/telega)
(should launched))))
(ert-deftest test-telega-config-launcher-skips-image-check-without-a-pin ()
"Boundary: with no pin, telega infers and pulls its own image; no check runs."
(let ((cj/telega-docker-image nil)
(checked nil)
(launched nil))
(cl-letf (((symbol-function 'locate-library) (lambda (&rest _) "telega.el"))
((symbol-function 'cj/--telega-docker-image-present-p)
(lambda (_) (setq checked t) nil))
((symbol-function 'telega) (lambda (&rest _) (setq launched t))))
(cj/telega)
(should-not checked)
(should launched))))
(provide 'test-telega-config--docker-pin)
;;; test-telega-config--docker-pin.el ends here
|