{-|
Module      :  Data.GraphQL.TestUtils
Maintainer  :  Brandon Chinn <brandon@leapyear.io>
Stability   :  experimental
Portability :  portable

Defines test utilities for testing GraphQL queries.
-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}

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
  { ResultMock query -> query
query  :: query
  , ResultMock query -> Value
result :: Value
  } deriving (Int -> ResultMock query -> ShowS
[ResultMock query] -> ShowS
ResultMock query -> String
(Int -> ResultMock query -> ShowS)
-> (ResultMock query -> String)
-> ([ResultMock query] -> ShowS)
-> Show (ResultMock query)
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 :: ResultMock query -> AnyResultMock
mocked = ResultMock query -> AnyResultMock
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 :: query -> AnyResultMock -> Bool
isMatch query
testQuery (AnyResultMock ResultMock query
mock) = query -> Value
forall query. GraphQLQuery query => query -> Value
getArgs (ResultMock query -> query
forall query. ResultMock query -> query
query ResultMock query
mock) Value -> Value -> Bool
forall a. Eq a => a -> a -> Bool
== query -> Value
forall query. GraphQLQuery query => query -> Value
getArgs query
testQuery

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

{- MockQueryT -}

newtype MockQueryT m a = MockQueryT { MockQueryT m a -> StateT [AnyResultMock] m a
unMockQueryT :: StateT [AnyResultMock] m a }
  deriving (a -> MockQueryT m b -> MockQueryT m a
(a -> b) -> MockQueryT m a -> MockQueryT m b
(forall a b. (a -> b) -> MockQueryT m a -> MockQueryT m b)
-> (forall a b. a -> MockQueryT m b -> MockQueryT m a)
-> Functor (MockQueryT m)
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
<$ :: a -> MockQueryT m b -> MockQueryT m a
$c<$ :: forall (m :: * -> *) a b.
Functor m =>
a -> MockQueryT m b -> MockQueryT m a
fmap :: (a -> b) -> MockQueryT m a -> MockQueryT m b
$cfmap :: forall (m :: * -> *) a b.
Functor m =>
(a -> b) -> MockQueryT m a -> MockQueryT m b
Functor, Functor (MockQueryT m)
a -> MockQueryT m a
Functor (MockQueryT m)
-> (forall a. a -> MockQueryT m a)
-> (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 a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m b)
-> (forall a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m a)
-> Applicative (MockQueryT m)
MockQueryT m a -> MockQueryT m b -> MockQueryT m b
MockQueryT m a -> MockQueryT m b -> MockQueryT m a
MockQueryT m (a -> b) -> MockQueryT m a -> MockQueryT m b
(a -> b -> c) -> MockQueryT m a -> MockQueryT m b -> MockQueryT m c
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
<* :: 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
*> :: 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 :: (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
<*> :: 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 :: a -> MockQueryT m a
$cpure :: forall (m :: * -> *) a. Monad m => a -> MockQueryT m a
$cp1Applicative :: forall (m :: * -> *). Monad m => Functor (MockQueryT m)
Applicative, Applicative (MockQueryT m)
a -> MockQueryT m a
Applicative (MockQueryT m)
-> (forall a b.
    MockQueryT m a -> (a -> MockQueryT m b) -> MockQueryT m b)
-> (forall a b. MockQueryT m a -> MockQueryT m b -> MockQueryT m b)
-> (forall a. a -> MockQueryT m a)
-> Monad (MockQueryT m)
MockQueryT m a -> (a -> MockQueryT m b) -> MockQueryT m b
MockQueryT m a -> MockQueryT m b -> MockQueryT m b
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 :: a -> MockQueryT m a
$creturn :: forall (m :: * -> *) a. Monad m => a -> MockQueryT m a
>> :: 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
>>= :: 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
$cp1Monad :: forall (m :: * -> *). Monad m => Applicative (MockQueryT m)
Monad, Monad (MockQueryT m)
Monad (MockQueryT m)
-> (forall a. IO a -> MockQueryT m a) -> MonadIO (MockQueryT m)
IO a -> MockQueryT m a
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 :: IO a -> MockQueryT m a
$cliftIO :: forall (m :: * -> *) a. MonadIO m => IO a -> MockQueryT m a
$cp1MonadIO :: forall (m :: * -> *). MonadIO m => Monad (MockQueryT m)
MonadIO, MonadState [AnyResultMock], m a -> MockQueryT m a
(forall (m :: * -> *) a. Monad m => m a -> MockQueryT m a)
-> MonadTrans MockQueryT
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 :: 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 :: query -> MockQueryT m (GraphQLResult (Object schema))
runQuerySafe query
testQuery = Value -> GraphQLResult (Object schema)
forall a. FromJSON a => Value -> a
toGraphQLResult (Value -> GraphQLResult (Object schema))
-> MockQueryT m Value
-> MockQueryT m (GraphQLResult (Object schema))
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 :: (a -> Bool) -> [a] -> Maybe (a, [a])
takeWhere a -> Bool
f [a]
xs = case (a -> Bool) -> [a] -> ([a], [a])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break a -> Bool
f [a]
xs of
        ([a]
before, a
match:[a]
after) -> (a, [a]) -> Maybe (a, [a])
forall a. a -> Maybe a
Just (a
match, [a]
before [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a]
after)
        ([a]
_, []) -> Maybe (a, [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 = ([AnyResultMock] -> (Value, [AnyResultMock])) -> MockQueryT m Value
forall s (m :: * -> *) a. MonadState s m => (s -> (a, s)) -> m a
state (([AnyResultMock] -> (Value, [AnyResultMock]))
 -> MockQueryT m Value)
-> ([AnyResultMock] -> (Value, [AnyResultMock]))
-> MockQueryT m Value
forall a b. (a -> b) -> a -> b
$ \[AnyResultMock]
mocks ->
        case (AnyResultMock -> Bool)
-> [AnyResultMock] -> Maybe (AnyResultMock, [AnyResultMock])
forall a. (a -> Bool) -> [a] -> Maybe (a, [a])
takeWhere (query -> AnyResultMock -> Bool
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 -> String -> (Value, [AnyResultMock])
forall a. HasCallStack => String -> a
error (String -> (Value, [AnyResultMock]))
-> String -> (Value, [AnyResultMock])
forall a b. (a -> b) -> a -> b
$ String
"No more mocked responses for query: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
Text.unpack (query -> Text
forall query. GraphQLQuery query => query -> Text
getQueryName query
testQuery)

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

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