module TestUtils.ConstructTestTree
  ( appGoldenVsFile
  , appTest
  , appTestCmd
  , appTestCmdString
  , constructTestCollectorInputFragm
  , constructTestCollectorOutputFragm
  , constructTestInputFragmFSS
  , constructTestOutputFragmFSS
  , postCollectorCmdHookS3
  , postCmdHookS3
  ) where

import           Control.Monad       (when)
import           System.IO           (FilePath)
import           System.Process      (callCommand)
import           Test.Tasty          (TestTree)
import           Test.Tasty.Silver   (goldenVsFile)
import           TestUtils.S3Utils
import           TestUtils.TestCases

-- Conduct a single test
appGoldenVsFile
  :: (a -> String)
  -> (a -> FilePath)
  -> (a -> FilePath)
  -> (a -> FilePath)
  -> (a -> IO ())
  -> a
  -> TestTree
appGoldenVsFile :: forall a.
(a -> [Char])
-> (a -> [Char])
-> (a -> [Char])
-> (a -> [Char])
-> (a -> IO ())
-> a
-> TestTree
appGoldenVsFile a -> [Char]
constructTestName a -> [Char]
constructFilepathForTest a -> [Char]
constructFilepathForGolden a -> [Char]
constructFilepathForResult a -> IO ()
appTest a
testScenario
  = [Char] -> [Char] -> [Char] -> IO () -> TestTree
goldenVsFile (a -> [Char]
constructTestName a
testScenario)
                 (a -> [Char]
constructFilepathForGolden a
testScenario)
                 (a -> [Char]
constructFilepathForResult a
testScenario)
                 (a -> IO ()
appTest a
testScenario)

-- Perform a pre-command action, perform the command that the test will be
-- assessing, and then perform a post-command action
appTest :: (a -> IO ()) -> (a -> IO ()) -> (a -> IO ()) -> a -> IO ()
appTest :: forall a.
(a -> IO ()) -> (a -> IO ()) -> (a -> IO ()) -> a -> IO ()
appTest a -> IO ()
preCmdHook a -> IO ()
appTestCmd a -> IO ()
postCmdHook a
testScenario = do
  a -> IO ()
preCmdHook a
testScenario
  a -> IO ()
appTestCmd a
testScenario
  a -> IO ()
postCmdHook a
testScenario

-- Perform the command that the test will be assessing
appTestCmd :: (a -> String) -> a -> IO ()
appTestCmd :: forall a. (a -> [Char]) -> a -> IO ()
appTestCmd a -> [Char]
appTestCmdString a
testScenario = do
  forall a. Show a => a -> IO ()
print forall a b. (a -> b) -> a -> b
$ [Char]
"TEST COMMAND:  " forall a. [a] -> [a] -> [a]
++ [Char]
cmd
  [Char] -> IO ()
callCommand [Char]
cmd
  where cmd :: [Char]
cmd = a -> [Char]
appTestCmdString a
testScenario

-- Construct a shell string that can be run as a command. The string is
-- constructed in three string fragments: the excecutable command, a fragment
-- specifying where the command gets its input, and a fragment specifying where
-- the command gets its output
appTestCmdString
  :: (a -> String) -> (a -> String) -> (a -> String) -> a -> String
appTestCmdString :: forall a.
(a -> [Char]) -> (a -> [Char]) -> (a -> [Char]) -> a -> [Char]
appTestCmdString a -> [Char]
constructTestExecutableFragm a -> [Char]
constructTestInputFragm a -> [Char]
constructTestOutputFragm a
testScenario
  = [Char]
testExecutableFragm forall a. [a] -> [a] -> [a]
++ [Char]
" " forall a. [a] -> [a] -> [a]
++ [Char]
testInputFragm forall a. [a] -> [a] -> [a]
++ [Char]
" " forall a. [a] -> [a] -> [a]
++ [Char]
testOutputFragm
 where
  testExecutableFragm :: [Char]
testExecutableFragm = a -> [Char]
constructTestExecutableFragm a
testScenario
  testInputFragm :: [Char]
testInputFragm      = a -> [Char]
constructTestInputFragm a
testScenario
  testOutputFragm :: [Char]
testOutputFragm     = a -> [Char]
constructTestOutputFragm a
testScenario

-- Construct a fragment of a shell string specifying the input for a testing
-- scenario where the input can come from a file, standard input, or from Amazon
-- S3
--
-- Note that if the input is specified as coming from standard input, then a
-- fragment like `"< /path/to/file"` is inserted in the middle of the overall
-- command string. While this is not usual practice, the shell removes the
-- fragment prior to processing and things do indeed work as intended
constructTestInputFragmFSS
  :: (InputTypeAbleFSS a)
  => (a -> FilePath)
  -> (a -> String)
  -> (a -> String)
  -> a
  -> String
