packages feed

hledger-flow-0.16.3: test/CSVImport/ImportHelperTurtleTests.hs

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

module CSVImport.ImportHelperTurtleTests where

import Control.Concurrent.STM
import Data.Either
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import Hledger.Flow.Common
import Hledger.Flow.Import.ImportHelpersTurtle (allYearIncludeFiles, extractImportDirs, groupIncludeFiles, toIncludeFiles, toIncludeLine, yearsIncludeMap)
import Hledger.Flow.Import.Types (ImportDirs (..), TurtleFileBundle)
import Hledger.Flow.PathHelpers (TurtlePath)
import Path
import Test.HUnit
import TestHelpers (defaultOpts)
import TestHelpersTurtle
import Turtle

groupedJaneBogart :: TurtleFileBundle
groupedJaneBogart =
  [ ( "./import/jane/bogartbank/checking/2018-include.journal",
      ["import/jane/bogartbank/checking/3-journal/2018/2018-12-30.journal"]
    ),
    ( "./import/jane/bogartbank/checking/2019-include.journal",
      ["import/jane/bogartbank/checking/3-journal/2019/2019-01-30.journal"]
    ),
    ( "./import/jane/bogartbank/savings/2017-include.journal",
      ["import/jane/bogartbank/savings/3-journal/2017/2017-12-30.journal"]
    ),
    ( "./import/jane/bogartbank/savings/2018-include.journal",
      ["import/jane/bogartbank/savings/3-journal/2018/2018-01-30.journal"]
    )
  ]

groupedJaneOther :: TurtleFileBundle
groupedJaneOther =
  [ ( "./import/jane/otherbank/creditcard/2017-include.journal",
      ["import/jane/otherbank/creditcard/3-journal/2017/2017-12-30.journal"]
    ),
    ( "./import/jane/otherbank/creditcard/2018-include.journal",
      ["import/jane/otherbank/creditcard/3-journal/2018/2018-01-30.journal"]
    ),
    ( "./import/jane/otherbank/investments/2018-include.journal",
      ["import/jane/otherbank/investments/3-journal/2018/2018-12-30.journal"]
    ),
    ( "./import/jane/otherbank/investments/2019-include.journal",
      ["import/jane/otherbank/investments/3-journal/2019/2019-01-30.journal"]
    )
  ]

groupedJohnBogart :: TurtleFileBundle
groupedJohnBogart =
  [ ( "./import/john/bogartbank/checking/2018-include.journal",
      [ "import/john/bogartbank/checking/3-journal/2018/2018-11-30.journal",
        "import/john/bogartbank/checking/3-journal/2018/2018-10-30.journal",
        "import/john/bogartbank/checking/3-journal/2018/2018-12-30.journal"
      ]
    ),
    ( "./import/john/bogartbank/checking/2019-include.journal",
      [ "import/john/bogartbank/checking/3-journal/2019/2019-01-30.journal",
        "import/john/bogartbank/checking/3-journal/2019/2019-02-30.journal"
      ]
    ),
    ( "./import/john/bogartbank/savings/2017-include.journal",
      [ "import/john/bogartbank/savings/3-journal/2017/2017-11-30.journal",
        "import/john/bogartbank/savings/3-journal/2017/2017-12-30.journal"
      ]
    ),
    ( "./import/john/bogartbank/savings/2018-include.journal",
      [ "import/john/bogartbank/savings/3-journal/2018/2018-02-30.journal",
        "import/john/bogartbank/savings/3-journal/2018/2018-01-30.journal"
      ]
    )
  ]

groupedJohnOther :: TurtleFileBundle
groupedJohnOther =
  [ ( "./import/john/otherbank/creditcard/2017-include.journal",
      ["import/john/otherbank/creditcard/3-journal/2017/2017-12-30.journal"]
    ),
    ( "./import/john/otherbank/creditcard/2018-include.journal",
      ["import/john/otherbank/creditcard/3-journal/2018/2018-01-30.journal"]
    ),
    ( "./import/john/otherbank/investments/2018-include.journal",
      ["import/john/otherbank/investments/3-journal/2018/2018-12-30.journal"]
    ),
    ( "./import/john/otherbank/investments/2019-include.journal",
      ["import/john/otherbank/investments/3-journal/2019/2019-01-30.journal"]
    )
  ]

