packages feed

slack-web-0.2.1.0: tests/Web/Slack/PagerSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Web.Slack.PagerSpec (spec) where

-- base
import Control.Exception (throwIO)
import Data.Maybe (fromJust)

-- fakepull
import Test.Pull.Fake.IO (FakeStream, pull, newFakeStream)

-- hspec
import Test.Hspec

-- slack-web
import Web.Slack.Pager
import Web.Slack.Common
  ( mkSlackTimestamp
  , Cursor (..)
  , Message (..)
  , MessageType (..)
  , SlackMessageText (..)
  )
import Web.Slack.Conversation
  ( ConversationId (ConversationId)
  , mkHistoryReq
  , mkRepliesReq
  , HistoryReq (..)
  , HistoryRsp (..)
  , RepliesReq (..)
  , ResponseMetadata (..)
  )
import Web.Slack.Types (UserId (..))

-- text
import qualified Data.Text as Text

-- time
import Data.Time.Clock (getCurrentTime, nominalDay, addUTCTime)


stubbedSendRequest :: FakeStream (Response HistoryRsp) -> a -> IO (Response HistoryRsp)
stubbedSendRequest stream _request = fromJust <$> pull stream


spec :: Spec
spec = do
  let prepare = do
        nowUtc <- getCurrentTime
        let now = mkSlackTimestamp nowUtc
            oldest = mkSlackTimestamp $ addUTCTime (nominalDay * negate 20) nowUtc
            messagesPerPage = 3
            allResponses :: [Response HistoryRsp]
            allResponses = do
              -- According to https://api.slack.com/docs/pagination,
              -- The last page's cursor can be either an empty string, null, or non-exisitent in the object.
              (pageN, cursor) <- zip [1..3] ["cursor1=", "cursor2=", ""]
              let pageNT = Text.pack (show pageN)
              pure . Right $ HistoryRsp
                { historyRspMessages = do
                    messageN <- [1..messagesPerPage]
                    let messagesPerPageNDT = fromIntegral messagesPerPage
                        messageNDT = fromIntegral messageN
                        messageNT = Text.pack (show messageN)
                        createdBefore = negate $ nominalDay * ((pageN - 1) * messagesPerPageNDT + messageNDT)
                    pure $ Message
                        MessageTypeMessage
                        (Just . UserId $ "U" <> pageNT <> messageNT)
                        (SlackMessageText $ "message " <> pageNT <> "-" <> messageNT)
                        (mkSlackTimestamp $ addUTCTime createdBefore nowUtc)
                , historyRspResponseMetadata = Just . ResponseMetadata . Just $ Cursor cursor
                }
        responsesToReturn <- newFakeStream allResponses
        return (now, oldest, messagesPerPage, allResponses, responsesToReturn)

  describe "conversationsHistoryAllBy" $
    it "collect all results by sending requests" $ do
      (now, oldest, messagesPerPage, allResponses, responsesToReturn) <- prepare
      let initialRequest = (mkHistoryReq (ConversationId "C01234567"))
            { historyReqCount = messagesPerPage
            , historyReqLatest = Just now
            , historyReqOldest = Just oldest
            , historyReqInclusive = False
            }
      loadPage <- conversationsHistoryAllBy (stubbedSendRequest responsesToReturn) initialRequest
      let actual = unfoldPageM $ either throwIO return =<< loadPage
      expected <- fmap (map historyRspMessages) . either throwIO return $ sequenceA allResponses
      actual `shouldReturn` expected

  describe "repliesFetchAllBy" $
    it "collect all results by sending requests" $ do
      (now, oldest, messagesPerPage, allResponses, responsesToReturn) <- prepare
      let initialRequest = (mkRepliesReq (ConversationId "C98765432") oldest)
            { repliesReqLimit = messagesPerPage
            , repliesReqLatest = Just now
            , repliesReqOldest = Just oldest
            , repliesReqInclusive = False
            }
      loadPage <- repliesFetchAllBy (stubbedSendRequest responsesToReturn) initialRequest
      let actual = unfoldPageM $ either throwIO return =<< loadPage
      expected <- fmap (map historyRspMessages) . either throwIO return $ sequenceA allResponses
      actual `shouldReturn` expected


-- | Runs the given action repeatedly until it returns an empty list.
unfoldPageM :: Monad m => m [a] -> m [[a]]
unfoldPageM act = reverse <$> go []
 where
  go accum = do
    x <- act
    case x of
        [] -> return accum
        xs -> go $! xs : accum