packages feed

super-user-spark-0.4.0.0: test/SuperUserSpark/BakeSpec.hs

{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TemplateHaskell #-}

module SuperUserSpark.BakeSpec where

import TestImport

import Data.Either (isLeft)
import Data.Maybe (isNothing)

import qualified System.FilePath as FP
import System.Posix.Files (createSymbolicLink)

import SuperUserSpark.Bake
import SuperUserSpark.Bake.Gen ()
import SuperUserSpark.Bake.Internal
import SuperUserSpark.Bake.Types
import SuperUserSpark.Compiler.Types
import SuperUserSpark.OptParse.Gen ()
import SuperUserSpark.Parser.Gen

spec :: Spec
spec = do
    instanceSpec
    bakeSpec

instanceSpec :: Spec
instanceSpec =
    parallel $ do
        eqSpec @BakeAssignment
        genValidSpec @BakeAssignment
        eqSpec @BakeCardReference
        genValidSpec @BakeCardReference
        eqSpec @BakeSettings
        genValidSpec @BakeSettings
        eqSpec @BakeError
        genValidSpec @BakeError
        eqSpec @BakedDeployment
        genValidSpec @BakedDeployment
        jsonSpecOnValid @BakedDeployment
        eqSpec @AbsP
        genValidSpec @AbsP
        eqSpec @(DeploymentDirections AbsP)
        genValidSpec @(DeploymentDirections AbsP)
        jsonSpecOnValid @(DeploymentDirections AbsP)
        eqSpec @ID
        genValidSpec @ID

bakeSpec :: Spec
bakeSpec =
    parallel $ do
        describe "bakeFilePath" $ do
            it "works for these unit test cases without variables" $ do
                let b root fp s = do
                        absp <- AbsP <$> parseAbsFile s
                        rp <- parseAbsDir root
                        runReaderT
                            (runExceptT (bakeFilePath fp))
                            defaultBakeSettings {bakeRoot = rp} `shouldReturn`
                            Right absp
                b "/home/user/hello" "a/b/c" "/home/user/hello/a/b/c"
                b "/home/user/hello" "/home/user/.files/c" "/home/user/.files/c"
            it "works for a simple home-only variable situation" $ do
                forAll genValid $ \root -> do
                    let b home fp s = do
                            absp <- AbsP <$> parseAbsFile s
                            runReaderT
                                (runExceptT (bakeFilePath fp))
                                defaultBakeSettings
                                { bakeRoot = root
                                , bakeEnvironment = [("HOME", home)]
                                } `shouldReturn`
                                Right absp
                    b "/home/user" "~/a/b/c" "/home/user/a/b/c"
                    b "/home" "~/c" "/home/c"
            here <- runIO getCurrentDir
            let sandbox = here </> $(mkRelDir "test_sandbox")
            before_ (ensureDir sandbox) $
                after_ (removeDirRecur sandbox) $ do
                    it
                        "does not follow toplevel links when the completed path is relative" $ do
                        let file = $(mkRelFile "file")
                        let from = $(mkRelDir "from") </> file
                        let to = $(mkRelDir "to") </> file
                        withCurrentDir sandbox $ do
                            ensureDir $ parent $ sandbox </> from
                            writeFile (sandbox </> from) "contents"
                            ensureDir $ parent $ sandbox </> to
                            createSymbolicLink
                                (toFilePath $ sandbox </> from)
                                (toFilePath $ sandbox </> to)
                        let runBake f =
                                runReaderT
                                    (runExceptT f)
                                    (defaultBakeSettings
                                     {bakeRoot = sandbox, bakeEnvironment = []})
                        runBake (bakeFilePath (toFilePath to)) `shouldReturn`
                            (Right $ AbsP $ sandbox </> to)
                        runBake (bakeFilePath (toFilePath from)) `shouldReturn`
                            (Right $ AbsP $ sandbox </> from)
                    it
                        "follows directory-level links when the completed path is relative" $ do
                        let file = $(mkRelFile "file")
                        let fromdir = $(mkRelDir "from")
                        let from = fromdir </> file
                        let todir = $(mkRelDir "to")
                        let to = todir </> file
                        withCurrentDir sandbox $ do
                            ensureDir $ parent $ sandbox </> from
                            writeFile (sandbox </> from) "from contents"
                            ensureDir $ parent $ sandbox </> todir
                            createSymbolicLink
                                (FP.dropTrailingPathSeparator $
                                 toFilePath $ sandbox </> fromdir)
                                (FP.dropTrailingPathSeparator $
                                 toFilePath $ sandbox </> todir)
                            writeFile (sandbox </> to) "to contents"
                        let runBake f =
                                runReaderT
                                    (runExceptT f)
                                    (defaultBakeSettings
                                     {bakeRoot = sandbox, bakeEnvironment = []})
                        runBake (bakeFilePath (toFilePath to)) `shouldReturn`
                            (Right $ AbsP $ sandbox </> from)
                        runBake (bakeFilePath (toFilePath from)) `shouldReturn`
                            (Right $ AbsP $ sandbox </> from)
        describe "defaultBakeSettings" $
            it "is valid" $ isValid defaultBakeSettings
        describe "formatBakeError" $ do
            it "only ever produces valid strings" $
                producesValid formatBakeError
        describe "complete" $ do
            it "only ever produces a valid filepath" $ validIfSucceeds2 complete
            it
                "replaces the home directory as specified for simple home directories and simple paths" $ do
                forAll arbitrary $ \env ->
                    forAll generateWord $ \home ->
                        forAll generateWord $ \fp ->
                            complete (("HOME", home) : env) ("~" FP.</> fp) `shouldBe`
                            Right (home FP.</> fp)
        describe "parseId" $ do
            it "only ever produces valid IDs" $ producesValid parseId
            it "Figures out the home directory in these cases" $ do
                parseId "~" `shouldBe` [Var "HOME"]
                parseId "~/ab" `shouldBe` [Var "HOME", Plain "/ab"]
            it "Works for these cases" $ do
                parseId "" `shouldBe` []
                parseId "file" `shouldBe` [Plain "file"]
                parseId "something$(with)variable" `shouldBe`
                    [Plain "something", Var "with", Plain "variable"]
                parseId "$(one)$(two)$(three)" `shouldBe`
                    [Var "one", Var "two", Var "three"]
        describe "replaceId" $ do
            it "only ever produces valid FilePaths" $ validIfSucceeds2 replaceId
            it "leaves plain ID's unchanged in any environment" $
                forAll arbitrary $ \env ->
                    forAll arbitrary $ \s ->
                        replaceId env (Plain s) `shouldBe` Right s
            it "returns Left if a variable is not in the environment" $
                forAll arbitrary $ \var ->
                    forAll (arbitrary `suchThat` (isNothing . lookup var)) $ \env ->
                        replaceId env (Var var) `shouldSatisfy` isLeft
            it "replaces a variable if it's in the environment" $
                forAll arbitrary $ \var ->
                    forAll arbitrary $ \val ->
                        forAll (arbitrary `suchThat` (isNothing . lookup var)) $ \env1 ->
                            forAll
                                (arbitrary `suchThat` (isNothing . lookup var)) $ \env2 ->
                                replaceId
                                    (env1 ++ [(var, val)] ++ env2)
                                    (Var var) `shouldBe`
                                Right val