{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}

module Test.Syd.Wai.Def where

import GHC.Stack (HasCallStack)
import Network.HTTP.Client as HTTP
import Network.Socket (PortNumber)
import Network.Wai as Wai
import Network.Wai.Handler.Warp as Warp
import Test.Syd
import Test.Syd.Wai.Client

-- | Run a given 'Wai.Application' around every test.
--
-- This provides a 'WaiClient ()' which contains the port of the running application.
waiClientSpec :: Wai.Application -> TestDefM (HTTP.Manager ': outers) (WaiClient ()) result -> TestDefM outers oldInner result
waiClientSpec :: Application
-> TestDefM (Manager : outers) (WaiClient ()) result
-> TestDefM outers oldInner result
waiClientSpec Application
application = IO Application
-> TestDefM (Manager : outers) (WaiClient ()) result
-> TestDefM outers oldInner result
forall (outers :: [*]) result oldInner.
IO Application
-> TestDefM (Manager : outers) (WaiClient ()) result
-> TestDefM outers oldInner result
waiClientSpecWith (IO Application
 -> TestDefM (Manager : outers) (WaiClient ()) result
 -> TestDefM outers oldInner result)
-> IO Application
-> TestDefM (Manager : outers) (WaiClient ()) result
-> TestDefM outers oldInner result
forall a b. (a -> b) -> a -> b
$ Application -> IO Application
forall (f :: * -> *) a. Applicative f => a -> f a
pure Application
application

-- | Run a given 'Wai.Application', as built by the given action, around every test.
waiClientSpecWith :: IO Application -> TestDefM (HTTP.Manager ': outers) (WaiClient ()) result -> TestDefM outers oldInner result
waiClientSpecWith :: IO Application
-> TestDefM (Manager : outers) (WaiClient ()) result
-> TestDefM outers oldInner result
waiClientSpecWith IO Application
application = (Manager -> oldInner -> SetupFunc (Application, ()))
-> TestDefM (Manager : outers) (WaiClient ()) result
-> TestDefM outers oldInner result
forall oldInner env (outers :: [*]) result.
(Manager -> oldInner -> SetupFunc (Application, env))
-> TestDefM (Manager : outers) (WaiClient env) result
-> TestDefM outers oldInner result
waiClientSpecWithSetupFunc (\Manager
_ oldInner
_ -> IO (Application, ()) -> SetupFunc (Application, ())
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Application, ()) -> SetupFunc (Application, ()))
-> IO (Application, ()) -> SetupFunc (Application, ())
forall a b. (a -> b) -> a -> b
$ (,) (Application -> () -> (Application, ()))
-> IO Application -> IO (() -> (Application, ()))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO Application
application IO (() -> (Application, ())) -> IO () -> IO (Application, ())
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ())

-- | Run a given 'Wai.Application', as built by the given 'SetupFunc', around every test.
waiClientSpecWithSetupFunc ::
  (HTTP.Manager -> oldInner -> SetupFunc (Application, env)) ->
  TestDefM (HTTP.Manager ': outers) (WaiClient env) result ->
  TestDefM outers oldInner result
waiClientSpecWithSetupFunc :: (Manager -> oldInner -> SetupFunc (Application, env))
-> TestDefM (Manager : outers) (WaiClient env) result
-> TestDefM outers oldInner result
waiClientSpecWithSetupFunc Manager -> oldInner -> SetupFunc (Application, env)
setupFunc = TestDefM (Manager : outers) oldInner result
-> TestDefM outers oldInner result
forall (outers :: [*]) inner result.
TestDefM (Manager : outers) inner result
-> TestDefM outers inner result
managerSpec (TestDefM (Manager : outers) oldInner result
 -> TestDefM outers oldInner result)
-> (TestDefM (Manager : outers) (WaiClient env) result
    -> TestDefM (Manager : outers) oldInner result)
-> TestDefM (Manager : outers) (WaiClient env) result
-> TestDefM outers oldInner result
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Manager -> oldInner -> SetupFunc (Application, env))
-> TestDefM (Manager : outers) (WaiClient env) result
-> TestDefM (Manager : outers) oldInner result
forall oldInner env (outers :: [*]) result.
(Manager -> oldInner -> SetupFunc (Application, env))
-> TestDefM (Manager : outers) (WaiClient env) result
-> TestDefM (Manager : outers) oldInner result
waiClientSpecWithSetupFunc' Manager -> oldInner -> SetupFunc (Application, env)
setupFunc

