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,214 @@
(ns auto-ap.ssr.transaction.insights-test
(:require
[auto-ap.datomic :as datomic]
[auto-ap.integration.util :refer [admin-token setup-test-data wrap-setup]]
[auto-ap.rule-matching :refer [spread-cents]]
[auto-ap.ssr.transaction.insights :as sut]
[clj-time.coerce :as coerce]
[clj-time.core :as t]
[clojure.test :refer [deftest is testing use-fixtures]]
[datomic.api :as dc]))
(use-fixtures :each wrap-setup)
;; ============================================================================
;; Helpers
;; ============================================================================
(defn- make-request
([clients & {:as extra}]
(merge {:clients (mapv #(hash-map :db/id %) clients)
:identity (admin-token)
:session {}}
extra)))
(defn- create-transaction
[client-id bank-account-id {:keys [date amount description status outcome-rec]
:or {date #inst "2024-01-15"
amount 100.0
description "Test transaction"
status :transaction-approval-status/unapproved}}]
(let [base-tx {:db/id "tx"
:transaction/client client-id
:transaction/bank-account bank-account-id
:transaction/amount amount
:transaction/date date
:transaction/description-original description
:transaction/id (str (java.util.UUID/randomUUID))
:transaction/approval-status status}
final-tx (cond-> base-tx
outcome-rec (assoc :transaction/outcome-recommendation outcome-rec))
result @(dc/transact datomic/conn [[:upsert-transaction final-tx]])]
(get-in result [:tempids "tx"])))
(defn- count-forms-in-hiccup [hiccup]
(cond
(not (sequential? hiccup)) 0
(= :form (first hiccup)) 1
:else (reduce + 0 (map count-forms-in-hiccup hiccup))))
;; ============================================================================
;; 9.x Insights Page Display
;; ============================================================================
(deftest test-show-up-to-50-recommendations
(testing "Behavior 9.4: Show up to 50 recommendations at a time with no pagination"
(let [now (t/now)
recent-date (coerce/to-date (t/minus now (t/days 10)))
{:strs [test-client-id test-bank-account-id test-vendor-id test-account-id]} (setup-test-data [])]
;; Create 55 transactions with recommendations
(dotimes [_ 55]
(create-transaction test-client-id test-bank-account-id
{:date recent-date
:amount 100.0
:description "Bulk tx"
:outcome-rec [[test-vendor-id test-account-id 1 true]]}))
(let [request (make-request [test-client-id])
recommendations (sut/transaction-recommendations (:identity request) (:clients request))]
(is (= 50 (count recommendations)))
(is (vector? recommendations))))))
;; ============================================================================
;; 10.x Recommendation Rows
;; ============================================================================
(deftest test-unapproved-transactions-last-300-days-with-recommendations
(testing "Behavior 10.1: Unapproved transactions from last 300 days with outcome-recommendation"
(let [now (t/now)
recent-date (coerce/to-date (t/minus now (t/days 10)))
old-date (coerce/to-date (t/minus now (t/days 400)))
{:strs [test-client-id test-bank-account-id test-vendor-id test-account-id]} (setup-test-data [])]
;; Old transaction (>300 days ago) with recommendation
(create-transaction test-client-id test-bank-account-id
{:date old-date
:amount 50.0
:description "Old tx"
:outcome-rec [[test-vendor-id test-account-id 1 true]]})
;; Recent unapproved with recommendation
(let [recent-id (create-transaction test-client-id test-bank-account-id
{:date recent-date
:amount 100.0
:description "Recent tx"
:outcome-rec [[test-vendor-id test-account-id 5 true]]})]
(let [request (make-request [test-client-id])
recommendations (sut/transaction-recommendations (:identity request) (:clients request))]
(is (= 1 (count recommendations)))
(is (= recent-id (:db/id (first recommendations))))
(is (= "Recent tx" (:transaction/description-original (first recommendations)))))))))
(deftest test-up-to-3-recommendation-buttons-sorted-by-frequency
(testing "Behavior 10.4: Up to 3 recommendation buttons per row sorted by frequency"
(let [now (t/now)
recent-date (coerce/to-date (t/minus now (t/days 10)))
{:strs [test-client-id test-bank-account-id test-vendor-id test-account-id]} (setup-test-data [])]
;; Create extra vendor and account entities
(let [extras @(dc/transact datomic/conn
[{:db/id "v2" :vendor/name "Vendor B"}
{:db/id "v3" :vendor/name "Vendor C"}
{:db/id "a2" :account/name "Account B"}
{:db/id "a3" :account/name "Account C"}])
vendor-2-id (get-in extras [:tempids "v2"])
vendor-3-id (get-in extras [:tempids "v3"])
account-2-id (get-in extras [:tempids "a2"])
account-3-id (get-in extras [:tempids "a3"])]
;; Create transaction with 4 recommendations of varying frequency
(create-transaction test-client-id test-bank-account-id
{:date recent-date
:amount 100.0
:description "Multi rec tx"
:outcome-rec [[test-vendor-id test-account-id 10 true]
[vendor-2-id account-2-id 5 true]
[vendor-3-id account-3-id 8 true]
[test-vendor-id account-2-id 2 true]]})
(let [request (make-request [test-client-id])
recommendations (sut/transaction-recommendations (:identity request) (:clients request))
tx (first recommendations)
recs (:transaction/outcome-recommendation tx)
;; transaction-recommendations returns raw unsorted data; sorting happens at render time
sorted-counts (map :count (sort-by (comp - :count) recs))]
;; All 4 raw recommendations should be present after parse-outcome
(is (= 4 (count recs)))
;; When sorted by count descending (as done by transaction-row), should be [10 8 5 2]
(is (= [10 8 5 2] sorted-counts))
;; Verify transaction-row renders at most 3 forms (buttons)
(let [row-hiccup (sut/transaction-row tx)]
(is (<= (count-forms-in-hiccup row-hiccup) 3))))))))
;; ============================================================================
;; 11.x Coding Actions
;; ============================================================================
(deftest test-code-transaction
(testing "Behavior 11.1: Approve and code a transaction via sut/code"
(let [now (t/now)
recent-date (coerce/to-date (t/minus now (t/days 10)))
{:strs [test-client-id test-bank-account-id test-vendor-id test-account-id]} (setup-test-data [])]
(let [tx-id (create-transaction test-client-id test-bank-account-id
{:date recent-date
:amount 100.0
:description "Code me"
:outcome-rec [[test-vendor-id test-account-id 5 true]]})]
;; Note: code expects route-params with string transaction-id and string form params
(sut/code {:identity (admin-token)
:session {}
:route-params {:transaction-id (str tx-id)}
:form-params {"vendor" (str test-vendor-id)
"account" (str test-account-id)}})
(let [tx-after (dc/pull (dc/db datomic/conn)
[{:transaction/approval-status [:db/ident]}
:transaction/vendor
{:transaction/accounts [:transaction-account/account
:transaction-account/amount
:transaction-account/location]}]
tx-id)]
(is (= :transaction-approval-status/approved
(:db/ident (:transaction/approval-status tx-after))))
;; 11.2: Assign vendor and account when coding
(is (= test-vendor-id (:db/id (:transaction/vendor tx-after))))
(is (= test-account-id (:db/id (:transaction-account/account (first (:transaction/accounts tx-after)))))))))))
(deftest test-disapprove-transaction
(testing "Behavior 11.5: Reject a recommendation via sut/disapprove"
(let [now (t/now)
recent-date (coerce/to-date (t/minus now (t/days 10)))
{:strs [test-client-id test-bank-account-id test-vendor-id test-account-id]} (setup-test-data [])]
(let [tx-id (create-transaction test-client-id test-bank-account-id
{:date recent-date
:amount 100.0
:description "Reject me"
:outcome-rec [[test-vendor-id test-account-id 5 true]]})]
;; Verify recommendation exists before
(let [tx-before (dc/pull (dc/db datomic/conn) [:transaction/outcome-recommendation] tx-id)]
(is (some? (:transaction/outcome-recommendation tx-before))))
;; Call disapprove
(sut/disapprove {:identity (admin-token)
:session {}
:route-params {:transaction-id (str tx-id)}})
;; 11.6: outcome-recommendation should be cleared
(let [tx-after (dc/pull (dc/db datomic/conn) [:transaction/outcome-recommendation {:transaction/approval-status [:db/ident]}] tx-id)]
(is (nil? (:transaction/outcome-recommendation tx-after)))
;; Approval status should remain unapproved
(is (= :transaction-approval-status/unapproved
(:db/ident (:transaction/approval-status tx-after)))))))))
;; ============================================================================
;; 11.3 Unit: spread-cents / amount distribution
;; ============================================================================
(deftest test-spread-cents-distribution
(testing "Behavior 11.3: Distribute amount across valid locations using spread-cents"
(testing "Even distribution"
(is (= [50 50] (spread-cents 100 2)))
(is (= [34 33 33] (spread-cents 100 3)))
(is (= [25 25 25 25] (spread-cents 100 4))))
(testing "Single location gets all"
(is (= [100] (spread-cents 100 1))))
(testing "Uneven amounts distribute remainder to first locations"
(is (= [34 33 33] (spread-cents 100 3)))
(is (= [17 17 17 17 16 16] (spread-cents 100 6))))
(testing "Larger amounts"
(is (= [5000 5000] (spread-cents 10000 2)))
(is (= [3334 3333 3333] (spread-cents 10000 3))))
(testing "Sum equals original cents"
(is (= 10000 (reduce + (spread-cents 10000 7))))
(is (= 12345 (reduce + (spread-cents 12345 3)))))))