packages feed

symbiote-0.0.3: test/Spec/Protocol.hs

{-# LANGUAGE
    MultiParamTypeClasses
  , TypeFamilies
  , FlexibleInstances
  , FlexibleContexts
  , OverloadedStrings
  #-}

module Spec.Protocol where

import ServerOrClient (ServerOrClient (..))

import qualified Data.Aeson as Json
import qualified Data.Aeson.Types as Json
import qualified Data.Serialize as Cereal
import qualified Data.ByteString as BS
import Data.Proxy
import Test.Serialization.Symbiote
  (SymbioteOperation (..), Generating, Operating, First, Second, Topic, SymbioteT, register)
import Test.Serialization.Symbiote.Debug (Debug (..))
import Test.Serialization.Symbiote.WebSocket
  ( firstPeerWebSocketJson, firstPeerWebSocketByteString
  , secondPeerWebSocketJson, secondPeerWebSocketByteString
  , WebSocketParams (..), WebSocketServerOrClient (..))
import Test.Serialization.Symbiote.ZeroMQ
  ( firstPeerZeroMQ, secondPeerZeroMQ, ZeroMQParams (..), ZeroMQServerOrClient (..))
import Test.Serialization.Symbiote.Aeson ()
import Test.Serialization.Symbiote.Cereal ()
import Test.QuickCheck (Arbitrary (..))
import Test.Tasty (TestTree, testGroup, askOption)
import Test.Tasty.HUnit (testCase)
import Network.WebSockets (runClient)
import Network.WebSockets.Connection (defaultConnectionOptions)
import Network.WebSockets.Simple (accept)
import Network.WebSockets.Trans (runServerAppT)
import Network.Wai (responseLBS)
import Network.Wai.Handler.WebSockets (websocketsOr)
import Network.Wai.Handler.Warp (run)
import Network.Wai.Middleware.RequestLogger (logStdoutDev)
import Network.HTTP.Types (status400)


protocolTests :: [TestTree]
protocolTests =
  [ askOption $ \serverOrClient -> askOption $ \network -> case serverOrClient of
      Server -> testGroup "WebSocket Server"
        [ testCase "Json" $
            secondPeerWebSocketJson
              WebSocketParams
                { runWebSocket = \client -> do
                    let server = accept client
                    server' <- runServerAppT server
                    run 3000 $ logStdoutDev $ websocketsOr defaultConnectionOptions server' $ \_ respond ->
                      respond $ responseLBS status400 [] "Not a websocket"
                , webSocketServerOrClient = WebSocketServer
                , webSocketNetwork = network
                }
              NoDebug jsonTests
        , testCase "ByteString" $
            secondPeerWebSocketByteString
              WebSocketParams
                { runWebSocket = \client -> do
                    let server = accept client
                    server' <- runServerAppT server
                    run 3001 $ logStdoutDev $ websocketsOr defaultConnectionOptions server' $ \_ respond ->
                      respond $ responseLBS status400 [] "Not a websocket"
                , webSocketServerOrClient = WebSocketServer
                , webSocketNetwork = network
                }
              NoDebug byteStringTests
        ]
      Client -> testGroup "WebSocket Client"
        [ testCase "Json" $
            firstPeerWebSocketJson
              WebSocketParams
                { runWebSocket = \client -> do
                    runClient "localhost" 3000 "/" client
                , webSocketServerOrClient = WebSocketClient
                , webSocketNetwork = network
                }
              NoDebug jsonTests
        , testCase "ByteString" $
            firstPeerWebSocketByteString
              WebSocketParams
                { runWebSocket = \client -> do
                    runClient "localhost" 3001 "/" client
                , webSocketServerOrClient = WebSocketClient
                , webSocketNetwork = network
                }
              NoDebug byteStringTests
        ]
  , askOption $ \serverOrClient -> askOption $ \network -> case serverOrClient of
      Server -> testGroup "ZeroMQ Server"
        [ testCase "ByteString" $
            secondPeerZeroMQ
              ZeroMQParams
                { zmqHost = "tcp://*:3002"
                , zmqServerOrClient = ZeroMQServer
                , zmqNetwork = network
                }
              NoDebug byteStringTests
        ]
      Client -> testGroup "ZeroMQ Client"
        [ testCase "ByteString" $
            firstPeerZeroMQ
              ZeroMQParams
                { zmqHost = "tcp://127.0.0.1:3002"
                , zmqServerOrClient = ZeroMQClient
                , zmqNetwork = network
                }
              NoDebug byteStringTests
        ]
  ]
  where
    jsonTests :: SymbioteT Json.Value IO ()
    jsonTests = do
      register "Generating Topic" 100 (Proxy :: Proxy (Generating Topic))
      register "Operating Topic" 100 (Proxy :: Proxy (Operating Topic))
      register "First Topic" 100 (Proxy :: Proxy (First Topic))
      register "Second Topic" 100 (Proxy :: Proxy (Second Topic))
      register "Topic" 100 (Proxy :: Proxy Topic)
    byteStringTests :: SymbioteT BS.ByteString IO ()
    byteStringTests = do
      register "Generating ByteString" 50 (Proxy :: Proxy (Generating BS.ByteString))
      register "Operating ByteString" 50 (Proxy :: Proxy (Operating BS.ByteString))
      register "First ByteString" 50 (Proxy :: Proxy (First BS.ByteString))
      register "Second ByteString" 50 (Proxy :: Proxy (Second BS.ByteString))
      register "Topic" 50 (Proxy :: Proxy Topic)

-- Internal instances
instance SymbioteOperation (Generating s) (Generating s) where
  data Operation (Generating s) = GeneratingId
  perform GeneratingId x = x
instance Arbitrary (Operation (Generating s)) where
  arbitrary = pure GeneratingId
instance Json.ToJSON (Operation (Generating s)) where
  toJSON GeneratingId = Json.String "id"
instance Json.FromJSON (Operation (Generating s)) where
  parseJSON (Json.String s)
    | s == "id" = pure GeneratingId
  parseJSON json = Json.typeMismatch "Operation (Generating s)" json
instance Cereal.Serialize (Operation (Generating s)) where
  put GeneratingId = Cereal.putWord8 0
  get = do
    x <- Cereal.getWord8
    case x of
      0 -> pure GeneratingId
      _ -> fail "Operation (Generating s)"

instance SymbioteOperation (Operating s) (Operating s) where
  data Operation (Operating s) = OperatingId
  perform OperatingId x = x
instance Arbitrary (Operation (Operating s)) where
  arbitrary = pure OperatingId
instance Json.ToJSON (Operation (Operating s)) where
  toJSON OperatingId = Json.String "id"
instance Json.FromJSON (Operation (Operating s)) where
  parseJSON (Json.String s)
    | s == "id" = pure OperatingId
  parseJSON json = Json.typeMismatch "Operation (Operating S)" json
instance Cereal.Serialize (Operation (Operating s)) where
  put OperatingId = Cereal.putWord8 0
  get = do
    x <- Cereal.getWord8
    case x of
      0 -> pure OperatingId
      _ -> fail "Operation (Operating s)"

instance SymbioteOperation (First s) (First s) where
  data Operation (First s) = FirstId
  perform FirstId x = x
instance Arbitrary (Operation (First s)) where
  arbitrary = pure FirstId
instance Json.ToJSON (Operation (First s)) where
  toJSON FirstId = Json.String "id"
instance Json.FromJSON (Operation (First s)) where
  parseJSON (Json.String s)
    | s == "id" = pure FirstId
  parseJSON json = Json.typeMismatch "Operation (First S)" json
instance Cereal.Serialize (Operation (First s)) where
  put FirstId = Cereal.putWord8 0
  get = do
    x <- Cereal.getWord8
    case x of
      0 -> pure FirstId
      _ -> fail "Operation (First s)"

instance SymbioteOperation (Second s) (Second s) where
  data Operation (Second s) = SecondId
  perform SecondId x = x
instance Arbitrary (Operation (Second s)) where
  arbitrary = pure SecondId
instance Json.ToJSON (Operation (Second s)) where
  toJSON SecondId = Json.String "id"
instance Json.FromJSON (Operation (Second s)) where
  parseJSON (Json.String s)
    | s == "id" = pure SecondId
  parseJSON json = Json.typeMismatch "Operation (Second S)" json
instance Cereal.Serialize (Operation (Second s)) where
  put SecondId = Cereal.putWord8 0
  get = do
    x <- Cereal.getWord8
    case x of
      0 -> pure SecondId
      _ -> fail "Operation (Second s)"

instance SymbioteOperation Topic Topic where
  data Operation Topic = TopicId
  perform TopicId x = x
instance Arbitrary (Operation Topic) where
  arbitrary = pure TopicId
instance Json.ToJSON (Operation Topic) where
  toJSON TopicId = Json.String "id"
instance Json.FromJSON (Operation Topic) where
  parseJSON (Json.String s)
    | s == "id" = pure TopicId
  parseJSON json = Json.typeMismatch "Operation Topic" json
instance Cereal.Serialize (Operation Topic) where
  put TopicId = Cereal.putWord8 0
  get = do
    x <- Cereal.getWord8
    case x of
      0 -> pure TopicId
      _ -> fail "Operation Topic"