groupedIncludeFiles :: TurtleFileBundle
groupedIncludeFiles =
  groupedJaneBogart
    <> groupedJaneOther
    <> groupedJohnBogart
    <> groupedJohnOther

testYearsIncludeMap :: Test
testYearsIncludeMap =
  TestCase
    ( do
        let maps = allYearIncludeFiles groupedJohnOther
        let yearGrouping =
              [ ( "./import/john/otherbank/creditcard/all-years.journal",
                  [ "./import/john/otherbank/creditcard/2017-include.journal",
                    "./import/john/otherbank/creditcard/2018-include.journal"
                  ]
                ),
                ( "./import/john/otherbank/investments/all-years.journal",
                  [ "./import/john/otherbank/investments/2018-include.journal",
                    "./import/john/otherbank/investments/2019-include.journal"
                  ]
                )
              ]
        let expected = (groupedJohnOther, yearGrouping)
        assertEqual "An augmented map with grouped years per level added" expected maps
    )

testYearsIncludeGrouping :: Test
testYearsIncludeGrouping =
  TestCase
    ( do
        let yearsMap = yearsIncludeMap (Map.keys groupedJohnOther)
        let expected =
              [ ( "./import/john/otherbank/creditcard/all-years.journal",
                  [ "./import/john/otherbank/creditcard/2017-include.journal",
                    "./import/john/otherbank/creditcard/2018-include.journal"
                  ]
                ),
                ( "./import/john/otherbank/investments/all-years.journal",
                  [ "./import/john/otherbank/investments/2018-include.journal",
                    "./import/john/otherbank/investments/2019-include.journal"
                  ]
                )
              ]
        assertEqual "A basic map with grouped years per level" expected yearsMap
    )

testGroupIncludeFilesTinySet :: Test
testGroupIncludeFilesTinySet =
  TestCase
    ( do
        let journals1 = ["import/jane/bogartbank/savings/3-journals/2017/2017-12-30.journal"]
        let expected1 = [("./import/jane/bogartbank/savings/2017-include.journal", journals1)] :: TurtleFileBundle
        let expectedAllYears1 = [("./import/jane/bogartbank/savings/all-years.journal", ["./import/jane/bogartbank/savings/2017-include.journal"])]
        let (group1, allYears1) = groupIncludeFiles journals1
        assertEqual "groupIncludeFiles small allYears 1" expectedAllYears1 allYears1
        assertEqual "groupIncludeFiles small set 1" expected1 group1

        journals2 <- case Map.keys expected1 of
          (k : _) -> return [k]
          [] -> assertFailure "Expected non-empty include map for journals2"
        let expected2 = [("./import/jane/bogartbank/2017-include.journal", journals2)] :: TurtleFileBundle
        let expectedAllYears2 = [("./import/jane/bogartbank/all-years.journal", ["./import/jane/bogartbank/2017-include.journal"])]
        let (group2, allYears2) = groupIncludeFiles journals2
        assertEqual "groupIncludeFiles small allYears 2" expectedAllYears2 allYears2
        assertEqual "groupIncludeFiles small set 2" expected2 group2

        journals3 <- case Map.keys expected2 of
          (k : _) -> return [k]
          [] -> assertFailure "Expected non-empty include map for journals3"
        let expected3 = [("./import/jane/2017-include.journal", journals3)] :: TurtleFileBundle
        let expectedAllYears3 = [("./import/jane/all-years.journal", ["./import/jane/2017-include.journal"])]
        let (group3, allYears3) = groupIncludeFiles journals3
        assertEqual "groupIncludeFiles small allYears 3" expectedAllYears3 allYears3
        assertEqual "groupIncludeFiles small set 3" expected3 group3
    )

