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:
214
test/clj/auto_ap/ssr/transaction/insights_test.clj
Normal file
214
test/clj/auto_ap/ssr/transaction/insights_test.clj
Normal 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)))))))
|
||||
Reference in New Issue
Block a user