{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TypeApplications #-}
module TestCase
( TestCase(..)
, runTest
, withTempLogFile
) where
import Control.Monad.Logger.Aeson (LoggedMessage(..), LoggingT, runFileLoggingT)
import Data.Aeson (Result(..), Value)
import Data.Aeson.Diff (Patch(..))
import GHC.Stack (HasCallStack)
import System.IO (IOMode(..))
import Test.Hspec (Expectation, expectationFailure, shouldBe)
import qualified Control.Exception as Exception
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Diff as Diff
import qualified Data.ByteString.Char8 as BS8
import qualified System.Directory as Directory
import qualified System.IO as IO
data TestCase = TestCase
{ actionUnderTest :: LoggingT IO ()
, logFilePath :: FilePath
, expectedValue :: Value
, expectedPatch :: Value
, expectedLoggedMessage :: LoggedMessage
}
runTest :: (HasCallStack) => TestCase -> IO ()
runTest testCase = do
clearFile logFilePath
runFileLoggingT logFilePath actionUnderTest
curLine <- IO.withFile logFilePath ReadMode BS8.hGetLine
case Aeson.decodeStrict' @Value curLine of
Nothing -> expectationFailure $ "Failed to parse log line as Value: " <> show curLine
Just actualValue -> do
case Aeson.fromJSON expectedPatch of
Error errStr -> do
expectationFailure $ "Patch is invalid: " <> show errStr
Success expectedPatch' -> do
actualValue `shouldMatchWithPatch` (expectedValue, expectedPatch')
Diff.patch expectedPatch' actualValue `shouldBe` Success expectedValue
case Aeson.fromJSON expectedValue of
Error errStr -> do
expectationFailure $ "LoggedMessage parse failed: " <> errStr
Success actualLoggedMessage -> do
actualLoggedMessage `shouldBe` expectedLoggedMessage
Aeson.toJSON actualLoggedMessage `shouldBe` Aeson.toJSON expectedLoggedMessage
Aeson.toEncoding actualLoggedMessage `shouldBe` Aeson.toEncoding expectedLoggedMessage
where
TestCase
{ actionUnderTest
, logFilePath
, expectedValue
, expectedPatch
, expectedLoggedMessage
} = testCase
withTempLogFile :: (FilePath -> IO ()) -> IO ()
withTempLogFile f = do
Exception.bracket
(IO.openTempFile "." "monad-logger-aeson")
(\(filePath, handle) -> IO.hClose handle *> Directory.removeFile filePath)
(\(filePath, handle) -> do
IO.hClose handle
f filePath)
shouldMatchWithPatch :: (HasCallStack) => Value -> (Value, Patch) -> Expectation
shouldMatchWithPatch actualValue (expectedValue, expectedPatch) = do
Diff.diff actualValue expectedValue `shouldBe` expectedPatch
clearFile :: FilePath -> IO ()
clearFile filePath = IO.withFile filePath WriteMode $ flip IO.hSetFileSize 0