packages feed

line-bot-sdk-0.4.0.0: test/Line/Bot/WebhookSpec.hs

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TypeOperators     #-}

module Line.Bot.WebhookSpec (spec) where

import qualified Crypto.Hash.SHA256        as SHA256
import           Data.Aeson                (Value, encode)
import           Data.Aeson.QQ
import           Data.Aeson.Types          (emptyObject)
import qualified Data.ByteString.Base64    as Base64
import           Line.Bot.Types            (ChannelSecret (..))
import           Line.Bot.Webhook          (Webhook)
import           Line.Bot.Webhook.Events   (Events)
import           Network.HTTP.Types        (HeaderName, hContentType)
import           Network.HTTP.Types.Method
import           Network.HTTP.Types.Status
import           Servant
import           Servant.Server            (Context ((:.), EmptyContext))
import           Test.Hspec                hiding (context)
import           Test.Hspec.Wai

hSignature :: HeaderName
hSignature = "X-Line-Signature"

secret :: ChannelSecret
secret = "shhhh"

context :: Context (ChannelSecret ': '[])
context = secret :. EmptyContext

app :: Application
app = serveWithContext (Proxy :: Proxy Webhook) context webhook

webhook :: Server Webhook
webhook = handleEvents

handleEvents :: Events -> Handler NoContent
handleEvents _ = return NoContent

testBody :: Value
testBody = [aesonQQ|
  {
    destination: "xxxxxxxxxx",
    events: [
      {
        replyToken: "8cf9239d56244f4197887e939187e19e",
        type: "follow",
        timestamp: 1462629479859,
        source: {
          type: "user",
          userId: "U4af4980629..."
        }
      }
    ]
  }
|]

spec :: Spec
spec = with (pure app) $ do
  describe "Webhook server" $ do

    it "should return 200 with a signed request" $ do
      let body    = encode testBody
          digest  = Base64.encode $ SHA256.hmaclazy (unChannelSecret secret) body
          headers = [(hContentType, "application/json"), (hSignature, digest)]

      request methodPost "/" headers body `shouldRespondWith` 200

    it "should return 400 for an invalid body" $ do
      let body    = encode emptyObject
          digest  = Base64.encode $ SHA256.hmaclazy (unChannelSecret secret) body
          headers = [(hContentType, "application/json"), (hSignature, digest)]

      request methodPost "/" headers body `shouldRespondWith` 400

    it "should return 401 for requests missing the signature header" $ do
      let body    = encode testBody
          headers = [(hContentType, "application/json")]

      request methodPost "/" headers body `shouldRespondWith` 401

    it "should return 401 when secret is incorrect" $ do
      let body    = encode testBody
          digest  = Base64.encode $ SHA256.hmaclazy "incorrect" body
          headers = [(hContentType, "application/json"), (hSignature, digest)]

      request methodPost "/" headers body `shouldRespondWith` 401