aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-27 21:55:39 -0500
committerCraig Jennings <c@cjennings.net>2026-05-27 21:55:39 -0500
commitbfec0eab1132b7713a35500323e06ebea2da17a4 (patch)
tree6a41cdb82ec097713d614126262c677eb546c8cd /tests
parent2a6ffb0e6a10fdf4fd42fe3e5689f300b25c7cf6 (diff)
downloaddotemacs-bfec0eab1132b7713a35500323e06ebea2da17a4.tar.gz
dotemacs-bfec0eab1132b7713a35500323e06ebea2da17a4.zip
test(signel): cover the JSON-RPC success-result dispatch contract
The fork commit (4740d97 in the signel fork) added a request-callback table, extended signel--send-rpc with an optional success-callback, and routed result responses through signel--dispatch. These tests lock that contract from the consuming project so a future fork change can't silently break the picker that will read listContacts through it. Five tests, Normal / Boundary / Error categories plus a reconnect-invalidation case: - result-invokes-callback (Normal): a result response with a registered id fires the callback with the value and removes the handler. - send-rpc-registers-success-callback (Normal): passing a success callback stores it under the returned id. - unknown-id-is-noop (Boundary): a result with no registered id is silent — no receive or error handler fires, map stays empty. - error-cleans-up-handler (Error): an error response removes the handler without firing the callback, so a retry starts clean. - stop-clears-handler-map: signel-stop empties the map, so a restart can't replay stale callbacks waiting on responses that will never arrive. The dispatch tests synthesize JSON alists directly. No live process is needed. The send-rpc test stubs get-process and process-send-string so it doesn't need a running signal-cli. Refactor audit on signel.el surfaced one unrelated pre-existing smell I'm not fixing here: signel--handle-error reads from signel--request-buffer-map but never remhashes, so error responses leak request-id → buffer-name entries. Filed as a separate [#C] follow-up under the Signal parent task; the maps clear on stop/start so the impact is bounded to a single live session. todo.org: the dispatch task flips to DOING (the fork commit is in, the test contract is locked) and gets the leak follow-up appended.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-signel-rpc-dispatch.el94
1 files changed, 94 insertions, 0 deletions
diff --git a/tests/test-signel-rpc-dispatch.el b/tests/test-signel-rpc-dispatch.el
new file mode 100644
index 00000000..5ae023d6
--- /dev/null
+++ b/tests/test-signel-rpc-dispatch.el
@@ -0,0 +1,94 @@
+;;; test-signel-rpc-dispatch.el --- Tests for signel JSON-RPC success-result dispatch -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; signel's JSON-RPC dispatch (signel.el in the fork at ~/code/signel) routes
+;; incoming `receive' notifications and errors, but successful
+;; `((id . N) (result . VALUE))' responses had no path until this work added a
+;; request-callback table. These tests cover the new behavior: a registered
+;; callback fires with the result and is then removed; an error response
+;; also removes the handler so a retry starts clean; an unregistered id is a
+;; silent no-op; passing SUCCESS-CALLBACK to `signel--send-rpc' registers it
+;; under the returned id.
+;;
+;; The dispatch tests exercise `signel--dispatch' directly with synthetic JSON
+;; alists; no live process is needed. The send-rpc test stubs `get-process'
+;; and `process-send-string' so it doesn't require a running signal-cli.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+(eval-and-compile
+ (add-to-list 'load-path (expand-file-name "~/code/signel")))
+(require 'signel)
+
+(defun test-signel-rpc--reset ()
+ "Reset signel dispatch state to a clean baseline before each test."
+ (clrhash signel--request-handler-map)
+ (clrhash signel--request-buffer-map)
+ (setq signel--rpc-id-counter 0))
+
+(ert-deftest test-signel-rpc-dispatch-result-invokes-callback ()
+ "Normal: a result response with a registered id fires the callback with the
+result value and removes the handler."
+ (test-signel-rpc--reset)
+ (let ((captured nil))
+ (puthash 7 (lambda (val) (setq captured val)) signel--request-handler-map)
+ (signel--dispatch '((jsonrpc . "2.0") (id . 7)
+ (result . ((contacts . [1 2 3])))))
+ (should (equal captured '((contacts . [1 2 3]))))
+ (should-not (gethash 7 signel--request-handler-map))))
+
+(ert-deftest test-signel-rpc-dispatch-unknown-id-is-noop ()
+ "Boundary: a result response with an unregistered id is a silent no-op:
+neither receive nor error handler fires, and the handler map stays empty."
+ (test-signel-rpc--reset)
+ (let ((called nil))
+ (cl-letf (((symbol-function 'signel--handle-error)
+ (lambda (&rest _) (setq called 'error)))
+ ((symbol-function 'signel--handle-receive)
+ (lambda (&rest _) (setq called 'receive))))
+ (signel--dispatch '((jsonrpc . "2.0") (id . 99) (result . "anything"))))
+ (should-not called)
+ (should (zerop (hash-table-count signel--request-handler-map)))))
+
+(ert-deftest test-signel-rpc-dispatch-error-cleans-up-handler ()
+ "Error: an error response with a registered id removes the handler without
+firing the callback, leaving the map clean for a retry."
+ (test-signel-rpc--reset)
+ (let ((fired nil))
+ (puthash 11 (lambda (&rest _) (setq fired t))
+ signel--request-handler-map)
+ (cl-letf (((symbol-function 'signel--handle-error) (lambda (&rest _) nil)))
+ (signel--dispatch '((jsonrpc . "2.0") (id . 11)
+ (error . ((code . -1) (message . "boom"))))))
+ (should-not fired)
+ (should-not (gethash 11 signel--request-handler-map))))
+
+(ert-deftest test-signel-rpc-send-rpc-registers-success-callback ()
+ "Normal: passing a SUCCESS-CALLBACK to `signel--send-rpc' stores it under
+the returned id so the matching response can route to it."
+ (test-signel-rpc--reset)
+ (let ((cb (lambda (_) 'ok))
+ (sent nil))
+ (cl-letf (((symbol-function 'get-process) (lambda (&rest _) 'fake-proc))
+ ((symbol-function 'process-send-string)
+ (lambda (_ s) (setq sent s)))
+ ((symbol-function 'signel--log) (lambda (&rest _) nil)))
+ (let ((id (signel--send-rpc "listContacts" nil nil cb)))
+ (should (eq cb (gethash id signel--request-handler-map)))
+ (should (stringp sent))))))
+
+(ert-deftest test-signel-rpc-stop-clears-handler-map ()
+ "Normal (reconnect-invalidation): `signel-stop' clears the handler map so a
+restart starts with no stale callbacks waiting for responses that will never
+arrive."
+ (test-signel-rpc--reset)
+ (puthash 13 (lambda (&rest _) nil) signel--request-handler-map)
+ (cl-letf (((symbol-function 'get-process) (lambda (&rest _) nil)))
+ (signel-stop))
+ (should (zerop (hash-table-count signel--request-handler-map))))
+
+(provide 'test-signel-rpc-dispatch)
+;;; test-signel-rpc-dispatch.el ends here