<commit_before>(ns clj-money.import-test
  (:refer-clojure :exclude [update])
  (:require [clojure.test :refer :all]
            [clojure.data :refer [diff]]
            [clojure.java.io :as io]
            [clojure.pprint :refer [pprint]]
            [clojure.core.async :refer [go <! <!! chan]]
            [clj-time.core :as t]
            [environ.core :refer [env]]
            [clj-factory.core :refer [factory]]
            [clj-money.io :refer [read-bytes]]
            [clj-money.serialization :as serialization]
            [clj-money.factories.user-factory]
            [clj-money.test-helpers :refer [reset-db]]
            [clj-money.models.entities :as entities]
            [clj-money.models.commodities :as commodities]
            [clj-money.models.accounts :as accounts]
            [clj-money.models.transactions :as transactions]
            [clj-money.models.budgets :as budgets]
            [clj-money.models.imports :as imports]
            [clj-money.models.lots :as lots]
            [clj-money.models.prices :as prices]
            [clj-money.reports :as reports]
            [clj-money.import :refer [import-data]]
            [clj-money.import.gnucash :as gnucash]))

(def storage-spec (env :db))

(use-fixtures :each (partial reset-db storage-spec))

(def gnucash-sample
  (io/input-stream "resources/fixtures/sample.gnucash"))

(def import-context
  {:users [(factory :user, {:email "<EMAIL>"})]
   :images [{:body (read-bytes gnucash-sample)
             :content-type "application/gnucash"
             :original-filename "sample.gnucash"}]
   :imports [{:entity-name "Personal"
              :image-id "sample.gnucash"}]})

(def expected-updates
  (concat [{:commodity {:total 1}}
           {:commodity {:total 1}
            :account {:total 9}}
           {:commodity {:total 1}
            :account {:total 9}
            :transaction {:total 6}}
           {:commodity {:total 1
                        :imported 1}
            :account {:total 9}
            :transaction {:total 6}}]
          (map (fn [i] {:commodity {:total 1
                                    :imported 1}
                        :account {:total 9
                                  :imported (+ 1 i)}
                        :transaction {:total 6}})
               (range 9))
          (map (fn [i] {:commodity {:total 1
                                    :imported 1}
                        :account {:total 9
                                  :imported 9}
                        :transaction {:total 6
                                      :imported (+ 1 i)}})
               (range 6))))

(def ^:private expected-inc-stmt
  [{:caption "Income"
    :value 2000M
    :style :header}
   {:caption "Salary"
    :value 2000M
    :style :data
    :depth 0}
   {:caption "Expense"
    :value 290M
    :style :header}
   {:caption "Groceries"
    :value 290M
    :style :data
    :depth 0}
   {:caption "Net"
    :value 1710M
    :style :summary}])

(def ^:private expected-bal-sheet
  [{:caption "Asset"
                             :value 1810.00M
                             :style :header}
                            {:caption "Checking"
                             :value 1810.00M
                             :style :data
                             :depth 0}
                            {:caption "Liability"
                             :value 100.00M
                             :style :header}
                            {:caption "Credit Card"
                             :value 100.00M
                             :style :data
                             :depth 0}
                            {:caption "Equity"
                             :value 1710.00M
                             :style :header}
                            {:caption "Retained Earnings"
                             :value 1710.00M
                             :style :data
                             :depth 0}
                            {:caption "Unrealized Gains"
                             :value 0M
                             :style :data
                             :depth 0}
                            {:caption "Liabilities + Equity"
                             :value 1810.00M
                             :style :summary}])

(def ^:private expected-accounts
  [{:name "Checking"
    :type :asset
    :commodity-id "USD"
    :balance 1810M}
   {:name "<NAME>"
    :type :liability
    :commodity-id "USD"
    :balance 100M}
   {:name "Groceries"
    :type :expense
    :commodity-id "USD"
    :balance 290M}
   {:name "Salary"
    :type :income
    :commodity-id "USD"
    :balance 2000M}])

