packages feed

seihou-core-0.10.0.0: test/Seihou/Core/SeedSpec.hs

module Seihou.Core.SeedSpec (tests) where

import Data.Either (isLeft)
import Data.Generics.Labels ()
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Text qualified as T
import Data.Text.IO qualified as TIO
import Data.Time (UTCTime (..), fromGregorian)
import Seihou.Core.Module (checkSeedSteps)
import Seihou.Core.Seed
import Seihou.Core.Types
import Seihou.Dhall.Eval (evalModuleFromFile)
import Seihou.Manifest.Types
import Seihou.Prelude
import System.Directory (getCurrentDirectory)
import System.FilePath (takeDirectory)
import System.IO.Temp (withSystemTempDirectory)
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec (testSpec)

tests :: IO TestTree
tests = testSpec "seed foundations" $ do
  let now = UTCTime (fromGregorian 2026 10 15) 0
      first = SeedRecord "demo" (Set.singleton (ApplicationId "a")) SeedCreated now
      second = SeedRecord "demo" (Set.singleton (ApplicationId "b")) SeedFoundExisting now
      manifest = mergeSeedRecord "CHANGELOG.md" second (mergeSeedRecord "CHANGELOG.md" first (emptyManifest now))
  it "preserves the first settlement while merging owners" $ do
    let Just receipt = Map.lookup "CHANGELOG.md" (manifest ^. #seeds)
    receipt ^. #outcome `shouldBe` SeedCreated
    receipt ^. #applicationIds `shouldBe` Set.fromList [ApplicationId "a", ApplicationId "b"]
  it "releases only selected owners and drops the last claim" $ do
    let one = dropSeedOwner (Set.singleton (ApplicationId "a")) "CHANGELOG.md" manifest
    Map.size (one ^. #seeds) `shouldBe` 1
    Map.null (dropSeedOwner (Set.singleton (ApplicationId "b")) "CHANGELOG.md" one ^. #seeds) `shouldBe` True
  it "round trips receipts without hashes or baselines" $ do
    manifestFromJSON (manifestToJSON manifest) `shouldBe` Right manifest
  it "rejects a managed/seed collision at decoding" $ do
    let file = FileRecord (SHA256 "hash") "demo" Copy now Nothing Set.empty SharedWriteUnknown
        invalid = manifest & #files .~ Map.singleton "CHANGELOG.md" file
    validateManifestInvariants invalid `shouldBe` Left (PathBothManagedAndSeeded "CHANGELOG.md")
    manifestFromJSON (manifestToJSON invalid) `shouldSatisfy` isLeft
  it "keeps schema 7 unchanged and diagnoses seed information loss" $ do
    let old = emptyManifest now & #version .~ ManifestSchemaVersion 7
    manifestFromJSON (manifestToJSON old) `shouldBe` Right old
  it "reports facts an older schema cannot encode" $ do
    manifestEncodingLoss (manifest & #version .~ ManifestSchemaVersion 7) `shouldSatisfy` (not . null)
  it "decodes omitted lifecycle and rejects invalid lifecycle values" $
    withSystemTempDirectory "lifecycle" $ \dir -> do
      cwd <- getCurrentDirectory
      let schema = takeDirectory cwd </> "schema/package.dhall"
          source field =
            T.unlines
              [ "let S = " <> T.pack schema,
                "in S.Module::{ name = \"demo\", version = Some \"1.0\", steps = [S.Step::{ strategy = \"template\", src = \"a\", dest = \"b\"" <> field <> " }] }"
              ]
      TIO.writeFile (dir </> "module.dhall") (source "")
      Right old <- evalModuleFromFile (dir </> "module.dhall")
      map (^. #lifecycle) (old ^. #steps) `shouldBe` [Managed]
      TIO.writeFile (dir </> "module.dhall") (source ", lifecycle = Some \"seed\"")
      Right seed <- evalModuleFromFile (dir </> "module.dhall")
      checkSeedSteps seed `shouldBe` []
      let patched = seed & #steps %~ map (#patch ?~ AppendFile)
      checkSeedSteps patched `shouldSatisfy` (not . null)
      checkSeedSteps (seed & #steps %~ map (#strategy .~ Structured)) `shouldSatisfy` (not . null)
      checkSeedSteps (seed & #steps %~ (<> map (#lifecycle .~ Managed) (seed ^. #steps))) `shouldSatisfy` (not . null)
      checkSeedSteps (seed & #removal ?~ Removal [RemovalStep RemoveFileAction "b" Nothing] []) `shouldSatisfy` (not . null)
      TIO.writeFile (dir </> "module.dhall") (source ", lifecycle = Some \"sede\"")
      invalid <- evalModuleFromFile (dir </> "module.dhall")
      invalid `shouldSatisfy` isLeft