{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}

{-|
Module      :  Data.GraphQL.TestUtils
Maintainer  :  Brandon Chinn <brandonchinn178@gmail.com>
Stability   :  experimental
Portability :  portable

Defines test utilities for testing GraphQL queries.
-}
module Data.GraphQL.TestUtils (
  ResultMock (..),
  mocked,
  MockQueryT,
  runMockQueryT,
  AnyResultMock,
) where

import Control.Monad.IO.Class (MonadIO)
import Control.Monad.State.Strict (MonadState, StateT, evalStateT, state)
import Control.Monad.Trans.Class (MonadTrans)
import Data.Aeson (FromJSON, Value, object, (.=))
import qualified Data.Aeson.Types as Aeson
import qualified Data.Text as Text

import Data.GraphQL.Error (GraphQLError)
import Data.GraphQL.Monad (MonadGraphQLQuery (..))
import Data.GraphQL.Query (GraphQLQuery (..))

data ResultMock query = ResultMock
  { forall query. ResultMock query -> query
query :: query
  , forall query. ResultMock query -> Value
result :: Value
  }
  deriving (Int -> ResultMock query -> ShowS
forall query. Show query => Int -> ResultMock query -> ShowS
forall query. Show query => [ResultMock query] -> ShowS
forall query. Show query => ResultMock query -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ResultMock query] -> ShowS
$cshowList :: forall query. Show query => [ResultMock query] -> ShowS
show :: ResultMock query -> String
$cshow :: forall query. Show query => ResultMock query -> String
showsPrec :: Int -> ResultMock query -> ShowS
$cshowsPrec :: forall query. Show query => Int -> ResultMock query -> ShowS
Show)

mocked :: (Show query, GraphQLQuery query) => ResultMock query -> AnyResultMock
mocked :: forall query.
(Show query, GraphQLQuery query) =>
ResultMock query -> AnyResultMock
mocked = forall query.
(Show query, GraphQLQuery query) =>
ResultMock query -> AnyResultMock
AnyResultMock

{- AnyResultMock -}

data AnyResultMock = forall query. (Show query, GraphQLQuery query) => AnyResultMock (ResultMock query)

deriving instance Show AnyResultMock

isMatch :: (GraphQLQuery query) => query -> AnyResultMock -> Bool
isMatch :: forall query. GraphQLQuery query => query -> AnyResultMock -> Bool
isMatch query
testQuery (AnyResultMock ResultMock query
mock) = forall query. GraphQLQuery query => query -> Value
getArgs (forall query. ResultMock query -> query
query ResultMock query
mock) forall a. Eq a => a -> a -> Bool
== forall query. GraphQLQuery query => query -> Value
getArgs query
testQuery

getResult :: AnyResultMock -> Value
getResult :: AnyResultMock -> Value
getResult (AnyResultMock ResultMock query
mock) = forall query. ResultMock query -> Value
result ResultMock query
mock

{- MockQueryT -}