-- | Run a given 'Wai.Application', as built by the given 'SetupFunc', around every test.
--
-- This function doesn't set up the 'HTTP.Manager' like 'waiClientSpecWithSetupFunc' does.
waiClientSpecWithSetupFunc' ::
  (HTTP.Manager -> oldInner -> SetupFunc (Application, env)) ->
  TestDefM (HTTP.Manager ': outers) (WaiClient env) result ->
  TestDefM (HTTP.Manager ': outers) oldInner result
waiClientSpecWithSetupFunc' :: (Manager -> oldInner -> SetupFunc (Application, env))
-> TestDefM (Manager : outers) (WaiClient env) result
-> TestDefM (Manager : outers) oldInner result
waiClientSpecWithSetupFunc' Manager -> oldInner -> SetupFunc (Application, env)
setupFunc = (Manager -> oldInner -> SetupFunc (WaiClient env))
-> TestDefM (Manager : outers) (WaiClient env) result
-> TestDefM (Manager : outers) oldInner result
forall (outers :: [*]) outer oldInner newInner result.
HContains outers outer =>
(outer -> oldInner -> SetupFunc newInner)
-> TestDefM outers newInner result
-> TestDefM outers oldInner result
setupAroundWith' ((Manager -> oldInner -> SetupFunc (WaiClient env))
 -> TestDefM (Manager : outers) (WaiClient env) result
 -> TestDefM (Manager : outers) oldInner result)
-> (Manager -> oldInner -> SetupFunc (WaiClient env))
-> TestDefM (Manager : outers) (WaiClient env) result
-> TestDefM (Manager : outers) oldInner result
forall a b. (a -> b) -> a -> b
$ \Manager
man oldInner
oldInner -> do
  (Application
application, env
env) <- Manager -> oldInner -> SetupFunc (Application, env)
setupFunc Manager
man oldInner
oldInner
  Manager -> Application -> env -> SetupFunc (WaiClient env)
forall env.
Manager -> Application -> env -> SetupFunc (WaiClient env)
waiClientSetupFunc Manager
man Application
application env
env

-- | A 'SetupFunc' for a 'WaiClient', given an 'Application' and user-defined @env@.
waiClientSetupFunc :: HTTP.Manager -> Application -> env -> SetupFunc (WaiClient env)
waiClientSetupFunc :: Manager -> Application -> env -> SetupFunc (WaiClient env)
waiClientSetupFunc Manager
man Application
application env
env = do
  PortNumber
p <- Application -> SetupFunc PortNumber
applicationSetupFunc Application
application
  let client :: WaiClient env
client =
        WaiClient :: forall env. Manager -> env -> PortNumber -> WaiClient env
WaiClient
          { waiClientManager :: Manager
waiClientManager = Manager
man,
            waiClientEnv :: env
waiClientEnv = env
env,
            waiClientPort :: PortNumber
waiClientPort = PortNumber
p
          }
  WaiClient env -> SetupFunc (WaiClient env)
forall (f :: * -> *) a. Applicative f => a -> f a
pure WaiClient env
client

-- | Define a test in the 'WaiClientM site' monad instead of 'IO'.
--
-- Example usage:
--
-- > waiClientSpec exampleApplication $ do
-- >   describe "/" $ do
-- >     wit "works with a get" $
-- >       get "/" `shouldRespondWith` 200
-- >     wit "works with a post" $
-- >       post "/" `shouldRespondWith` ""
wit ::
  forall env e outers.
  ( HasCallStack,
    IsTest (WaiClient env -> IO e),
    Arg1 (WaiClient env -> IO e) ~ (),
    Arg2 (WaiClient env -> IO e) ~ WaiClient env
  ) =>
  String ->
  WaiClientM env e ->
  TestDefM outers (WaiClient env) ()
wit :: String -> WaiClientM env e -> TestDefM outers (WaiClient env) ()
wit String
s WaiClientM env e
f = String
-> (WaiClient env -> IO e) -> TestDefM outers (WaiClient env) ()
forall (outers :: [*]) inner test.
(HasCallStack, IsTest test, Arg1 test ~ (), Arg2 test ~ inner) =>
String -> test -> TestDefM outers inner ()
it String
s ((\WaiClient env
cenv -> WaiClient env -> WaiClientM env e -> IO e
forall env a. WaiClient env -> WaiClientM env a -> IO a
runWaiClientM WaiClient env
cenv WaiClientM env e
f) :: WaiClient env -> IO e)

-- | Run a given 'Wai.Application' around every test.
--
-- This provides the port on which the application is running.
waiSpec :: Wai.Application -> TestDef outers PortNumber -> TestDef outers ()
waiSpec :: Application -> TestDef outers PortNumber -> TestDef outers ()
waiSpec Application
application = SetupFunc Application
-> TestDef outers PortNumber -> TestDef outers ()
forall (outers :: [*]).
SetupFunc Application
-> TestDef outers PortNumber -> TestDef outers ()
waiSpecWithSetupFunc (SetupFunc Application
 -> TestDef outers PortNumber -> TestDef outers ())
-> SetupFunc Application
-> TestDef outers PortNumber
-> TestDef outers ()
forall a b. (a -> b) -> a -> b
$ Application -> SetupFunc Application
forall (f :: * -> *) a. Applicative f => a -> f a
pure Application
application

-- | Run a 'Wai.Application' around every test by setting it up with the given setup function.
--
-- This provides the port on which the application is running.
waiSpecWith :: (forall r. (Application -> IO r) -> IO r) -> TestDef outers PortNumber -> TestDef outers ()
waiSpecWith :: (forall r. (Application -> IO r) -> IO r)
-> TestDef outers PortNumber -> TestDef outers ()
waiSpecWith forall r. (Application -> IO r) -> IO r
appFunc = SetupFunc Application
-> TestDef outers PortNumber -> TestDef outers ()
forall (outers :: [*]).
SetupFunc Application
-> TestDef outers PortNumber -> TestDef outers ()
waiSpecWithSetupFunc (SetupFunc Application
 -> TestDef outers PortNumber -> TestDef outers ())
-> SetupFunc Application
-> TestDef outers PortNumber
-> TestDef outers ()
forall a b. (a -> b) -> a -> b
$ (forall r. (Application -> IO r) -> IO r) -> SetupFunc Application
forall resource.
(forall r. (resource -> IO r) -> IO r) -> SetupFunc resource
SetupFunc ((forall r. (Application -> IO r) -> IO r)
 -> SetupFunc Application)
-> (forall r. (Application -> IO r) -> IO r)
-> SetupFunc Application
forall a b. (a -> b) -> a -> b
$ \Application -> IO r
takeApp -> (Application -> IO r) -> IO r
forall r. (Application -> IO r) -> IO r
appFunc Application -> IO r
takeApp

-- | Run a 'Wai.Application' around every test by setting it up with the given setup function that can take an argument.
-- a
-- This provides the port on which the application is running.
waiSpecWith' :: (forall r. (Application -> IO r) -> (inner -> IO r)) -> TestDef outers PortNumber -> TestDef outers inner
waiSpecWith' :: (forall r. (Application -> IO r) -> inner -> IO r)
-> TestDef outers PortNumber -> TestDef outers inner
waiSpecWith' forall r. (Application -> IO r) -> inner -> IO r
appFunc = (inner -> SetupFunc Application)
-> TestDef outers PortNumber -> TestDef outers inner
forall inner (outers :: [*]).
(inner -> SetupFunc Application)
-> TestDef outers PortNumber -> TestDef outers inner
waiSpecWithSetupFunc' ((inner -> SetupFunc Application)
 -> TestDef outers PortNumber -> TestDef outers inner)
