summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2026-04-25 16:19:52 -0300
committerEuAndreh <eu@euandre.org>2026-04-25 16:19:52 -0300
commita72bdd32f9f6e83858ad2670fabfd5136a2edefe (patch)
tree49aa60f9f860eff6b2646d261e17a78bf53c91c6 /tests
parentExpand RPL_ISUPPORT and have OPER emit MODE +o (diff)
downloadpapod-a72bdd32f9f6e83858ad2670fabfd5136a2edefe.tar.gz
papod-a72bdd32f9f6e83858ad2670fabfd5136a2edefe.tar.xz
Enforce +i, +k, +l on JOIN; add @ symbol for secret channels
JOIN now respects the channel modes that gate access: - +i (invite-only): rejects with 473 unless the user is in the per-channel invite list, which INVITE now populates - +k <key>: rejects with 475 unless the JOIN key argument matches the value tracked alongside +k in :chan-keys - +l <n>: rejects with 471 once :chan-limits is at capacity NAMES (and the burst sent on JOIN) now picks the channel symbol based on chan-modes — '@' for +s, '*' for +p, '=' otherwise — so secret/private channels are reported correctly.
Diffstat (limited to 'tests')
-rw-r--r--tests/integration.clj5
-rw-r--r--tests/unit.clj114
2 files changed, 117 insertions, 2 deletions
diff --git a/tests/integration.clj b/tests/integration.clj
index 3e12833..04efc72 100644
--- a/tests/integration.clj
+++ b/tests/integration.clj
@@ -46,7 +46,10 @@
:channels (atom {})
:ops (atom {})
:voiced (atom {})
- :chan-modes (atom {})}))
+ :chan-modes (atom {})
+ :chan-keys (atom {})
+ :chan-limits (atom {})
+ :invites (atom {})}))
(defn- make-client
"Creates a simulated client connection using piped streams.
diff --git a/tests/unit.clj b/tests/unit.clj
index 6b9e36b..5439c71 100644
--- a/tests/unit.clj
+++ b/tests/unit.clj
@@ -112,7 +112,10 @@
{:conn conn :cracha cracha-state :process-id proc-id
:clients (atom {}) :channels (atom {})
:ops (atom {}) :voiced (atom {})
- :chan-modes (atom {})})))
+ :chan-modes (atom {})
+ :chan-keys (atom {})
+ :chan-limits (atom {})
+ :invites (atom {})})))
(defn test-network!
[conn]
@@ -1788,6 +1791,115 @@
(is (= 1 (count replies)))
(is (string/includes? (first replies) "391"))))))
+(deftest test_channel-modes
+ (testing "secret channel (+s) uses @ symbol in NAMES"
+ (let [{:keys [test-network-id] :as components}
+ (test-components-with-network)
+ out (java.io.ByteArrayOutputStream.)
+ c (registered-client "alice" out test-network-id)
+ comp (assoc components
+ :clients
+ (atom {"alice"
+ {:w out :client-atom c}}))]
+ (handle-join ["#sec"] c comp)
+ ;; Mark channel as secret
+ (swap! (:chan-modes comp) assoc "#sec" "+nts")
+ (let [replies (replies-for!
+ {:command "NAMES" :params ["" "#sec"]}
+ c comp)]
+ (is (string/includes? (first replies) "353"))
+ (is (string/includes? (first replies) "@ #sec")))))
+ (testing "+i (invite-only) blocks JOIN with 473"
+ (let [{:keys [test-network-id] :as components}
+ (test-components-with-network)
+ a-out (java.io.ByteArrayOutputStream.)
+ b-out (java.io.ByteArrayOutputStream.)
+ alice (registered-client "alice" a-out test-network-id)
+ bob (registered-client "bob" b-out test-network-id)
+ comp (assoc components
+ :clients (atom
+ {"alice" {:w a-out
+ :client-atom alice}
+ "bob" {:w b-out
+ :client-atom bob}})
+ :channels (atom {}))]
+ (handle-join ["#priv"] alice comp)
+ (swap! (:chan-modes comp) assoc "#priv" "+nti")
+ (let [replies (handle-join ["#priv"] bob comp)]
+ (is (= 1 (count replies)))
+ (is (string/includes? (first replies) "473")))))
+ (testing "INVITE allows +i bypass"
+ (let [{:keys [test-network-id] :as components}
+ (test-components-with-network)
+ a-out (java.io.ByteArrayOutputStream.)
+ b-out (java.io.ByteArrayOutputStream.)
+ alice (registered-client "alice" a-out test-network-id)
+ bob (registered-client "bob" b-out test-network-id)
+ comp (assoc components
+ :clients (atom
+ {"alice" {:w a-out
+ :client-atom alice}
+ "bob" {:w b-out
+ :client-atom bob}})
+ :channels (atom {}))]
+ (handle-join ["#priv"] alice comp)
+ (swap! (:chan-modes comp) assoc "#priv" "+nti")
+ (replies-for!
+ {:command "INVITE" :params ["" "bob" "#priv"]}
+ alice comp)
+ ;; Bob should now be able to join
+ (let [replies (handle-join ["#priv"] bob comp)]
+ (is (empty?
+ (filter #(re-find #" 473 " %) replies))))))
+ (testing "+l limit blocks JOIN with 471"
+ (let [{:keys [test-network-id] :as components}
+ (test-components-with-network)
+ a-out (java.io.ByteArrayOutputStream.)
+ b-out (java.io.ByteArrayOutputStream.)
+ alice (registered-client "alice" a-out test-network-id)
+ bob (registered-client "bob" b-out test-network-id)
+ comp (assoc components
+ :clients (atom
+ {"alice" {:w a-out
+ :client-atom alice}
+ "bob" {:w b-out
+ :client-atom bob}})
+ :channels (atom {}))]
+ (handle-join ["#small"] alice comp)
+ (swap! (:chan-modes comp) assoc "#small" "+ntl")
+ (swap! (:chan-limits comp) assoc "#small" 1)
+ (let [replies (handle-join ["#small"] bob comp)]
+ (is (string/includes? (first replies) "471")))))
+ (testing "+k key blocks JOIN with 475"
+ (let [{:keys [test-network-id] :as components}
+ (test-components-with-network)
+ a-out (java.io.ByteArrayOutputStream.)
+ b-out (java.io.ByteArrayOutputStream.)
+ alice (registered-client "alice" a-out test-network-id)
+ bob (registered-client "bob" b-out test-network-id)
+ comp (assoc components
+ :clients (atom
+ {"alice" {:w a-out
+ :client-atom alice}
+ "bob" {:w b-out
+ :client-atom bob}})
+ :channels (atom {}))]
+ (handle-join ["#locked"] alice comp)
+ (swap! (:chan-modes comp) assoc "#locked" "+ntk")
+ (swap! (:chan-keys comp) assoc "#locked" "secret")
+ ;; No key
+ (let [replies (handle-join ["#locked"] bob comp)]
+ (is (string/includes? (first replies) "475")))
+ ;; Wrong key
+ (let [replies (handle-join ["#locked" "wrong"]
+ bob comp)]
+ (is (string/includes? (first replies) "475")))
+ ;; Right key
+ (let [replies (handle-join ["#locked" "secret"]
+ bob comp)]
+ (is (empty?
+ (filter #(re-find #" 475 " %) replies)))))))
+
(defn -main
[& _args]
(binding [*out* *err*]