(ns pocketbook.auth-test (:require [clojure.test :refer [deftest is testing use-fixtures]] [pocketbook.server :as server] [pocketbook.transit :as t]) (:import [java.io File] [java.net URI] [java.net.http HttpClient HttpRequest HttpResponse$BodyHandlers HttpRequest$BodyPublishers])) (def ^:dynamic *port* nil) (def ^:dynamic *server* nil) (defn- free-port [] (with-open [s (java.net.ServerSocket. 0)] (.getLocalPort s))) (def test-users {"alice" {:token "alice-secret" :groups #{"todo" "settings"}} "bob" {:token "bob-secret" :groups #{"todo"}}}) (use-fixtures :each (fn [f] (let [port (free-port) db-path (str (File/createTempFile "pocketbook-auth-test" ".db")) srv (server/start! {:port port :db-path db-path :users test-users})] (Thread/sleep 200) (try (binding [*server* srv *port* port] (f)) (finally (server/stop! srv) (.delete (File. db-path))))))) (def ^:private client (HttpClient/newHttpClient)) (defn- url [path & [query]] (str "http://localhost:" *port* path (when query (str "?" query)))) (defn- get-transit [path query & [token]] (let [req (-> (HttpRequest/newBuilder) (.uri (URI. (url path query))) (.header "Accept" "application/transit+json") (cond-> token (.header "Authorization" (str "Bearer " token))) (.GET) (.build)) resp (.send client req (HttpResponse$BodyHandlers/ofByteArray))] {:status (.statusCode resp) :body (t/decode (.body resp))})) (defn- post-transit [path body & [token]] (let [bytes (t/encode body) req (-> (HttpRequest/newBuilder) (.uri (URI. (url path))) (.header "Content-Type" "application/transit+json") (.header "Accept" "application/transit+json") (cond-> token (.header "Authorization" (str "Bearer " token))) (.POST (HttpRequest$BodyPublishers/ofByteArray bytes)) (.build)) resp (.send client req (HttpResponse$BodyHandlers/ofByteArray))] {:status (.statusCode resp) :body (t/decode (.body resp))})) (deftest unauthorized-without-token (let [resp (get-transit "/sync" "group=todo&since=0")] (is (= 401 (:status resp))))) (deftest unauthorized-with-bad-token (let [resp (get-transit "/sync" "group=todo&since=0" "wrong-token")] (is (= 401 (:status resp))))) (deftest alice-can-access-todo (let [resp (post-transit "/sync" [{:id "todo:1" :value {:text "Alice todo"} :base-version 0}] "alice-secret")] (is (= 200 (:status resp))) (is (= :ok (:status (first (:body resp))))))) (deftest bob-cannot-access-settings (let [resp (post-transit "/sync" [{:id "settings:theme" :value {:dark true} :base-version 0}] "bob-secret")] (is (= 403 (:status resp))))) (deftest alice-can-access-settings (let [resp (post-transit "/sync" [{:id "settings:theme" :value {:dark true} :base-version 0}] "alice-secret")] (is (= 200 (:status resp))) (is (= :ok (:status (first (:body resp)))))))