packages feed

seihou-core-0.12.0.0: test/Seihou/Core/RecipeSpec.hs

module Seihou.Core.RecipeSpec (tests) where

import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Seihou.Core.Recipe (validateRecipe)
import Seihou.Core.Types
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec (testSpec)

tests :: IO TestTree
tests = testSpec "Seihou.Core.Recipe" spec

hasError :: T.Text -> [T.Text] -> Bool
hasError needle = any (T.isInfixOf needle)

spec :: Spec
spec = do
  describe "validateRecipe" $ do
    it "passes a valid recipe" $ do
      let recipe =
            Recipe
              { name = "valid-recipe",
                version = Just "1.0.0",
                description = Just "A valid recipe",
                modules = [simpleDep "mod-a", simpleDep "mod-b"],
                vars = [],
                prompts = []
              }
      validateRecipe recipe `shouldBe` Right recipe

    it "rejects an empty modules list" $ do
      let recipe =
            Recipe
              { name = "empty-recipe",
                version = Nothing,
                description = Nothing,
                modules = [],
                vars = [],
                prompts = []
              }
      case validateRecipe recipe of
        Left errs -> hasError "at least one module" errs `shouldBe` True
        Right _ -> expectationFailure "Expected validation failure"

    it "rejects an invalid recipe name" $ do
      let recipe =
            Recipe
              { name = "BadName",
                version = Nothing,
                description = Nothing,
                modules = [simpleDep "mod-a"],
                vars = [],
                prompts = []
              }
      case validateRecipe recipe of
        Left errs -> hasError "recipe name must match" errs `shouldBe` True
        Right _ -> expectationFailure "Expected validation failure"

    it "rejects duplicate module names" $ do
      let recipe =
            Recipe
              { name = "dup-recipe",
                version = Nothing,
                description = Nothing,
                modules = [simpleDep "mod-a", simpleDep "mod-b", simpleDep "mod-a"],
                vars = [],
                prompts = []
              }
      case validateRecipe recipe of
        Left errs -> hasError "duplicate module" errs `shouldBe` True
        Right _ -> expectationFailure "Expected validation failure"

    it "rejects invalid variable binding name" $ do
      let recipe =
            Recipe
              { name = "bad-var-recipe",
                version = Nothing,
                description = Nothing,
                modules =
                  [ Dependency (ModuleName "mod-a") (Map.singleton (VarName "INVALID") "val") Nothing
                  ],
                vars = [],
                prompts = []
              }
      case validateRecipe recipe of
        Left errs -> hasError "invalid var binding name" errs `shouldBe` True
        Right _ -> expectationFailure "Expected validation failure"

    it "accepts valid variable binding names with dots" $ do
      let recipe =
            Recipe
              { name = "dotted-vars",
                version = Nothing,
                description = Nothing,
                modules =
                  [ Dependency (ModuleName "mod-a") (Map.singleton (VarName "nix.system") "aarch64-darwin") Nothing
                  ],
                vars = [],
                prompts = []
              }
      validateRecipe recipe `shouldBe` Right recipe

    it "collects multiple errors at once" $ do
      let recipe =
            Recipe
              { name = "BadName",
                version = Nothing,
                description = Nothing,
                modules = [],
                vars = [],
                prompts = []
              }
      case validateRecipe recipe of
        Left errs -> length errs `shouldSatisfy` (>= 2)
        Right _ -> expectationFailure "Expected validation failure"

    it "accepts unanswered required declarations and nested conditions" $ do
      validateRecipe interactiveRecipe `shouldBe` Right interactiveRecipe

    it "reports duplicate declarations, prompt targets and all nested references together" $ do
      let recipe =
            Recipe
              "interactive"
              Nothing
              Nothing
              [simpleDep "unavailable"]
              [requiredVar "enabled", requiredVar "enabled"]
              [Prompt "missing-target" "Question" (Just (ExprAnd (ExprIsSet "unknown-a") (ExprNot (ExprOr (ExprLit True) (ExprEq "unknown-b" (VBool False)))))) Nothing]
      case validateRecipe recipe of
        Left errs -> do
          length errs `shouldBe` 4
          mapM_
            (\needle -> hasError needle errs `shouldBe` True)
            ["vars: duplicate", "missing-target", "unknown-a", "unknown-b"]
        Right _ -> expectationFailure "Expected all four descriptor errors"

requiredVar :: VarName -> VarDecl
requiredVar name = VarDecl name VTBool Nothing Nothing True Nothing

interactiveRecipe :: Recipe
interactiveRecipe =
  Recipe
    "interactive"
    Nothing
    Nothing
    [Dependency "unavailable" (Map.singleton "member.only" "literal ${unchanged}") Nothing]
    [requiredVar "enabled", requiredVar "owner"]
    [Prompt "owner" "Owner?" (Just (ExprAnd (ExprEq "enabled" (VBool True)) (ExprNot (ExprOr (ExprIsSet "owner") (ExprLit False))))) Nothing]