packages feed

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
  }
-}