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:
2026-05-08 16:12:08 -07:00
parent d9d9263824
commit 6b5d33a32f
64 changed files with 9005 additions and 2086 deletions

View 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)))))))