packages feed

Spock-core-0.12.0.0: test/Web/Spock/SafeSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DoAndIfThenElse #-}
{-# LANGUAGE DeriveGeneric #-}
module Web.Spock.SafeSpec (spec) where

import Web.Spock.Core
import Web.Spock.FrameworkSpecHelper

import Control.Exception.Base
import Control.Monad
import Data.Aeson
import Data.Monoid
import GHC.Generics
import Network.HTTP.Types.Status
import Test.Hspec
import qualified Data.ByteString.Lazy.Char8 as BSLC
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Network.Wai as Wai
import qualified Test.Hspec.Wai as Test

data SampleJson
    = SampleJson
    { sampleJson :: T.Text
    } deriving (Show, Eq, Generic)

instance ToJSON SampleJson
instance FromJSON SampleJson

app :: SpockT IO ()
app =
    do get root $ text "root"
       get "verb-test" $ text "GET"
       post "verb-test" $ text "POST"
       getpost "verb-test-gp" $ text "GETPOST"
       put "verb-test" $ text "PUT"
       delete "verb-test" $ text "DELETE"
       patch "verb-test" $ text "PATCH"
       get "test-slash" $ text "ok"
       get "/test-noslash" $ text "ok"
       get ("param-test" <//> var) $ \(i :: Int) ->
           text $ "int" <> T.pack (show i)
       get ("param-test" <//> "static") $
           text "static"
       get ("cookie" <//> "single") $
           do setCookie "single" "test" defaultCookieSettings { cs_EOL = CookieValidFor 3600 }
              text "set"
       get ("cookie" <//> "multiple") $
           do setCookie "multiple1" "test1" defaultCookieSettings { cs_EOL = CookieValidFor 3600 }
              setCookie "multiple2" "test2" defaultCookieSettings { cs_EOL = CookieValidFor 3600 }
              text "set"
       get "set-header" $
           do setHeader "X-FooBar" "Baz"
              text "ok"
       get "set-multi-header" $
           do setHeader "Content-Language" "de"
              setHeader "Content-Language" "en"
              text "ok"
       get "get-params" $
           do gp <- paramsGet
              text (T.pack $ show gp)
       post "post-params" $
           do gp <- paramsPost
              text (T.pack $ show gp)
       post "json" $
           do jbody <- jsonBody'
              text (sampleJson jbody)
       post "raw-body" $
           do b <- body
              text (T.decodeUtf8 b)
       let subcomp x =
               "subcomponent" <//> x
           subsubcomp x =
               subcomp ("subcomponent2" <//> x)
       get (subcomp "foo") $ text "foo"
       get (subsubcomp "bar") $ text "bar"
       get "preferred-format" $
         do fmt <- preferredFormat
            case fmt of
              PrefHTML -> text "html"
              x -> text (T.pack (show x))
       get ("auth" <//> var <//> var) $ \user pass ->
           let checker user' pass' =
                   unless (user == user' && pass == pass') $
                   do setStatus status401
                      text "err"
           in requireBasicAuth "Foo" checker $ \() -> text "ok"
       hookRouteCustom "NOTIFY" ("notify" <//> var) $ \notification -> text notification
       hookAny GET $ text . T.intercalate "/"
       hookAnyCustom "MYVERB" $ text . T.intercalate "/"
       get ("wai" <//> wildcard) $ \_ ->
           respondApp dummyWai

dummyWai :: Wai.Application
dummyWai req respond =
    respond $ Wai.responseLBS status200 [] (BSLC.pack $ show $ Wai.pathInfo req)

routeRenderingSpec :: Spec
routeRenderingSpec =
    describe "Route Rendering" $
    do it "should work with argument-less routes" $
          do renderRoute "foo" `shouldBe` "/foo"
             renderRoute "/foo" `shouldBe` "/foo"
             renderRoute "/foo/" `shouldBe` "/foo"
             renderRoute ("foo" <//> "bar") `shouldBe` "/foo/bar"
       it "should work with routes with args" $
          do let r1 = var :: Var Int
             renderRoute r1 1 `shouldBe` "/1"
             let r2 = "blog" <//> (var :: Var Int)
             renderRoute r2 2 `shouldBe` "/blog/2"
             let r3 = "blog" <//> (var :: Var Int) <//> (var :: Var T.Text)
             renderRoute r3 2 "BIIM" `shouldBe` "/blog/2/BIIM"

ctxApp :: SpockT IO ()
ctxApp =
    prehook hook $
    do get "test" $ getContext >>= text
       post "test" $ getContext >>= text
    where
      hook =
          do sid <- header "X-ApiKey"
             case sid of
               Just s -> return s
               Nothing -> text "Missing ApiKey"

ctxSpec :: Spec
ctxSpec =
    describe "Contexts" $
    Test.with (spockAsApp $ spockT id ctxApp) $
    it "should work" $
       do Test.request "GET" "/test" [] "" `Test.shouldRespondWith` "Missing ApiKey"
          Test.request "GET" "/test" [("X-ApiKey", "foo")] "" `Test.shouldRespondWith` "foo"
          Test.request "POST" "/test" [("X-ApiKey", "foo")] "" `Test.shouldRespondWith` "foo"

spec :: Spec
spec =
    describe "SafeRouting" $
    do frameworkSpec (spockAsApp $ spockT id app)
       ctxSpec
       routeRenderingSpec
       sizeLimitSpec $ \lim -> spockAsApp $ spockConfigT (defaultSpockConfig { sc_maxRequestSize = Just lim }) id $
          post "size" $ body >>= bytes
       errorHandlerSpec $ spockAsApp $ spockConfigT specConfig id $ do
          get ("failing" <//> "route") $
            throw Overflow
          get ("user" <//> "error") $
            do setStatus status403
               text "UNAUTHORIZED"
  where
    specConfig = defaultSpockConfig { sc_errorHandler = errorHandler }
    errorHandler status = case statusCode status of
      500 -> text "SERVER ERROR"
      404 -> text "NOT FOUND"
      _ -> text "OTHER ERROR"