feat(tests): implement integration and unit tests for auth, company, and ledger behaviors
- 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
This commit is contained in:
131
test/clj/auto_ap/auth/oauth_test.clj
Normal file
131
test/clj/auto_ap/auth/oauth_test.clj
Normal file
@@ -0,0 +1,131 @@
|
||||
(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)))))))
|
||||
Reference in New Issue
Block a user