# LANGUAGE TypeOperators #
# LANGUAGE DataKinds #
{-# LANGUAGE OverloadedStrings #-}
# LANGUAGE DeriveGeneric #
# LANGUAGE QuasiQuotes #
# LANGUAGE StandaloneDeriving #
# OPTIONS_GHC -fno - warn - orphans #

module Main (main) where

import           Test.Hspec
import           Test.Hspec.Wai
import           Test.Hspec.Wai.JSON
import           Test.Hspec.Wai.QuickCheck
import           Test.QuickCheck (genericShrink)

import           Data.Aeson hiding (json)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Builder as BSB
import           Data.Hashable
import qualified Data.Scientific as S
import qualified Data.Text as T
import qualified Data.HashMap.Strict as HMS
import qualified Data.Vector as V
import           Data.Traversable
import           GHC.Generics (Generic)
import           Network.HTTP.Types.URI
import           Network.Wai.Test

import           Solga

main :: IO ()
main = hspec spec

data TestAPI = TestAPI
  { basic :: "basic" /> Get T.Text
  , echoJSON :: "echo-json" /> ReqBodyJSON Value :> Post Value
  , internalError :: "fubar" /> Get T.Text
  , echoCapture :: "echo-capture" /> Capture T.Text :> Get T.Text
  } deriving (Generic)
instance Router TestAPI

testAPI :: TestAPI
testAPI = TestAPI
  { basic = brief (return "basic")
  , echoJSON = brief return
  , internalError = brief (return $ error "quality programming")
  , echoCapture = brief return
  }

spec :: Spec
spec = with (return $ serve testAPI) $ do
  -- tests basic routing
  describe "GET /basic" $ do
    it "responds with 200" $ do
      get "/basic" `shouldRespondWith` 200

    it "responds with 404 for the wrong method" $ do
      post "/basic" "" `shouldRespondWith` 404

    it "responds with \"basic\"" $ do
      get "/basic" `shouldRespondWith` [json|"basic"|]

  describe "GET /doesnt-exist" $
    it "responds with 404" $ do
      get "/doesnt-exist" `shouldRespondWith` 404

  tests ReqBodyJSON and JSON
  describe "POST /echo-json" $ do
    it "responds with 200" $
      post "/echo-json" [json|"test"|] `shouldRespondWith` 200

    it "responds with correct content type" $
      post "/echo-json" [json|"test"|] `shouldRespondWith`
        200 { matchHeaders = ["Content-Type" <:> "application/json"] }

    it "responds with same JSON" $ property $ \val -> do
      resp <- post "/echo-json" (encode val)
      liftIO $ decode (simpleBody resp) `shouldBe` Just (val :: Value)

  -- tests exception handling
  describe "GET /fubar" $ do
    it "responds with 500" $
      get "/fubar" `shouldRespondWith` 500

  -- tests Capture
  describe "GET /echo-capture" $ do
    it "responds with 200" $
      get "/echo-capture/test" `shouldRespondWith` 200

    it "responds with captured segment" $ property $ \seg -> do
      let path = LBS.toStrict $ BSB.toLazyByteString $ encodePathSegments [ "echo-capture", seg ]
      resp <- get path
      liftIO $ decode (simpleBody resp) `shouldBe` Just (String seg)

deriving instance Generic Value

instance Arbitrary Value where
  arbitrary = sized arbJSON
   where
    arbJSON :: Int -> Gen Value
    arbJSON n
      | n == 0 = oneof leaves
      | otherwise = oneof (leaves ++ branches (arbJSON (n `div` 4)))
    leaves =
      [ String <$> arbitrary
      , Number <$> arbitrary
      , Bool <$> arbitrary
      , pure Null
      ]
    branches child =
      [ do
          values <- scale (`div` 4) (listOf child)
          entries <- for values $ \val -> do
            key <- arbitrary
            return ( key, val )
          return $ Object $ HMS.fromList entries
      , (Array . V.fromList) <$> scale (`div` 4) (listOf child)
      ]
  shrink = genericShrink

instance (Eq key, Hashable key, Arbitrary key, Arbitrary value) => Arbitrary (HMS.HashMap key value) where
  arbitrary = HMS.fromList <$> arbitrary
  shrink = map HMS.fromList . shrink . HMS.toList

instance Arbitrary T.Text where
  arbitrary = T.pack <$> arbitrary
  shrink = map T.pack . shrink . T.unpack

instance Arbitrary a => Arbitrary (V.Vector a) where
  arbitrary = V.fromList <$> arbitrary
  shrink = map V.fromList . shrink . V.toList

instance Arbitrary S.Scientific where
  arbitrary = S.scientific <$> arbitrary <*> arbitrary
  shrink s = map (uncurry S.scientific) $ shrink $ ( S.coefficient s, S.base10Exponent s )
# LANGUAGE OverloadedStrings #
 tests basic routing
 tests exception handling
 tests Capture
# LANGUAGE TypeOperators #
# LANGUAGE DataKinds #
# LANGUAGE DeriveGeneric #
# LANGUAGE QuasiQuotes #
# LANGUAGE StandaloneDeriving #
# OPTIONS_GHC -fno - warn - orphans #

module Main (main) where

import           Test.Hspec
import           Test.Hspec.Wai
import           Test.Hspec.Wai.JSON
import           Test.Hspec.Wai.QuickCheck
import           Test.QuickCheck (genericShrink)

import           Data.Aeson hiding (json)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Builder as BSB
import           Data.Hashable
import qualified Data.Scientific as S
import qualified Data.Text as T
import qualified Data.HashMap.Strict as HMS
import qualified Data.Vector as V
import           Data.Traversable
import           GHC.Generics (Generic)
import           Network.HTTP.Types.URI
import           Network.Wai.Test

import           Solga

main :: IO ()
main = hspec spec

data TestAPI = TestAPI
  { basic :: "basic" /> Get T.Text
  , echoJSON :: "echo-json" /> ReqBodyJSON Value :> Post Value
  , internalError :: "fubar" /> Get T.Text
  , echoCapture :: "echo-capture" /> Capture T.Text :> Get T.Text
  } deriving (Generic)
instance Router TestAPI

testAPI :: TestAPI
testAPI = TestAPI
  { basic = brief (return "basic")
  , echoJSON = brief return
  , internalError = brief (return $ error "quality programming")
  , echoCapture = brief return
  }

spec :: Spec
spec = with (return $ serve testAPI) $ do
  describe "GET /basic" $ do
    it "responds with 200" $ do
      get "/basic" `shouldRespondWith` 200

    it "responds with 404 for the wrong method" $ do
      post "/basic" "" `shouldRespondWith` 404

    it "responds with \"basic\"" $ do
      get "/basic" `shouldRespondWith` [json|"basic"|]

  describe "GET /doesnt-exist" $
    it "responds with 404" $ do
      get "/doesnt-exist" `shouldRespondWith` 404

  tests ReqBodyJSON and JSON
  describe "POST /echo-json" $ do
    it "responds with 200" $
      post "/echo-json" [json|"test"|] `shouldRespondWith` 200

    it "responds with correct content type" $
      post "/echo-json" [json|"test"|] `shouldRespondWith`
        200 { matchHeaders = ["Content-Type" <:> "application/json"] }

    it "responds with same JSON" $ property $ \val -> do
      resp <- post "/echo-json" (encode val)
      liftIO $ decode (simpleBody resp) `shouldBe` Just (val :: Value)

  describe "GET /fubar" $ do
    it "responds with 500" $
      get "/fubar" `shouldRespondWith` 500

  describe "GET /echo-capture" $ do
    it "responds with 200" $
      get "/echo-capture/test" `shouldRespondWith` 200

    it "responds with captured segment" $ property $ \seg -> do
      let path = LBS.toStrict $ BSB.toLazyByteString $ encodePathSegments [ "echo-capture", seg ]
      resp <- get path
      liftIO $ decode (simpleBody resp) `shouldBe` Just (String seg)

deriving instance Generic Value

instance Arbitrary Value where
  arbitrary = sized arbJSON
   where
    arbJSON :: Int -> Gen Value
    arbJSON n
      | n == 0 = oneof leaves
      | otherwise = oneof (leaves ++ branches (arbJSON (n `div` 4)))
    leaves =
      [ String <$> arbitrary
      , Number <$> arbitrary
      , Bool <$> arbitrary
      , pure Null
      ]
    branches child =
      [ do
          values <- scale (`div` 4) (listOf child)
          entries <- for values $ \val -> do
            key <- arbitrary
            return ( key, val )
          return $ Object $ HMS.fromList entries
      , (Array . V.fromList) <$> scale (`div` 4) (listOf child)
      ]
  shrink = genericShrink

instance (Eq key, Hashable key, Arbitrary key, Arbitrary value) => Arbitrary (HMS.HashMap key value) where
  arbitrary = HMS.fromList <$> arbitrary
  shrink = map HMS.fromList . shrink . HMS.toList

instance Arbitrary T.Text where
  arbitrary = T.pack <$> arbitrary
  shrink = map T.pack . shrink . T.unpack

instance Arbitrary a => Arbitrary (V.Vector a) where
  arbitrary = V.fromList <$> arbitrary
  shrink = map V.fromList . shrink . V.toList

instance Arbitrary S.Scientific where
  arbitrary = S.scientific <$> arbitrary <*> arbitrary
  shrink s = map (uncurry S.scientific) $ shrink $ ( S.coefficient s, S.base10Exponent s )