{-# LANGUAGE GADTs               #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
module URI.ByteStringTests (tests) where

-------------------------------------------------------------------------------
import qualified Blaze.ByteString.Builder  as BB
import           Control.Applicative       (Const (..))
import           Data.ByteString           (ByteString)
import qualified Data.ByteString.Char8     as B8
import           Data.Either
import           Data.Function.Compat      ((&))
import           Data.Functor.Identity     (Identity (..))
import qualified Data.Map.Strict           as M
import           Data.Monoid
import           Hedgehog
import qualified Hedgehog.Gen              as Gen
import           Safe                      (readMay)
import           Test.Tasty
import           Test.Tasty.Hedgehog
import           Test.Tasty.HUnit
-------------------------------------------------------------------------------
import           Prelude
-------------------------------------------------------------------------------
import           URI.ByteString
import           URI.ByteString.Generators
-------------------------------------------------------------------------------
import           URI.ByteStringQQTests     ()

infixr 4 .~
(.~) :: ((a -> Identity b) -> s -> Identity t) -> b -> s -> t
(.~) l b s = runIdentity (l (const (Identity b)) s)

infixl ^.
(^.) :: s -> ((a -> Const a a) -> s -> Const a s) -> a
s ^. l = getConst (l Const s)


tests :: TestTree
tests = testGroup "URI.Bytestring"
  [
    parseUriTests
  , uriParseErrorInstancesTests
  , lensTests
  , serializeURITests
  , normalizeURITests
  ]


-------------------------------------------------------------------------------
parseUriTests :: TestTree
parseUriTests = testGroup "parseUri"
  [
    testParses "http://www.example.org/" $
      URI (Scheme "http")
          (Just (Authority Nothing (Host "www.example.org") Nothing))
          "/"
          mempty
          Nothing
  , testParseHost "http://www.example.org" "www.example.org"
  -- IPV4
  , testParseHost "http://192.168.1.1" "192.168.1.1"
  -- IPV6
  , testParseHost "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]" "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"
  , testParseHost "http://[1080:0:0:0:8:800:200C:417A]" "1080:0:0:0:8:800:200C:417A"
  , testParseHost "http://[3ffe:2a00:100:7031::1]" "3ffe:2a00:100:7031::1"
  , testParseHost "http://[::192.9.5.5]" "::192.9.5.5"
  , testParseHost "http://[::FFFF:129.144.52.38]" "::FFFF:129.144.52.38"
  , testParseHost "http://[2010:836B:4179::836B:4179]" "2010:836B:4179::836B:4179"
  , testParseHost "http://[2010:836B:4179::836B:4179]" "2010:836B:4179::836B:4179"
  -- IPVFuture
  , testParseHost "http://[v1.fe80::a+en1]" "v1.fe80::a+en1"
  , testParses "https://user:pass:wo%20rd@www.example.org?foo=bar&foo=baz+quux#frag" $
      URI (Scheme "https")
          (Just (Authority (Just (UserInfo "user" "pass:wo rd")) (Host "www.example.org") Nothing))
          ""
          (Query [("foo", "bar"), ("foo", "baz quux")])
          (Just "frag")
  -- trailing &
  , testParses "http://www.example.org?foo=bar&" $
      URI (Scheme "http")
          (Just (Authority Nothing (Host "www.example.org") Nothing))
          ""
          (Query [("foo", "bar")])
          Nothing
  , testParses "http://www.google.com:80/aclk?sa=l&ai=CChPOVvnoU8fMDI_QsQeE4oGwDf664-EF7sq01HqV1MMFCAAQAigDUO3VhpcDYMnGqYvApNgPoAGq3vbiA8gBAaoEKE_QQwekDUoMeW9IQghV4HRuzL_l-7vVjlML559kix6XOcC1c4Tb9xeAB76hiR2QBwGoB6a-Gw&sig=AOD64_3Ulyu0DcDsc1AamOIxq63RF9u4zQ&rct=j&q=&ved=0CCUQ0Qw&adurl=http://www.aruba.com/where-to-stay/hotels-and-resorts%3Ftid%3D122"
      URI { uriScheme = Scheme {schemeBS = "http"}
          , uriAuthority = Just Authority {authorityUserInfo = Nothing, authorityHost = Host {hostBS = "www.google.com"}, authorityPort = Just (Port 80)}
          , uriPath = "/aclk"
          , uriQuery = Query {queryPairs =
              [("sa", "l")
              ,("ai", "CChPOVvnoU8fMDI_QsQeE4oGwDf664-EF7sq01HqV1MMFCAAQAigDUO3VhpcDYMnGqYvApNgPoAGq3vbiA8gBAaoEKE_QQwekDUoMeW9IQghV4HRuzL_l-7vVjlML559kix6XOcC1c4Tb9xeAB76hiR2QBwGoB6a-Gw")
              ,("sig", "AOD64_3Ulyu0DcDsc1AamOIxq63RF9u4zQ")
              ,("rct", "j")
              ,("q", "")
              ,("ved", "0CCUQ0Qw")
              ,("adurl", "http://www.aruba.com/where-to-stay/hotels-and-resorts?tid=122")
              ]}
          , uriFragment = Nothing
          }

  , testParseFailure "$$$$://www.example.org/" (MalformedScheme NonAlphaLeading)
  , testParses "http://www.example.org/foo#bar" $
      URI (Scheme "http")
          (Just (Authority Nothing (Host "www.example.org") Nothing))
          "/foo"
          mempty
          (Just "bar")
  , testParses "http://www.example.org/foo#" $
      URI (Scheme "http")
          (Just (Authority Nothing (Host "www.example.org") Nothing))
          "/foo"
          mempty
          (Just "")
  , testParseFailure "http://www.example.org/foo#bar#baz" MalformedFragment
  , testParseFailure "https://www.example.org?listParam[]=foo,bar" MalformedQuery
  , testParsesLax "https://www.example.org?listParam[]=foo,bar" $
          URI (Scheme "https")
          (Just (Authority Nothing (Host "www.example.org") Nothing))
          ""
          (Query [("listParam[]", "foo,bar")])
          Nothing

  , testParses "https://www.example.org?listParam%5B%5D=foo,bar" $
      URI (Scheme "https")
          (Just (Authority Nothing (Host "www.example.org") Nothing))
          ""
          (Query [("listParam[]", "foo,bar")])
          Nothing

  , testParses "https://www.example.org#only-fragment" $
      URI (Scheme "https")
          (Just (Authority Nothing (Host "www.example.org") Nothing))
          ""
          (Query [])
          (Just "only-fragment")
  ,  testParses "https://www.example.org/weird%20path" $
       URI (Scheme "https")
           (Just (Authority Nothing (Host "www.example.org") Nothing))
           "/weird path"
           (Query [])
           Nothing

  , parseTestURI strictURIParserOptions "http://www.example.org/." $
      Right $ URI
          (Scheme "http")
          (Just (Authority Nothing (Host "www.example.org") Nothing))
          "/."
          (Query [])
          Nothing
  , parseTestURI strictURIParserOptions "http:/." $
      Right $ URI
          (Scheme "http")
          Nothing
          "/."
          (Query [])
          Nothing

  , roundtripTestURI strictURIParserOptions "ftp://ftp.is.co.za/rfc/rfc1808.txt"
  , roundtripTestURI strictURIParserOptions "http://www.ietf.org/rfc/rfc2396.txt"
  , roundtripTestURI strictURIParserOptions "mailto:John.Doe@example.com"
  , roundtripTestURI strictURIParserOptions "news:comp.infosystems.www.servers.unix"
  , roundtripTestURI strictURIParserOptions "tel:+1-816-555-1212"
  , roundtripTestURI strictURIParserOptions "telnet://192.0.2.16:80/"

  -- RFC 3986, Section 4.2
  , parseTestRelativeRef strictURIParserOptions "verysimple" $
      Right $ RelativeRef
          Nothing
          "verysimple"
          (Query [])
          Nothing
  , parseTestRelativeRef strictURIParserOptions "this:that/thap/sub?1=2" $
      Left $ MalformedPath
  , parseTestRelativeRef strictURIParserOptions "./this:that/thap/sub?1=2" $
      Right $ RelativeRef
          Nothing
          "./this:that/thap/sub"
          (Query [("1", "2")])
          Nothing
  ]


-------------------------------------------------------------------------------
uriParseErrorInstancesTests :: TestTree
uriParseErrorInstancesTests = testGroup "URIParseError instances"
  [
    testProperty "roundtrips between Show and Read" $ property $ do
      parseError <- forAll genURIParseError
      trippingShow parseError
  ]


-------------------------------------------------------------------------------
lensTests :: TestTree
lensTests = testGroup "lenses"
  [
    testProperty "schemeBSL Lens" $ property $ do
      wrapped <- forAll genScheme
      Scheme bs' <- forAll genScheme
      (wrapped ^. schemeBSL) === schemeBS wrapped
      (wrapped & schemeBSL .~ bs') === wrapped { schemeBS = bs'}
  , testProperty "hostBSL Lens" $ property $ do
      wrapped <- forAll genHost
      Host bs' <- forAll genHost
      (wrapped ^. hostBSL) === hostBS wrapped
      (wrapped & hostBSL .~ bs') === wrapped { hostBS = bs'}
  , testProperty "portNumberL Lens" $ property $ do
      wrapped <- forAll genPort
      Port n' <- forAll genPort
      (wrapped ^. portNumberL) === portNumber wrapped
      (wrapped & portNumberL .~ n') === wrapped { portNumber = n'}
  , testProperty "queryPairsL Lens" $ property $ do
      wrapped <- forAll genQuery
      Query ps' <- forAll genQuery
      wrapped ^. queryPairsL === queryPairs wrapped
      (wrapped & queryPairsL .~ ps') === wrapped { queryPairs = ps'}
  , testProperty "authorityUserInfoL Lens" $ property $ do
      authority <- forAll genAuthority
      userInfo <- forAll (Gen.maybe genUserInfo)
      (authority ^. authorityUserInfoL === authorityUserInfo authority)
      (authority & authorityUserInfoL .~ userInfo) === authority { authorityUserInfo = userInfo }
  , testProperty "authorityHostL Lens" $ property $ do
      authority <- forAll genAuthority
      host <- forAll genHost
      (authority ^. authorityHostL === authorityHost authority)
      (authority & authorityHostL .~ host) === authority { authorityHost = host }
  , testProperty "authorityPortL Lens" $ property $ do
      authority <- forAll genAuthority
      port <- forAll (Gen.maybe genPort)
      (authority ^. authorityPortL === authorityPort authority)
      (authority & authorityPortL .~ port) === authority { authorityPort = port }
  , testProperty "uiUsernameL Lens" $ property $ do
      ui <- forAll genUserInfo
      bs <- forAll genBS
      (ui ^. uiUsernameL === uiUsername ui)
      (ui & uiUsernameL .~ bs) === ui { uiUsername = bs }
  , testProperty "uiPasswordL Lens" $ property $ do
      ui <- forAll genUserInfo
      bs <- forAll genBS
      (ui ^. uiPasswordL === uiPassword ui)
      (ui & uiPasswordL .~ bs) === ui { uiPassword = bs }
  , testProperty "uriSchemeL Lens" $ property $ do
      uri <- forAll genAbsoluteURIRef
      x <- forAll genScheme
      uri ^. uriSchemeL === uriScheme uri
      (uri & uriSchemeL .~ x) === uri { uriScheme = x }
  , testProperty "authorityL Lens on URI" $ property $ do
      uri <- forAll genAbsoluteURIRef
      x <- forAll (Gen.maybe genAuthority)
      uri ^. authorityL === uriAuthority uri
      (uri & authorityL .~ x) === uri { uriAuthority = x }
  , testProperty "pathL Lens on URI" $ property $ do
      uri <- forAll genAbsoluteURIRef
      x <- forAll genBS
      uri ^. pathL === uriPath uri
      (uri & pathL .~ x) === uri { uriPath = x }
  , testProperty "queryL Lens on URI" $ property $ do
      uri <- forAll genAbsoluteURIRef
      x <- forAll genQuery
      uri ^. queryL === uriQuery uri
      (uri & queryL .~ x) === uri { uriQuery = x }
  , testProperty "fragmentL Lens on URI" $ property $ do
      uri <- forAll genAbsoluteURIRef
      x <- forAll (Gen.maybe genBS)
      uri ^. fragmentL === uriFragment uri
      (uri & fragmentL .~ x) === uri { uriFragment = x }
  , testProperty "authorityL Lens on relative ref" $ property $ do
      rr <- forAll genRelativeURIRef
      x <- forAll (Gen.maybe genAuthority)
      (rr ^. authorityL === rrAuthority rr)
      (rr & authorityL .~ x) === rr { rrAuthority = x }
  , testProperty "pathL Lens on relative ref" $ property $ do
      rr <- forAll genRelativeURIRef
      x <- forAll genBS
      rr ^. pathL === rrPath rr
      (rr & pathL .~ x) === rr { rrPath = x }
  , testProperty "queryL Lens on relative ref" $ property $ do
      rr <- forAll genRelativeURIRef
      x <- forAll genQuery
      rr ^. queryL === rrQuery rr
      (rr & queryL .~ x) === rr { rrQuery = x }
  , testProperty "fragmentL Lens on relative ref" $ property $ do
      rr <- forAll genRelativeURIRef
      x <- forAll (Gen.maybe genBS)
      rr ^. fragmentL === rrFragment rr
      (rr & fragmentL .~ x) === rr { rrFragment = x }
  ]


-------------------------------------------------------------------------------
testParses :: ByteString -> URI -> TestTree
testParses = testParses' strictURIParserOptions


-------------------------------------------------------------------------------
testParseHost :: ByteString -> ByteString -> TestTree
testParseHost uri expectedHost =
  testParses uri $
    URI (Scheme "http")
        (Just (Authority Nothing (Host expectedHost) Nothing))
        mempty
        mempty
        Nothing



-------------------------------------------------------------------------------
testParsesLax :: ByteString -> URI -> TestTree
testParsesLax = testParses' laxURIParserOptions


-------------------------------------------------------------------------------
testParses' :: URIParserOptions -> ByteString -> URI -> TestTree
testParses' opts s u = testGroup "testParses'"
    [ parseTestURI opts s $ Right u
    , parseTestRelativeRef opts (makeRelativeRefBS s) $ Right (makeRelativeRefTyped u)
    ]


-------------------------------------------------------------------------------
makeRelativeRefTyped :: URI -> RelativeRef
makeRelativeRefTyped (URI _ a p q f) = RelativeRef a p q f


-------------------------------------------------------------------------------
makeRelativeRefBS :: ByteString -> ByteString
makeRelativeRefBS s = B8.tail x
  where
    (_, x) = B8.break (==':') s


-------------------------------------------------------------------------------
testParseFailure :: ByteString -> URIParseError -> TestTree
testParseFailure s = parseTestURI strictURIParserOptions s . Left


-------------------------------------------------------------------------------
parseTestURI
    :: URIParserOptions
    -> ByteString
    -> Either URIParseError URI
    -> TestTree
parseTestURI opts s r = testCase (B8.unpack s) $ parseURI opts s @?= r


-------------------------------------------------------------------------------
roundtripTestURI
    :: URIParserOptions
    -> ByteString
    -> TestTree
roundtripTestURI opts s =
    testCase (B8.unpack s) $ (parseURI opts s >>= return . serializeURIRef') @?= Right s


-------------------------------------------------------------------------------
parseTestRelativeRef
    :: URIParserOptions
    -> ByteString
    -> Either URIParseError RelativeRef
    -> TestTree
parseTestRelativeRef opts s r =
  testCase (B8.unpack s) $ parseRelativeRef opts s @?= r


-------------------------------------------------------------------------------
serializeURITests :: TestTree
serializeURITests = testGroup "serializeURIRef"
  [
    testCase "renders userinfo correctly" $ do
       let ui = UserInfo "user" "pass"
       let uri = URI (Scheme "http")
                 (Just (Authority (Just ui) (Host "www.example.org") (Just port)))
                 "/"
                 (Query [("foo", "bar")])
                 (Just "somefragment")
       let res = BB.toLazyByteString (serializeURIRef uri)
       res @?= "http://user:pass@www.example.org:123/?foo=bar#somefragment"
  , testCase "encodes decoded paths" $ do
       let uri = URI (Scheme "http")
                 (Just (Authority Nothing (Host "www.example.org") (Just port)))
                 "/weird path"
                 (Query [])
                 Nothing
       let res = BB.toLazyByteString (serializeURIRef uri)
       res @?= "http://www.example.org:123/weird%20path"
  , testCase "encodes relative refs" $ do
      let ui = UserInfo "user" "pass"
      let uri = RelativeRef (Just (Authority (Just ui) (Host "www.example.org") (Just port)))
                "/"
                (Query [("foo", "bar")])
                (Just "somefragment")
      let res = BB.toLazyByteString (serializeURIRef uri)
      res @?= "//user:pass@www.example.org:123/?foo=bar#somefragment"
  ]
  where
    port = Port 123


-------------------------------------------------------------------------------
normalizeURITests :: TestTree
normalizeURITests = testGroup "normalization"
  [
    testCase "downcase schema" $ do
      normalizeURIBS o { unoDowncaseScheme = True } "hTtP://example.org" @?=
       "http://example.org"

  , testCase "downcase host" $ do
      normalizeURIBS o { unoDowncaseHost = True } "http://ExAmPlE.org" @?=
       "http://example.org"

  , testCase "drop default port http" $ do
      normalizeURIBS o { unoDropDefPort = True } "http://example.org:80" @?=
       "http://example.org"
  , testCase "drop default port https" $ do
      normalizeURIBS o { unoDropDefPort = True } "https://example.org:443" @?=
       "https://example.org"
  , testCase "drop default port no port" $ do
      normalizeURIBS o { unoDropDefPort = True } "http://example.org" @?=
       "http://example.org"
  , testCase "drop default port nondefault" $ do
      normalizeURIBS o { unoDropDefPort = True } "http://example.org:8000" @?=
       "http://example.org:8000"
  , testCase "drop default unknown schema" $ do
      normalizeURIBS o { unoDropDefPort = True } "bogus://example.org:9999" @?=
       "bogus://example.org:9999"
  , testCase "user-extensable port defaulting hit" $ do
      normalizeURIBS o { unoDropDefPort = True
                       , unoDefaultPorts = M.singleton (Scheme "ftp") (Port 21) }
                       "ftp://example.org:21" @?=
       "ftp://example.org"
  , testCase "user-extensable port defaulting off" $ do
      normalizeURIBS o { unoDropDefPort = False
                       , unoDefaultPorts = M.singleton (Scheme "ftp") (Port 21) }
                       "ftp://example.org:21" @?=
       "ftp://example.org:21"
  , testCase "user-extensable port defaulting miss" $ do
      normalizeURIBS o { unoDropDefPort = True
                       , unoDefaultPorts = M.singleton (Scheme "ftp") (Port 21) }
                       "http://example.org:80" @?=
       "http://example.org:80"

  , testCase "slash empty path" $ do
      normalizeURIBS o { unoSlashEmptyPath = True } "http://example.org" @?=
        "http://example.org/"
  , testCase "slash empty path with nonempty path" $ do
      normalizeURIBS o { unoSlashEmptyPath = True } "http://example.org/foo/bar" @?=
        "http://example.org/foo/bar"

  , testCase "drop redundant slashes" $ do
      normalizeURIBS o { unoDropExtraSlashes = True } "http://example.org/foo//bar///baz" @?=
        "http://example.org/foo/bar/baz"

  , testCase "sort params" $ do
      normalizeURIBS o { unoSortParameters = True } "http://example.org/foo?zulu=1&charlie=&alpha=1" @?=
        "http://example.org/foo?alpha=1&charlie=&zulu=1"

  , testCase "remove dot segments" $ do
      normalizeURIBS o { unoRemoveDotSegments = True } "http://example.org/a/b/c/./../../g" @?=
        "http://example.org/a/g"

  , testCase "percent encoding is upcased automatically" $ do
      normalizeURIBS o "http://example.org/a?foo%3abar=baz" @?=
        "http://example.org/a?foo%3Abar=baz"
  , testCase "aggressive normalization retains slashes (issue 41)" $ do
      normalizeURIBS aggressiveNormalization "http://example.org/" @?=
        "http://example.org/"
  ]
  where
    o = noNormalization
    normalizeURIBS opts bs = let Right x = parseURI laxURIParserOptions bs
                             in normalizeURIRef' opts x

trippingShow
  :: ( Show a
     , Read a
     , Eq a
     , MonadTest m
     )
  => a
  -> m ()
trippingShow a = tripping a show readMay
