{-# OPTIONS_GHC -Wno-warnings-deprecations #-}
{-# LANGUAGE OverloadedStrings #-}
module Spec.Client where

import           Data.Aeson              (Value (Null))
import           Data.ByteString         (ByteString)
import           Data.Text               (unpack)
import           Data.Text.Encoding      (decodeUtf8)
import           Network.HTTP.Client     (newManager)
import           Network.HTTP.Client.TLS (tlsManagerSettings)
import           Network.HTTP.Types      (urlEncode)
import           Test.Hspec              (Spec, describe, it, shouldContain,
                                          shouldNotContain, shouldThrow)
import           Web.OIDC.Client

import           Prelude                 hiding (exp)

clientId, clientSecret, redirectUri, nonce' :: ByteString
clientId = "dummy client id"
clientSecret = "dummy client secret"
redirectUri = "http://localhost"
nonce' = "dummy nonce"

tests :: Spec
tests = do
    describe "CodeFlow.getAuthenticationRequestUrl" $ do

        it "should return a url that has required parameters" $ do
            manager  <- newManager tlsManagerSettings
            provider <- discover google manager
            let oidc = setCredentials clientId clientSecret redirectUri $ newOIDC provider
            url <- getAuthenticationRequestUrl oidc [] Nothing []
            show url `shouldContain` "response_type=code"
            show url `shouldContain` "scope=openid"
            show url `shouldContain` (toES "client_id" ++ "=" ++ toES clientId)
            show url `shouldContain` (toES "redirect_uri" ++ "=" ++ toES redirectUri)
            show url `shouldNotContain` toES clientSecret

        it "should return a url that has other parameters" $ do
            manager  <- newManager tlsManagerSettings
            provider <- discover google manager
            let oidc = setCredentials clientId clientSecret redirectUri $ newOIDC provider
                state = "dummy state"
            url <- getAuthenticationRequestUrl oidc [email] (Just state) [("nonce", Just nonce')]
            show url `shouldContain` (toES "scope" ++ "=" ++ toES "openid email")
            show url `shouldContain` (toES "state" ++ "=" ++ toES state)
            show url `shouldContain` (toES "nonce" ++ "=" ++ toES nonce')

    describe "CodeFlow.validateClaims" $ do
        let issuer' = "http://localhost"
            clientId' = decodeUtf8 clientId
            createValidClaims now =
                IdTokenClaims
                    { iss = issuer'
                    , sub = "sub"
                    , aud = [clientId']
                    , exp = add 10 now
                    , iat = now
                    , nonce = Just nonce'
                    , otherClaims = Null
                    }
        it "should succeed at a validation of correct claims" $ do
            now <- getCurrentIntDate
            let claims' = createValidClaims now
            validateClaims issuer' clientId' now (Just nonce') claims'
            validateClaims issuer' clientId' now (Just nonce') claims' { aud = ["other id", clientId'] }

        it "should throw ValidationException if 'iss' field is invalid" $ do
            now <- getCurrentIntDate
            let claims' = createValidClaims now
            validateClaims issuer' clientId' now (Just nonce') claims' { iss = "http://localhost/hoge" }
                `shouldThrow` isValidationException

        it "should throw ValidationException if 'aud' field does not contain Client ID" $ do
            now <- getCurrentIntDate
            let claims' = createValidClaims now
            validateClaims issuer' clientId' now (Just nonce') claims' { aud = ["other id"] }
                `shouldThrow` isValidationException

        it "should throw ValidationException if 'exp' field expired" $ do
            now <- getCurrentIntDate
            let claims' = createValidClaims now
            validateClaims issuer' clientId' now (Just nonce') claims' { exp = add (-1) now }
                `shouldThrow` isValidationException

        it "should throw ValidationException if 'nonce' is not given" $ do
            now <- getCurrentIntDate
            let claims' = createValidClaims now
            validateClaims issuer' clientId' now (Just nonce') claims' { nonce = Nothing }
                `shouldThrow` isValidationException

        it "should throw ValidationException if 'nonce' is invalid" $ do
            now <- getCurrentIntDate
            let claims' = createValidClaims now
            validateClaims issuer' clientId' now (Just nonce') claims' { nonce = Just "other nonce" }
                `shouldThrow` isValidationException

  where
    toES = unpack . decodeUtf8 . urlEncode True
    add sec (IntDate t) = IntDate $ t + sec
    isValidationException e = case e of
        (ValidationException _) -> True
        _                       -> False
