- 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
352 lines
21 KiB
Clojure
352 lines
21 KiB
Clojure
(ns auto-ap.ledger.grid-test
|
|
(:require
|
|
[auto-ap.integration.util :refer [wrap-setup setup-test-data test-client test-account test-vendor test-bank-account]]
|
|
[auto-ap.datomic :refer [conn]]
|
|
[auto-ap.ssr.ledger.common :as ledger.common]
|
|
[auto-ap.datomic.ledger :as d-ledger]
|
|
[datomic.api :as dc]
|
|
[clojure.test :refer [deftest testing is use-fixtures]]))
|
|
|
|
(use-fixtures :each wrap-setup)
|
|
|
|
(defn setup-journal-entries
|
|
"Create test journal entries and return relevant IDs"
|
|
[{:keys [client-id account-id vendor-id bank-account-id]}]
|
|
(let [tx-result @(dc/transact conn
|
|
[{:db/id "je-1"
|
|
:journal-entry/client client-id
|
|
:journal-entry/date #inst "2023-01-15"
|
|
:journal-entry/source "test-source"
|
|
:journal-entry/external-id "test-ext-123"
|
|
:journal-entry/vendor vendor-id
|
|
:journal-entry/amount 100.0
|
|
:journal-entry/line-items [{:db/id "jel-1"
|
|
:journal-entry-line/account account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/debit 100.0}
|
|
{:db/id "jel-2"
|
|
:journal-entry-line/account account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/credit 100.0}]}
|
|
{:db/id "je-2"
|
|
:journal-entry/client client-id
|
|
:journal-entry/date #inst "2023-02-20"
|
|
:journal-entry/source "another-source"
|
|
:journal-entry/external-id "test-ext-456"
|
|
:journal-entry/vendor vendor-id
|
|
:journal-entry/amount 200.0
|
|
:journal-entry/alternate-description "Alt Description"
|
|
:journal-entry/line-items [{:db/id "jel-3"
|
|
:journal-entry-line/account account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/debit 200.0}
|
|
{:db/id "jel-4"
|
|
:journal-entry-line/account account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/credit 200.0}]}])]
|
|
tx-result))
|
|
|
|
;; 1.2: Client column visibility
|
|
(deftest test-display-client-column-visibility
|
|
(testing "Client column hidden when single client with single location"
|
|
(let [client-header (first (filter #(= "client" (:key %)) (:headers ledger.common/grid-page)))
|
|
hide-fn (:hide? client-header)]
|
|
(is (fn? hide-fn))
|
|
(is (hide-fn {:clients [{:db/id 1}] :client {:client/locations ["DT"]}}))
|
|
(is (not (hide-fn {:clients [{:db/id 1}] :client {:client/locations ["DT" "MH"]}})))
|
|
(is (not (hide-fn {:clients [{:db/id 1} {:db/id 2}] :client {:client/locations ["DT"]}}))))))
|
|
|
|
;; 1.3: Vendor column with alternate-description fallback
|
|
(deftest test-display-vendor-column-fallback
|
|
(testing "Vendor column shows vendor name when present"
|
|
(let [vendor-header (first (filter #(= "vendor" (:key %)) (:headers ledger.common/grid-page)))
|
|
render-fn (:render vendor-header)]
|
|
(is (= "Test Vendor" (render-fn {:journal-entry/vendor {:vendor/name "Test Vendor"}})))))
|
|
|
|
(testing "Vendor column falls back to alternate-description"
|
|
(let [vendor-header (first (filter #(= "vendor" (:key %)) (:headers ledger.common/grid-page)))
|
|
render-fn (:render vendor-header)
|
|
result (render-fn {:journal-entry/vendor nil
|
|
:journal-entry/alternate-description "Fallback Description"})]
|
|
(is (vector? result))
|
|
(is (re-find #"Fallback Description" (str result))))))
|
|
|
|
;; 2.1: Filter by vendor
|
|
(deftest test-filtering-by-vendor
|
|
(testing "Filter entries by vendor"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result-without-filter (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {}})
|
|
result-with-filter (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:vendor {:db/id test-vendor-id}}})]
|
|
(is (= 2 (:count result-without-filter)))
|
|
(is (= 2 (:count result-with-filter))))))
|
|
|
|
;; 2.2: Filter by account
|
|
(deftest test-filtering-by-account
|
|
(testing "Filter entries by account"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:account {:db/id test-account-id}}})]
|
|
(is (= 2 (:count result))))))
|
|
|
|
;; 2.3: Filter by bank account
|
|
(deftest test-filtering-by-bank-account
|
|
(testing "Filter entries by bank account"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id test-bank-account-id]} (setup-test-data [])
|
|
_ @(dc/transact conn [{:db/id "je-bank"
|
|
:journal-entry/client test-client-id
|
|
:journal-entry/date #inst "2023-03-01"
|
|
:journal-entry/source "bank-source"
|
|
:journal-entry/external-id "bank-ext-1"
|
|
:journal-entry/vendor test-vendor-id
|
|
:journal-entry/amount 50.0
|
|
:journal-entry/line-items [{:db/id "jel-bank"
|
|
:journal-entry-line/account test-bank-account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/debit 50.0}
|
|
{:db/id "jel-bank-2"
|
|
:journal-entry-line/account test-account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/credit 50.0}]}])
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:bank-account {:db/id test-bank-account-id}}})]
|
|
(is (= 1 (:count result))))))
|
|
|
|
;; 2.5: Filter by date range
|
|
(deftest test-filtering-by-date-range
|
|
(testing "Filter entries by date range"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:start-date #inst "2023-01-01"
|
|
:end-date #inst "2023-01-31"}})]
|
|
(is (= 1 (:count result))))))
|
|
|
|
;; 2.6: Filter by invoice number
|
|
(deftest test-filtering-by-invoice-number
|
|
(testing "Filter entries by invoice number"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ @(dc/transact conn [{:db/id "inv-1"
|
|
:invoice/client test-client-id
|
|
:invoice/date #inst "2023-01-01"
|
|
:invoice/vendor test-vendor-id
|
|
:invoice/invoice-number "INV-TEST-123"
|
|
:invoice/total 100.0
|
|
:invoice/outstanding-balance 100.0
|
|
:invoice/status :invoice-status/unpaid
|
|
:invoice/import-status :import-status/imported}
|
|
{:db/id "je-inv"
|
|
:journal-entry/client test-client-id
|
|
:journal-entry/date #inst "2023-01-01"
|
|
:journal-entry/original-entity "inv-1"
|
|
:journal-entry/amount 100.0
|
|
:journal-entry/line-items [{:db/id "jel-inv-1"
|
|
:journal-entry-line/account test-account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/debit 100.0}
|
|
{:db/id "jel-inv-2"
|
|
:journal-entry-line/account test-account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/credit 100.0}]}])
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:invoice-number "INV-TEST-123"}})]
|
|
(is (= 1 (:count result))))))
|
|
|
|
;; 2.7: Filter by account code range
|
|
(deftest test-filtering-by-account-code-range
|
|
(testing "Filter entries by account code range"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data
|
|
[(test-account :db/id "test-account-id"
|
|
:account/numeric-code 50000)])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:numeric-code-gte 40000
|
|
:numeric-code-lte 60000}})]
|
|
(is (= 2 (:count result))))))
|
|
|
|
;; 2.8: Filter by amount range
|
|
(deftest test-filtering-by-amount-range
|
|
(testing "Filter entries by amount range"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result-gte (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:amount-gte 150.0}})
|
|
result-lte (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:amount-lte 150.0}})]
|
|
(is (= 1 (:count result-gte)))
|
|
(is (= 1 (:count result-lte))))))
|
|
|
|
;; 2.9: Filter unbalanced entries
|
|
(deftest test-filtering-unbalanced
|
|
(testing "Filter to show only unbalanced entries"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
;; Create an unbalanced entry: debits (60) != credits (40)
|
|
_ @(dc/transact conn [{:db/id "je-unbal"
|
|
:journal-entry/client test-client-id
|
|
:journal-entry/date #inst "2023-01-01"
|
|
:journal-entry/source "unbal-source"
|
|
:journal-entry/external-id "unbal-ext"
|
|
:journal-entry/vendor test-vendor-id
|
|
:journal-entry/amount 100.0
|
|
:journal-entry/line-items [{:db/id "jel-unbal-1"
|
|
:journal-entry-line/account test-account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/debit 60.0}
|
|
{:db/id "jel-unbal-2"
|
|
:journal-entry-line/account test-account-id
|
|
:journal-entry-line/location "DT"
|
|
:journal-entry-line/credit 40.0}]}])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:only-unbalanced true}})]
|
|
(is (= 1 (:count result))))))
|
|
|
|
;; 2.10: Exact match ID filter
|
|
(deftest test-filtering-by-exact-match-id
|
|
(testing "Exact match ID filter bypasses other filters"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
all-ids (:ids (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {}}))
|
|
exact-id (first all-ids)
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:exact-match-id exact-id
|
|
:source "non-existent"}})]
|
|
(is (= 1 (:count result)))
|
|
(is (= [exact-id] (vec (:ids result)))))))
|
|
|
|
;; 2.12: Combined filters
|
|
(deftest test-filtering-combined-filters
|
|
(testing "Combined filters refresh together"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:source "test-source"
|
|
:start-date #inst "2023-01-01"
|
|
:end-date #inst "2023-01-31"}})]
|
|
(is (= 1 (:count result))))))
|
|
|
|
;; 3.1-3.7, 3.11: Sorting
|
|
(deftest test-sorting-by-date
|
|
(testing "3.5: Sort by date ascending/descending"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result-asc (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:sort [{:name "Date" :sort-key "date" :asc? true}]}})
|
|
result-desc (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:sort [{:name "Date" :sort-key "date" :asc? false}]}})]
|
|
(is (= 2 (:count result-asc)))
|
|
(is (= 2 (:count result-desc))))))
|
|
|
|
(deftest test-sorting-by-amount
|
|
(testing "3.6: Sort by amount ascending/descending"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:sort [{:name "Amount" :sort-key "amount" :asc? true}]}})]
|
|
(is (= 2 (:count result))))))
|
|
|
|
;; 3.8: Default sort
|
|
(deftest test-default-sort
|
|
(testing "3.8: Default sort is date ascending"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {}})]
|
|
(is (= 2 (:count result))))))
|
|
|
|
;; 3.9: Group by vendor
|
|
(deftest test-sort-group-by-vendor
|
|
(testing "3.9: Sort by vendor groups rows with break headers"
|
|
(let [break-fn (:break-table ledger.common/grid-page)
|
|
mock-entity {:journal-entry/vendor {:vendor/name "Test Vendor"}}]
|
|
(is (= "Test Vendor" (break-fn {:query-params {:sort [{:name "Vendor"}]}} mock-entity))))))
|
|
|
|
;; 3.10: Group by source
|
|
(deftest test-sort-group-by-source
|
|
(testing "3.10: Sort by source groups rows with break headers"
|
|
(let [break-fn (:break-table ledger.common/grid-page)
|
|
mock-entity {:journal-entry/source "Some Source"}]
|
|
(is (= "Some Source" (break-fn {:query-params {:sort [{:name "Source"}]}} mock-entity))))))
|
|
|
|
;; 3.11: Sort toggle
|
|
(deftest test-sort-toggle
|
|
(testing "3.11: Sort direction toggles"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result-asc (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:sort [{:name "Amount" :sort-key "amount" :asc? true}]}})
|
|
result-desc (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:sort [{:name "Amount" :sort-key "amount" :asc? false}]}})]
|
|
(is (= 2 (:count result-asc)))
|
|
(is (= 2 (:count result-desc))))))
|
|
|
|
;; 4.1: Default per page
|
|
(deftest test-pagination-default
|
|
(testing "4.1: Default 25 entries per page"
|
|
(is (some? (:query-schema ledger.common/grid-page)))))
|
|
|
|
;; 4.2: Change per page
|
|
(deftest test-pagination-change
|
|
(testing "4.2: Changing per-page count"
|
|
(let [{:strs [test-client-id test-account-id test-vendor-id]} (setup-test-data [])
|
|
_ (setup-journal-entries {:client-id test-client-id
|
|
:account-id test-account-id
|
|
:vendor-id test-vendor-id})
|
|
result (ledger.common/fetch-ids (dc/db conn) {:clients [{:db/id test-client-id}]
|
|
:query-params {:per-page 1 :start 0}})]
|
|
(is (= 2 (:count result)))
|
|
(is (= 1 (count (:ids result)))))))
|
|
|
|
;; 6.2: CSV export columns
|
|
(deftest test-csv-export-columns
|
|
(testing "6.2: CSV export includes correct columns"
|
|
(let [csv-headers (filter #(contains? (:render-for % #{:html :csv}) :csv)
|
|
(:headers ledger.common/grid-page))
|
|
csv-keys (set (map :key csv-headers))]
|
|
(is (contains? csv-keys "id"))
|
|
(is (contains? csv-keys "client"))
|
|
(is (contains? csv-keys "vendor"))
|
|
(is (contains? csv-keys "source"))
|
|
(is (contains? csv-keys "external-id"))
|
|
(is (contains? csv-keys "date"))
|
|
(is (contains? csv-keys "amount"))
|
|
(is (contains? csv-keys "account"))
|
|
(is (contains? csv-keys "debit"))
|
|
(is (contains? csv-keys "credit")))))
|
|
|
|
;; 6.1: CSV export line items
|
|
(deftest test-csv-export-line-items
|
|
(testing "6.1: CSV export has line-item-level rows"
|
|
(let [page->csv-fn (:page->csv-entities ledger.common/grid-page)
|
|
mock-entries [[{:db/id 1
|
|
:journal-entry/line-items [{:db/id 11} {:db/id 12}]}]]]
|
|
(is (= 2 (count (page->csv-fn mock-entries)))))))
|