packages feed

symantic-http-test-0.0.1.20190410: Hspec/Server/Error.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-missing-signatures #-}
module Hspec.Server.Error where

import Control.Monad (when)
import Data.Eq (Eq(..))
import Data.Int (Int)
import Prelude ((+))
import System.IO (IO)
import Test.Hspec.Wai (liftIO)
import Text.Show (Show(..))
import qualified Data.ByteString.Base64 as BS64
import qualified Network.HTTP.Types as HTTP
import qualified Network.Wai.Handler.Warp as Warp
import qualified Test.Hspec.Wai as Wai

import Symantic.HTTP
import Symantic.HTTP.Server
import Hspec.Utils

api =
 "good" </> "path" </> capture @Int "i"
                   <.> queryParams @Int "param"
                   <.> basicAuth @User "realm"
                   <.> body @Int @'[PlainText]
                   <.> post @Int @'[PlainText]
 <!>
 "unauthorized" </> basicAuth @UnauthorizedUser "realm"
                <.> post @Int @'[PlainText]

data User = User
instance ServerBasicAuth User where
	serverBasicAuth user pass =
		return $
			if user=="user"
			then if pass=="pass"
				then BasicAuth_Authorized User
				else BasicAuth_BadPassword
			else BasicAuth_NoSuchUser
data UnauthorizedUser = UnauthorizedUser
instance ServerBasicAuth UnauthorizedUser where
	serverBasicAuth user pass =
		return $
			if user=="user"
			then if pass=="pass"
				then BasicAuth_Unauthorized
				else BasicAuth_BadPassword
			else BasicAuth_NoSuchUser

srv = server api $
	route_good :!:
	route_unauthorized
	where
	route_good i _params User (ServerBodyArg b) = return (i+b)
	route_unauthorized UnauthorizedUser = return 0

warp :: IO ()
warp = Warp.run 8080 srv

hspec = testSpecs $ describe "Error" $ Wai.with (return srv) $ do
	describe "Path" $ do
		it "checks shorter path" $ do
			Wai.get "/good"
			 `Wai.shouldRespondWith` 404
		it "checks longer path" $ do
			Wai.get "/good/path/bad"
			 `Wai.shouldRespondWith` 404
	describe "BasicAuth" $ do
		it "can decode username and password" $ do
			Wai.request goodMethod goodURI goodHeaders goodBody
			 `Wai.shouldRespondWith` 200
		it "checks username" $ do
			Wai.request goodMethod goodURI
			 [ (HTTP.hAuthorization, "Basic "<>BS64.encode "no-such-user:pass")
			 , goodAccept
			 , goodContentType
			 ] goodBody
			 `Wai.shouldRespondWith` 401
		it "checks password" $ do
			Wai.request goodMethod goodURI
			 [ (HTTP.hAuthorization, "Basic "<>BS64.encode "user:wrong-pass")
			 , goodAccept
			 , goodContentType
			 ] goodBody
			 `Wai.shouldRespondWith` 401
		it "can deny access" $ do
			Wai.request goodMethod "/unauthorized" goodHeaders goodBody
			 `Wai.shouldRespondWith` 403
	describe "Priorities" $ do
		it "has 404 as its highest priority error (path)" $ do
			Wai.request badMethod badURI [badAuth, badAccept, badContentType] badBody
			`Wai.shouldRespondWith` 404
		it "has 405 as its second highest priority error (method)" $ do
			Wai.request badMethod badParam [badAuth, badAccept, badContentType] badBody
			`Wai.shouldRespondWith` 405
		it "has 401 as its third highest priority error (auth)" $ do
			Wai.request goodMethod badParam [badAuth, badAccept, badContentType] badBody
			`Wai.shouldRespondWith` 401
		it "has 406 as its fourth highest priority error (accept)" $ do
			Wai.request goodMethod badParam [goodAuth, badAccept, badContentType] badBody
			`Wai.shouldRespondWith` 406
		it "has 415 as its fifth highest priority error (content type)" $ do
			Wai.request goodMethod badParam [goodAuth, goodAccept, badContentType] badBody
			`Wai.shouldRespondWith` 415
		it "has 400 as its sixth highest priority error (query and body)" $ do
			badParamsRes <- Wai.request goodMethod badParam goodHeaders goodBody
			badBodyRes   <- Wai.request goodMethod goodURI  goodHeaders badBody
			
			-- Both bad body and bad params result in 400
			return badParamsRes `Wai.shouldRespondWith` 400
			return badBodyRes   `Wai.shouldRespondWith` 400
			
			-- Param check should occur before body checks
			badBothRes <- Wai.request goodMethod badParam
			 [goodAuth, goodAccept, goodContentType] badBody
			when (badBothRes /= badParamsRes) $ liftIO $
				expectationFailure $ "badParam + badBody /= badParam: "
				 <> show badBothRes <> ", " <> show badParamsRes
			when (badBothRes == badBodyRes) $ liftIO $
				expectationFailure $ "badParam + badBody == badBody: "
				 <> show badBothRes

badContentType  = (HTTP.hContentType, "application/json")
badAccept       = (HTTP.hAccept, "application/json")
badMethod       = HTTP.methodGet
badURI          = "bad"
badBody         = "bad"
badAuth         = (HTTP.hAuthorization, "Basic foofoofoo")
goodContentType = (HTTP.hContentType, "text/plain;charset=utf-8")
goodAccept      = (HTTP.hAccept, "text/plain")
goodMethod      = HTTP.methodPost
goodPath        = "good/path/4"
goodURI         = goodPath<>"?param=2"
badParam        = goodPath<>"?param=foo"
goodBody        = "42" -- {-encode-} (42::Int)
goodAuth        = (HTTP.hAuthorization, "Basic "<>BS64.encode "user:pass")
goodHeaders     = [goodAuth, goodAccept, goodContentType]