{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
module Hpack.Syntax.DependenciesSpec (spec) where

import           Helper

import           Data.Aeson.Config.FromValueSpec (shouldDecodeTo, shouldDecodeTo_)

import           Data.Aeson.Config.FromValue
import           Hpack.Syntax.DependencyVersion
import           Hpack.Syntax.Dependencies

left :: String -> Result Dependencies
left = Left

defaultInfo :: DependencyInfo
defaultInfo = DependencyInfo [] anyVersion

spec :: Spec
spec = do
  describe "parseDependency" $ do
    it "accepts dependencies" $ do
      parseDependency "dependency" "foo" `shouldReturn` ("foo", DependencyVersion Nothing AnyVersion)

    it "accepts dependencies with a subcomponent" $ do
      parseDependency "dependency" "foo:bar" `shouldReturn` ("foo:bar", DependencyVersion Nothing AnyVersion)

    it "accepts dependencies with multiple subcomponents" $ do
      parseDependency "dependency" "foo:{bar,baz}" `shouldReturn` ("foo:{bar,baz}", DependencyVersion Nothing AnyVersion)

  describe "fromValue" $ do
    context "when parsing Dependencies" $ do
      context "with a scalar" $ do
        it "accepts dependencies without constraints" $ do
          [yaml|
            hpack
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo)]

        it "accepts dependencies with constraints" $ do
          [yaml|
            hpack >= 2 && < 4
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange ">=2 && <4" })]

        context "with invalid constraint" $ do
          it "returns an error message" $ do
            [yaml|
              hpack ==
            |] `shouldDecodeTo` left "Error while parsing $ - invalid dependency \"hpack ==\""

      context "with a list" $ do
        it "accepts dependencies without constraints" $ do
          [yaml|
            - hpack
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo)]

        it "accepts dependencies with constraints" $ do
          [yaml|
            - hpack >= 2 && < 4
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange ">=2 && <4" })]

        it "accepts ^>=" $ do
          [yaml|
            - hpack ^>= 1.2.3.4
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange ">=1.2.3.4 && <1.3" })]

        it "accepts objects with name and version" $ do
          [yaml|
            - name: hpack
              version: 0.1.0
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==0.1.0" })]

        it "accepts git dependencies with version" $ do
          let source = Just (GitRef "https://github.com/sol/hpack" "master" Nothing)
          [yaml|
            - name: hpack
              version: 0.1.0
              git: https://github.com/sol/hpack
              ref: master
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = DependencyVersion source (VersionRange "==0.1.0") })]

        it "accepts git dependencies" $ do
          let source = Just (GitRef "https://github.com/sol/hpack" "master" Nothing)
          [yaml|
            - name: hpack
              git: https://github.com/sol/hpack
              ref: master
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = DependencyVersion source AnyVersion })]

        it "accepts github dependencies" $ do
          let source = Just (GitRef "https://github.com/sol/hpack" "master" Nothing)
          [yaml|
            - name: hpack
              github: sol/hpack
              ref: master
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = DependencyVersion source AnyVersion })]

        it "accepts an optional subdirectory for git dependencies" $ do
          let source = Just (GitRef "https://github.com/yesodweb/wai" "master" (Just "warp"))
          [yaml|
            - name: warp
              github: yesodweb/wai
              ref: master
              subdir: warp
          |] `shouldDecodeTo_` Dependencies [("warp", defaultInfo { dependencyInfoVersion = DependencyVersion source AnyVersion })]

        it "accepts local dependencies" $ do
          let source = Just (Local "../hpack")
          [yaml|
            - name: hpack
              path: ../hpack
          |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo {dependencyInfoVersion = DependencyVersion source AnyVersion })]

        context "when ref is missing" $ do
          it "produces accurate error messages" $ do
            [yaml|
              - name: hpack
                git: sol/hpack
                ef: master
            |] `shouldDecodeTo` left "Error while parsing $[0] - key \"ref\" not present"

        context "when both git and github are missing" $ do
          it "produces accurate error messages" $ do
            [yaml|
              - name: hpack
                gi: sol/hpack
                ref: master
            |] `shouldDecodeTo` left "Error while parsing $[0] - neither key \"git\" nor key \"github\" present"

      context "with a mapping from dependency names to constraints" $ do
        it "accepts dependencies without constraints" $ do
          [yaml|
            array:
          |] `shouldDecodeTo_` Dependencies [("array", defaultInfo)]

        it "rejects invalid values" $ do
          [yaml|
            hpack: []
          |] `shouldDecodeTo` left "Error while parsing $.hpack - expected Null, Object, Number, or String, but encountered Array"

        context "when the constraint is a Number" $ do
          it "accepts 1" $ do
            [yaml|
              hpack: 1
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==1" })]

          it "accepts 1.0" $ do
            [yaml|
              hpack: 1.0
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==1.0" })]

          it "accepts 0.11" $ do
            [yaml|
              hpack: 0.11
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==0.11" })]

          it "accepts 0.110" $ do
            [yaml|
              hpack: 0.110
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==0.110" })]

          it "accepts 1e2" $ do
            [yaml|
              hpack: 1e2
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==100" })]

        context "when the constraint is a String" $ do
          it "accepts version ranges" $ do
            [yaml|
              hpack: '>=2'
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange ">=2" })]

          it "accepts specific versions" $ do
            [yaml|
              hpack: 0.10.8.2
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==0.10.8.2" })]

          it "accepts wildcard versions" $ do
            [yaml|
              hpack: 2.*
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==2.*" })]

          it "accepts ^>=" $ do
            [yaml|
              hpack: ^>= 1.2.3.4
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange ">=1.2.3.4 && <1.3" })]

          it "reports parse errors" $ do
            [yaml|
              hpack: foo
            |] `shouldDecodeTo` left "Error while parsing $.hpack - invalid constraint \"foo\""

        context "when the constraint is an Object" $ do
          it "accepts explicit version field" $ do
            [yaml|
            hpack:
              version: 0.1.0
            |] `shouldDecodeTo_` Dependencies [("hpack", defaultInfo { dependencyInfoVersion = versionRange "==0.1.0" })]

          it "accepts github dependencies" $ do
            let source = Just (GitRef "https://github.com/haskell/cabal" "d53b6e0d908dfedfdf4337b2935519fb1d689e76" (Just "Cabal"))
            [yaml|
              Cabal:
                github: haskell/cabal
                ref: d53b6e0d908dfedfdf4337b2935519fb1d689e76
                subdir: Cabal
            |] `shouldDecodeTo_` Dependencies [("Cabal", defaultInfo { dependencyInfoVersion = DependencyVersion source AnyVersion })]

          it "ignores names in nested hashes" $ do
            let source = Just (Local "somewhere")
            [yaml|
              outer-name:
                name: inner-name
                path: somewhere
            |] `shouldDecodeTo` Right (Dependencies [("outer-name", defaultInfo { dependencyInfoVersion = DependencyVersion source AnyVersion })], ["$.outer-name.name"])

          it "defaults to any version" $ do
            [yaml|
              foo: {}
            |] `shouldDecodeTo_` Dependencies [("foo", defaultInfo)]

          context "with a version key" $ do
            it "rejects objects" $ do
              [yaml|
                foo:
                  version: {}
              |] `shouldDecodeTo` left "Error while parsing $.foo.version - expected Null, Number, or String, but encountered Object"

            it "accepts a string" $ do
              [yaml|
                foo:
                  version: ">= 3.2.5 && < 3.3"
              |] `shouldDecodeTo_` Dependencies [("foo", defaultInfo { dependencyInfoVersion = versionRange ">=3.2.5 && <3.3" })]

            it "accepts a specific version as a number" $ do
              [yaml|
                foo:
                  version: 3.0
              |] `shouldDecodeTo_` Dependencies [("foo", defaultInfo { dependencyInfoVersion = versionRange "==3.0" })]

            it "accepts a specific version as a string" $ do
              [yaml|
                foo:
                  version: 3.0.2
              |] `shouldDecodeTo_` Dependencies [("foo", defaultInfo { dependencyInfoVersion = versionRange "==3.0.2" })]

          context "with mixin" $ do
            it "accepts a single value" $ do
              [yaml|
                foo:
                  mixin: (Foo as Bar)
              |] `shouldDecodeTo_` Dependencies [("foo", defaultInfo { dependencyInfoMixins = ["(Foo as Bar)"] })]

            it "accepts a list" $ do
              [yaml|
                foo:
                  mixin:
                    - (Foo as Bar)
                    - hiding (Spam)
              |] `shouldDecodeTo_` Dependencies [("foo", defaultInfo { dependencyInfoMixins = ["(Foo as Bar)", "hiding (Spam)"] })]
