;;; noise-receive-test.el --- Tests for noise-receive -*- lexical-binding: t; -*- (require 'ert) (require 'noise-receive) (defvar noise-receive-test--db (expand-file-name "noise-receive-test.db" temporary-file-directory)) (defmacro noise-receive-test--with-db (&rest body) "Run BODY with a fresh test database and clean up conversation buffers." `(let ((noise-db-file noise-receive-test--db)) (unwind-protect (progn (when (file-exists-p noise-db-file) (delete-file noise-db-file)) (noise-db-init) ,@body) (noise-db--close) (when (file-exists-p noise-receive-test--db) (delete-file noise-receive-test--db)) (dolist (buf (buffer-list)) (when (string-prefix-p "*signal:" (buffer-name buf)) (kill-buffer buf)))))) (defun noise-receive-test--ht (&rest kvs) "Build a hash table from KVS key/value pairs." (let ((ht (make-hash-table :test 'equal))) (while kvs (puthash (car kvs) (cadr kvs) ht) (setq kvs (cddr kvs))) ht)) (defun noise-receive-test--run (&rest envelopes) "Run `noise-receive' against mocked ENVELOPES, returning its result." (cl-letf (((symbol-function 'noise-rpc-call-for-account) (lambda (method &rest _) (should (string= "receive" method)) (apply #'vector (mapcar (lambda (e) (noise-receive-test--ht "envelope" e)) envelopes))))) (noise-receive))) (ert-deftest noise-receive-direct-message () "An incoming direct message creates contact, conversation, and message." (noise-receive-test--with-db (should (= 1 (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+1" "sourceName" "Sarah" "dataMessage" (noise-receive-test--ht "timestamp" 1717930000000 "message" "are you coming?"))))) (should (string= "Sarah" (gethash "name" (noise-db-get-contact "+1")))) (let ((conv (noise-db-get-conversation "+1"))) (should (string= "direct" (gethash "type" conv))) (should (string= "Sarah" (gethash "name" conv))) (should (= 1 (gethash "unread_count" conv))) ;; direct chats have no sender prefix in the preview (should (string= "" (gethash "last_message_sender" conv))) (should (string= "are you coming?" (gethash "last_message_preview" conv)))) (let ((msg (car (noise-db-get-messages "+1")))) (should (string= "Sarah" (gethash "source" msg))) (should (= 1717930000.0 (gethash "timestamp" msg)))))) (ert-deftest noise-receive-group-message () "An incoming group message creates a group conversation keyed by groupId." (noise-receive-test--with-db (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+2" "sourceName" "Priya" "dataMessage" (noise-receive-test--ht "timestamp" 1717930000000 "message" "saturday 7am, usual spot?" "groupInfo" (noise-receive-test--ht "groupId" "grp1" "groupName" "cycling crew")))) (let ((conv (noise-db-get-conversation "grp1"))) (should (string= "group" (gethash "type" conv))) (should (string= "cycling crew" (gethash "name" conv))) ;; group previews keep the sender prefix (should (string= "Priya" (gethash "last_message_sender" conv)))))) (ert-deftest noise-receive-attachment-only-message () "An attachment-only message is stored and gets a photo preview." (noise-receive-test--with-db (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+1" "sourceName" "Sarah" "dataMessage" (noise-receive-test--ht "timestamp" 1717930000000 "message" :null "attachments" (vector (noise-receive-test--ht "filename" "IMG_2041.jpg"))))) (let ((msg (car (noise-db-get-messages "+1")))) (should (= 1 (gethash "has_attachment" msg))) (should (string= "IMG_2041.jpg" (gethash "attachment_path" msg)))) (should (string= "photo: IMG_2041.jpg" (gethash "last_message_preview" (noise-db-get-conversation "+1")))))) (ert-deftest noise-receive-sync-message () "A sync sentMessage from another device is stored as from `me'." (noise-receive-test--with-db (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+me" "syncMessage" (noise-receive-test--ht "sentMessage" (noise-receive-test--ht "timestamp" 1717930000000 "destinationNumber" "+1" "message" "sent from my phone")))) (let ((msg (car (noise-db-get-messages "+1")))) (should (string= "me" (gethash "source" msg))) (should (string= "sent" (gethash "receipt_state" msg))) (should (string= "sent from my phone" (gethash "body" msg)))) ;; own messages don't bump unread (should (= 0 (gethash "unread_count" (noise-db-get-conversation "+1")))))) (ert-deftest noise-receive-delivery-receipt () "A delivery receipt upgrades the receipt state of the matching message." (noise-receive-test--with-db (noise-db-upsert-conversation "+1" :type "direct" :name "Sarah") (noise-db-insert-message "+1" :timestamp 1717930000.0 :source "me" :body "hi" :receipt-state "sent") (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+1" "receiptMessage" (noise-receive-test--ht "isDelivery" t "isRead" :false "timestamps" (vector 1717930000000)))) (should (string= "delivered" (gethash "receipt_state" (car (noise-db-get-messages "+1"))))))) (ert-deftest noise-receive-receipt-never-downgrades () "A delivery receipt arriving after a read receipt is ignored." (noise-receive-test--with-db (noise-db-upsert-conversation "+1" :type "direct" :name "Sarah") (noise-db-insert-message "+1" :timestamp 1717930000.0 :source "me" :body "hi" :receipt-state "read") (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+1" "receiptMessage" (noise-receive-test--ht "isDelivery" t "isRead" :false "timestamps" (vector 1717930000000)))) (should (string= "read" (gethash "receipt_state" (car (noise-db-get-messages "+1"))))))) (ert-deftest noise-receive-receipt-ignores-incoming-messages () "Receipts only apply to own messages, not the contact's." (noise-receive-test--with-db (noise-db-upsert-conversation "+1" :type "direct" :name "Sarah") (noise-db-insert-message "+1" :timestamp 1717930000.0 :source "Sarah" :body "hi") (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+1" "receiptMessage" (noise-receive-test--ht "isDelivery" t "isRead" :false "timestamps" (vector 1717930000000)))) (should (string= "" (gethash "receipt_state" (car (noise-db-get-messages "+1"))))))) (ert-deftest noise-receive-typing-without-buffer () "Typing indicators for conversations without open buffers are harmless." (noise-receive-test--with-db (should (= 0 (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+1" "sourceName" "Sarah" "typingMessage" (noise-receive-test--ht "action" "STARTED" "timestamp" 1717930000000))))))) (ert-deftest noise-receive-refreshes-open-conversation () "Receiving a message re-renders an open conversation buffer." (noise-receive-test--with-db (noise-db-upsert-conversation "+1" :type "direct" :name "Sarah" :members '("+1")) (require 'noise-conversation) (with-current-buffer (noise-conversation-open "+1") (noise-receive-test--run (noise-receive-test--ht "sourceNumber" "+1" "sourceName" "Sarah" "dataMessage" (noise-receive-test--ht "timestamp" 1717930000000 "message" "new message here"))) (should (string-match-p "Sarah> new message here" (buffer-string)))))) (ert-deftest noise-receive-handle-sse-event-stores-message () "An SSE envelope event stores the message and refreshes the open buffer." (noise-receive-test--with-db (noise-db-upsert-conversation "+1" :type "direct" :name "Sarah" :members '("+1")) (require 'noise-conversation) (with-current-buffer (noise-conversation-open "+1") (noise-receive--handle-sse-event (noise-receive-test--ht "envelope" (noise-receive-test--ht "sourceNumber" "+1" "sourceName" "Sarah" "dataMessage" (noise-receive-test--ht "timestamp" 1717930000000 "message" "from sse")))) (should (string-match-p "Sarah> from sse" (buffer-string)))) (let ((msg (car (noise-db-get-messages "+1")))) (should (string= "from sse" (gethash "body" msg))))))