{-# LANGUAGE OverloadedStrings #-}

module BaseSpec where

import Web.Twitter.Conduit.Base
import Web.Twitter.Conduit.Response

import Control.Applicative
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.Conduit
import qualified Data.Conduit.Attoparsec as CA
import Data.Maybe
import qualified Data.Text as T
import qualified Network.HTTP.Types as HT

import Test.Hspec

spec :: Spec
spec = do
    unit

unit :: Spec
unit = do
    describe "checkResponse" $ do
        describe "when the response has \"errors\" key" $ do
            let errorMessage = fromJust . decode $ "{\"errors\":[{\"message\":\"Sorry, that page does not exist\",\"code\":34}]}"
                response = Response HT.status404 [] errorMessage
                result = checkResponse response

            it "returns TwitterErrorResponse" $ do
                case result of
                    Left res@(TwitterErrorResponse _ _ msgs) -> do
                        res `shouldBe` TwitterErrorResponse HT.status404 [] [TwitterErrorMessage 34 ""]
                        twitterErrorMessage (head msgs) `shouldBe` "Sorry, that page does not exist"
                    _ -> expectationFailure $ "Unexpected " ++ show result
        describe "when the response does not has \"errors\" key but have error status code" $ do
            let errorMessage = fromJust . decode $ "{}"
                response = Response HT.status404 [] errorMessage
                result = checkResponse response
            it "returns TwitterStatusError" $ do
                case result of
                    Left (TwitterStatusError st hdr body) -> do
                        st `shouldBe` HT.status404
                        hdr `shouldBe` []
                        body `shouldBe` errorMessage
                    _ -> expectationFailure $ "Unexpected " ++ show result

    describe "sinkJSON" $ do
        describe "when valid JSON input" $ do
            let input = "{\"test\": \"input\", \"status\": 200 }"
            it "can consume the input from Source and returns JSON Value" $ do
                res <- yield input $$ sinkJSON
                res ^. key "test" . _String `shouldBe` "input"
                res ^? key "status" . _Integer `shouldBe` Just 200
        describe "when invalid JSON input" $ do
            let input = "{]"
            it "should raise Data.Conduit.Attoparsec.ParseError" $ do
                let parseErrorException (CA.ParseError {}) = True
                    parseErrorException _ = False
                    action = yield input $$ sinkJSON
                action `shouldThrow` parseErrorException

    describe "sinkFromJSON" $ do
        describe "when valid JSON input" $ do
            let input = "{\"test\": \"input\", \"status\": 200 }"
            it "can consume the input from Source and returns a value which type is the specified one" $ do
                res <- yield input $$ sinkFromJSON
                res `shouldBe` TestJSON "input" 200
        describe "when the JSON value does not have expected format" $ do
            let input = "{\"status\": 200}"
            it "should raise FromJSONError" $ do
                let fromJSONException (FromJSONError {}) = True
                    fromJSONException _ = False
                    action :: IO TestJSON
                    action = yield input $$ sinkFromJSON
                action `shouldThrow` fromJSONException

data TestJSON = TestJSON
    { testField :: T.Text
    , testStatus :: Int
    }
    deriving (Show, Eq)
instance FromJSON TestJSON where
    parseJSON (Object o) =
        TestJSON <$> o .: "test"
            <*> o .: "status"
    parseJSON v = fail $ "Unexpected: " ++ show v
