{-# LANGUAGE CPP #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Yaml.IncludeSpec (main, spec) where

import           Test.Hspec
import           Data.List (isPrefixOf)
import qualified Data.ByteString.Lazy as LB
import           Data.Aeson
#if MIN_VERSION_aeson(2,2,0)
import           Data.Aeson.Types (JSONPathElement(..))
#else
import           Data.Aeson.Internal (JSONPathElement(..))
#endif
import           Data.Yaml (ParseException(InvalidYaml))
import           Data.Yaml.Include
import           Data.Yaml.Internal
import           Text.Libyaml (YamlException(YamlException))
import           Test.Mockery.Directory
import           Text.RawString.QQ
import           Data.Yaml.TH (yamlQQ)

main :: IO ()
main = hspec spec

asInt :: Int -> Int
asInt = id

spec :: Spec
spec = do
  describe "decodeFile" $ do
    it "supports includes" $ do
      decodeFile "test/resources/foo.yaml" `shouldReturn` Just (object
          [ "foo" .= asInt 23
          , "bar" .= object
              [ "one" .= asInt 1
              , "two" .= asInt 2
              ]
          , "baz" .= asInt 42
          ])

    it "supports recursive includes" $ do
      decodeFile "test/resources/baz.yaml" `shouldReturn` Just (object
        [ "foo" .= object
          [ "foo" .= asInt 23
          , "bar" .= object
            [ "one" .= asInt 1
            , "two" .= asInt 2
            ]
          , "baz" .= asInt 42
          ]
        ])

    it "aborts on cyclic includes" $ do
      (decodeFile "test/resources/loop/foo.yaml" :: IO (Maybe Value)) `shouldThrow` anyException

    context "when file does not exist" $ do
      it "throws Left (InvalidYaml (Just (YamlException \"Yaml file not found: ...\")))" $ do
        (decodeFile "./does_not_exist.yaml" :: IO (Maybe Value)) `shouldThrow` isYamlFileNotFoundException

    context "with a 1K stack size limit" $ around_ inTempDirectory $ do
      context "with a large list" $ do
        it "succeeds" $ do
          let
            xs :: [Value]
            xs = replicate 5000 (Number 23)
          LB.writeFile "foo.yaml" (encode xs)
          decodeFile "foo.yaml" `shouldReturn` Just xs

  describe "decodeFileEither" $ do
    context "when file does not exist" $ do
      it "returns Left (InvalidYaml (Just (YamlException \"Yaml file not found: ...\")))" $ do
        (decodeFileEither "./does_not_exist.yaml" :: IO (Either ParseException Value)) >>=
          (`shouldSatisfy` either isYamlFileNotFoundException (const False))

  describe "decodeFileWithWarnings" $ around_ inTempDirectory $ do
    it "warns on duplicate keys" $ do
      writeFile "foo.yaml" [r|
        foo: 23
        foo: bar
        |]
      Right result <- decodeFileWithWarnings "foo.yaml"
      result `shouldBe` ([DuplicateKey [Key "foo"]], [yamlQQ|
        foo: bar
        |])

    it "warns on nested duplicate keys" $ do
      writeFile "foo.yaml" [r|
        foo:
          - 42
          - bar: 23
            bar: baz
        |]
      Right result <- decodeFileWithWarnings "foo.yaml"
      result `shouldBe` ([DuplicateKey [Key "foo", Index 1, Key "bar"]], [yamlQQ|
        foo:
          - 42
          - bar: baz
        |])

    context "when overriding a merged key" $ do
      it "does not warn" $ do
        writeFile "foo.yaml" [r|
          foo-1: &my-ref
            bar: 23
          foo-2:
            <<: *my-ref
            bar: 42
          |]
        Right result <- decodeFileWithWarnings "foo.yaml"
        result `shouldBe` ([], [yamlQQ|
          foo-1:
            bar: 23
          foo-2:
            bar: 42
          |])

      context "when overriding twice" $ do
        it "warns" $ do
          writeFile "foo.yaml" [r|
            foo-1: &my-ref
              bar: 23
            foo-2:
              <<: *my-ref
              bar: 42
              bar: 65
            |]
          Right result <- decodeFileWithWarnings "foo.yaml"
          result `shouldBe` ([DuplicateKey [Key "foo-2", Key "bar"]], [yamlQQ|
            foo-1:
              bar: 23
            foo-2:
              bar: 65
            |])

isYamlFileNotFoundException :: ParseException -> Bool
isYamlFileNotFoundException (InvalidYaml (Just (YamlException msg)))
  | "Yaml file not found: " `isPrefixOf` msg = True
isYamlFileNotFoundException _ = False