(deftest import-a-simple-file
  (let [context (serialization/realize storage-spec import-context)
        user (-> context :users first)
        image (-> context :images first)
        imp (-> context :imports first)
        updates (atom [])
        entity (import-data storage-spec imp (fn [p] (swap! updates #(conj % p))))
        actual-accounts (->> {:entity-id (:id entity)}
                             (accounts/search storage-spec)
                             (map #(dissoc % :created-at
                                             :updated-at
                                             :id
                                             :entity-id
                                             :commodity)))
        expected-accounts (->> expected-accounts
                               (map #(assoc % :tags #{}))
                               (map #(update-in %
                                                [:commodity-id]
                                                (fn [sym]
                                                  (->> {:entity-id (:id entity)
                                                        :symbol sym}
                                                       (commodities/search storage-spec)
                                                       first
                                                       :id)))))
        actual-inc-stmt (reports/income-statement storage-spec
                                                  (:id entity)
                                                  (t/local-date 1999 1 1)
                                                  (t/local-date 9999 12 31))
        actual-bal-sheet (reports/balance-sheet storage-spec
                                                (:id entity)
                                                (t/local-date 9999 12 31))]
    (is entity "It returns a value")
    (is (= "Personal" (:name entity)) "It returns the new entity")
    (is (= expected-accounts actual-accounts)
        "The correct accounts are created")
    (is (= expected-inc-stmt actual-inc-stmt)
        "The income statement is correct after import")
    (is (= expected-bal-sheet actual-bal-sheet)
        "The balance sheet is correct after import")
    (is (= expected-updates @updates)
        "The import record is updated at each insert")))

(def gnucash-budget-sample
  (io/input-stream "resources/fixtures/budget_sample.gnucash"))

(def import-budget-context
  {:users [(factory :user, {:email "<EMAIL>"})]
   :images [{:body (read-bytes gnucash-budget-sample)<commit_msg>change open-ended temp date<commit_after>(ns clj-money.import-test
  (:refer-clojure :exclude [update])
  (:require [clojure.test :refer :all]
            [clojure.data :refer [diff]]
            [clojure.java.io :as io]
            [clojure.pprint :refer [pprint]]
            [clojure.core.async :refer [go <! <!! chan]]
            [clj-time.core :as t]
            [environ.core :refer [env]]
            [clj-factory.core :refer [factory]]
            [clj-money.io :refer [read-bytes]]
            [clj-money.serialization :as serialization]
            [clj-money.factories.user-factory]
            [clj-money.test-helpers :refer [reset-db]]
            [clj-money.models.entities :as entities]
            [clj-money.models.commodities :as commodities]
            [clj-money.models.accounts :as accounts]
            [clj-money.models.transactions :as transactions]
            [clj-money.models.budgets :as budgets]
            [clj-money.models.imports :as imports]
            [clj-money.models.lots :as lots]
            [clj-money.models.prices :as prices]
            [clj-money.reports :as reports]
            [clj-money.import :refer [import-data]]
            [clj-money.import.gnucash :as gnucash]))

(def storage-spec (env :db))

(use-fixtures :each (partial reset-db storage-spec))

(def gnucash-sample
  (io/input-stream "resources/fixtures/sample.gnucash"))

(def import-context
  {:users [(factory :user, {:email "<EMAIL>"})]
   :images [{:body (read-bytes gnucash-sample)
             :content-type "application/gnucash"
             :original-filename "sample.gnucash"}]
   :imports [{:entity-name "Personal"
              :image-id "sample.gnucash"}]})

(def expected-updates
  (concat [{:commodity {:total 1}}
           {:commodity {:total 1}
            :account {:total 9}}
           {:commodity {:total 1}
            :account {:total 9}
            :transaction {:total 6}}
           {:commodity {:total 1
                        :imported 1}
            :account {:total 9}
            :transaction {:total 6}}]
          (map (fn [i] {:commodity {:total 1
                                    :imported 1}
                        :account {:total 9
                                  :imported (+ 1 i)}
                        :transaction {:total 6}})
               (range 9))
          (map (fn [i] {:commodity {:total 1
                                    :imported 1}
                        :account {:total 9
                                  :imported 9}
                        :transaction {:total 6
                                      :imported (+ 1 i)}})
               (range 6))))

(def ^:private expected-inc-stmt
  [{:caption "Income"
    :value 2000M
    :style :header}
   {:caption "Salary"
    :value 2000M
    :style :data
    :depth 0}
   {:caption "Expense"
    :value 290M
    :style :header}
   {:caption "Groceries"
    :value 290M
    :style :data
    :depth 0}
   {:caption "Net"
    :value 1710M
    :style :summary}])

(def ^:private expected-bal-sheet
  [{:caption "Asset"
                             :value 1810.00M
                             :style :header}
                            {:caption "Checking"
                             :value 1810.00M
                             :style :data
                             :depth 0}
                            {:caption "Liability"
                             :value 100.00M
                             :style :header}
                            {:caption "Credit Card"
                             :value 100.00M
                             :style :data
                             :depth 0}
                            {:caption "Equity"
                             :value 1710.00M
                             :style :header}
                            {:caption "Retained Earnings"
                             :value 1710.00M
                             :style :data
                             :depth 0}
                            {:caption "Unrealized Gains"
                             :value 0M
                             :style :data
                             :depth 0}
                            {:caption "Liabilities + Equity"
                             :value 1810.00M
                             :style :summary}])

(def ^:private expected-accounts
  [{:name "Checking"
    :type :asset
    :commodity-id "USD"
    :balance 1810M}
   {:name "<NAME>"
    :type :liability
    :commodity-id "USD"
    :balance 100M}
   {:name "Groceries"
    :type :expense
    :commodity-id "USD"
    :balance 290M}
   {:name "Salary"
    :type :income
    :commodity-id "USD"
    :balance 2000M}])

(deftest import-a-simple-file
  (let [context (serialization/realize storage-spec import-context)
        user (-> context :users first)
        image (-> context :images first)
        imp (-> context :imports first)
        updates (atom [])
        entity (import-data storage-spec imp (fn [p] (swap! updates #(conj % p))))
        actual-accounts (->> {:entity-id (:id entity)}
                             (accounts/search storage-spec)
                             (map #(dissoc % :created-at
                                             :updated-at
                                             :id
                                             :entity-id
                                             :commodity)))
        expected-accounts (->> expected-accounts
                               (map #(assoc % :tags #{}))
                               (map #(update-in %
                                                [:commodity-id]
                                                (fn [sym]
                                                  (->> {:entity-id (:id entity)
                                                        :symbol sym}
                                                       (commodities/search storage-spec)
                                                       first
                                                       :id)))))
        actual-inc-stmt (reports/income-statement storage-spec
                                                  (:id entity)
                                                  (t/local-date 1999 1 1)
                                                  (t/today)) ; TODO: allow open-ended
        actual-bal-sheet (reports/balance-sheet storage-spec
                                                (:id entity)
                                                (t/today))] ; TODO: allow open-ended
    (is entity "It returns a value")
    (is (= "Personal" (:name entity)) "It returns the new entity")
    (is (= expected-accounts actual-accounts)
        "The correct accounts are created")
    (is (= expected-inc-stmt actual-inc-stmt)
        "The income statement is correct after import")
    (is (= expected-bal-sheet actual-bal-sheet)
        "The balance sheet is correct after import")
    (is (= expected-updates @updates)
        "The import record is updated at each insert")))

(def gnucash-budget-sample
  (io/input-stream "resources/fixtures/budget_sample.gnucash"))

(def import-budget-context
  {:users [(factory :user, {:email "<EMAIL>"})]
   :images [{:body (read-bytes gnucash-budget-sample)
{'fraction_non_alphanumeric': 0.11139680333564976, 'fraction_numerical': 0.016122307157748435, 'mean_word_length': 1.1297913275122096, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 74, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3373194', 'n_tokens_mistral': 4078, 'n_tokens_neox': 3726, 'n_words': 938}