diff --git a/clj-browserchannel/test/net/thegeez/browserchannel/common.clj b/clj-browserchannel/test/net/thegeez/browserchannel/common.clj index 0a97660..93be896 100644 --- a/clj-browserchannel/test/net/thegeez/browserchannel/common.clj +++ b/clj-browserchannel/test/net/thegeez/browserchannel/common.clj @@ -1,4 +1,7 @@ -(ns net.thegeez.browserchannel.common) +(ns net.thegeez.browserchannel.common + (:require + [clojure.string :as string] + [cheshire.core :as json])) (defn ->queue [& x] @@ -18,3 +21,31 @@ (= (get m k) v))) (remove true?) (empty?))) + +;; each chunk of arrays that the server sends out looks like this: +;; +;; length_of_following_array\n +;; [[array_id, array], +;; [array_id, array], +;; [array_id, array]] +;; +;; there may be 1 or more arrays. this splits each chunk up based on +;; the "length_of_following_array\n" part, and returns all the actual +;; arrays of data as one vector (all chunks together) +(defn get-response-arrays + [body] + (if (string? body) + (as-> body x + (string/split x #"\d+\n") + (remove string/blank? x) + (mapv json/parse-string x)))) + +;; HACK: sleep for an arbitrary period that is based off me throwing in a +;; random "feels good" number in there... i think this says it all, really +(defn wait-for-agent-send-offs + [] + (Thread/sleep 500)) + +(defn wait-for-heartbeat-interval + [secs] + (Thread/sleep (+ 1000 (* 1000 secs)))) diff --git a/clj-browserchannel/test/net/thegeez/browserchannel/server/bind_channel_http_request_tests.clj b/clj-browserchannel/test/net/thegeez/browserchannel/server/bind_channel_http_request_tests.clj index 39264f4..a3a02ec 100644 --- a/clj-browserchannel/test/net/thegeez/browserchannel/server/bind_channel_http_request_tests.clj +++ b/clj-browserchannel/test/net/thegeez/browserchannel/server/bind_channel_http_request_tests.clj @@ -5,6 +5,7 @@ net.thegeez.browserchannel.server net.thegeez.browserchannel.test-async-adapter) (:require + [clojure.edn :as edn] [clojure.string :as string] [cheshire.core :as json] [ring.util.response :as response] @@ -15,19 +16,13 @@ (reset! sessions {}) (f) (doseq [[_ session-agent] @sessions] - (send-off session-agent close nil "reset-sessions-fixture"))) + (send-off session-agent close nil "reset-sessions-fixture")) + ;; send-off dispatches on another thread, so we need to wait a small bit + ;; before returning (otherwise the next test might start while a previous + ;; test's session is still closing) + (wait-for-agent-send-offs)) -(use-fixtures :each async-output-fixture reset-sessions-fixture) - -(defn parse-channel-response - [body] - (if-not (string/blank? body) - (let [[len arrays] (string/split body #"\n" 2) - arrays (if-not (string/blank? arrays) - (json/parse-string arrays) - arrays)] - [len arrays]) - body)) +(use-fixtures :each reset-sessions-fixture async-output-fixture) (defn app [request & [options]] @@ -51,13 +46,142 @@ "zx" (random-string) "t" 1})) options)] + (wait-for-agent-send-offs) (is (= 200 (:status resp))) (is (contains-all-of? (:headers resp) (:headers options))) - (let [[len arrays] (parse-channel-response (:body resp)) - [[id [c session-id host-prefix version]]] arrays] - (is (= "57" len)) + (let [arrays (get-response-arrays (:body resp)) + [[id [c session-id host-prefix version]]] (first arrays)] (is (= "c" c)) (is (and (string? session-id) (not (string/blank? session-id)))) (is (nil? host-prefix)) - (is (= 8 version))))) + (is (= 8 version)) + (is (get @sessions session-id)) + (is (connected? session-id))))) + +(deftest backchannel-request-with-no-session-test + (let [resp (app (-> (mock/request + :get "/channel/bind") + (mock/query-string + {"VER" 8 + "RID" "rpc" + "CVER" 8 + "CI" 0 + "AID" 0 + "TYPE" "xmlhttp" + "zx" (random-string) + "t" 1})))] + (is (= 400 (:status resp))) + (is (empty? @sessions)))) + +(deftest backchannel-request-with-invalid-sid-test + (let [resp (app (-> (mock/request + :get "/channel/bind") + (mock/query-string + {"VER" 8 + "RID" "rpc" + "SID" "foobar" + "CVER" 8 + "CI" 0 + "AID" 0 + "TYPE" "xmlhttp" + "zx" (random-string) + "t" 1})))] + (is (= 400 (:status resp))) + (is (empty? @sessions)))) + +(defn ->new-session-request + [] + (-> (mock/request + :post "/channel/bind") + (mock/query-string + {"VER" 8 + "RID" 1 + "CVER" 8 + "zx" (random-string) + "t" 1}))) + +(defn get-session-id + [new-session-response] + (is (= 200 (:status new-session-response))) + (let [arrays (get-response-arrays (:body new-session-response)) + [[_ [c session-id _ _]]] (first arrays)] + (is (= "c" c)) + (is (and (string? session-id) + (not (string/blank? session-id)))) + session-id)) + +(deftest backchannel-request-test + (let [options (update-in default-options [:headers] dissoc "Content-Type") + create-resp (app (->new-session-request) options) + session-id (get-session-id create-resp) + back-resp (app (-> (mock/request + :get "/channel/bind") + (mock/query-string + {"VER" 8 + "RID" "rpc" + "SID" session-id + "CVER" 8 + "CI" 0 + "AID" 0 + "TYPE" "xmlhttp" + "zx" (random-string) + "t" 1}))) + async-resp @async-output] + (wait-for-agent-send-offs) + (let [status (get-status session-id)] + (is (= 200 (:status back-resp))) + (is (= 200 (:status async-resp))) + (is (not (:closed? async-resp))) + (is (contains-all-of? (:headers async-resp) (:headers options))) + (is (:connected? status)) + (is (:has-back-channel? status))))) + +(defn ->new-backchannel-request + [session-id] + (-> (mock/request + :get "/channel/bind") + (mock/query-string + {"VER" 8 + "RID" "rpc" + "SID" session-id + "CVER" 8 + "CI" 0 + "AID" 0 + "TYPE" "xmlhttp" + "zx" (random-string) + "t" 1}))) + +(deftest backchannel-request-send-data-to-client-test + (let [options (update-in default-options [:headers] dissoc "Content-Type") + create-resp (app (->new-session-request) options) + session-id (get-session-id create-resp) + back-resp (app (->new-backchannel-request session-id))] + (send-data session-id {:foo "bar"}) + (wait-for-agent-send-offs) + (let [async-resp @async-output + arrays (get-response-arrays (:body async-resp))] + (is (= 200 (:status back-resp))) + (is (= 200 (:status async-resp))) + (is (not (:closed? async-resp))) + (is (contains-all-of? (:headers async-resp) (:headers options))) + (is (= (-> arrays ffirst second (get "__edn") (edn/read-string)) + {:foo "bar"}))))) + +(deftest backchannel-request-heartbeat-test + (let [options (-> default-options + (update-in [:headers] dissoc "Content-Type") + ; shortened heartbeat interval for the purposes of this test + (assoc :keep-alive-interval 2)) + create-resp (app (->new-session-request) options) + session-id (get-session-id create-resp) + back-resp (app (->new-backchannel-request session-id))] + (wait-for-heartbeat-interval (:keep-alive-interval options)) + (let [async-resp @async-output + arrays (get-response-arrays (:body async-resp))] + (is (= 200 (:status back-resp))) + (is (= 200 (:status async-resp))) + (is (not (:closed? async-resp))) + (is (contains-all-of? (:headers async-resp) (:headers options))) + (is (= (-> arrays ffirst second) + ["noop"])))))