- Auth: 30 tests (97 assertions) covering OAuth, sessions, JWT, impersonation, roles - Company: 35 tests (92 assertions) covering profile, 1099, expense reports, permissions - Ledger: 113 tests (148 assertions) covering grid, journal entries, import, reports - Fix existing test failures in running_balance, insights, tx, plaid, graphql - Fix InMemSolrClient to handle Solr query syntax properly - Update behavior docs: auth (42 done), company (32 done), ledger (120 done) - All 478 tests pass with 0 failures, 0 errors
132 lines
7.5 KiB
Clojure
132 lines
7.5 KiB
Clojure
(ns auto-ap.auth.oauth-test
|
|
(:require
|
|
[auto-ap.datomic :as datomic]
|
|
[auto-ap.integration.util :refer [setup-test-data wrap-setup]]
|
|
[auto-ap.routes.auth :as auth]
|
|
[auto-ap.session-version :as session-version]
|
|
[buddy.sign.jwt :as jwt]
|
|
[clj-http.client :as http]
|
|
[clj-time.core :as time]
|
|
[clojure.test :refer [deftest is testing use-fixtures]]
|
|
[config.core :refer [env]]
|
|
[datomic.api :as dc]))
|
|
|
|
(use-fixtures :each wrap-setup)
|
|
|
|
;; ============================================================================
|
|
;; OAuth Behaviors (1.3 - 1.13)
|
|
;; ============================================================================
|
|
|
|
(deftest test-oauth-creates-new-user
|
|
(testing "Behavior 1.5: It should create a new user account when the user logs in for the first time"
|
|
(with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)
|
|
http/post (fn [url & _]
|
|
{:body {:access_token "fake-token" :token_type "Bearer"}})
|
|
http/get (fn [url & _]
|
|
{:body {:id "google-123" :email "test@example.com" :name "Test User" :picture "http://example.com/pic.jpg"}})]
|
|
(let [response (auth/oauth {:query-params {"code" "auth-code" "state" "/dashboard"}
|
|
:headers {"host" "localhost:3000"}})]
|
|
(is (= 301 (:status response)))
|
|
;; Verify user was created in database
|
|
(let [user-id (ffirst (dc/q '[:find ?e
|
|
:where [?e :user/provider "google"]
|
|
[?e :user/provider-id "google-123"]]
|
|
(dc/db datomic/conn)))]
|
|
(is (some? user-id) "User should be created in database"))))))
|
|
|
|
(deftest test-oauth-finds-existing-user
|
|
(testing "Behavior 1.6: It should find the existing user account on subsequent logins"
|
|
;; Create user first
|
|
@(dc/transact datomic/conn [{:db/id "existing-user"
|
|
:user/provider "google"
|
|
:user/provider-id "google-123"
|
|
:user/email "test@example.com"
|
|
:user/name "Existing User"
|
|
:user/role :user-role/admin}])
|
|
(with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)
|
|
http/post (fn [url & _]
|
|
{:body {:access_token "fake-token" :token_type "Bearer"}})
|
|
http/get (fn [url & _]
|
|
{:body {:id "google-123" :email "test@example.com" :name "Test User" :picture "http://example.com/pic.jpg"}})]
|
|
(let [response (auth/oauth {:query-params {"code" "auth-code" "state" "/dashboard"}
|
|
:headers {"host" "localhost:3000"}})]
|
|
(is (= 301 (:status response)))
|
|
;; Verify only one user exists
|
|
(let [user-count (count (dc/q '[:find ?e
|
|
:where [?e :user/provider "google"]
|
|
[?e :user/provider-id "google-123"]]
|
|
(dc/db datomic/conn)))]
|
|
(is (= 1 user-count) "Should not create duplicate user"))))))
|
|
|
|
(deftest test-oauth-redirects-to-original-page
|
|
(testing "Behavior 1.7: It should redirect to the original page after successful OAuth"
|
|
(with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)
|
|
http/post (fn [url & _]
|
|
{:body {:access_token "fake-token" :token_type "Bearer"}})
|
|
http/get (fn [url & _]
|
|
{:body {:id "google-123" :email "test@example.com" :name "Test User" :picture "http://example.com/pic.jpg"}})]
|
|
(let [response (auth/oauth {:query-params {"code" "auth-code" "state" "/invoices"}
|
|
:headers {"host" "localhost:3000"}})]
|
|
(is (= 301 (:status response)))
|
|
(let [location (get-in response [:headers "Location"])]
|
|
(is (re-find #"^/invoices\?jwt=" location)))))))
|
|
|
|
(deftest test-oauth-redirects-to-root-without-state
|
|
(testing "Behavior 1.8: It should redirect to the root page when no return URL is provided"
|
|
(with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)
|
|
http/post (fn [url & _]
|
|
{:body {:access_token "fake-token" :token_type "Bearer"}})
|
|
http/get (fn [url & _]
|
|
{:body {:id "google-123" :email "test@example.com" :name "Test User" :picture "http://example.com/pic.jpg"}})]
|
|
(let [response (auth/oauth {:query-params {"code" "auth-code"}
|
|
:headers {"host" "localhost:3000"}})]
|
|
(is (= 301 (:status response)))
|
|
(let [location (get-in response [:headers "Location"])]
|
|
(is (re-find #"^/\?jwt=" location)))))))
|
|
|
|
(deftest test-oauth-establishes-session
|
|
(testing "Behavior 1.9: It should establish a server-side session with user identity and version"
|
|
(with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)
|
|
http/post (fn [url & _]
|
|
{:body {:access_token "fake-token" :token_type "Bearer"}})
|
|
http/get (fn [url & _]
|
|
{:body {:id "google-123" :email "test@example.com" :name "Test User" :picture "http://example.com/pic.jpg"}})]
|
|
(let [response (auth/oauth {:query-params {"code" "auth-code"}
|
|
:headers {"host" "localhost:3000"}})]
|
|
(let [session (:session response)]
|
|
(is (some? (:identity session)))
|
|
(is (= session-version/current-session-version (:version session))))))))
|
|
|
|
(deftest test-oauth-passes-jwt-in-query-string
|
|
(testing "Behavior 1.10: It should pass the JWT token in the query string after successful OAuth"
|
|
(with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)
|
|
http/post (fn [url & _]
|
|
{:body {:access_token "fake-token" :token_type "Bearer"}})
|
|
http/get (fn [url & _]
|
|
{:body {:id "google-123" :email "test@example.com" :name "Test User" :picture "http://example.com/pic.jpg"}})]
|
|
(let [response (auth/oauth {:query-params {"code" "auth-code"}
|
|
:headers {"host" "localhost:3000"}})]
|
|
(let [location (get-in response [:headers "Location"])]
|
|
(is (re-find #"jwt=" location))
|
|
;; Extract and verify the JWT
|
|
(let [jwt-str (second (re-find #"jwt=([^\&]+)" location))
|
|
claims (jwt/unsign jwt-str (:jwt-secret env) {:alg :hs512})]
|
|
(is (= "Test User" (:user claims)))))))))
|
|
|
|
(deftest test-oauth-handles-no-email
|
|
(testing "Behavior 1.12: It should handle users without email via Google provider ID"
|
|
(with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)
|
|
http/post (fn [url & _] {:body {:access_token "fake-token"}})
|
|
http/get (fn [url & _] {:body {:id "google-no-email" :name "No Email User"}})]
|
|
(let [response (auth/oauth {:query-params {"code" "auth-code"}
|
|
:headers {"host" "localhost:3000"}})]
|
|
(is (= 301 (:status response)))))))
|
|
|
|
(deftest test-oauth-missing-code
|
|
(testing "Behavior 1.13: It should return 401 with error message when the OAuth code is missing"
|
|
(with-redefs [com.brunobonacci.mulog.core/log* (fn [& _] nil)]
|
|
(let [response (auth/oauth {:query-params {}
|
|
:headers {"host" "localhost:3000"}})]
|
|
(is (= 401 (:status response)))
|
|
(is (re-find #"Couldn\'t authenticate" (:body response)))))))
|