-> (inner -> SetupFunc Application)
-> TestDef outers PortNumber
-> TestDef outers inner
forall a b. (a -> b) -> a -> b
$ \inner
inner -> (forall r. (Application -> IO r) -> IO r) -> SetupFunc Application
forall resource.
(forall r. (resource -> IO r) -> IO r) -> SetupFunc resource
SetupFunc ((forall r. (Application -> IO r) -> IO r)
 -> SetupFunc Application)
-> (forall r. (Application -> IO r) -> IO r)
-> SetupFunc Application
forall a b. (a -> b) -> a -> b
$ \Application -> IO r
takeApp -> (Application -> IO r) -> inner -> IO r
forall r. (Application -> IO r) -> inner -> IO r
appFunc Application -> IO r
takeApp inner
inner

-- | Run a 'Wai.Application' around every test by setting it up with the given 'SetupFunc'.
-- a
-- This provides the port on which the application is running.
waiSpecWithSetupFunc :: SetupFunc Application -> TestDef outers PortNumber -> TestDef outers ()
waiSpecWithSetupFunc :: SetupFunc Application
-> TestDef outers PortNumber -> TestDef outers ()
waiSpecWithSetupFunc SetupFunc Application
setupFunc = (() -> SetupFunc Application)
-> TestDef outers PortNumber -> TestDef outers ()
forall inner (outers :: [*]).
(inner -> SetupFunc Application)
-> TestDef outers PortNumber -> TestDef outers inner
waiSpecWithSetupFunc' ((() -> SetupFunc Application)
 -> TestDef outers PortNumber -> TestDef outers ())
