(ns auto-ap.integration.graphql (:require [auto-ap.graphql :as sut] [auto-ap.datomic.migrate :as m] [venia.core :as v] [clojure.test :as t :refer [deftest is testing use-fixtures]] [clj-time.core :as time] [datomic.api :as d] [auto-ap.datomic :refer [uri conn]] [buddy.sign.jwt :as jwt] [config.core :refer [env]])) (defn wrap-setup [f] (with-redefs [auto-ap.datomic/uri "datomic:mem://datomic-transactor:4334/invoice"] (d/create-database uri) (with-redefs [auto-ap.datomic/conn (d/connect uri)] (m/migrate conn) (f) (d/release conn) (d/delete-database uri)))) (defn admin-token [] {:user "TEST ADMIN" :exp (time/plus (time/now) (time/days 1)) :user/role "admin" :user/name "TEST ADMIN"}) (defn user-token [] {:user "TEST USER" :exp (time/plus (time/now) (time/days 1)) :user/role "user" :user/name "TEST USER" :user/clients [{:db/id 1}]}) (defn new-client [args] (merge {:client/name "Test client" :client/code (.toString (java.util.UUID/randomUUID)) :client/locations ["AB"]} args)) (defn new-transaction [args] (merge {:transaction/amount 100.0 :transaction/id (.toString (java.util.UUID/randomUUID))} args)) (defn new-invoice [args] (merge {:invoice/total 100.0 :invoice/invoice-number (.toString (java.util.UUID/randomUUID))} args)) (use-fixtures :each wrap-setup) (deftest transaction-page (testing "transaction page" @(d/transact (d/connect uri) [(new-client {:db/id "client"}) (new-transaction {:transaction/client "client"})]) (testing "It should find all transactions" (let [result (:transaction-page (:data (sut/query (admin-token) "{ transaction_page(filters: {client_id: null}) { count, start, data { id } }}")))] (is (= 1 (:count result))) (is (= 0 (:start result))) (is (= 1 (count (:data result)))))) (testing "Users should not see transactions they don't own" (let [result (:transaction-page (:data (sut/query (user-token) "{ transaction_page(filters: {client_id: null}) { count, start, data { id } }}")))] (is (= 0 (:count result))) (is (= 0 (:start result))) (is (= 0 (count (:data result)))))))) (deftest invoice-page (testing "invoice page" @(d/transact (d/connect uri) [(new-client {:db/id "client"}) (new-invoice {:invoice/client "client" :invoice/status :invoice-status/paid})]) (testing "It should find all invoices" (let [result (first (:invoice-page (:data (sut/query (admin-token) "{ invoice_page(client_id: null, status:paid) { count, start, invoices { id } }}"))))] (is (= 1 (:count result))) (is (= 0 (:start result))) (is (= 1 (count (:invoices result)))))) (testing "Users should not see transactions they don't own" (let [result (first (:invoice-page (:data (sut/query (user-token) "{ invoice_page(client_id: null) { count, start, invoices { id } }}"))))] (is (= 0 (:count result))) (is (= 0 (:start result))) (is (= 0 (count (:data result)))))))) (deftest ledger-page (testing "ledger" (testing "it should find ledger entries" (let [result (:ledger-page (:data (sut/query (admin-token) "{ ledger_page(client_id: null) { count, start, journal_entries { id } }}")))] (is (int? (:count result))) (is (int? (:start result))) (is (seqable? (:journal-entries result))))))) (deftest vendors (testing "vendors" (testing "it should find vendors" (let [result (:ledger-page (:data (sut/query (admin-token) "{ ledger_page(client_id: null) { count, start, journal_entries { id } }}")))] (is (int? (:count result))) (is (int? (:start result))) (is (seqable? (:journal-entries result))))))) (deftest transaction-rule-page (testing "it should find rules" (let [result (-> (sut/query (admin-token) "{ transaction_rule_page(client_id: null) { count, start, transaction_rules { id } }}") :data :transaction-rule-page)] (is (int? (:count result))) (is (int? (:start result))) (is (seqable? (:transaction-rules result)))))) (deftest upsert-transaction-rule (let [{:strs [vendor-id account-id yodlee-merchant-id]} (-> (d/connect uri) (d/transact [{:vendor/name "Bryce's Meat Co" :db/id "vendor-id"} {:account/name "hello" :db/id "account-id"} {:yodlee-merchant/name "yodlee" :db/id "yodlee-merchant-id"}]) deref :tempids)] (testing "it should reject rules that don't add up to 100%" (let [q (v/graphql-query {:venia/operation {:operation/type :mutation :operation/name "UpsertTransactionRule"} :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule {:transaction-rule {:accounts [{:account-id account-id :percentage "0.25" :location "B"}]}} [:id ]])}]})] (is (thrown? clojure.lang.ExceptionInfo (sut/query (admin-token) q))))) (testing "It should reject rules that are missing both description and merchant" (let [q (v/graphql-query {:venia/operation {:operation/type :mutation :operation/name "UpsertTransactionRule"} :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule {:transaction-rule {:accounts [{:account-id account-id :percentage "1.0" :location "B"}]}} [:id ]])}]})] (is (thrown? clojure.lang.ExceptionInfo (sut/query (admin-token) q))))) (testing "it should add rules" (let [q (v/graphql-query {:venia/operation {:operation/type :mutation :operation/name "UpsertTransactionRule"} :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule {:transaction-rule {:description "123" :yodlee-merchant-id yodlee-merchant-id :vendor-id vendor-id :transaction-approval-status :approved :accounts [{:account-id account-id :percentage "0.5" :location "B"} {:account-id account-id :percentage "0.5" :location "A"}]}} [:id :description :transaction-approval-status [:vendor [:name]] [:yodlee-merchant [:name]] [:accounts [:id :percentage [:account [:name]]]]]])}]}) result (-> (sut/query (admin-token) q) :data :upsert-transaction-rule)] (is (= "123" (:description result))) (is (= "Bryce's Meat Co" (-> result :vendor :name))) (is (= "yodlee" (-> result :yodlee-merchant :name))) (is (= :approved (:transaction-approval-status result))) (is (= "hello" (-> result :accounts (get 0) :account :name ))) (is (:id result)) (testing "it should unset removed fields" (let [q (v/graphql-query {:venia/operation {:operation/type :mutation :operation/name "UpsertTransactionRule"} :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule {:transaction-rule {:id (:id result) :description "123" :vendor-id nil :accounts [{:id (-> result :accounts (get 0) :id) :account-id account-id :percentage "1.0" :location "B"}]}} [[:vendor [:name]]]])}]}) result (-> (sut/query (admin-token) q) :data :upsert-transaction-rule)] (is (nil? (:vendor result))))) (testing "it should delete removed rules" (let [q (v/graphql-query {:venia/operation {:operation/type :mutation :operation/name "UpsertTransactionRule"} :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule {:transaction-rule {:id (:id result) :description "123" :vendor-id vendor-id :accounts [{:id (-> result :accounts (get 0) :id) :account-id account-id :percentage "1.0" :location "B"}]}} [[:accounts [:id :percentage [:account [:name]]]]]])}]}) result (-> (sut/query (admin-token) q) :data :upsert-transaction-rule)] (is (= 1 (count (:accounts result)))))))))) (deftest test-get-yodlee-merchants (testing "it should find yodlee merchants" @(d/transact (d/connect uri) [{:yodlee-merchant/name "Merchant 1" :yodlee-merchant/yodlee-id "123"} {:yodlee-merchant/name "Merchant 2" :yodlee-merchant/yodlee-id "456"}]) (is (= [{:name "Merchant 1" :yodlee-id "123"} {:name "Merchant 2" :yodlee-id "456"}] (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :query :operation/name "GetYodleeMerchants"} :venia/queries [{:query/data (sut/->graphql [:yodlee-merchants [:yodlee-id :name]])}]})) :data :yodlee-merchants))))) (deftest test-transaction-rule (testing "it should match rules" (let [matching-transaction @(d/transact (d/connect uri) [{:transaction/description-original "matching-desc" :transaction/date #inst "2019-01-05T00:00:00.000-08:00" :transaction/client {:client/name "1" :db/id "client-1"} :transaction/bank-account {:db/id "bank-account-1" :bank-account/name "1"} :transaction/amount 1.00 :transaction/id "2019-01-05 matching-desc 1"} {:transaction/description-original "nonmatching-desc" :transaction/client {:client/name "2" :db/id "client-2"} :transaction/bank-account {:db/id "bank-account-2" :bank-account/name "2"} :transaction/date #inst "2019-01-15T23:23:00.000-08:00" :transaction/amount 2.00 :transaction/id "2019-01-15 nonmatching-desc 2"}]) {:strs [client-1 client-2 bank-account-1 bank-account-2]} (get-in matching-transaction [:tempids]) rule-test (fn [rule] (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :query :operation/name "TestTransactionRule"} :venia/queries [{:query/data (sut/->graphql [:test-transaction-rule {:transaction-rule rule} [:id]])}]})) :data :test-transaction-rule))] (testing "based on date " (is (= [{:id "2019-01-15 nonmatching-desc 2"}] (rule-test {:dom-gte 14 :dom-lte 16}))) (is (= [{:id "2019-01-15 nonmatching-desc 2"}] (rule-test {:dom-gte 14}))) (is (= (set [{:id "2019-01-05 matching-desc 1"} {:id "2019-01-15 nonmatching-desc 2"}]) (set (rule-test {:dom-lte 15})))) (is (= [{:id "2019-01-15 nonmatching-desc 2"}] (rule-test {:dom-gte 15}))) (is (= [{:id "2019-01-15 nonmatching-desc 2"}] (rule-test {:dom-gte 15 :dom-lte 15})))) (testing "based on description" (is (= [{:id "2019-01-05 matching-desc 1"}] (rule-test {:description "^match"})))) (testing "based on amount" (is (= [{:id "2019-01-05 matching-desc 1"}] (rule-test {:amount-gte 1.0 :amount-lte 1.0}))) (is (= (set [{:id "2019-01-05 matching-desc 1"} {:id "2019-01-15 nonmatching-desc 2"}]) (set (rule-test {:amount-gte 1.0 }))) ) (is (= (set [{:id "2019-01-05 matching-desc 1"} {:id "2019-01-15 nonmatching-desc 2"}]) (set (rule-test {:amount-lte 2.0 }))) )) (testing "based on client" (is (= [{:id "2019-01-05 matching-desc 1"}] (rule-test {:client-id client-1}))) (is (= [{:id "2019-01-15 nonmatching-desc 2"}] (rule-test {:client-id client-2})))) (testing "based on bank account" (is (= [{:id "2019-01-05 matching-desc 1"}] (rule-test {:bank-account-id bank-account-1}))) (is (= [{:id "2019-01-15 nonmatching-desc 2"}] (rule-test {:bank-account-id bank-account-2}))))))) (deftest test-match-transaction-rule (testing "it should apply a rules" (let [{:strs [transaction-id transaction-rule-id uneven-transaction-rule-id]} (-> @(d/transact (d/connect uri) [{:transaction/description-original "matching-desc" :transaction/date #inst "2019-01-05T00:00:00.000-08:00" :transaction/client {:client/name "1" :db/id "client-1"} :transaction/bank-account {:db/id "bank-account-1" :bank-account/name "1"} :transaction/amount 1.00 :db/id "transaction-id"} {:db/id "transaction-rule-id" :transaction-rule/note "transaction rule note" :transaction-rule/description "matching-desc" :transaction-rule/accounts [{:transaction-rule-account/location "A" :transaction-rule-account/account {:account/numeric-code 123 :db/id "123"} :transaction-rule-account/percentage 1.0}]} {:db/id "uneven-transaction-rule-id" :transaction-rule/note "transaction rule note" :transaction-rule/description "matching-desc" :transaction-rule/accounts [{:transaction-rule-account/location "A" :transaction-rule-account/account {:account/numeric-code 123 :db/id "123"} :transaction-rule-account/percentage 0.3333333} {:transaction-rule-account/location "B" :transaction-rule-account/account {:account/numeric-code 123 :db/id "123"} :transaction-rule-account/percentage 0.33333333} {:transaction-rule-account/location "c" :transaction-rule-account/account {:account/numeric-code 123 :db/id "123"} :transaction-rule-account/percentage 0.333333}]}]) :tempids) rule-test (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :mutation :operation/name "MatchTransactionRules"} :venia/queries [{:query/data (sut/->graphql [:match-transaction-rules {:transaction-rule-id transaction-rule-id :transaction-ids [transaction-id]} [[:matched-rule [:id :note]] [:accounts [:id]] ]])}]})) :data :match-transaction-rules)] (is (= "transaction rule note" (-> rule-test first :matched-rule :note))) (is (= 1 (-> rule-test first :accounts count))) (testing "Should replace accounts when matching a second time" (let [rule-test (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :mutation :operation/name "MatchTransactionRules"} :venia/queries [{:query/data (sut/->graphql [:match-transaction-rules {:transaction-rule-id transaction-rule-id :transaction-ids [transaction-id]} [[:matched-rule [:id :note]] [:accounts [:id]] ]])}]})) :data :match-transaction-rules)] (is (= 1 (-> rule-test first :accounts count))))) (testing "Should round when the transaction can't be divided eventy" (let [rule-test (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :mutation :operation/name "MatchTransactionRules"} :venia/queries [{:query/data (sut/->graphql [:match-transaction-rules {:transaction-rule-id uneven-transaction-rule-id :transaction-ids [transaction-id]} [[:matched-rule [:id :note]] [:accounts [:id :amount]] ]])}]})) :data :match-transaction-rules)] (is (= 3 (-> rule-test first :accounts count))) (is (= "0.33" (-> rule-test first :accounts (nth 0) :amount))) (is (= "0.33" (-> rule-test first :accounts (nth 1) :amount))) (is (= "0.34" (-> rule-test first :accounts (nth 2) :amount))))))))