packages feed

tricorder-0.5.0.0: test/Unit/Tricorder/SessionSpec.hs

module Unit.Tricorder.SessionSpec (test_Session) where

import Atelier.Config (LoadedConfig (..))
import Atelier.Effects.FileSystem (runFileSystemState)
import Atelier.Effects.Input (runInputConst)
import Atelier.Effects.Log (Message (..), Severity (..), runLogNoOp, runLogWriter)
import Data.Aeson (Value (Null), object, (.=))
import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe)
import Effectful (runPureEff)
import Effectful.Reader.Static (runReader)
import Effectful.State.Static.Shared (evalState)
import Effectful.Writer.Static.Shared (execWriter)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))

import Tricorder.Runtime (ProjectRoot (..))
import Tricorder.Session (Session (..), loadSession)
import Tricorder.Session.CabalFile (CabalFile (..))
import Tricorder.Session.IdleTimeout (IdleTimeout (..))
import Unit.Tricorder.Session.Helpers (libWithPreludeCabal, preludeOnlyLibCabal)


test_Session :: TestTree
test_Session =
    testGroup
        "Session"
        [ testLoadSession
        , testIdleTimeout
        , testMissingTargetPlaceholder
        ]


testLoadSession :: TestTree
testLoadSession =
    testGroup
        "loadSession"
        [ testGroup
            "when every resolved target exposes a custom Prelude module"
            [ testCase "emits a WARN" do
                let msgs = captureSessionLogs [preludeOnlyCF]
                any (\m -> m.severity == WARN) msgs @?= True
            ]
        , testGroup
            "when not every resolved target exposes a custom Prelude module"
            [ testCase "does not emit a WARN" do
                -- libWithPreludeCabal has both a lib (custom Prelude) and an exe (no Prelude)
                let msgs = captureSessionLogs [mixedCF]
                any (\m -> m.severity == WARN) msgs @?= False
            ]
        , testCase "does not emit a WARN when there are no resolved targets" do
            any (\m -> m.severity == WARN) (captureSessionLogs []) @?= False
        ]
  where
    preludeOnlyCF =
        CabalFile "/p.cabal"
            $ fromMaybe (error "preludeOnlyLibCabal failed to parse")
            $ parseGenericPackageDescriptionMaybe (preludeOnlyLibCabal "p")
    mixedCF =
        CabalFile "/mixed.cabal"
            $ fromMaybe (error "libWithPreludeCabal failed to parse")
            $ parseGenericPackageDescriptionMaybe (libWithPreludeCabal "mixed")
    captureSessionLogs cabalFiles =
        runPureEff
            . execWriter @[Message]
            . runLogWriter
            . evalState @(Map FilePath ByteString) mempty
            . runFileSystemState
            . runInputConst cabalFiles
            . runReader (ProjectRoot "/")
            . runInputConst (LoadedConfig Null)
            $ loadSession


testMissingTargetPlaceholder :: TestTree
testMissingTargetPlaceholder =
    testGroup
        "loadSession missing {target} placeholder"
        [ testGroup
            "test.command_template"
            [ testCase "emits a WARN when it has no {target} placeholder" do
                let cfg = sessionCfg ["test" .= object ["command_template" .= ("cabal repl test:foo" :: Text)]]
                any (\m -> m.severity == WARN) (captureLogsFor cfg) @?= True
            , testCase "does not emit a WARN when it has the {target} placeholder" do
                let cfg = sessionCfg ["test" .= object ["command_template" .= ("cabal repl {target}" :: Text)]]
                any (\m -> m.severity == WARN) (captureLogsFor cfg) @?= False
            ]
        , testGroup
            "eval.command_template"
            [ testCase "emits a WARN when it has no {target} placeholder" do
                let cfg = sessionCfg ["eval" .= object ["command_template" .= ("cabal repl Tricorder.Foo" :: Text)]]
                any (\m -> m.severity == WARN) (captureLogsFor cfg) @?= True
            , testCase "does not emit a WARN when it has the {target} placeholder" do
                let cfg = sessionCfg ["eval" .= object ["command_template" .= ("cabal repl {target}" :: Text)]]
                any (\m -> m.severity == WARN) (captureLogsFor cfg) @?= False
            ]
        , testGroup
            "build.command_template"
            [ testCase "does not emit a WARN when it has no {targets} placeholder" do
                let cfg = sessionCfg ["build" .= object ["command_template" .= ("cabal repl lib:foo" :: Text)]]
                any (\m -> m.severity == WARN) (captureLogsFor cfg) @?= False
            ]
        ]
  where
    sessionCfg session = LoadedConfig $ object ["session" .= object session]
    captureLogsFor cfg =
        runPureEff
            . execWriter @[Message]
            . runLogWriter
            . evalState @(Map FilePath ByteString) mempty
            . runFileSystemState
            . runInputConst ([] :: [CabalFile])
            . runReader (ProjectRoot "/")
            . runInputConst cfg
            $ loadSession


testIdleTimeout :: TestTree
testIdleTimeout =
    testGroup
        "loadSession idleTimeout"
        [ testCase "defaults to 300 seconds when unset" do
            (loadSessionWith (LoadedConfig Null)).idleTimeout @?= IdleTimeout 300
        , testCase "reads idle_timeout_seconds from the session config" do
            let cfg = LoadedConfig $ object ["session" .= object ["idle_timeout_seconds" .= (5 :: Int)]]
            (loadSessionWith cfg).idleTimeout @?= IdleTimeout 5
        ]
  where
    loadSessionWith cfg =
        runPureEff
            . runLogNoOp
            . evalState @(Map FilePath ByteString) mempty
            . runFileSystemState
            . runInputConst ([] :: [CabalFile])
            . runReader (ProjectRoot "/")
            . runInputConst cfg
            $ loadSession