project-m36-1.2.3: test/Server/WebSocket.hs
{-# LANGUAGE CPP, TypeApplications #-}
-- test the websocket server
import Test.HUnit
import qualified Network.WebSockets as WS
import ProjectM36.Server.WebSocket
import ProjectM36.Server.Config
import ProjectM36.Server
import ProjectM36.Client
import qualified Network.RPC.Curryer.Server as S
import Network.Socket
import Control.Exception
import Control.Concurrent
import System.Exit
import Data.Typeable
import Data.Text (Text)
import Data.Aeson
import qualified Data.ByteString.Lazy as BS
import ProjectM36.Relation
import Control.Monad (void)
import Data.UUID.V4 (nextRandom)
--start the websocket server
-- run some tutoriald against it
launchTestServer :: IO (PortNumber, DatabaseName)
launchTestServer = do
addressMVar <- newEmptyMVar
let config = defaultServerConfig { databaseName = testDatabaseName,
bindAddress = RemoteServerHostAddress "127.0.0.1" 0,
checkFS = False,
connConfig = S.UnencryptedConnectionConfig
}
testDatabaseName = "test"
-- start normal server
void $ forkIO (void $ launchServer config (Just addressMVar))
(SockAddrInet dbPort _) <- takeMVar addressMVar
let wsServerHost = "127.0.0.1"
wsServerPort = 8889
dbHost = "127.0.0.1"
-- start websocket server proxy -- runServer doesn't support returning an arbitrary socket
_ <- forkIO (WS.runServer wsServerHost wsServerPort (websocketProxyServer (fromIntegral dbPort) dbHost))
--wait for socket to be listening
waitForListenSocket 5 (fromIntegral wsServerPort)
pure (fromIntegral wsServerPort, testDatabaseName)
data TestException = WaitForSocketListenException
deriving (Show, Typeable)
instance Exception TestException
waitForListenSocket :: Int -> PortNumber -> IO ()
waitForListenSocket secondsToTry port =
if secondsToTry <= 0 then
throw WaitForSocketListenException
else do
--hostaddr <- inet_addr "127.0.0.1"
hostaddr:_ <- getAddrInfo Nothing (Just "127.0.0.1") (Just (show port))
sock <- socket AF_INET Stream defaultProtocol
let handler :: IOException -> IO ()
handler _ = do
threadDelay 1000000
waitForListenSocket (secondsToTry - 1) port
catch (connect sock (addrAddress hostaddr)) handler
main :: IO ()
main = do
(port, dbname) <- launchTestServer
tcounts <- runTestTT (testList port dbname)
if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
testList :: PortNumber -> DatabaseName -> Test
testList port dbName = TestList $ map (\f -> f port dbName) [testBasicConnection,
testTutorialD]
basicConnection :: PortNumber -> WS.ClientApp () -> IO ()
basicConnection port = WS.runClient "127.0.0.1" (fromIntegral port) "/"
basicConnectionWithDatabase :: PortNumber -> DatabaseName -> WS.ClientApp () -> IO ()
basicConnectionWithDatabase port dbname block = basicConnection port
(\conn -> do
--setup connection
let connectMsg = encode (ConnectionSetupRequest dbname)
WS.sendTextData conn connectMsg
setupResponse <- WS.receiveData conn
case eitherDecode setupResponse of
Left err -> error (show err)
Right ConnectionSetupResponse{} -> pure ()
Right _other -> error (show setupResponse)
block conn)
testBasicConnection :: PortNumber -> DatabaseName -> Test
testBasicConnection port _ = TestCase $ basicConnection port (\conn -> WS.sendClose conn (""::Text))
testTutorialD :: PortNumber -> DatabaseName -> Test
testTutorialD port dbname = TestCase $ basicConnectionWithDatabase port dbname testtutd
where
discardPromptInfo conn = do
response <- WS.receiveData conn :: IO BS.ByteString
case eitherDecode @Response response of
Right _promptInfo -> pure ()
Left err -> assertFailure ("failed to decode prompt info: " ++ err ++ ": " ++ show response)
testtutd conn = do
-- create new session at master
reqId <- RequestId <$> nextRandom
let createSessionMsg = encode (CreateSessionAtHeadRequest reqId "master")
WS.sendTextData conn createSessionMsg
sessionResponse <- WS.receiveData conn :: IO BS.ByteString
sessionId <- case eitherDecode sessionResponse of
Left err -> assertFailure err
Right (CreateSessionAtHeadResponse reqId' sid) -> do
assertEqual "request ID round-trip" reqId' reqId
pure sid
Right _other -> assertFailure ("expected session response but got: " <> show sessionResponse)
-- send tutd message
reqIdB <- RequestId <$> nextRandom
let tutdMsg = encode (ExecuteTutorialDRequest reqIdB sessionId jsonOnlyPresentation (TutorialDText ":showexpr true"))
WS.sendTextData conn tutdMsg
discardPromptInfo conn
--receive relation response
response <- WS.receiveData conn :: IO BS.ByteString
let decoded = decode @Response response
case decoded of
Nothing -> assertFailure "failed to decode"
Just (RelationResponse _ trueRel _) -> do
assertEqual "round-trip true relation" trueRel relationTrue
WS.sendClose conn ("" :: Text)
Just _other -> assertFailure ("unexpected response: " <> show response)