packages feed

stackctl-1.6.1.1: test/Stackctl/RemovedStackSpec.hs

{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

module Stackctl.RemovedStackSpec
  ( spec
  ) where

import Stackctl.Test.App

import qualified Amazonka
import qualified Amazonka.CloudFormation as CloudFormation
import Amazonka.CloudFormation.DescribeStacks
import Amazonka.CloudFormation.Types.Stack
import qualified Data.Text as T
import Data.Time (UTCTime (..))
import Data.Time.Calendar (DayOfMonth, MonthOfYear, Year, fromGregorian)
import Network.HTTP.Types.Status (status400)
import Stackctl.AWS.CloudFormation
import Stackctl.DirectoryOption (DirectoryOption (..), directoryOptionL)
import Stackctl.FilterOption (filterOptionFromText, filterOptionL)
import Stackctl.RemovedStack
import UnliftIO.Directory (createDirectoryIfMissing)

spec :: Spec
spec = do
  describe "inferRemovedStacks" $ do
    it "returns stacks in filters that aren't on disk" $ example $ runTestAppT $ do
      let
        Just filterOption =
          filterOptionFromText
            $ T.intercalate
              ","
              [ pack $ testAppStackFilePath "stack-exists"
              , pack $ testAppStackFilePath "stack-is-missing"
              , "stacks/0123456789.test/us-east-2/wrong-region.yaml"
              , "stacks/2123456789.test/us-east-1/wrong-account.yaml"
              ]

        setup :: TestApp -> TestApp
        setup = filterOptionL .~ filterOption

        matchers =
          [ describeStackMatcher "stack-exists" $ Just $ someStack "stack-exists"
          , describeStackMatcher "stack-is-missing" Nothing
          , describeStackMatcher "wrong-region" Nothing
          , describeStackMatcher "wrong-account" Nothing
          ]

      stacks <- local setup $ withMatchers matchers inferRemovedStacks
      map (^. stack_stackName) stacks `shouldBe` ["stack-exists"]

    -- If we don't check for file existence respecting STACKCTL_DIRECTORY, then
    -- any non-default value will cause all specs to appear non-existent and be
    -- flagged for removal. Eek.
    it "respects STACKCTL_DIRECTORY" $ example $ runTestAppT $ do
      let
        dir = "/tmp/stackctl-test"
        toRemove = "stack-to-remove"
        toKeep = "stack-to-keep"
        relativeToRemove = testAppStackFilePath toRemove
        relativeToKeep = testAppStackFilePath toKeep
        absoluteToKeep = dir </> relativeToKeep
        Just filterOption =
          filterOptionFromText
            $ pack relativeToRemove
            <> ","
            <> pack relativeToKeep

        setup :: TestApp -> TestApp
        setup app =
          app
            & filterOptionL .~ filterOption
            & directoryOptionL .~ DirectoryOption dir

        matchers =
          [ describeStackMatcher toRemove $ Just $ someStack toRemove
          , describeStackMatcher toKeep $ Just $ someStack toKeep
          ]

      -- Create a spec on disk for toKeep, then we should only find toRemove
      createDirectoryIfMissing True $ takeDirectory absoluteToKeep
      writeFileUtf8 absoluteToKeep "{}"

      stacks <- local setup $ withMatchers matchers inferRemovedStacks
      map (^. stack_stackName) stacks `shouldBe` [toRemove]

describeStackMatcher :: Text -> Maybe Stack -> Matcher
describeStackMatcher name =
  SendMatcher ((== Just name) . (^. describeStacks_stackName))
    . maybe
      (Left cloudFormationValidationError)
      ( \stack ->
          Right
            $ newDescribeStacksResponse 200
            & describeStacksResponse_stacks ?~ [stack]
      )

someStack :: Text -> Stack
someStack name = newStack name (midnight 2024 1 1) StackStatus_CREATE_COMPLETE

midnight :: Year -> MonthOfYear -> DayOfMonth -> UTCTime
midnight y m d =
  UTCTime
    { utctDay = fromGregorian y m d
    , utctDayTime = 0
    }

cloudFormationValidationError :: Amazonka.Error
cloudFormationValidationError =
  Amazonka.ServiceError
    $ Amazonka.ServiceError'
      { Amazonka.abbrev = CloudFormation.defaultService ^. Amazonka.service_abbrev
      , Amazonka.status = status400
      , Amazonka.headers = []
      , Amazonka.code = "ValidationError"
      , Amazonka.message = Nothing
      , Amazonka.requestId = Nothing
      }