packages feed

seihou-core-0.4.0.0: test/Seihou/Integration/GenerationSpec.hs

module Seihou.Integration.GenerationSpec (tests) where

import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Seihou.Core.Module (loadModule)
import Seihou.Core.Types
import Seihou.Core.Variable (resolveVariables)
import Seihou.Engine.Plan (compilePlan)
import System.Directory (getCurrentDirectory)
import System.FilePath ((</>))
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec (testSpec)

tests :: IO TestTree
tests = testSpec "Seihou.Integration.Generation" spec

fixtureDir :: IO FilePath
fixtureDir = do
  cwd <- getCurrentDirectory
  pure (cwd </> "test" </> "fixtures")

-- | Helper to extract the resolved variable values map.
resolvedValues :: Map.Map VarName ResolvedVar -> Map.Map VarName VarValue
resolvedValues = Map.map (.value)

spec :: Spec
spec = do
  describe "full pipeline: load, resolve, compile" $ do
    it "produces correct README.md content" $ do
      fixtures <- fixtureDir
      result <- loadModule [fixtures] "haskell-base"
      case result of
        Left err -> expectationFailure ("Failed to load module: " <> show err)
        Right modul -> do
          let cli = Map.fromList [("project.name", "my-app")]
              env = Map.empty
          case resolveVariables (modul.vars) cli env "" "" Map.empty Map.empty Map.empty Map.empty Map.empty of
            Left errs -> expectationFailure ("Failed to resolve: " <> show errs)
            Right resolved -> do
              planResult <- compilePlan (fixtures </> "haskell-base") modul (resolvedValues resolved)
              case planResult of
                Left errs -> expectationFailure ("Plan failed: " <> show errs)
                Right ops -> do
                  let readmeOps = [op | op@(WriteFileOp d _ _) <- ops, d == "README.md"]
                  length readmeOps `shouldBe` 1
                  let (WriteFileOp _ content _) = readmeOps !! 0
                  content `shouldBe` "# my-app\n\nVersion: 0.1.0.0\n"

    it "produces correct cabal file with expanded destination" $ do
      fixtures <- fixtureDir
      result <- loadModule [fixtures] "haskell-base"
      case result of
        Left err -> expectationFailure ("Failed to load module: " <> show err)
        Right modul -> do
          let cli = Map.fromList [("project.name", "my-app")]
              env = Map.empty
          case resolveVariables (modul.vars) cli env "" "" Map.empty Map.empty Map.empty Map.empty Map.empty of
            Left errs -> expectationFailure ("Failed to resolve: " <> show errs)
            Right resolved -> do
              planResult <- compilePlan (fixtures </> "haskell-base") modul (resolvedValues resolved)
              case planResult of
                Left errs -> expectationFailure ("Plan failed: " <> show errs)
                Right ops -> do
                  let cabalOps = [op | op@(WriteFileOp d _ _) <- ops, d == "my-app.cabal"]
                  length cabalOps `shouldBe` 1
                  let (WriteFileOp _ content _) = cabalOps !! 0
                  T.isInfixOf "name: my-app" content `shouldBe` True
                  T.isInfixOf "version: 0.1.0.0" content `shouldBe` True
                  T.isInfixOf "license: MIT" content `shouldBe` True

    it "includes LICENSE step when license is set" $ do
      fixtures <- fixtureDir
      result <- loadModule [fixtures] "haskell-base"
      case result of
        Left err -> expectationFailure ("Failed to load module: " <> show err)
        Right modul -> do
          let cli = Map.fromList [("project.name", "my-app")]
              env = Map.empty
          case resolveVariables (modul.vars) cli env "" "" Map.empty Map.empty Map.empty Map.empty Map.empty of
            Left errs -> expectationFailure ("Failed to resolve: " <> show errs)
            Right resolved -> do
              planResult <- compilePlan (fixtures </> "haskell-base") modul (resolvedValues resolved)
              case planResult of
                Left errs -> expectationFailure ("Plan failed: " <> show errs)
                Right ops -> do
                  let licenseOps = [op | op@(WriteFileOp d _ _) <- ops, d == "LICENSE"]
                  length licenseOps `shouldBe` 1

    it "excludes LICENSE step when license is not set" $ do
      fixtures <- fixtureDir
      result <- loadModule [fixtures] "haskell-base"
      case result of
        Left err -> expectationFailure ("Failed to load module: " <> show err)
        Right modul -> do
          -- Provide all vars but use an empty var map for IsSet evaluation.
          -- The real scenario: license has a default so it's always set.
          -- To test the conditional, we use a stripped-down module with only the LICENSE step.
          let licenseStep = Step Copy "LICENSE" "LICENSE" (Just (ExprIsSet "license")) Nothing
              smallModule = modul {steps = [licenseStep]}
              vars = Map.empty -- no license variable set
          planResult <- compilePlan (fixtures </> "haskell-base") smallModule vars
          case planResult of
            Left errs -> expectationFailure ("Plan failed: " <> show errs)
            Right ops -> do
              let licenseOps = [op | op@(WriteFileOp d _ _) <- ops, d == "LICENSE"]
              length licenseOps `shouldBe` 0

    it "produces DhallText output for cabal.project" $ do
      fixtures <- fixtureDir
      result <- loadModule [fixtures] "haskell-base"
      case result of
        Left err -> expectationFailure ("Failed to load module: " <> show err)
        Right modul -> do
          let cli = Map.fromList [("project.name", "my-app")]
              env = Map.empty
          case resolveVariables (modul.vars) cli env "" "" Map.empty Map.empty Map.empty Map.empty Map.empty of
            Left errs -> expectationFailure ("Failed to resolve: " <> show errs)
            Right resolved -> do
              planResult <- compilePlan (fixtures </> "haskell-base") modul (resolvedValues resolved)
              case planResult of
                Left errs -> expectationFailure ("Plan failed: " <> show errs)
                Right ops -> do
                  let projectOps = [op | op@(WriteFileOp d _ _) <- ops, d == "cabal.project"]
                  length projectOps `shouldBe` 1
                  let (WriteFileOp _ content _) = projectOps !! 0
                  T.isInfixOf "my-app" content `shouldBe` True

    it "resolves variables from env and applies precedence" $ do
      fixtures <- fixtureDir
      result <- loadModule [fixtures] "haskell-base"
      case result of
        Left err -> expectationFailure ("Failed to load module: " <> show err)
        Right modul -> do
          -- CLI overrides project.name, env overrides license
          let cli = Map.fromList [("project.name", "cli-app")]
              env = Map.fromList [("SEIHOU_VAR_LICENSE", "BSD3")]
          case resolveVariables (modul.vars) cli env "" "" Map.empty Map.empty Map.empty Map.empty Map.empty of
            Left errs -> expectationFailure ("Failed to resolve: " <> show errs)
            Right resolved -> do
              (.value) (resolved Map.! "project.name") `shouldBe` VText "cli-app"
              (.source) (resolved Map.! "project.name") `shouldBe` FromCLI
              (.value) (resolved Map.! "license") `shouldBe` VText "BSD3"
              (.source) (resolved Map.! "license") `shouldBe` FromEnv "SEIHOU_VAR_LICENSE"
              (.value) (resolved Map.! "project.version") `shouldBe` VText "0.1.0.0"
              (.source) (resolved Map.! "project.version") `shouldBe` FromDefault