testGroupIncludeFilesSmallSet :: Test
testGroupIncludeFilesSmallSet =
  TestCase
    ( do
        let (group1, allYears1) = groupIncludeFiles $ toJournals inputJaneBogart
        let expectedAllYears1 =
              [ ( "./import/jane/bogartbank/checking/all-years.journal",
                  [ "./import/jane/bogartbank/checking/2018-include.journal",
                    "./import/jane/bogartbank/checking/2019-include.journal"
                  ]
                ),
                ( "./import/jane/bogartbank/savings/all-years.journal",
                  [ "./import/jane/bogartbank/savings/2017-include.journal",
                    "./import/jane/bogartbank/savings/2018-include.journal"
                  ]
                )
              ]

        assertEqual "groupIncludeFiles Jane AllYears 1" expectedAllYears1 allYears1
        assertEqual "groupIncludeFiles Jane 1" groupedJaneBogart group1

        let (group2, allYears2) = groupIncludeFiles (Map.keys group1)
        let expectedAllYears2 =
              [ ( "./import/jane/bogartbank/all-years.journal",
                  [ "./import/jane/bogartbank/2017-include.journal",
                    "./import/jane/bogartbank/2018-include.journal",
                    "./import/jane/bogartbank/2019-include.journal"
                  ]
                )
              ]
        assertEqual "groupIncludeFiles Jane AllYears 2" expectedAllYears2 allYears2
        let expectedGroup2 =
              [ ( "./import/jane/bogartbank/2017-include.journal",
                  ["./import/jane/bogartbank/savings/2017-include.journal"]
                ),
                ( "./import/jane/bogartbank/2018-include.journal",
                  [ "./import/jane/bogartbank/checking/2018-include.journal",
                    "./import/jane/bogartbank/savings/2018-include.journal"
                  ]
                ),
                ( "./import/jane/bogartbank/2019-include.journal",
                  ["./import/jane/bogartbank/checking/2019-include.journal"]
                )
              ]
        assertEqual "groupIncludeFiles Jane 2" expectedGroup2 group2

        let (group3, allYears3) = groupIncludeFiles (Map.keys group2)
        let expectedAllYears3 =
              [ ( "./import/jane/all-years.journal",
                  [ "./import/jane/2017-include.journal",
                    "./import/jane/2018-include.journal",
                    "./import/jane/2019-include.journal"
                  ]
                )
              ]
        assertEqual "groupIncludeFiles Jane AllYears 3" expectedAllYears3 allYears3
        let expectedGroup3 =
              [ ( "./import/jane/2017-include.journal",
                  ["./import/jane/bogartbank/2017-include.journal"]
                ),
                ( "./import/jane/2018-include.journal",
                  ["./import/jane/bogartbank/2018-include.journal"]
                ),
                ( "./import/jane/2019-include.journal",
                  ["./import/jane/bogartbank/2019-include.journal"]
                )
              ]
        assertEqual "groupIncludeFiles Jane 3" expectedGroup3 group3

        let (group4, allYears4) = groupIncludeFiles (Map.keys group3)
        let expectedAllYears4 =
              [ ( "./import/all-years.journal",
                  [ "./import/2017-include.journal",
                    "./import/2018-include.journal",
                    "./import/2019-include.journal"
                  ]
                )
              ]
        assertEqual "groupIncludeFiles Jane AllYears 4" expectedAllYears4 allYears4
        let expectedGroup4 =
              [ ( "./import/2017-include.journal",
                  ["./import/jane/2017-include.journal"]
                ),
                ( "./import/2018-include.journal",
                  ["./import/jane/2018-include.journal"]
                ),
                ( "./import/2019-include.journal",
                  ["./import/jane/2019-include.journal"]
                )
              ]
        assertEqual "groupIncludeFiles Jane 4" expectedGroup4 group4

        let (group5, allYears5) = groupIncludeFiles (Map.keys group4)
        let expectedAllYears5 = [("./all-years.journal", ["./2017-include.journal", "./2018-include.journal", "./2019-include.journal"])]
        assertEqual "groupIncludeFiles Jane AllYears 5" expectedAllYears5 allYears5
        let expectedGroup5 =
              [ ( "./2017-include.journal",
                  ["./import/2017-include.journal"]
                ),
                ( "./2018-include.journal",
                  ["./import/2018-include.journal"]
                ),
                ( "./2019-include.journal",
                  ["./import/2019-include.journal"]
                )
              ]
        assertEqual "groupIncludeFiles Jane 5" expectedGroup5 group5
    )

