summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2026-04-22 08:27:17 -0300
committerEuAndreh <eu@euandre.org>2026-04-22 08:27:17 -0300
commitf6f6167d54c2644b51a71f964adb768682e8227e (patch)
tree68d33fb79a50d3ff813ea12bdc177a96d54c379a /tests
parentm (diff)
downloadpapod-f6f6167d54c2644b51a71f964adb768682e8227e.tar.gz
papod-f6f6167d54c2644b51a71f964adb768682e8227e.tar.xz
m
Diffstat (limited to 'tests')
-rw-r--r--tests/unit.clj246
1 files changed, 205 insertions, 41 deletions
diff --git a/tests/unit.clj b/tests/unit.clj
index af13931..8e8eb0a 100644
--- a/tests/unit.clj
+++ b/tests/unit.clj
@@ -91,6 +91,45 @@
(defn client [] (atom {:nick nil :user nil :pass nil :registered? false}))
(def no-conn {})
+(defn test-components
+ []
+ (let [fiinha-state (fiinha/initdb!
+ (str "datomic:mem://fiinha-" (java.util.UUID/randomUUID)))
+ cracha-state (cracha/init!
+ (str "datomic:mem://cracha-" (java.util.UUID/randomUUID))
+ fiinha-state)
+ papod-uri (str "datomic:mem://papod-" (java.util.UUID/randomUUID))
+ _ (d/create-database papod-uri)
+ conn (d/connect papod-uri)]
+ @(d/transact conn @#'papod/schema)
+ (let [proc-id (java.util.UUID/randomUUID)]
+ @(d/transact conn
+ [{:db/ensure :papod.process/attrs
+ :papod.process/id proc-id
+ :papod.process/pid 0
+ :papod.process/hostname "test"
+ :papod.process/started-at (java.util.Date.)}])
+ {:conn conn :cracha cracha-state :process-id proc-id
+ :clients (atom {}) :channels (atom {})})))
+
+(defn test-network!
+ [conn]
+ (let [net-id (java.util.UUID/randomUUID)]
+ @(d/transact conn
+ [{:db/ensure :papod.network/attrs
+ :papod.network/id net-id
+ :papod.network/name (str "test-" net-id)
+ :papod.network/description ""
+ :papod.network/type "public"
+ :papod.network/created-at (java.util.Date.)}])
+ net-id))
+
+(defn test-components-with-network
+ []
+ (let [components (test-components)
+ net-id (test-network! (:conn components))]
+ (assoc components :test-network-id net-id)))
+
(deftest test_clean-params
(testing "removes leading empty strings"
(is (= (clean-params ["" "a" "b"]) ["a" "b"]))
@@ -98,12 +137,21 @@
(is (= (clean-params []) []))))
(deftest test_replies-for!
- (testing "PASS stores password"
- (let [c (client)]
- (is (= (replies-for! {:command "PASS" :params ["" "secret"]}
- c no-conn)
+ (testing "PASS selects network"
+ (let [{:keys [test-network-id] :as components} (test-components-with-network)
+ c (client)]
+ (is (= (replies-for! {:command "PASS"
+ :params ["" (str test-network-id)]}
+ c components)
[]))
- (is (= (:pass @c) "secret"))))
+ (is (= (:network-id @c) test-network-id))))
+ (testing "PASS with invalid UUID"
+ (let [components (test-components)
+ c (client)]
+ (is (string/includes?
+ (first (replies-for! {:command "PASS" :params ["" "not-a-uuid"]}
+ c components))
+ "403"))))
(testing "PASS with no params"
(let [c (client)]
(is (string/includes?
@@ -180,38 +228,6 @@
c no-conn))
"409")))))
-(defn test-components
- []
- (let [fiinha-state (fiinha/initdb!
- (str "datomic:mem://fiinha-" (java.util.UUID/randomUUID)))
- cracha-state (cracha/init!
- (str "datomic:mem://cracha-" (java.util.UUID/randomUUID))
- fiinha-state)
- papod-uri (str "datomic:mem://papod-" (java.util.UUID/randomUUID))
- _ (d/create-database papod-uri)
- conn (d/connect papod-uri)]
- @(d/transact conn @#'papod/schema)
- {:conn conn :cracha cracha-state
- :clients (atom {}) :channels (atom {})}))
-
-(defn test-network!
- [conn]
- (let [net-id (java.util.UUID/randomUUID)]
- @(d/transact conn
- [{:db/ensure :papod.network/attrs
- :papod.network/id net-id
- :papod.network/name (str "test-" net-id)
- :papod.network/description ""
- :papod.network/type "public"
- :papod.network/created-at (java.util.Date.)}])
- net-id))
-
-(defn test-components-with-network
- []
- (let [components (test-components)
- net-id (test-network! (:conn components))]
- (assoc components :test-network-id net-id)))
-
(defn registered-client
([nick w]
(atom {:nick nick :user {:username nick} :pass nil :registered? true :w w}))
@@ -265,7 +281,7 @@
[{:papod.channel/id (java.util.UUID/randomUUID)
:papod.channel/network [:papod.network/id test-network-id]
:papod.channel/name "#test"
- :papod.channel/type "public"
+ :papod.channel/type :papod.channel.type/public
:papod.channel/description ""
:papod.channel/created-at (java.util.Date.)}])
(handle-privmsg ["#test" ":hi everyone"] sender components)
@@ -437,9 +453,10 @@
;; Send credentials
(let [creds (b64 "\u0000alice\u0000mypass")
replies (handle-authenticate [creds] c2 components)]
- (is (= 2 (count replies)))
+ (is (= 3 (count replies)))
(is (string/includes? (first replies) "900"))
(is (string/includes? (second replies) "903"))
+ (is (string/includes? (nth replies 2) "Session ID"))
(is (:authenticated? @c2)))
;; CAP END completes registration
(let [replies (handle-cap ["END"] c2 components)]
@@ -688,7 +705,7 @@
:db/id "new-channel"
:papod.channel/id chan-id
:papod.channel/network [:papod.network/id test-network-id]
- :papod.channel/type "private"
+ :papod.channel/type :papod.channel.type/private
:papod.channel/label "secret-project"
:papod.channel/description ""
:papod.channel/created-at (java.util.Date.)}
@@ -714,7 +731,7 @@
[{:db/ensure :papod.channel/attrs
:papod.channel/id (java.util.UUID/randomUUID)
:papod.channel/network [:papod.network/id test-network-id]
- :papod.channel/type "private"
+ :papod.channel/type :papod.channel.type/private
:papod.channel/label "bob-firing-wg"
:papod.channel/description ""
:papod.channel/created-at (java.util.Date.)}])]
@@ -811,6 +828,153 @@
:papod.network/id (java.util.UUID/randomUUID)
:papod.network/name "incomplete"}]))))))
+(deftest test_session-tracking
+ (testing "SASL auth creates session + logon"
+ (let [{:keys [test-network-id] :as components} (test-components-with-network)
+ out (java.io.ByteArrayOutputStream.)
+ c (client)]
+ ;; Register user in cracha first
+ (swap! c assoc :nick "alice" :registered? true :w out
+ :network-id test-network-id)
+ (handle-privmsg ["NickServ" ":REGISTER mypass"] c components)
+ ;; Fresh connection with connection-id (simulating client-loop!)
+ (let [conn-id (java.util.UUID/randomUUID)
+ conn (:conn components)
+ _ @(d/transact conn
+ [{:db/ensure :papod.connection/attrs
+ :papod.connection/id conn-id
+ :papod.connection/process [:papod.process/id
+ (:process-id components)]
+ :papod.connection/created-at (java.util.Date.)}])
+ c2 (atom {:nick "alice" :caps #{"sasl"}
+ :w out :connection-id conn-id})]
+ (handle-authenticate ["PLAIN"] c2 components)
+ (let [creds (b64 "\u0000alice\u0000mypass")
+ replies (handle-authenticate [creds] c2 components)]
+ (is (string/includes? (first replies) "900"))
+ (is (some? (:session-id @c2)))
+ ;; Verify session in DB
+ (let [db (d/db conn)
+ sessions (d/q '{:find [?nick]
+ :where [[?s :papod.session/nick ?nick]]}
+ db)]
+ (is (= #{["alice"]} sessions)))
+ ;; Verify logon in DB
+ (let [db (d/db conn)
+ logons (d/q '{:find [?sid ?cid]
+ :where [[?l :papod.logon/session ?s]
+ [?l :papod.logon/connection ?c]
+ [?s :papod.session/id ?sid]
+ [?c :papod.connection/id ?cid]]}
+ db)]
+ (is (= 1 (count logons)))
+ (is (= conn-id (second (first logons)))))))))
+ (testing "NickServ IDENTIFY creates session + logon"
+ (let [{:keys [test-network-id] :as components} (test-components-with-network)
+ out (java.io.ByteArrayOutputStream.)
+ conn (:conn components)
+ conn-id (java.util.UUID/randomUUID)
+ _ @(d/transact conn
+ [{:db/ensure :papod.connection/attrs
+ :papod.connection/id conn-id
+ :papod.connection/process [:papod.process/id
+ (:process-id components)]
+ :papod.connection/created-at (java.util.Date.)}])
+ c (registered-client "bob" out test-network-id)]
+ (swap! c assoc :connection-id conn-id)
+ ;; Register user
+ (handle-privmsg ["NickServ" ":REGISTER secret"] c components)
+ ;; Identify
+ (let [replies (handle-privmsg ["NickServ" ":IDENTIFY secret"]
+ c components)]
+ (is (string/includes? (first replies) "now identified"))
+ (is (some? (:session-id @c)))
+ ;; Session + logon in DB
+ (let [db (d/db conn)
+ logons (d/q '{:find [?nick ?cid]
+ :where [[?l :papod.logon/session ?s]
+ [?l :papod.logon/connection ?c]
+ [?s :papod.session/nick ?nick]
+ [?c :papod.connection/id ?cid]]}
+ db)]
+ (is (= 1 (count logons)))
+ (is (= ["bob" conn-id] (vec (first logons)))))))
+ (testing "SASL RESUME resumes existing session"
+ (let [{:keys [test-network-id] :as components} (test-components-with-network)
+ out (java.io.ByteArrayOutputStream.)
+ conn (:conn components)
+ ;; First: register + auth to create a session
+ conn-id1 (java.util.UUID/randomUUID)
+ _ @(d/transact conn
+ [{:db/ensure :papod.connection/attrs
+ :papod.connection/id conn-id1
+ :papod.connection/process [:papod.process/id
+ (:process-id components)]
+ :papod.connection/created-at (java.util.Date.)}])
+ c1 (atom {:nick "alice" :registered? true :w out
+ :caps #{"sasl"} :connection-id conn-id1
+ :network-id test-network-id})]
+ ;; Register user in cracha
+ (handle-privmsg ["NickServ" ":REGISTER mypass"] c1 components)
+ ;; Auth via SASL PLAIN to get session
+ (handle-authenticate ["PLAIN"] c1 components)
+ (handle-authenticate [(b64 "\u0000alice\u0000mypass")] c1 components)
+ (let [session-id (:session-id @c1)]
+ (is (some? session-id))
+ ;; Now simulate reconnect: new connection, RESUME with session ID
+ (let [conn-id2 (java.util.UUID/randomUUID)
+ _ @(d/transact conn
+ [{:db/ensure :papod.connection/attrs
+ :papod.connection/id conn-id2
+ :papod.connection/process [:papod.process/id
+ (:process-id components)]
+ :papod.connection/created-at (java.util.Date.)}])
+ c2 (atom {:nick "alice" :w out
+ :caps #{"sasl"} :connection-id conn-id2})]
+ (handle-authenticate ["RESUME"] c2 components)
+ (let [replies (handle-authenticate
+ [(b64 (str session-id))] c2 components)]
+ (is (string/includes? (first replies) "900"))
+ (is (:authenticated? @c2))
+ (is (= session-id (:session-id @c2)))
+ ;; Two logons for the same session (two connections)
+ (let [db (d/db conn)
+ logons (d/q '{:find [?cid]
+ :in [$ ?sid]
+ :where [[?l :papod.logon/session ?s]
+ [?l :papod.logon/connection ?c]
+ [?s :papod.session/id ?sid]
+ [?c :papod.connection/id ?cid]]}
+ db session-id)]
+ (is (= 2 (count logons)))
+ (is (= #{[conn-id1] [conn-id2]} logons))))))))
+ (testing "SASL RESUME fails with wrong nick"
+ (let [components (test-components-with-network)
+ out (java.io.ByteArrayOutputStream.)
+ conn (:conn components)
+ conn-id (java.util.UUID/randomUUID)
+ _ @(d/transact conn
+ [{:db/ensure :papod.connection/attrs
+ :papod.connection/id conn-id
+ :papod.connection/process [:papod.process/id
+ (:process-id components)]
+ :papod.connection/created-at (java.util.Date.)}])
+ ;; Create a session for "alice"
+ session-id (java.util.UUID/randomUUID)
+ _ @(d/transact conn
+ [{:db/ensure :papod.session/attrs
+ :papod.session/id session-id
+ :papod.session/nick "alice"
+ :papod.session/created-at (java.util.Date.)}])
+ ;; Try to resume as "bob"
+ c (atom {:nick "bob" :w out :caps #{"sasl"}
+ :connection-id conn-id})]
+ (handle-authenticate ["RESUME"] c components)
+ (let [replies (handle-authenticate
+ [(b64 (str session-id))] c components)]
+ (is (string/includes? (first replies) "904"))
+ (is (not (:authenticated? @c))))))))
+
(defn -main
[& _args]
(binding [*out* *err*]