newtype MockQueryT m a = MockQueryT {forall (m :: * -> *) a.
MockQueryT m a -> StateT [AnyResultMock] m a
unMockQueryT :: StateT [AnyResultMock] m a}
  deriving (forall a b. a -> MockQueryT m b -> MockQueryT m a
forall a b. (a -> b) -> MockQueryT m a -> MockQueryT m b
forall (m :: * -> *) a b.
Functor m =>
a -> MockQueryT m b -> MockQueryT m a
forall (m :: * -> *) a b.
Functor m =>
(a -> b) -> MockQueryT m a -> MockQueryT m b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: forall a b. a -> MockQueryT m b -> MockQueryT m a
$c<$ :: forall (m :: * -> *) a b.
Functor m =>
a -> MockQueryT m b -> MockQueryT m a
fmap :: forall a b. (a -> b) -> MockQueryT m a -> MockQueryT m b
$cfmap :: forall (m :: * -> *) a b.
Functor m =>
(a -> b) -> MockQueryT m a -> MockQueryT m b
Functor, forall a. a -> MockQueryT m a
forall a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m a
forall a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m b
forall a b.
MockQueryT m (a -> b) -> MockQueryT m a -> MockQueryT m b
forall a b c.
(a -> b -> c) -> MockQueryT m a -> MockQueryT m b -> MockQueryT m c
forall {m :: * -> *}. Monad m => Functor (MockQueryT m)
forall (m :: * -> *) a. Monad m => a -> MockQueryT m a
forall (m :: * -> *) a b.
Monad m =>
MockQueryT m a -> MockQueryT m b -> MockQueryT m a
forall (m :: * -> *) a b.
Monad m =>
MockQueryT m a -> MockQueryT m b -> MockQueryT m b
forall (m :: * -> *) a b.
Monad m =>
MockQueryT m (a -> b) -> MockQueryT m a -> MockQueryT m b
forall (m :: * -> *) a b c.
Monad m =>
(a -> b -> c) -> MockQueryT m a -> MockQueryT m b -> MockQueryT m c
forall (f :: * -> *).
Functor f
-> (forall a. a -> f a)
-> (forall a b. f (a -> b) -> f a -> f b)
-> (forall a b c. (a -> b -> c) -> f a -> f b -> f c)
-> (forall a b. f a -> f b -> f b)
-> (forall a b. f a -> f b -> f a)
-> Applicative f
<* :: forall a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m a
$c<* :: forall (m :: * -> *) a b.
Monad m =>
MockQueryT m a -> MockQueryT m b -> MockQueryT m a
*> :: forall a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m b
$c*> :: forall (m :: * -> *) a b.
Monad m =>
MockQueryT m a -> MockQueryT m b -> MockQueryT m b
liftA2 :: forall a b c.
(a -> b -> c) -> MockQueryT m a -> MockQueryT m b -> MockQueryT m c
$cliftA2 :: forall (m :: * -> *) a b c.
Monad m =>
(a -> b -> c) -> MockQueryT m a -> MockQueryT m b -> MockQueryT m c
<*> :: forall a b.
MockQueryT m (a -> b) -> MockQueryT m a -> MockQueryT m b
$c<*> :: forall (m :: * -> *) a b.
Monad m =>
MockQueryT m (a -> b) -> MockQueryT m a -> MockQueryT m b
pure :: forall a. a -> MockQueryT m a
$cpure :: forall (m :: * -> *) a. Monad m => a -> MockQueryT m a
Applicative, forall a. a -> MockQueryT m a
forall a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m b
forall a b.
MockQueryT m a -> (a -> MockQueryT m b) -> MockQueryT m b
forall (m :: * -> *). Monad m => Applicative (MockQueryT m)
forall (m :: * -> *) a. Monad m => a -> MockQueryT m a
forall (m :: * -> *) a b.
Monad m =>
MockQueryT m a -> MockQueryT m b -> MockQueryT m b
forall (m :: * -> *) a b.
Monad m =>
MockQueryT m a -> (a -> MockQueryT m b) -> MockQueryT m b
forall (m :: * -> *).
Applicative m
-> (forall a b. m a -> (a -> m b) -> m b)
-> (forall a b. m a -> m b -> m b)
-> (forall a. a -> m a)
-> Monad m
return :: forall a. a -> MockQueryT m a
$creturn :: forall (m :: * -> *) a. Monad m => a -> MockQueryT m a
>> :: forall a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m b
$c>> :: forall (m :: * -> *) a b.
Monad m =>
MockQueryT m a -> MockQueryT m b -> MockQueryT m b
>>= :: forall a b.
MockQueryT m a -> (a -> MockQueryT m b) -> MockQueryT m b
$c>>= :: forall (m :: * -> *) a b.
Monad m =>
MockQueryT m a -> (a -> MockQueryT m b) -> MockQueryT m b
Monad, forall a. IO a -> MockQueryT m a
forall (m :: * -> *).
Monad m -> (forall a. IO a -> m a) -> MonadIO m
forall {m :: * -> *}. MonadIO m => Monad (MockQueryT m)
forall (m :: * -> *) a. MonadIO m => IO a -> MockQueryT m a
liftIO :: forall a. IO a -> MockQueryT m a
$cliftIO :: forall (m :: * -> *) a. MonadIO m => IO a -> MockQueryT m a
MonadIO, MonadState [AnyResultMock], forall (m :: * -> *) a. Monad m => m a -> MockQueryT m a
forall (t :: (* -> *) -> * -> *).
(forall (m :: * -> *) a. Monad m => m a -> t m a) -> MonadTrans t
lift :: forall (m :: * -> *) a. Monad m => m a -> MockQueryT m a
$clift :: forall (m :: * -> *) a. Monad m => m a -> MockQueryT m a
MonadTrans)