testGroupIncludeFiles :: Test
testGroupIncludeFiles =
  TestCase
    ( do
        let (group1, allYears1) = groupIncludeFiles journalFiles
        let expectedAllYears1 =
              [ ( "./import/jane/bogartbank/checking/all-years.journal",
                  [ "./import/jane/bogartbank/checking/2018-include.journal",
                    "./import/jane/bogartbank/checking/2019-include.journal"
                  ]
                ),
                ( "./import/jane/bogartbank/savings/all-years.journal",
                  [ "./import/jane/bogartbank/savings/2017-include.journal",
                    "./import/jane/bogartbank/savings/2018-include.journal"
                  ]
                ),
                ( "./import/jane/otherbank/creditcard/all-years.journal",
                  [ "./import/jane/otherbank/creditcard/2017-include.journal",
                    "./import/jane/otherbank/creditcard/2018-include.journal"
                  ]
                ),
                ( "./import/jane/otherbank/investments/all-years.journal",
                  [ "./import/jane/otherbank/investments/2018-include.journal",
                    "./import/jane/otherbank/investments/2019-include.journal"
                  ]
                ),
                ( "./import/john/bogartbank/checking/all-years.journal",
                  [ "./import/john/bogartbank/checking/2018-include.journal",
                    "./import/john/bogartbank/checking/2019-include.journal"
                  ]
                ),
                ( "./import/john/bogartbank/savings/all-years.journal",
                  [ "./import/john/bogartbank/savings/2017-include.journal",
                    "./import/john/bogartbank/savings/2018-include.journal"
                  ]
                ),
                ( "./import/john/otherbank/creditcard/all-years.journal",
                  [ "./import/john/otherbank/creditcard/2017-include.journal",
                    "./import/john/otherbank/creditcard/2018-include.journal"
                  ]
                ),
                ( "./import/john/otherbank/investments/all-years.journal",
                  [ "./import/john/otherbank/investments/2018-include.journal",
                    "./import/john/otherbank/investments/2019-include.journal"
                  ]
                )
              ]
        assertEqual "groupIncludeFiles Jane AllYears 1" expectedAllYears1 allYears1
        assertEqual "groupIncludeFiles 1" groupedIncludeFiles group1

        let (group2, allYears2) = groupIncludeFiles (Map.keys group1)
        let expectedAllYears2 =
              [ ( "./import/jane/bogartbank/all-years.journal",
                  [ "./import/jane/bogartbank/2017-include.journal",
                    "./import/jane/bogartbank/2018-include.journal",
                    "./import/jane/bogartbank/2019-include.journal"
                  ]
                ),
                ( "./import/jane/otherbank/all-years.journal",
                  [ "./import/jane/otherbank/2017-include.journal",
                    "./import/jane/otherbank/2018-include.journal",
                    "./import/jane/otherbank/2019-include.journal"
                  ]
                ),
                ( "./import/john/bogartbank/all-years.journal",
                  [ "./import/john/bogartbank/2017-include.journal",
                    "./import/john/bogartbank/2018-include.journal",
                    "./import/john/bogartbank/2019-include.journal"
                  ]
                ),
                ( "./import/john/otherbank/all-years.journal",
                  [ "./import/john/otherbank/2017-include.journal",
                    "./import/john/otherbank/2018-include.journal",
                    "./import/john/otherbank/2019-include.journal"
                  ]
                )
              ]

        assertEqual "groupIncludeFiles Jane AllYears 2" expectedAllYears2 allYears2
        let expectedGroup2 =
              [ ( "./import/jane/bogartbank/2017-include.journal",
                  ["./import/jane/bogartbank/savings/2017-include.journal"]
                ),
                ( "./import/jane/bogartbank/2018-include.journal",
                  [ "./import/jane/bogartbank/checking/2018-include.journal",
                    "./import/jane/bogartbank/savings/2018-include.journal"
                  ]
                ),
                ( "./import/jane/bogartbank/2019-include.journal",
                  ["./import/jane/bogartbank/checking/2019-include.journal"]
                ),
                ( "./import/jane/otherbank/2017-include.journal",
                  ["./import/jane/otherbank/creditcard/2017-include.journal"]
                ),
                ( "./import/jane/otherbank/2018-include.journal",
                  [ "./import/jane/otherbank/creditcard/2018-include.journal",
                    "./import/jane/otherbank/investments/2018-include.journal"
                  ]
                ),
                ( "./import/jane/otherbank/2019-include.journal",
                  ["./import/jane/otherbank/investments/2019-include.journal"]
                ),
                ( "./import/john/bogartbank/2017-include.journal",
                  ["./import/john/bogartbank/savings/2017-include.journal"]
                ),
                ( "./import/john/bogartbank/2018-include.journal",
                  [ "./import/john/bogartbank/checking/2018-include.journal",
                    "./import/john/bogartbank/savings/2018-include.journal"
                  ]
                ),
                ( "./import/john/bogartbank/2019-include.journal",
                  ["./import/john/bogartbank/checking/2019-include.journal"]
                ),
                ( "./import/john/otherbank/2017-include.journal",
                  ["./import/john/otherbank/creditcard/2017-include.journal"]
                ),
                ( "./import/john/otherbank/2018-include.journal",
                  [ "./import/john/otherbank/creditcard/2018-include.journal",
                    "./import/john/otherbank/investments/2018-include.journal"
                  ]
                ),
                ( "./import/john/otherbank/2019-include.journal",
                  ["./import/john/otherbank/investments/2019-include.journal"]
                )
              ]
        assertEqual "groupIncludeFiles 2 - diff 1" [] (expectedGroup2 Map.\\ group2)
        assertEqual "groupIncludeFiles 2 - diff 2" [] (group2 Map.\\ expectedGroup2)
        assertEqual "groupIncludeFiles 2" expectedGroup2 group2

        let (group3, allYears3) = groupIncludeFiles (Map.keys group2)
        let expectedAllYears3 =
              [ ( "./import/jane/all-years.journal",
                  [ "./import/jane/2017-include.journal",
                    "./import/jane/2018-include.journal",
                    "./import/jane/2019-include.journal"
                  ]
                ),
                ( "./import/john/all-years.journal",
                  [ "./import/john/2017-include.journal",
                    "./import/john/2018-include.journal",
                    "./import/john/2019-include.journal"
                  ]
                )
              ]

        assertEqual "groupIncludeFiles Jane AllYears 3" expectedAllYears3 allYears3
        let expectedGroup3 =
              [ ( "./import/jane/2017-include.journal",
                  [ "./import/jane/bogartbank/2017-include.journal",
                    "./import/jane/otherbank/2017-include.journal"
                  ]
                ),
                ( "./import/jane/2018-include.journal",
                  [ "./import/jane/bogartbank/2018-include.journal",
                    "./import/jane/otherbank/2018-include.journal"
                  ]
                ),
                ( "./import/jane/2019-include.journal",
                  [ "./import/jane/bogartbank/2019-include.journal",
                    "./import/jane/otherbank/2019-include.journal"
                  ]
                ),
                ( "./import/john/2017-include.journal",
                  [ "./import/john/bogartbank/2017-include.journal",
                    "./import/john/otherbank/2017-include.journal"
                  ]
                ),
                ( "./import/john/2018-include.journal",
                  [ "./import/john/bogartbank/2018-include.journal",
                    "./import/john/otherbank/2018-include.journal"
                  ]
                ),
                ( "./import/john/2019-include.journal",
                  [ "./import/john/bogartbank/2019-include.journal",
                    "./import/john/otherbank/2019-include.journal"
                  ]
                )
              ]
        assertEqual "groupIncludeFiles 3 - diff 1" [] (expectedGroup3 Map.\\ group3)
        assertEqual "groupIncludeFiles 3 - diff 2" [] (group3 Map.\\ expectedGroup3)
        assertEqual "groupIncludeFiles 3" expectedGroup3 group3

        let (group4, allYears4) = groupIncludeFiles (Map.keys group3)
        let expectedAllYears4 =
              [ ( "./import/all-years.journal",
                  [ "./import/2017-include.journal",
                    "./import/2018-include.journal",
                    "./import/2019-include.journal"
                  ]
                )
              ]
        assertEqual "groupIncludeFiles Jane AllYears 4" expectedAllYears4 allYears4
        let expectedGroup4 =
              [ ( "./import/2017-include.journal",
                  [ "./import/jane/2017-include.journal",
                    "./import/john/2017-include.journal"
                  ]
                ),
                ( "./import/2018-include.journal",
                  [ "./import/jane/2018-include.journal",
                    "./import/john/2018-include.journal"
                  ]
                ),
                ( "./import/2019-include.journal",
                  [ "./import/jane/2019-include.journal",
                    "./import/john/2019-include.journal"
                  ]
                )
              ]
        assertEqual "groupIncludeFiles 4 - diff 1" [] (expectedGroup4 Map.\\ group4)
        assertEqual "groupIncludeFiles 4 - diff 2" [] (group4 Map.\\ expectedGroup4)
        assertEqual "groupIncludeFiles 4" expectedGroup4 group4

        let (group5, allYears5) = groupIncludeFiles (Map.keys group4)
        let expectedAllYears5 = [("./all-years.journal", ["./2017-include.journal", "./2018-include.journal", "./2019-include.journal"])]
        assertEqual "groupIncludeFiles Jane AllYears 5" expectedAllYears5 allYears5
        let expectedGroup5 =
              [ ( "./2017-include.journal",
                  ["./import/2017-include.journal"]
                ),
                ( "./2018-include.journal",
                  ["./import/2018-include.journal"]
                ),
                ( "./2019-include.journal",
                  ["./import/2019-include.journal"]
                )
              ]
        assertEqual "groupIncludeFiles 5 - diff 1" [] (expectedGroup5 Map.\\ group5)
        assertEqual "groupIncludeFiles 5 - diff 2" [] (group5 Map.\\ expectedGroup5)
        assertEqual "groupIncludeFiles 5" expectedGroup5 group5
    )