constructTestInputFragmFSS :: forall a.
InputTypeAbleFSS a =>
(a -> [Char]) -> (a -> [Char]) -> (a -> [Char]) -> a -> [Char]
constructTestInputFragmFSS a -> [Char]
constructFilepathForTest a -> [Char]
constructBucketForTest a -> [Char]
constructS3KeyForTest a
testScenario
  = case forall a. InputTypeAbleFSS a => a -> TestInputType
extractTestInputType a
testScenario of
    TestInputType
TestInputFile  -> [Char]
"-f " forall a. [a] -> [a] -> [a]
++ [Char]
filepathForTest
    TestInputType
TestInputStdin -> [Char]
"< " forall a. [a] -> [a] -> [a]
++ [Char]
filepathForTest
    TestInputType
TestInputS3    -> [Char]
"-r us-east-1 -b " forall a. [a] -> [a] -> [a]
++ [Char]
bucket forall a. [a] -> [a] -> [a]
++ [Char]
" -k " forall a. [a] -> [a] -> [a]
++ [Char]
s3KeyForTest
 where
  filepathForTest :: [Char]
filepathForTest = a -> [Char]
constructFilepathForTest a
testScenario
  bucket :: [Char]
bucket          = a -> [Char]
constructBucketForTest a
testScenario
  s3KeyForTest :: [Char]
s3KeyForTest    = a -> [Char]
constructS3KeyForTest a
testScenario

-- NOTE: this is nearly identical to the above function, but with different
-- input types (and `-m` option instead of `-k`). Can we combine the two
-- somehow?
constructTestCollectorInputFragm
  :: (TestCollectorScenario -> FilePath)
  -> (TestCollectorScenario -> String)
  -> (TestCollectorScenario -> String)
  -> TestCollectorScenario
  -> String
constructTestCollectorInputFragm :: (TestCollectorScenario -> [Char])
-> (TestCollectorScenario -> [Char])
-> (TestCollectorScenario -> [Char])
-> TestCollectorScenario
-> [Char]
constructTestCollectorInputFragm TestCollectorScenario -> [Char]
constructFilepathForTest TestCollectorScenario -> [Char]
constructBucketForTest TestCollectorScenario -> [Char]
constructS3KeyForTest TestCollectorScenario
testCollectorScenario
  = case TestCollectorScenario -> TestCollectorInputType
getTestCollectorInputType TestCollectorScenario
testCollectorScenario of
    TestCollectorInputType
TestCollectorInputFile -> [Char]
"-f " forall a. [a] -> [a] -> [a]
++ [Char]
filepathForTest
    TestCollectorInputType
TestCollectorInputS3   -> [Char]
"-b " forall a. [a] -> [a] -> [a]
++ [Char]
bucket forall a. [a] -> [a] -> [a]
++ [Char]
" -m " forall a. [a] -> [a] -> [a]
++ [Char]
s3KeyForTest
 where
  filepathForTest :: [Char]
filepathForTest = TestCollectorScenario -> [Char]
constructFilepathForTest TestCollectorScenario
testCollectorScenario
  bucket :: [Char]
bucket          = TestCollectorScenario -> [Char]
constructBucketForTest TestCollectorScenario
testCollectorScenario
  s3KeyForTest :: [Char]
s3KeyForTest    = TestCollectorScenario -> [Char]
constructS3KeyForTest TestCollectorScenario
testCollectorScenario

-- Construct a fragment of a shell string specifying the output for a testing
-- scenario where the output can be written to a file, standard output, or from
-- Amazon S3
constructTestOutputFragmFSS
  :: (TestScenarioCohort -> FilePath)
  -> (TestScenarioCohort -> String)
  -> (TestScenarioCohort -> String)
  -> TestScenarioCohort
  -> String
constructTestOutputFragmFSS :: (TestScenarioCohort -> [Char])
-> (TestScenarioCohort -> [Char])
-> (TestScenarioCohort -> [Char])
-> TestScenarioCohort
-> [Char]
constructTestOutputFragmFSS TestScenarioCohort -> [Char]
constructFilepathForResult TestScenarioCohort -> [Char]
constructBucketForResult TestScenarioCohort -> [Char]
constructS3KeyForResult TestScenarioCohort
testScenario
  = case TestScenarioCohort -> TestOutputType
getCohortTestOutputType TestScenarioCohort
testScenario of
    TestOutputType
TestOutputFile   -> [Char]
"-o " forall a. [a] -> [a] -> [a]
++ [Char]
filepathForResult
    TestOutputType
TestOutputStdout -> [Char]
"> " forall a. [a] -> [a] -> [a]
++ [Char]
filepathForResult
    TestOutputType
TestOutputS3 ->
      [Char]
"--outregion us-east-1 --outbucket "
        forall a. [a] -> [a] -> [a]
++ [Char]
bucket
        forall a. [a] -> [a] -> [a]
++ [Char]
" --outkey "
        forall a. [a] -> [a] -> [a]
++ [Char]
s3KeyForResult
 where
  filepathForResult :: [Char]
filepathForResult = TestScenarioCohort -> [Char]
constructFilepathForResult TestScenarioCohort
testScenario
  bucket :: [Char]
