diff options
| author | EuAndreh <eu@euandre.org> | 2026-04-22 08:27:17 -0300 |
|---|---|---|
| committer | EuAndreh <eu@euandre.org> | 2026-04-22 08:27:17 -0300 |
| commit | f6f6167d54c2644b51a71f964adb768682e8227e (patch) | |
| tree | 68d33fb79a50d3ff813ea12bdc177a96d54c379a /tests | |
| parent | m (diff) | |
| download | papod-f6f6167d54c2644b51a71f964adb768682e8227e.tar.gz papod-f6f6167d54c2644b51a71f964adb768682e8227e.tar.xz | |
m
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/unit.clj | 246 |
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*] |
