packages feed

oauth2-jwt-bearer-0.0.1: test/Test/Network/OAuth2/JWT/Client/TestServer.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Test.Network.OAuth2.JWT.Client.TestServer (
    good
  , good3600
  , bad
  , withServer
  , withServerIO
  ) where

import           Control.Exception (bracket)
import qualified Control.Concurrent.Async as Async
import           Control.Monad.IO.Class (MonadIO (..))

import           Data.IORef (IORef)
import qualified Data.IORef as IORef
import           Data.Text (Text)
import qualified Data.Text as Text
import           Data.Aeson ((.=))
import qualified Data.Aeson as Aeson
import qualified Data.Streaming.Network as Network

import           Hedgehog

import           Network.OAuth2.JWT.Client
import qualified Network.Socket as Socket
import qualified Network.Wai.Handler.Warp as Warp

import qualified Network.HTTP.Types as HTTP

import qualified Web.Spock.Core as Spock

good :: Int -> Text -> (HTTP.Status, Aeson.Value)
good expires token =
  (HTTP.status200, Aeson.object [
      "token_type" .= ("Bearer" :: Text)
    , "expires_in" .= expires
    , "access_token" .= token
    ])

good3600 :: (HTTP.Status, Aeson.Value)
good3600 =
  good 3600 "default-token-3600"

bad :: (HTTP.Status, Aeson.Value)
bad =
  (HTTP.status400, Aeson.object [
      "error" .= ("invalid_grant" :: Text)
    ])

routes :: IORef [(HTTP.Status, Aeson.Value)] -> Spock.SpockT IO ()
routes responses = do
  Spock.post "oauth/token" $ do
    mflow <- Spock.param "grant_type"
    massertion <- Spock.param "assertion"
    case (massertion, mflow) of
      (Just _assertion, Just "urn:ietf:params:oauth:grant-type:jwt-bearer") -> do
        (status, response) <- liftIO $ IORef.atomicModifyIORef responses $ \rs -> case rs of
          [] ->
            ([], good3600)
          (x:xs) ->
            (xs, x)
        Spock.setStatus status
        Spock.json response
      (_ :: Maybe Text, _ :: Maybe Text) -> do
        Spock.setStatus HTTP.status400
        Spock.json $ Aeson.object [
            "error" .= ("invalid_grant" :: Text)
          ]

withServer :: (MonadIO m, MonadTest m) => [(HTTP.Status, Aeson.Value)] -> (TokenEndpoint -> IO a) -> m a
withServer responses =
  evalIO . withServerIO responses

withServerIO :: [(HTTP.Status, Aeson.Value)] -> (TokenEndpoint -> IO a) -> IO a
withServerIO responses testing = do
  r <- IORef.newIORef responses
  app <- Spock.spockAsApp $ Spock.spockConfigT Spock.defaultSpockConfig id (routes r)
  Socket.withSocketsDo $
    bracket
      (Network.bindPortTCP 0 "127.0.0.1")
      Socket.close
      (\socket -> do
        name <- Socket.getSocketName socket
        case name of
          Socket.SockAddrInet port _ -> do
            Async.withAsync (Warp.runSettingsSocket Warp.defaultSettings socket app) $ \_ -> do
              testing . TokenEndpoint . mconcat $ ["http://localhost:", Text.pack . show $ port, "/oauth/token"]
          _ -> do
            error "<invariant> forcing inet above.")