bucket            = TestScenarioCohort -> [Char]
constructBucketForResult TestScenarioCohort
testScenario
  s3KeyForResult :: [Char]
s3KeyForResult    = TestScenarioCohort -> [Char]
constructS3KeyForResult TestScenarioCohort
testScenario

-- Construct a fragment of a shell string specifying the output for a testing
-- scenario for the cohort-collector application
--
-- NOTE: this is nearly identical to the above function, but with different
-- output types. Can we combine the two somehow?
constructTestCollectorOutputFragm
  :: (TestCollectorScenario -> FilePath)
  -> (TestCollectorScenario -> String)
  -> (TestCollectorScenario -> String)
  -> TestCollectorScenario
  -> String
constructTestCollectorOutputFragm :: (TestCollectorScenario -> [Char])
-> (TestCollectorScenario -> [Char])
-> (TestCollectorScenario -> [Char])
-> TestCollectorScenario
-> [Char]
constructTestCollectorOutputFragm TestCollectorScenario -> [Char]
constructFilepathForResult TestCollectorScenario -> [Char]
constructBucketForResult TestCollectorScenario -> [Char]
constructS3KeyForResult TestCollectorScenario
testScenario
  = case TestCollectorScenario -> TestCollectorOutputType
getTestCollectorOutputType TestCollectorScenario
testScenario of
    TestCollectorOutputType
TestCollectorOutputFile   -> [Char]
"-o " forall a. [a] -> [a] -> [a]
++ [Char]
filepathForResult
    TestCollectorOutputType
TestCollectorOutputStdout -> [Char]
"> " forall a. [a] -> [a] -> [a]
++ [Char]
filepathForResult
    TestCollectorOutputType
TestCollectorOutputS3 ->
      [Char]
"--outregion us-east-1 --outbucket "
        forall a. [a] -> [a] -> [a]
++ [Char]
bucket
        forall a. [a] -> [a] -> [a]
++ [Char]
" --outkey "
        forall a. [a] -> [a] -> [a]
++ [Char]
s3KeyForResult
 where
  filepathForResult :: [Char]
filepathForResult = TestCollectorScenario -> [Char]
constructFilepathForResult TestCollectorScenario
testScenario
  bucket :: [Char]
bucket            = TestCollectorScenario -> [Char]
constructBucketForResult TestCollectorScenario
testScenario
  s3KeyForResult :: [Char]
s3KeyForResult    = TestCollectorScenario -> [Char]
constructS3KeyForResult TestCollectorScenario
testScenario

-- Construct a post command hook that copies the output produced by the command
-- under test from S3 to the local filesystem
postCmdHookS3
  :: (TestScenarioCohort -> String)
  -> (TestScenarioCohort -> FilePath)
  -> TestScenarioCohort
  -> IO ()
postCmdHookS3 :: (TestScenarioCohort -> [Char])
-> (TestScenarioCohort -> [Char]) -> TestScenarioCohort -> IO ()
postCmdHookS3 TestScenarioCohort -> [Char]
constructS3UriForResult TestScenarioCohort -> [Char]
constructFilepathForResult TestScenarioCohort
testScenario =
  forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when
    Bool
isS3out
    ([Char] -> [Char] -> IO ()
s3Copy (TestScenarioCohort -> [Char]
constructS3UriForResult TestScenarioCohort
testScenario)
            (TestScenarioCohort -> [Char]
constructFilepathForResult TestScenarioCohort
testScenario)
    )
 where
  isS3out :: Bool
isS3out = case TestScenarioCohort -> TestOutputType
getCohortTestOutputType TestScenarioCohort
testScenario of
    TestOutputType
TestOutputS3 -> Bool
True
    TestOutputType
_            -> Bool
False

-- NOTE: this is very similar to `postCmdHookS3`, can they be combined?
postCollectorCmdHookS3
  :: (TestCollectorScenario -> String)
  -> (TestCollectorScenario -> FilePath)
  -> TestCollectorScenario
  -> IO ()
postCollectorCmdHookS3 :: (TestCollectorScenario -> [Char])
-> (TestCollectorScenario -> [Char])
-> TestCollectorScenario
-> IO ()
postCollectorCmdHookS3 TestCollectorScenario -> [Char]
constructS3UriForResult TestCollectorScenario -> [Char]
constructFilepathForResult TestCollectorScenario
testScenario
  = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when
    Bool
isS3out
    ([Char] -> [Char] -> IO ()
s3Copy (TestCollectorScenario -> [Char]
constructS3UriForResult TestCollectorScenario
testScenario)
            (TestCollectorScenario -> [Char]
constructFilepathForResult TestCollectorScenario
testScenario)
    )
 where
  isS3out :: Bool
isS3out = case TestCollectorScenario -> TestCollectorOutputType
getTestCollectorOutputType TestCollectorScenario
testScenario of
    TestCollectorOutputType
TestCollectorOutputS3 -> Bool
True
    TestCollectorOutputType
_                     -> Bool
False