{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} module CSVImport.Integration (tests) where import Control.Concurrent.STM import qualified Data.List as List (sort) import qualified Data.Map.Strict as Map import qualified Data.Text.IO as T import Hledger.Flow.Common import Hledger.Flow.Import.ImportHelpersTurtle (extraIncludesForFile, groupAndWriteIncludeFiles, includePreamble, toIncludeFiles, writeIncludesUpTo, writeToplevelAllYearsInclude) import Hledger.Flow.PathHelpers import Test.HUnit import TestHelpers (defaultOpts) import TestHelpersTurtle (extraFiles, hiddenFiles, journalFiles, touchAll) import Turtle import Prelude hiding (readFile, writeFile) testExtraIncludesForFile :: Test testExtraIncludesForFile = TestCase ( sh ( do currentDir <- pwd tmpdir <- using (mktempdir currentDir "hlflow") tmpdirAbsPath <- fromTurtleAbsDir tmpdir let importedJournals = map (tmpdir ) journalFiles :: [TurtlePath] let accountDir = "import/john/bogartbank/savings" let opening = tmpdir accountDir "2017-opening.journal" let closing = tmpdir accountDir "2017-closing.journal" let hidden = map (tmpdir ) hiddenFiles :: [TurtlePath] touchAll $ importedJournals ++ hidden let accountInclude = tmpdir accountDir "2017-include.journal" let expectedEmpty = [(accountInclude, [])] ch <- liftIO newTChanIO extraOpening1 <- liftIO $ extraIncludesForFile (defaultOpts tmpdirAbsPath) ch accountInclude ["opening.journal"] [] [] liftIO $ assertEqual "The opening journal should not be included when it is not on disk" expectedEmpty extraOpening1 extraClosing1 <- liftIO $ extraIncludesForFile (defaultOpts tmpdirAbsPath) ch accountInclude ["closing.journal"] [] [] liftIO $ assertEqual "The closing journal should not be included when it is not on disk" expectedEmpty extraClosing1 touchAll [opening, closing] extraOpening2 <- liftIO $ extraIncludesForFile (defaultOpts tmpdirAbsPath) ch accountInclude ["opening.journal"] [] [] liftIO $ assertEqual "The opening journal should be included when it is on disk" [(accountInclude, [opening])] extraOpening2 extraClosing2 <- liftIO $ extraIncludesForFile (defaultOpts tmpdirAbsPath) ch accountInclude ["closing.journal"] [] [] liftIO $ assertEqual "The closing journal should be included when it is on disk" [(accountInclude, [closing])] extraClosing2 ) ) testExtraIncludesPrices :: Test testExtraIncludesPrices = TestCase ( sh ( do currentDir <- pwd tmpdir <- using (mktempdir currentDir "hlflow") tmpdirAbsPath <- fromTurtleAbsDir tmpdir let importedJournals = map (tmpdir ) journalFiles :: [TurtlePath] touchAll $ importedJournals let priceFile = "prices" "2020" "prices.journal" let includeFile = tmpdir "import" "2020-include.journal" let expectedEmpty = [(includeFile, [])] ch <- liftIO newTChanIO price1 <- liftIO $ extraIncludesForFile (defaultOpts tmpdirAbsPath) ch includeFile [] [] ["prices.journal"] liftIO $ assertEqual "The price file should not be included when it is not on disk" expectedEmpty price1 touchAll [tmpdir priceFile] let expectedPricePath = tmpdir "import" ".." priceFile price2 <- liftIO $ extraIncludesForFile (defaultOpts tmpdirAbsPath) ch includeFile [] [] ["prices.journal"] liftIO $ assertEqual "The price file should be included when it is on disk" [(includeFile, [expectedPricePath])] price2 ) ) testIncludesPrePost :: Test testIncludesPrePost = TestCase ( sh ( do currentDir <- pwd tmpdir <- using (mktempdir currentDir "hlflow") tmpdirAbsPath <- fromTurtleAbsDir tmpdir let ownerDir = tmpdir "import" "john" let includeFile = ownerDir "2019-include.journal" let pre = ownerDir "_manual_" "2019" "pre-import.journal" let post = ownerDir "_manual_" "2019" "post-import.journal" touchAll [pre, post] let includeMap = Map.singleton includeFile [ ownerDir "bank1" "2019-include.journal", ownerDir "bank2" "2019-include.journal" ] ch <- liftIO newTChanIO fileMap <- liftIO $ toIncludeFiles (defaultOpts tmpdirAbsPath) ch includeMap let expectedText = includePreamble <> "\n" <> "include _manual_/2019/pre-import.journal\n" <> "include bank1/2019-include.journal\n" <> "include bank2/2019-include.journal\n" <> "include _manual_/2019/post-import.journal\n" let expectedMap = Map.singleton includeFile expectedText liftIO $ assertEqual "All pre/post files on disk should be included" expectedMap fileMap ) ) testIncludesOpeningClosing :: Test testIncludesOpeningClosing = TestCase ( sh ( do currentDir <- pwd tmpdir <- using (mktempdir currentDir "hlflow") tmpdirAbsPath <- fromTurtleAbsDir tmpdir let ownerDir = tmpdir "import/john" let accountDir = ownerDir "bank1" "savings" let includeFile = accountDir "2019-include.journal" let opening = accountDir "2019-opening.journal" let closing = accountDir "2019-closing.journal" touchAll [opening, closing] let includeMap = Map.singleton includeFile [accountDir "3-journal" "2019" "2019-01-30.journal"] ch <- liftIO newTChanIO fileMap <- liftIO $ toIncludeFiles (defaultOpts tmpdirAbsPath) ch includeMap let expectedText = includePreamble <> "\n" <> "include 2019-opening.journal\n" <> "include 3-journal/2019/2019-01-30.journal\n" <> "include 2019-closing.journal\n" let expectedMap = Map.singleton includeFile expectedText liftIO $ assertEqual "All opening/closing files on disk should be included" expectedMap fileMap ) ) testIncludesPrices :: Test testIncludesPrices = TestCase ( sh ( do currentDir <- pwd tmpdir <- using (mktempdir currentDir "hlflow") tmpdirAbsPath <- fromTurtleAbsDir tmpdir let importDir = tmpdir "import" let includeFile = importDir "2020-include.journal" let prices = tmpdir "prices" "2020" "prices.journal" let pre = importDir "_manual_" "2020" "pre-import.journal" let post = importDir "_manual_" "2020" "post-import.journal" touchAll [prices, pre, post] let includeMap = Map.singleton includeFile [importDir "john" "2020-include.journal"] ch <- liftIO newTChanIO fileMap <- liftIO $ toIncludeFiles (defaultOpts tmpdirAbsPath) ch includeMap let expectedText = includePreamble <> "\n" <> "include _manual_/2020/pre-import.journal\n" <> "include john/2020-include.journal\n" <> "include ../prices/2020/prices.journal\n" <> "include _manual_/2020/post-import.journal\n" let expectedMap = Map.singleton includeFile expectedText liftIO $ assertEqual "The price file should be included together with any pre/post files" expectedMap fileMap ) ) testWriteIncludeFiles :: Test testWriteIncludeFiles = TestCase ( sh ( do currentDir <- pwd tmpdir <- using (mktempdir currentDir "hlflow") tmpdirAbsPath <- fromTurtleAbsDir tmpdir let importedJournals = map (tmpdir ) journalFiles :: [TurtlePath] let extras = map (tmpdir ) extraFiles :: [TurtlePath] let hidden = map (tmpdir ) hiddenFiles :: [TurtlePath] touchAll $ importedJournals ++ extras ++ hidden let jane1 = tmpdir "import/jane/bogartbank/checking/2018-include.journal" let jane2 = tmpdir "import/jane/bogartbank/checking/2019-include.journal" let jane3 = tmpdir "import/jane/bogartbank/savings/2017-include.journal" let jane4 = tmpdir "import/jane/bogartbank/savings/2018-include.journal" let jane5 = tmpdir "import/jane/otherbank/creditcard/2017-include.journal" let jane6 = tmpdir "import/jane/otherbank/creditcard/2018-include.journal" let jane7 = tmpdir "import/jane/otherbank/investments/2018-include.journal" let jane8 = tmpdir "import/jane/otherbank/investments/2019-include.journal" let john1 = tmpdir "import/john/bogartbank/checking/2018-include.journal" let john2 = tmpdir "import/john/bogartbank/checking/2019-include.journal" let john3 = tmpdir "import/john/bogartbank/savings/2017-include.journal" let john4 = tmpdir "import/john/bogartbank/savings/2018-include.journal" let john5 = tmpdir "import/john/otherbank/creditcard/2017-include.journal" let john6 = tmpdir "import/john/otherbank/creditcard/2018-include.journal" let john7 = tmpdir "import/john/otherbank/investments/2018-include.journal" let john8 = tmpdir "import/john/otherbank/investments/2019-include.journal" let expectedIncludes = [ jane1, jane2, jane3, jane4, jane5, jane6, jane7, jane8, john1, john2, john3, john4, john5, john6, john7, john8 ] ch <- liftIO newTChanIO reportedAsWritten <- liftIO $ groupAndWriteIncludeFiles (defaultOpts tmpdirAbsPath) ch importedJournals liftIO $ assertEqual "groupAndWriteIncludeFiles should return which files it wrote" expectedIncludes reportedAsWritten let allYears = [ tmpdir "import/jane/bogartbank/checking/all-years.journal", tmpdir "import/jane/bogartbank/savings/all-years.journal", tmpdir "import/jane/otherbank/creditcard/all-years.journal", tmpdir "import/jane/otherbank/investments/all-years.journal", tmpdir "import/john/bogartbank/checking/all-years.journal", tmpdir "import/john/bogartbank/savings/all-years.journal", tmpdir "import/john/otherbank/creditcard/all-years.journal", tmpdir "import/john/otherbank/investments/all-years.journal" ] let expectedOnDisk = List.sort $ reportedAsWritten ++ extras ++ importedJournals ++ allYears allFilesOnDisk <- single $ sort $ onlyFiles $ lstree tmpdir liftIO $ assertEqual "The actual files on disk should match what groupAndWriteIncludeFiles reported" expectedOnDisk allFilesOnDisk let expectedJohn1Contents = includePreamble <> "\n" <> "include 3-journal/2018/2018-10-30.journal\n" <> "include 3-journal/2018/2018-11-30.journal\n" <> "include 3-journal/2018/2018-12-30.journal\n" actualJohn1Contents <- liftIO $ T.readFile john1 liftIO $ assertEqual "John1: The include file contents should be the journal files" expectedJohn1Contents actualJohn1Contents let expectedJohn2Contents = includePreamble <> "\n" <> "include 3-journal/2019/2019-01-30.journal\n" <> "include 3-journal/2019/2019-02-30.journal\n" actualJohn2Contents <- liftIO $ T.readFile john2 liftIO $ assertEqual "John2: The include file contents should be the journal files" expectedJohn2Contents actualJohn2Contents let expectedJohn3Contents = includePreamble <> "\n" <> "include 2017-opening.journal\n" <> "include 3-journal/2017/2017-11-30.journal\n" <> "include 3-journal/2017/2017-12-30.journal\n" actualJohn3Contents <- liftIO $ T.readFile john3 liftIO $ assertEqual "John3: The include file contents should be the journal files" expectedJohn3Contents actualJohn3Contents let expectedJohn4Contents = includePreamble <> "\n" <> "include 3-journal/2018/2018-01-30.journal\n" <> "include 3-journal/2018/2018-02-30.journal\n" actualJohn4Contents <- liftIO $ T.readFile john4 liftIO $ assertEqual "John4: The include file contents should be the journal files" expectedJohn4Contents actualJohn4Contents let expectedJane7Contents = includePreamble <> "\n" <> "include 3-journal/2018/2018-12-30.journal\n" actualJane7Contents <- liftIO $ T.readFile jane7 liftIO $ assertEqual "Jane7: The include file contents should be the journal files" expectedJane7Contents actualJane7Contents ) ) testWriteToplevelAllYearsInclude :: Test testWriteToplevelAllYearsInclude = TestCase ( sh ( do currentDir <- pwd tmpdir <- using (mktempdir currentDir "hlflow") tmpdirAbsPath <- fromTurtleAbsDir tmpdir written1 <- liftIO $ writeToplevelAllYearsInclude (defaultOpts tmpdirAbsPath) let expectedFile = tmpdir "all-years.journal" liftIO $ assertEqual "Should write the top-level all-years include file" [expectedFile] written1 let expectedNoDirectives = includePreamble <> "\n" <> "include import/all-years.journal\n" actualNoDirectives <- liftIO $ T.readFile expectedFile liftIO $ assertEqual "Top-level include should not reference directives when missing" expectedNoDirectives actualNoDirectives let directives = tmpdir "directives.journal" touch directives written2 <- liftIO $ writeToplevelAllYearsInclude (defaultOpts tmpdirAbsPath) liftIO $ assertEqual "Should overwrite the top-level all-years include file" [expectedFile] written2 let expectedWithDirectives = includePreamble <> "\n" <> "include directives.journal\n" <> "include import/all-years.journal\n" actualWithDirectives <- liftIO $ T.readFile expectedFile liftIO $ assertEqual "Top-level include should reference directives when present" expectedWithDirectives actualWithDirectives ) ) testWriteIncludesUpTo :: Test testWriteIncludesUpTo = TestCase ( sh ( do currentDir <- pwd tmpdir <- using (mktempdir currentDir "hlflow") tmpdirAbsPath <- fromTurtleAbsDir tmpdir let journal = tmpdir "import/john/mybank/checking/3-journal/2019/2019-01-01.journal" touchAll [journal] ch <- liftIO newTChanIO let yearDir = Turtle.directory journal let stateDir = Turtle.parent yearDir let accountDir = Turtle.parent stateDir let bankDir = Turtle.parent accountDir let ownerDir = Turtle.parent bankDir let stopAt = Turtle.parent ownerDir written <- liftIO $ writeIncludesUpTo (defaultOpts tmpdirAbsPath) ch stopAt [journal] let expectedFinal = tmpdir "import/2019-include.journal" liftIO $ assertEqual "writeIncludesUpTo should stop at the requested directory" [expectedFinal] written finalContents <- liftIO $ T.readFile expectedFinal let expectedContents = includePreamble <> "\n" <> "include john/2019-include.journal\n" liftIO $ assertEqual "Top-level year include should reference the owner include" expectedContents finalContents ) ) tests :: Test tests = TestList [ testExtraIncludesForFile, testExtraIncludesPrices, testIncludesPrePost, testIncludesOpeningClosing, testIncludesPrices, testWriteIncludeFiles, testWriteToplevelAllYearsInclude, testWriteIncludesUpTo ]