packages feed

mockcat-0.5.0.0: test/Test/MockCat/TypeClassSpec.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# LANGUAGE TypeApplications #-}

module Test.MockCat.TypeClassSpec (spec) where

import Data.Text (Text, pack)
import Test.Hspec (Spec, it, shouldBe)
import Test.MockCat
import Prelude hiding (readFile, writeFile)
import Data.Data
import Data.List (find)
import GHC.TypeLits (KnownSymbol, symbolVal)
import Unsafe.Coerce (unsafeCoerce)
import Data.Maybe (fromMaybe)
import GHC.IO (unsafePerformIO)
import Control.Monad.Reader (MonadReader)
import Control.Monad.Reader.Class (ask, MonadReader (local))
import Control.Monad.State

class Monad m => FileOperation m where
  readFile :: FilePath -> m Text
  writeFile :: FilePath -> Text -> m ()

class Monad m => ApiOperation m where
  post :: Text -> m ()

program ::
  (MonadReader String m, FileOperation m, ApiOperation m) =>
  FilePath ->
  FilePath ->
  (Text -> Text) ->
  m ()
program inputPath outputPath modifyText = do
  e <- ask
  content <- readFile inputPath
  let modifiedContent = modifyText content
  writeFile outputPath modifiedContent
  post $ modifiedContent <> pack ("+" <> e)

instance (Monad m) => FileOperation (MockT m) where
  readFile path = MockT do
    defs <- get
    let
      mock = fromMaybe (error "no answer found stub function `readFile`.") $ findParam (Proxy :: Proxy "readFile") defs
      !result = stubFn mock path
    pure result

  writeFile path content = MockT do
    defs <- get
    let
      mock = fromMaybe (error "no answer found stub function `writeFile`.") $ findParam (Proxy :: Proxy "writeFile") defs
      !result = stubFn mock path content
    pure result

instance Monad m => ApiOperation (MockT m) where
  post content = MockT do
    defs <- get
    let
      mock = fromMaybe (error "no answer found stub function `post`.") $ findParam (Proxy :: Proxy "post") defs
      !result = stubFn mock content
    pure result

instance Monad m => MonadReader String (MockT m) where
  ask = MockT do
    defs <- get
    let
      mock = fromMaybe (error "no answer found stub function `ask`.") $ findParam (Proxy :: Proxy "ask") defs
      !result = stubFn mock
    pure result
  local = undefined

_ask :: Monad m => params -> MockT m ()
_ask p = MockT $ do
  modify (++ [Definition
    (Proxy :: Proxy "ask")
    (unsafePerformIO $ createNamedConstantMock "ask" p)
    shouldApplyToAnything])

_readFile :: (MockBuilder params (FilePath -> Text) (Param FilePath), Monad m) => params -> MockT m ()
_readFile p = MockT $ do
  modify (++ [Definition
    (Proxy :: Proxy "readFile")
    (unsafePerformIO $ createNamedMock "readFile" p)
    shouldApplyToAnything])

_writeFile :: (MockBuilder params (FilePath -> Text -> ()) (Param FilePath :> Param Text), Monad m) => params -> MockT m ()
_writeFile p = MockT $ modify (++ [Definition
  (Proxy :: Proxy "writeFile")
  (unsafePerformIO $ createNamedMock "writeFile" p)
  shouldApplyToAnything])

_post :: (MockBuilder params (Text -> ()) (Param Text), Monad m) => params -> MockT m ()
_post p = MockT $ modify (++ [Definition
  (Proxy :: Proxy "post")
  (unsafePerformIO $ createNamedMock "post" p)
  shouldApplyToAnything])

findParam :: KnownSymbol sym => Proxy sym -> [Definition] -> Maybe a
findParam pa definitions = do
  let definition = find (\(Definition s _ _) -> symbolVal s == symbolVal pa) definitions
  fmap (\(Definition _ mock _) -> unsafeCoerce mock) definition

class Monad m => TestClass m where
  echo :: String -> m ()
  getBy :: String -> m Int

echoProgram :: MonadIO m => TestClass m => String -> m ()
echoProgram s = do
  v <- getBy s
  liftIO $ print v
  echo $ show v

instance (Monad m) => TestClass (MockT m) where
  getBy a = MockT do
    defs <- get
    let
      mock = fromMaybe (error "no answer found stub function `_getBy`.") $ findParam (Proxy :: Proxy "_getBy") defs
      !result = stubFn mock a
    lift result

  echo a = MockT do
    defs <- get
    let
      mock = fromMaybe (error "no answer found stub function `_echo`.") $ findParam (Proxy :: Proxy "_echo") defs
      !result = stubFn mock a
    lift result

_getBy :: (MockBuilder params (String -> m Int) (Param String), Monad m) => params -> MockT m ()
_getBy p = MockT $ do
  modify (++ [Definition
    (Proxy :: Proxy "_getBy")
    (unsafePerformIO $ createNamedMock "_getBy" p)
    shouldApplyToAnything])

_echo :: (MockBuilder params (String -> m ()) (Param String), Monad m) => params -> MockT m ()
_echo p = MockT $ modify (++ [Definition
  (Proxy :: Proxy "_echo")
  (unsafePerformIO $ createNamedMock "_echo" p)
  shouldApplyToAnything])


class Monad m => Teletype m where
  readTTY :: m String
  writeTTY :: String -> m ()

echo2 :: Teletype m => m ()
echo2 = do
  i <- readTTY
  case i of
    "" -> pure ()
    _  -> writeTTY i >> echo2

instance (Monad m) => Teletype (MockT m) where
  readTTY = MockT do
    defs <- get
    let
      mock = fromMaybe (error "no answer found stub function `_readTTY`.") $ findParam (Proxy :: Proxy "_readTTY") defs
      !result = stubFn mock
    lift result

  writeTTY a = MockT do
    defs <- get
    let
      mock = fromMaybe (error "no answer found stub function `_writeTTY`.") $ findParam (Proxy :: Proxy "_writeTTY") defs
      !result = stubFn mock a
    lift result

_readTTY :: (MockBuilder params (m String) (), Monad m) => params -> MockT m ()
_readTTY p = MockT $ do
  modify (++ [Definition
    (Proxy :: Proxy "_readTTY")
    (unsafePerformIO $ createNamedMock "_readTTY" p)
    shouldApplyToAnything])

_writeTTY :: (MockBuilder params (String -> m ()) (Param String), Monad m) => params -> MockT m ()
_writeTTY p = MockT $ modify (++ [Definition
  (Proxy :: Proxy "_writeTTY")
  (unsafePerformIO $ createNamedMock "_writeTTY" p)
  shouldApplyToAnything])

spec :: Spec
spec = do
  it "echo" do
    result <- runMockT do
      _readTTY $ casesIO [
        "a",
        ""
        ]
      _writeTTY $ "a" |> pure @IO ()
      echo2
    result `shouldBe` ()

  it "Read, edit, and output files" do
    modifyContentStub <- createStubFn $ pack "content" |> pack "modifiedContent"

    result <- runMockT do
      _ask "environment"
      _readFile ("input.txt" |> pack "content")
      _writeFile $ "output.text" |> pack "modifiedContent" |> ()
      _post $ pack "modifiedContent+environment" |> ()
      program "input.txt" "output.text" modifyContentStub

    result `shouldBe` ()

  it "return monadic value test" do
    result <- runMockT do
      _getBy $ "s" |> pure @IO (10 :: Int)
      _echo $ "10" |> pure @IO ()
      echoProgram "s"

    result `shouldBe` ()