testIncludeYears :: Test
testIncludeYears =
  TestCase
    ( do
        let txterr = "Some text without years"
        let expectederr = ["Unable to extract years from the following text:", txterr, "Errors:"]
        let actualerr = case lefts [includeYears' txterr] of
              (errTxt : _) -> (init . T.lines) errTxt
              [] -> []
        assertEqual "Get a list of years from an include file - error case" expectederr actualerr

        let txt1 =
              "### Generated by hledger-flow - DO NOT EDIT ###\n\n"
                <> "include import/2014-include.journal\n"
                <> "include import/2015-include.journal\n"
                <> "include import/2016-include.journal\n"
                <> "include import/2017-include.journal\n"
                <> "include import/2018-include.journal\n"
                <> "include import/2019-include.journal"

        let expected1 = Right [2014 .. 2019]
        let actual1 = includeYears' txt1
        assertEqual "Get a list of years from an include file - success case 1" expected1 actual1

        let txt2 = "include 2019-include.journal"

        let expected2 = Right [2019]
        let actual2 = includeYears' txt2
        assertEqual "Get a list of years from an include file - success case 2" expected2 actual2
    )

testToIncludeLine :: Test
testToIncludeLine =
  TestCase
    ( do
        let expected = "include file1.journal"
        let relativeWithTrailingSlash = toIncludeLine "./base/dir/" "./base/dir/file1.journal"
        assertEqual "Include line - relative base dir with trailing slash" expected relativeWithTrailingSlash

        let relativeNoTrailingSlash = toIncludeLine "./base/dir" "./base/dir/file1.journal"
        assertEqual "Include line - relative base dir without a trailing slash" expected relativeNoTrailingSlash

        let absoluteWithTrailingSlash = toIncludeLine "/base/dir/" "/base/dir/file1.journal"
        assertEqual "Include line - absolute base dir with trailing slash" expected absoluteWithTrailingSlash

        let absoluteNoTrailingSlash = toIncludeLine "/base/dir" "/base/dir/file1.journal"
        assertEqual "Include line - absolute base dir without a trailing slash" expected absoluteNoTrailingSlash
    )

testToIncludeFiles :: Test
testToIncludeFiles =
  TestCase
    ( do
        let expected =
              [ ( "./import/john/bogartbank/checking/2018-include.journal",
                  "### Generated by hledger-flow - DO NOT EDIT ###\n\n"
                    <> "include import/john/bogartbank/checking/3-journal/2018/2018-10-30.journal\n"
                    <> "include import/john/bogartbank/checking/3-journal/2018/2018-11-30.journal\n"
                    <> "include import/john/bogartbank/checking/3-journal/2018/2018-12-30.journal\n"
                ),
                ( "./import/john/bogartbank/checking/2019-include.journal",
                  "### Generated by hledger-flow - DO NOT EDIT ###\n\n"
                    <> "include import/john/bogartbank/checking/3-journal/2019/2019-01-30.journal\n"
                    <> "include import/john/bogartbank/checking/3-journal/2019/2019-02-30.journal\n"
                ),
                ( "./import/john/bogartbank/savings/2017-include.journal",
                  "### Generated by hledger-flow - DO NOT EDIT ###\n\n"
                    <> "include import/john/bogartbank/savings/3-journal/2017/2017-11-30.journal\n"
                    <> "include import/john/bogartbank/savings/3-journal/2017/2017-12-30.journal\n"
                ),
                ( "./import/john/bogartbank/savings/2018-include.journal",
                  "### Generated by hledger-flow - DO NOT EDIT ###\n\n"
                    <> "include import/john/bogartbank/savings/3-journal/2018/2018-01-30.journal\n"
                    <> "include import/john/bogartbank/savings/3-journal/2018/2018-02-30.journal\n"
                )
              ]

        ch <- newTChanIO
        txt <- toIncludeFiles (defaultOpts [absdir|/|]) ch groupedJohnBogart
        assertEqual "Convert a grouped map of paths, to a map with text contents for each file" expected txt
    )

testExtractImportDirsSuccess :: Test
testExtractImportDirsSuccess =
  TestCase
    ( do
        let file = "import/john/mybank/checking/1-in/2019/2019-01-01.csv"
        case extractImportDirs file of
          Left err -> assertFailure (T.unpack err)
          Right dirs -> do
            let expectedYear = Turtle.directory file
            let expectedState = Turtle.parent expectedYear
            let expectedAccount = Turtle.parent expectedState
            let expectedBank = Turtle.parent expectedAccount
            let expectedOwner = Turtle.parent expectedBank
            let expectedImport = Turtle.parent expectedOwner
            assertEqual "importDir" expectedImport (importDir dirs)
            assertEqual "ownerDir" expectedOwner (ownerDir dirs)
            assertEqual "bankDir" expectedBank (bankDir dirs)
            assertEqual "accountDir" expectedAccount (accountDir dirs)
            assertEqual "stateDir" expectedState (stateDir dirs)
            assertEqual "yearDir" expectedYear (yearDir dirs)
    )

testExtractImportDirsFailure :: Test
testExtractImportDirsFailure =
  TestCase
    ( do
        let badFile = "import/john/mybank/checking/2019-01-01.csv"
        case extractImportDirs badFile of
          Left err ->
            assertBool
              "Error should explain expected structure"
              ("expects to find input files in this structure" `T.isInfixOf` err)
          Right _ -> assertFailure "Expected failure for malformed import path"
    )

testGroupIncludeFilesFiltersNonJournal :: Test
testGroupIncludeFilesFiltersNonJournal =
  TestCase
    ( do
        let journal = "import/john/mybank/checking/3-journal/2019/2019-01-01.journal"
        let nonJournal = "import/john/mybank/checking/3-journal/2019/2019-01-01.csv"
        let (grouped, _) = groupIncludeFiles [journal, nonJournal]
        assertEqual "Only journal files should be grouped" 1 (Map.size grouped)
        let files = concat (Map.elems grouped)
        assertEqual "Non-journal files should be ignored" [journal] files
    )

tests :: Test
tests =
  TestList
    [ testYearsIncludeMap,
      testYearsIncludeGrouping,
      testGroupIncludeFilesTinySet,
      testGroupIncludeFilesSmallSet,
      testGroupIncludeFiles,
      testIncludeYears,
      testToIncludeLine,
      testToIncludeFiles,
      testExtractImportDirsSuccess,
      testExtractImportDirsFailure,
      testGroupIncludeFilesFiltersNonJournal
    ]