servant-snap-0.8.3.2: test/Servant/StreamingSpec.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
-- | This module tests whether streaming works from client to server
-- with a server implemented with servant-server.
module Servant.StreamingSpec where
import Control.Concurrent
import Control.Exception hiding (Handler)
import Control.Monad.IO.Class
import qualified Data.ByteString as Strict
import Control.Monad.State (modify)
import qualified Data.ByteString.Lazy as Lazy
import Network.HTTP.Types
-- import Network.Wai
-- import Network.Wai.Internal
import Prelude
-- import Prelude.Compat
import Servant
import Snap.Core
import Snap.Internal.Http.Types
import Snap.Test
import Snap
import qualified System.Timeout
import qualified System.IO.Streams as IOS
import Test.Hspec
type TestAPI =
ReqBody '[OctetStream] Lazy.ByteString :> Post '[JSON] NoContent
testAPI :: Proxy TestAPI
testAPI = Proxy
spec :: Spec
spec = return ()
-- TODO: This test fails
{-
spec :: Spec
spec = do
-- The idea of this test is this:
--
-- - The mock client will
-- - send some data in the request body, but not all,
-- - wait for the server to acknowledge (outside of http, through an MVar)
-- that the server received some data,
-- - send the rest of the request body.
-- - The mock server will
-- - receive some data,
-- - notify the client that it received some data,
-- - receive the rest of the data,
-- - respond with an empty result.
it "client to server can stream lazy ByteStrings" $ timeout $ do
serverReceivedFirstChunk <- newWaiter
-- - streams some test data
-- - waits for serverReceivedFirstChunk
-- - streams some more test data
streamTestData <- do
mvar :: MVar [IO Strict.ByteString] <- newMVar $
map return (replicate 1000 "foo") ++
(waitFor serverReceivedFirstChunk >> return "foo") :
map return (replicate 1000 "foo")
return $ modifyMVar mvar $ \ actions -> case actions of
(a : r) -> (r, ) <$> a
[] -> return ([], "")
rqStream <- IOS.makeInputStream (Just <$> streamTestData)
rqStream2 <- IOS.makeInputStream (return (Just "foo"))
let makeRequest :: RequestBuilder IO ()
makeRequest = do
postRaw @IO "/" "application/octet-stream" "barbar"
-- transformRequestBody $ \inStream -> return rqStream
-- modify $ \rq -> rq { rqBody = rqStream }
-- - receives the first chunk
-- - notifies serverReceivedFirstChunk
-- - receives the rest of the request
let handler :: Lazy.ByteString -> Snap NoContent
handler input = do
liftIO $ putStrLn $ "handler Hello1"
-- rq <- getRequest
-- putRequest (rq { rqBody = rqStream })
-- transformRequestBody $ \_ -> return rqStream2
liftIO $ putStrLn $ "handler Hello2"
liftIO $ do
print "handler Hello3"
let prefix = Lazy.take 3 input
prefix `shouldBe` "foo"
print "handler Hello4"
notify serverReceivedFirstChunk ()
print "handler Hello5"
input `shouldBe` mconcat (replicate 2001 "foo")
return NoContent
app = serveSnap testAPI handler
putStrLn "Hello1"
response <- executeRequest rqStream makeRequest app
rspStatus response `shouldBe` 200
executeRequest :: IOS.InputStream Strict.ByteString -> RequestBuilder IO () -> Snap () -> IO Response
executeRequest rqStream makeRequest act = do
responseMVar <- newEmptyMVar
putStrLn "Hello2"
-- evalHandler (buildRequest get (Lazy.replicate "foo" 2001)) (act >> putMVar responseMVar response)
-- evalHandler makeRequest (act >> get >>= \r -> liftIO (putMVar responseMVar r))
evalHandler makeRequest $ do
liftIO $ putStrLn "Hello3"
-- rq <- getRequest
liftIO $ putStrLn "Hello3.5"
-- putRequest (rq { rqBody = rqStream })
transformRequestBody $ \_ -> return rqStream
liftIO $ putStrLn "Hello3.7"
act
liftIO $ putStrLn "Hello4"
r <- Snap.getResponse
liftIO (putMVar responseMVar r)
-- (act >> get >>= \r -> liftIO (putMVar responseMVar r))
-- let respond response = do
-- putMVar responseMVar response
-- return ResponseReceived
-- ResponseReceived <- app request respond
takeMVar responseMVar
-- executeRequest :: Application -> Request -> IO Response
-- executeRequest app request = do
-- responseMVar <- newEmptyMVar
-- let respond response = do
-- putMVar responseMVar response
-- return ResponseReceived
-- ResponseReceived <- app request respond
-- takeMVar responseMVar
timeout :: IO a -> IO a
timeout action = do
result <- System.Timeout.timeout 1000000 action
maybe (throwIO $ ErrorCall "timeout") return result
-- * waiter
data Waiter a
= Waiter {
notify :: a -> IO (),
waitFor :: IO a
}
newWaiter :: IO (Waiter a)
newWaiter = do
mvar <- newEmptyMVar
return $ Waiter {
notify = putMVar mvar,
waitFor = readMVar mvar
}
-}