packages feed

seihou-cli-0.6.0.0: test/Seihou/CLI/AppliedBlueprintMigrationSpec.hs

module Seihou.CLI.AppliedBlueprintMigrationSpec (tests) where

import Control.Lens ((&), (.~), (^.))
import Data.ByteString.Lazy qualified as LBS
import Data.Generics.Labels ()
import Data.Text qualified as T
import Data.Time (UTCTime, defaultTimeLocale, parseTimeOrError)
import Seihou.CLI.AppliedBlueprintMigration (recordAppliedBlueprintMigration)
import Seihou.Core.Types (AppliedBlueprintMigration (..), Manifest (..), ModuleName (..))
import Seihou.Manifest.Types (currentManifestVersion, manifestFromJSON)
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec (testSpec)

tests :: IO TestTree
tests = testSpec "Seihou.CLI.AppliedBlueprintMigration" spec

fixedTime :: UTCTime
fixedTime =
  parseTimeOrError True defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ" "2026-07-20T12:00:00Z"

fixedTime2 :: UTCTime
fixedTime2 =
  parseTimeOrError True defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ" "2026-07-20T13:00:00Z"

mkReceipt :: T.Text -> T.Text -> T.Text -> UTCTime -> AppliedBlueprintMigration
mkReceipt blueprintName fromVersion toVersion appliedAt =
  AppliedBlueprintMigration
    { name = ModuleName blueprintName,
      blueprintVersion = Just "0.4.0",
      fromVersion = fromVersion,
      toVersion = toVersion,
      appliedAt = appliedAt,
      agentSessionId = Nothing
    }

readManifestFile :: FilePath -> IO Manifest
readManifestFile path = do
  bytes <- LBS.readFile path
  case manifestFromJSON bytes of
    Right manifest -> pure manifest
    Left err -> error ("test fixture: malformed manifest: " <> err)

spec :: Spec
spec = describe "recordAppliedBlueprintMigration" $ do
  it "creates a version-5 manifest for the first receipt" $
    withSystemTempDirectory "seihou-blueprint-migration" $ \dir -> do
      let manifestPath = dir </> ".seihou" </> "manifest.json"
          receipt = mkReceipt "payments" "1.0.0" "2.0.0" fixedTime
      result <- recordAppliedBlueprintMigration manifestPath receipt
      result `shouldBe` Right ()
      manifest <- readManifestFile manifestPath
      (manifest ^. #version) `shouldBe` currentManifestVersion
      (manifest ^. #blueprintMigrations) `shouldBe` [receipt]

  it "upserts the same exact edge and retains unrelated edges" $
    withSystemTempDirectory "seihou-blueprint-migration" $ \dir -> do
      let manifestPath = dir </> "manifest.json"
          first = mkReceipt "payments" "1.0.0" "2.0.0" fixedTime
          second = mkReceipt "payments" "2.5.0" "3.0.0" fixedTime
          replacement =
            ( (mkReceipt "payments" "1.0.0" "2.0.0" fixedTime2)
                & #blueprintVersion .~ Just "0.5.0"
            )
      recordAppliedBlueprintMigration manifestPath first `shouldReturn` Right ()
      recordAppliedBlueprintMigration manifestPath second `shouldReturn` Right ()
      recordAppliedBlueprintMigration manifestPath replacement `shouldReturn` Right ()
      manifest <- readManifestFile manifestPath
      (manifest ^. #blueprintMigrations) `shouldBe` [replacement, second]

  it "returns Left and preserves a corrupt existing manifest" $
    withSystemTempDirectory "seihou-blueprint-migration" $ \dir -> do
      let manifestPath = dir </> "manifest.json"
          corrupt = "{ this is not valid json"
      writeFile manifestPath corrupt
      result <- recordAppliedBlueprintMigration manifestPath (mkReceipt "payments" "1.0.0" "2.0.0" fixedTime)
      case result of
        Left err -> err `shouldSatisfy` not . T.null
        Right () -> expectationFailure "expected corrupt manifest failure"
      readFile manifestPath `shouldReturn` corrupt