{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} -- | The matrix client specification tests module Main (main) where import Control.Monad (void) import qualified Data.Aeson.Encode.Pretty as Aeson import qualified Data.ByteString.Lazy as BS import Data.Either (isLeft) import Data.Text (Text, pack) import Data.Time.Clock.System (SystemTime (..), getSystemTime) import Network.Matrix.Client import Network.Matrix.Internal import System.Environment (lookupEnv) import Test.Hspec main :: IO () main = do env <- fmap (fmap pack) <$> traverse lookupEnv ["HOMESERVER_URL", "PRIMARY_TOKEN", "SECONDARY_TOKEN"] runIntegration <- case env of [Just url, Just tok1, Just tok2] -> do sess1 <- createSession url (MatrixToken tok1) sess2 <- createSession url (MatrixToken tok2) pure $ integration sess1 sess2 _ -> do putStrLn "Skipping integration test" pure $ pure mempty hspec (parallel spec >> runIntegration) integration :: ClientSession -> ClientSession -> Spec integration sess1 sess2 = do describe "integration tests" $ do it "create room" $ do resp <- createRoom sess1 ( RoomCreateRequest { rcrPreset = PublicChat, rcrRoomAliasName = "test", rcrName = "matrix-client-haskell-test", rcrTopic = "Testing matrix-client-haskell" } ) case resp of Left err -> meError err `shouldBe` "Alias already exists" Right (RoomID room) -> room `shouldSatisfy` (/= mempty) it "join room" $ do resp <- joinRoom sess1 "#test:localhost" case resp of Left err -> error (show err) Right (RoomID room) -> room `shouldSatisfy` (/= mempty) resp' <- joinRoom sess2 "#test:localhost" case resp' of Left err -> error (show err) Right (RoomID room) -> room `shouldSatisfy` (/= mempty) it "send message and reply" $ do -- Flush previous events Right sr <- sync sess2 Nothing Nothing Nothing Nothing Right (room:_) <- getJoinedRooms sess1 let msg body = RoomMessageText $ MessageText body TextType Nothing Nothing let since = srNextBatch sr Right eventID <- sendMessage sess1 room (EventRoomMessage $ msg "Hello") (TxnID since) Right reply <- sendMessage sess2 room (EventRoomReply eventID $ msg "Hi!") (TxnID since) reply `shouldNotBe` eventID it "invite private room" $ do Right room <- createRoom sess1 $ RoomCreateRequest PrivateChat "private" "private-test" "A test" Right user <- getTokenOwner sess2 Right inviteResult <- inviteToRoom sess1 room user (Just "Welcome!") inviteResult `shouldBe` () spec :: Spec spec = describe "unit tests" $ do it "decode unknown" $ (decodeResp "" :: Either String (Either MatrixError String)) `shouldSatisfy` isLeft it "decode error" $ (decodeResp "{\"errcode\": \"TEST\", \"error\":\"a error\"}" :: Either String (Either MatrixError String)) `shouldBe` (Right . Left $ MatrixError "TEST" "a error" Nothing) it "decode response" $ decodeResp "{\"user_id\": \"@tristanc_:matrix.org\"}" `shouldBe` (Right . Right $ UserID "@tristanc_:matrix.org") it "decode reply" $ do resp <- decodeResp <$> BS.readFile "test/data/message-reply.json" case resp of Right (Right (EventRoomReply eventID (RoomMessageText message))) -> do eventID `shouldBe` EventID "$eventID" mtBody message `shouldBe` "> <@tristanc_:matrix.org> :hello\n\nHello there!" _ -> error $ show resp it "decode edit" $ do resp <- decodeResp <$> BS.readFile "test/data/message-edit.json" case resp of Right (Right (EventRoomEdit (eventID, RoomMessageText srcMsg) (RoomMessageText message))) -> do eventID `shouldBe` EventID "$eventID" mtBody srcMsg `shouldBe` " * > :typo" mtBody message `shouldBe` "> :hello" _ -> error $ show resp it "encode room message" $ encodePretty (RoomMessageText (MessageText "Hello" TextType Nothing Nothing)) `shouldBe` "{\"body\":\"Hello\",\"msgtype\":\"m.text\"}" it "does not retry on success" $ checkPause (<=) $ do let resp = Right True res <- retry (pure resp) res `shouldBe` resp it "does not retry on regular failre" $ checkPause (<=) $ do let resp = Left $ MatrixError "test" "error" Nothing res <- (retry (pure resp) :: MatrixIO Int) res `shouldBe` resp it "retry on rate limit failure" $ checkPause (>=) $ do let resp = Left $ MatrixError "M_LIMIT_EXCEEDED" "error" (Just 1000) (retryWithLog 1 (const $ pure ()) (pure resp) :: MatrixIO Int) `shouldThrow` rateLimitSelector where rateLimitSelector :: MatrixException -> Bool rateLimitSelector MatrixRateLimit = True checkPause op action = do MkSystemTime startTS _ <- getSystemTime void action MkSystemTime endTS _ <- getSystemTime (endTS - startTS) `shouldSatisfy` (`op` 1) encodePretty = Aeson.encodePretty' ( Aeson.defConfig {Aeson.confIndent = Aeson.Spaces 0, Aeson.confCompare = compare @Text} )