instance (Monad m) => MonadGraphQLQuery (MockQueryT m) where
  runQuerySafe :: forall query (schema :: Schema).
(GraphQLQuery query, schema ~ ResultSchema query) =>
query -> MockQueryT m (GraphQLResult (Object schema))
runQuerySafe query
testQuery = forall a. FromJSON a => Value -> a
toGraphQLResult forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> MockQueryT m Value
lookupMock
    where
      takeWhere :: (a -> Bool) -> [a] -> Maybe (a, [a])
      takeWhere :: forall a. (a -> Bool) -> [a] -> Maybe (a, [a])
takeWhere a -> Bool
f [a]
xs = case forall a. (a -> Bool) -> [a] -> ([a], [a])
break a -> Bool
f [a]
xs of
        ([a]
before, a
match : [a]
after) -> forall a. a -> Maybe a
Just (a
match, [a]
before forall a. [a] -> [a] -> [a]
++ [a]
after)
        ([a]
_, []) -> forall a. Maybe a
Nothing

      -- Find the first matching mock and remove it from the state
      lookupMock :: MockQueryT m Value
      lookupMock :: MockQueryT m Value
lookupMock = forall s (m :: * -> *) a. MonadState s m => (s -> (a, s)) -> m a
state forall a b. (a -> b) -> a -> b
$ \[AnyResultMock]
mocks ->
        case forall a. (a -> Bool) -> [a] -> Maybe (a, [a])
takeWhere (forall query. GraphQLQuery query => query -> AnyResultMock -> Bool
isMatch query
testQuery) [AnyResultMock]
mocks of
          Just (AnyResultMock
mock, [AnyResultMock]
mocks') -> (AnyResultMock -> Value
getResult AnyResultMock
mock, [AnyResultMock]
mocks')
          Maybe (AnyResultMock, [AnyResultMock])
Nothing -> forall a. HasCallStack => String -> a
error forall a b. (a -> b) -> a -> b
$ String
"No more mocked responses for query: " forall a. [a] -> [a] -> [a]
++ Text -> String
Text.unpack (forall query. GraphQLQuery query => query -> Text
getQueryName query
testQuery)

      toGraphQLResult :: (FromJSON a) => Value -> a
      toGraphQLResult :: forall a. FromJSON a => Value -> a
toGraphQLResult Value
mockData =
        forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall a. HasCallStack => String -> a
error forall a. a -> a
id forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> Parser b) -> a -> Either String b
Aeson.parseEither forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON forall a b. (a -> b) -> a -> b
$
          [Pair] -> Value
object
            [ Key
"errors" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= ([] :: [GraphQLError])
            , Key
"data" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall a. a -> Maybe a
Just Value
mockData
            ]

runMockQueryT :: (Monad m) => MockQueryT m a -> [AnyResultMock] -> m a
runMockQueryT :: forall (m :: * -> *) a.
Monad m =>
MockQueryT m a -> [AnyResultMock] -> m a
runMockQueryT MockQueryT m a
mockQueryT [AnyResultMock]
mocks = (forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
`evalStateT` [AnyResultMock]
mocks) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MockQueryT m a -> StateT [AnyResultMock] m a
unMockQueryT forall a b. (a -> b) -> a -> b
$ MockQueryT m a
mockQueryT