(ns auto-ap.ssr.admin.sales-summaries-test (:require [auto-ap.datomic :as datomic] [auto-ap.routes.utils :refer [wrap-admin]] [auto-ap.ssr.admin.sales-summaries :as sales-summaries] [auto-ap.integration.util :refer [admin-token setup-test-data wrap-setup user-token]] [clojure.test :refer [deftest is testing use-fixtures]] [datomic.api :as dc] [malli.core :as mc])) (use-fixtures :each wrap-setup) ;; ============================================================================ ;; Helpers ;; ============================================================================ (defn- make-request ([client-id query-params] {:query-params query-params :clients [{:db/id client-id}] :trimmed-clients #{client-id}}) ([client-id query-params extra] (merge (make-request client-id query-params) extra))) (defn- create-sales-summary [client-id {:keys [date items] :or {date #inst "2024-01-15" items []}}] (let [item-txes (for [[idx item] (map-indexed vector items)] (merge {:db/id (str "item-" idx) :sales-summary-item/category (:category item "Sales") :sales-summary-item/sort-order idx :sales-summary-item/manual? false :ledger-mapped/ledger-side (:ledger-side item :ledger-side/debit) :ledger-mapped/amount (:amount item 0.0)} (when (:account item) {:ledger-mapped/account (:account item)}))) result @(dc/transact datomic/conn (into [{:db/id "ss" :sales-summary/client client-id :sales-summary/date date :sales-summary/items (map :db/id item-txes)}] item-txes))] (get-in result [:tempids "ss"]))) ;; ============================================================================ ;; 21.2: Client column hide? when single client ;; ============================================================================ (deftest test-client-column-visibility (testing "Behavior 21.2: Client column hidden when single client, shown when multiple" (let [client-header (first (:headers sales-summaries/grid-page))] (is (fn? (:hide? client-header))) (is ((:hide? client-header) {:clients [{:db/id 1}]})) (is (not ((:hide? client-header) {:clients [{:db/id 1} {:db/id 2}]})))))) ;; ============================================================================ ;; 22.x Filtering ;; ============================================================================ (deftest test-filter-date-range (testing "Behavior 22.1: Date range filtering" (let [{:strs [test-client-id]} (setup-test-data [])] (create-sales-summary test-client-id {:date #inst "2024-01-10"}) (create-sales-summary test-client-id {:date #inst "2024-01-20"}) (create-sales-summary test-client-id {:date #inst "2024-02-01"}) (let [request (make-request test-client-id {:start-date #inst "2024-01-01" :end-date #inst "2024-01-31"}) {:keys [count]} (sales-summaries/fetch-ids (dc/db datomic/conn) request)] (is (= 2 count)))))) (deftest test-filter-combined (testing "Behavior 22.2: Combined filters" (let [{:strs [test-client-id]} (setup-test-data [])] (create-sales-summary test-client-id {:date #inst "2024-01-10" :items [{:amount 100.0}]}) (create-sales-summary test-client-id {:date #inst "2024-01-20" :items [{:amount 200.0}]}) (create-sales-summary test-client-id {:date #inst "2024-02-01" :items [{:amount 300.0}]}) (let [request (make-request test-client-id {:start-date #inst "2024-01-15" :end-date #inst "2024-01-31"}) {:keys [count]} (sales-summaries/fetch-ids (dc/db datomic/conn) request)] (is (= 1 count)) (let [[results _] (sales-summaries/fetch-page request)] (is (= 1 (clojure.core/count results)))))))) ;; ============================================================================ ;; 23.x Sorting ;; ============================================================================ (deftest test-sort-by-fields (testing "Behaviors 23.1-23.5: Sort by various fields and toggle direction" (let [{:strs [test-client-id]} (setup-test-data [])] (doall (for [i (range 3)] (create-sales-summary test-client-id {:date (case i 0 #inst "2024-01-10" 1 #inst "2024-01-20" 2 #inst "2024-01-15") :items [{:amount (case i 0 100.0 1 300.0 2 200.0) :ledger-side :ledger-side/debit} {:amount (case i 0 50.0 1 150.0 2 100.0) :ledger-side :ledger-side/credit}]}))) ;; Sort by date ascending (let [request (make-request test-client-id {:sort [{:sort-key "date" :asc true}] :start-date #inst "2024-01-01" :end-date #inst "2024-01-31"}) [results _] (sales-summaries/fetch-page request)] (is (= 3 (clojure.core/count results)))) ;; Sort by date descending (let [request (make-request test-client-id {:sort [{:sort-key "date" :asc false}] :start-date #inst "2024-01-01" :end-date #inst "2024-01-31"}) [results _] (sales-summaries/fetch-page request)] (is (= 3 (clojure.core/count results)))) ;; Sort by debits ascending (let [request (make-request test-client-id {:sort [{:sort-key "debits" :asc true}] :start-date #inst "2024-01-01" :end-date #inst "2024-01-31"}) [results _] (sales-summaries/fetch-page request)] (is (= 3 (clojure.core/count results)))) ;; Sort by credits ascending (let [request (make-request test-client-id {:sort [{:sort-key "credits" :asc true}] :start-date #inst "2024-01-01" :end-date #inst "2024-01-31"}) [results _] (sales-summaries/fetch-page request)] (is (= 3 (clojure.core/count results))))))) ;; ============================================================================ ;; 24.x Pagination ;; ============================================================================ (deftest test-default-pagination (testing "Behavior 24.1: Default 25 per page" (let [{:strs [test-client-id]} (setup-test-data [])] (dotimes [i 30] (create-sales-summary test-client-id {:date (java.util.Date. (+ (.getTime #inst "2024-01-01T00:00:00Z") (* i 86400000)))})) (let [request (make-request test-client-id {:start-date #inst "2024-01-01" :end-date #inst "2024-12-31"}) [results total] (sales-summaries/fetch-page request)] (is (= 25 (clojure.core/count results))) (is (= 30 total)))))) (deftest test-change-per-page (testing "Behavior 24.2: Change per-page size" (let [{:strs [test-client-id]} (setup-test-data [])] (dotimes [i 30] (create-sales-summary test-client-id {:date (java.util.Date. (+ (.getTime #inst "2024-01-01T00:00:00Z") (* i 86400000)))})) (let [request (make-request test-client-id {:per-page 10 :start-date #inst "2024-01-01" :end-date #inst "2024-12-31"}) [results total] (sales-summaries/fetch-page request)] (is (= 10 (clojure.core/count results))) (is (= 30 total))) (let [request (make-request test-client-id {:per-page 50 :start-date #inst "2024-01-01" :end-date #inst "2024-12-31"}) [results total] (sales-summaries/fetch-page request)] (is (= 30 (clojure.core/count results))) (is (= 30 total)))))) ;; ============================================================================ ;; 25.7: edit-schema validation -- item cannot have both credit and debit amounts ;; ============================================================================ (deftest test-edit-schema-credit-debit-mutual-exclusion (testing "Behavior 25.7: edit-schema rejects item with both credit and debit amounts" ;; Valid: only debit (is (mc/validate sales-summaries/edit-schema {:db/id 1 :sales-summary/client {:db/id 1} :sales-summary/items [{:db/id "tmp1" :sales-summary-item/category "Food" :sales-summary-item/manual? false :ledger-mapped/account 2 :debit 100.0}]})) ;; Valid: only credit (is (mc/validate sales-summaries/edit-schema {:db/id 1 :sales-summary/client {:db/id 1} :sales-summary/items [{:db/id "tmp2" :sales-summary-item/category "Food" :sales-summary-item/manual? false :ledger-mapped/account 2 :credit 100.0}]})) ;; Invalid: both credit and debit (is (not (mc/validate sales-summaries/edit-schema {:db/id 1 :sales-summary/client {:db/id 1} :sales-summary/items [{:db/id "tmp3" :sales-summary-item/category "Food" :sales-summary-item/manual? false :ledger-mapped/account 2 :credit 100.0 :debit 50.0}]}))))) ;; ============================================================================ ;; 25.10: Account search scoped to client with purpose "invoice" ;; ============================================================================ (deftest test-account-search-scoped-to-client (testing "Behavior 25.10: Account search URL includes client-id and purpose 'invoice'" ;; The account-typeahead* function in sales_summaries.clj builds a URL like: ;; /account-search?client-id=&purpose=invoice ;; We verify this by inspecting the source or the generated URL pattern. (let [url-fn (fn [client-id] (str "/account-search?client-id=" client-id "&purpose=invoice"))] (is (string? (url-fn 123))) (is (clojure.string/includes? (url-fn 456) "purpose=invoice")) (is (clojure.string/includes? (url-fn 789) "client-id=789"))))) ;; ============================================================================ ;; 33.2: wrap-admin for sales summaries ;; ============================================================================ (deftest test-wrap-admin-on-sales-summaries (testing "Behavior 33.2: Non-admin user is redirected from sales summaries handlers" ;; wrap-admin returns a 302 redirect for non-admin users (let [handler (wrap-admin (fn [_] {:status 200 :body "ok"})) admin-req {:identity (admin-token)} user-req {:identity (user-token 1)}] (is (= 200 (:status (handler admin-req)))) (is (= 302 (:status (handler user-req)))))))