packages feed

canadian-income-tax-2025.0: test/Regression.hs

{-# LANGUAGE Haskell2010 #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Main where

import Tax.Canada.Federal qualified as Federal
import Tax.Canada.FormKey (FormKey)
import Tax.Canada.FormKey qualified as FormKey
import Tax.Canada.Province.ON qualified as ON
import Tax.Canada.T1 (fixT1)
import Tax.FDF qualified as FDF

import Control.Monad.IO.Class (liftIO)
import Data.ByteString qualified as ByteString
import Data.ByteString.Lazy (fromStrict)
import Data.Foldable (toList)
import Data.List qualified as List
import Data.Map qualified as Map
import GHC.Stack (HasCallStack)
import System.Directory (doesDirectoryExist, listDirectory)
import System.Exit (die)
import System.FilePath.Posix (combine)
import Text.FDF (parse, serialize)

import Test.Tasty
import Test.Tasty.Golden

main :: IO ()
main = listDirectory inputDir >>= traverse test >>= defaultMain . testGroup "Regression"

rootDir, inputDir, outputDir, referenceDir :: FilePath
rootDir = combine "test" "regression"
inputDir = combine rootDir "inputs"
outputDir = combine rootDir "outputs"
referenceDir = combine rootDir "reference"

test :: FilePath -> IO TestTree
test path = do
  let inputPath = combine inputDir path
  isDir <- liftIO (doesDirectoryExist inputPath)
  if isDir
    then testReturn path
    else testT1 path

testT1 :: FilePath -> IO TestTree
testT1 path = do
  fdfBytes <- ByteString.readFile $ combine inputDir path
  case parse fdfBytes of
    Left err -> die err
    Right fdf ->
      pure $
        goldenVsString path
          (combine referenceDir path)
          (pure $ fromStrict $ serialize $ either error id $ FDF.mapForm ON.t1Fields fixT1 fdf)

testReturn :: FilePath -> IO TestTree
testReturn path = do
  let inputPath = combine inputDir path
  fdfFileNames <- listDirectory inputPath
  fdfBytes <- traverse (ByteString.readFile . combine inputPath) fdfFileNames
  case do fdfs <- traverse parse fdfBytes
          let (inputs, keyedFillables) = foldMap decide $ zip fdfFileNames fdfs
              decide (name, fdf)
                | "t4-" `List.isPrefixOf` name = ([(FormKey.T4, fdf)], [])
                | otherwise = ([], [(formKey name, fdf)])
          loadedInputs <- Federal.loadInputForms inputs
          FDF.mapForms ON.returnFields (ON.fixReturns loadedInputs) $ Map.fromList keyedFillables
    of Left err -> die err
       Right filled
         | let fdfOutputs = fromStrict . serialize <$> filled
               diff ref new = ["diff", "-uw", ref, new]
               verify key fdfOutput
                 | let Just fileName = List.find ((key ==) . formKey) fdfFileNames
                 = pure $ goldenVsStringDiff fileName diff (combine referenceDir $ combine path fileName)
                   $ pure fdfOutput
           -> testGroup path . toList <$> Map.traverseWithKey verify fdfOutputs


formKey :: HasCallStack => FilePath -> FormKey
formKey "5000-s6-fill-25e.fdf" = FormKey.Schedule6
formKey "5000-s8-fill-25e.fdf" = FormKey.Schedule8
formKey "5006-c-fill-25e.fdf" = FormKey.Provincial428
formKey "5006-r-fill-25e.fdf" = FormKey.T1
formKey "t4-fill-25e.fdf" = FormKey.T4
formKey name = error ("File name " <> name <> " is not recognized as a form.")