content: {-# LANGUAGE RankNTypes #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE NoImplicitPrelude #-}
  
  module Test.Morpheus
    ( FileUrl,
      mkUrl,
      cd,
      file,
      deepScan,
      scan,
      -- get app
      getAppsBy,
      -- tests
      testApi,
      testSchema,
      testQueryRendering,
      renderingAssertion,
      testQuery,
      testQueryValidation,
      -- main
      mainTest,
    )
  where
  
  import Data.Aeson
    ( FromJSON (..),
      ToJSON (..),
      encode,
    )
  import Data.ByteString.Lazy.Char8 (ByteString)
  import Relude hiding (ByteString)
  import Test.Morpheus.File
  import Test.Morpheus.Response
  import Test.Morpheus.Utils
  import Test.Tasty
    ( TestTree,
      defaultMain,
      testGroup,
    )
  import Test.Tasty.HUnit
    ( assertFailure,
      testCase,
    )
  
  mainTest :: String -> [IO TestTree] -> IO ()
  mainTest name xs = do
    tests <- sequence xs
    defaultMain
      $ testGroup
        name
        tests
  
  --
  testApi ::
    (FromJSON req, ToJSON res) =>
    (req -> IO res) ->
    FileUrl ->
    TestTree
  testApi api =
    assertResponse
      (fmap expects . api <=< getQuery)
  
  testQuery ::
    (FromJSON req, ToJSON err, Eq err, FromJSON err) =>
    (req -> Either err a) ->
    FileUrl ->
    TestTree
  testQuery f =
    assertResponse
      (fmap (fromEither . f) . getQuery)
  
  testSchema ::
    (ToJSON err, Eq err, FromJSON err) =>
    (ByteString -> Either err a) ->
    FileUrl ->
    TestTree
  testSchema f =
    assertResponse
      (fmap (fromEither . f) . readSchemaFile)
  
  renderingAssertion ::
    (ToJSON e) =>
    (FileUrl -> IO (Either e ByteString)) ->
    FileUrl ->
    TestTree
  renderingAssertion api url = testCase (fileName url) $ do
    actual <- api url
    expected <- readGQL "rendering" url
    either
      (assertFailure . (" error: " <>) . show . encode)
      (requireEq id expected)
      actual
  
  getAppBy ::
    (Show err, FromJSON resolvers) =>
    ( ByteString -> Either err schema,
      schema -> resolvers -> app
    ) ->
    FileUrl ->
    IO app
  getAppBy (parseSchema, mkApp) url =
    mkApp
      <$> getSchema parseSchema url
      <*> getResolver url
  
  testQueryValidation ::
    ( ToJSON errors,
      Show errors,
      FromJSON request,
      FromJSON errors,
      Eq errors
    ) =>
    ( schema -> request -> Either errors a,
      ByteString -> Either errors schema
    ) ->
    FileUrl ->
    FileUrl ->
    TestTree
  testQueryValidation (parseRequest, parseSchema) schemaUrl =
    assertResponse
      ( \url -> do
          schema <- getSchema parseSchema schemaUrl
          query <- getQuery url
          pure $ fromEither (parseRequest schema query)
      )
  
  testQueryRendering ::
    ( ToJSON errors,
      Show errors,
      FromJSON request
    ) =>
    ( schema -> request -> Either errors ByteString,
      ByteString -> Either errors schema
    ) ->
    FileUrl ->
    FileUrl ->
    TestTree
  testQueryRendering (parseRequest, parseSchema) schemaUrl =
    renderingAssertion
      ( \requestUrl -> do
          request <- getQuery requestUrl
          schema <- getSchema parseSchema schemaUrl
          pure $ parseRequest schema request
      )
  
  getAppsWIth ::
    (Semigroup b, Show err, FromJSON resolvers) =>
    ( ByteString -> Either err schema,
      schema -> resolvers -> b
    ) ->
    FileUrl ->
    [FilePath] ->
    IO b
  getAppsWIth f url [] = getAppBy f url
  getAppsWIth f url (x : xs) = sconcat <$> traverse (getAppBy f . file url) (x :| xs)
  
  getAppsBy ::
    (Semigroup b, Show err, FromJSON resolvers) =>
    ( ByteString -> Either err schema,
      schema -> resolvers -> b
    ) ->
    FileUrl ->
    IO b
  getAppsBy f url = do
    files <- searchAppFiles url
    getAppsWIth f url (sort files)
  