-> (() -> SetupFunc Application)
-> TestDef outers PortNumber
-> TestDef outers ()
forall a b. (a -> b) -> a -> b
$ \() -> SetupFunc Application
setupFunc

-- | Run a 'Wai.Application' around every test by setting it up with the given 'SetupFunc' and inner resource.
-- a
-- This provides the port on which the application is running.
waiSpecWithSetupFunc' :: (inner -> SetupFunc Application) -> TestDef outers PortNumber -> TestDef outers inner
waiSpecWithSetupFunc' :: (inner -> SetupFunc Application)
-> TestDef outers PortNumber -> TestDef outers inner
waiSpecWithSetupFunc' inner -> SetupFunc Application
setupFunc = (inner -> SetupFunc PortNumber)
-> TestDef outers PortNumber -> TestDef outers inner
forall oldInner newInner (outers :: [*]) result.
(oldInner -> SetupFunc newInner)
-> TestDefM outers newInner result
-> TestDefM outers oldInner result
setupAroundWith ((inner -> SetupFunc PortNumber)
 -> TestDef outers PortNumber -> TestDef outers inner)
-> (inner -> SetupFunc PortNumber)
-> TestDef outers PortNumber
-> TestDef outers inner
forall a b. (a -> b) -> a -> b
$ \inner
inner -> do
  Application
application <- inner -> SetupFunc Application
setupFunc inner
inner
  Application -> SetupFunc PortNumber
applicationSetupFunc Application
application

-- | A 'SetupFunc' to run an application and provide its port.
applicationSetupFunc :: Application -> SetupFunc PortNumber
applicationSetupFunc :: Application -> SetupFunc PortNumber
applicationSetupFunc Application
application = (forall r. (PortNumber -> IO r) -> IO r) -> SetupFunc PortNumber
forall resource.
(forall r. (resource -> IO r) -> IO r) -> SetupFunc resource
SetupFunc ((forall r. (PortNumber -> IO r) -> IO r) -> SetupFunc PortNumber)
-> (forall r. (PortNumber -> IO r) -> IO r) -> SetupFunc PortNumber
forall a b. (a -> b) -> a -> b
$ \PortNumber -> IO r
func ->
  IO Application -> (Port -> IO r) -> IO r
forall a. IO Application -> (Port -> IO a) -> IO a
Warp.testWithApplication (Application -> IO Application
forall (f :: * -> *) a. Applicative f => a -> f a
pure Application
application) ((Port -> IO r) -> IO r) -> (Port -> IO r) -> IO r
forall a b. (a -> b) -> a -> b
$ \Port
p ->
    PortNumber -> IO r
func (Port -> PortNumber
forall a b. (Integral a, Num b) => a -> b
fromIntegral Port
p) -- Hopefully safe, because 'testWithApplication' should give us sensible port numbers

-- | Create a 'HTTP.Manager' before all tests in the given group.
managerSpec :: TestDefM (HTTP.Manager ': outers) inner result -> TestDefM outers inner result
managerSpec :: TestDefM (Manager : outers) inner result
-> TestDefM outers inner result
managerSpec = IO Manager
-> TestDefM (Manager : outers) inner result
-> TestDefM outers inner result
forall outer (otherOuters :: [*]) inner result.
IO outer
-> TestDefM (outer : otherOuters) inner result
-> TestDefM otherOuters inner result
beforeAll (IO Manager
 -> TestDefM (Manager : outers) inner result
 -> TestDefM outers inner result)
-> IO Manager
-> TestDefM (Manager : outers) inner result
-> TestDefM outers inner result
forall a b. (a -> b) -> a -> b
$ ManagerSettings -> IO Manager
HTTP.newManager ManagerSettings
HTTP.defaultManagerSettings