packages feed

http-tower-hs-0.3.0.0: test/Network/HTTP/Tower/Middleware/TestDoubleSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Network.HTTP.Tower.Middleware.TestDoubleSpec (spec) where

import Data.IORef
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Map.Strict as Map
import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Client.Internal as HTTP
import qualified Network.HTTP.Types as HTTP
import Test.Hspec

import Network.HTTP.Tower.Client (HttpResponse)
import Tower.Service
import Tower.Error
import Data.Function ((&))
import Network.HTTP.Tower.Middleware.TestDouble

spec :: Spec
spec = describe "TestDouble middleware" $ do
  describe "withMock" $ do
    it "replaces the inner service entirely" $ do
      innerCalled <- newIORef False
      let inner = Service $ \_ -> do
            writeIORef innerCalled True
            pure (Right fakeResponse)
          mocked = inner & withMock (\_ -> pure (Right fakeResponse))
      req <- HTTP.parseRequest "http://example.com"
      result <- runService mocked req
      case result of
        Right _ -> pure ()
        Left err -> expectationFailure $ show err
      readIORef innerCalled >>= (`shouldBe` False)

    it "can return errors" $ do
      let mocked = Service (\_ -> pure (Right fakeResponse)) & withMock (\_ -> pure (Left (CustomError "mock error")))
      req <- HTTP.parseRequest "http://example.com"
      result <- runService mocked req
      case result of
        Left (CustomError "mock error") -> pure ()
        other -> expectationFailure $ "Expected mock error, got: " ++ show other

  describe "withMockMap" $ do
    it "routes to matching mock response" $ do
      let mocks = Map.fromList
            [ ("example.com/api/users", Right fakeResponse)
            ]
          mocked = Service (\_ -> pure (Left (CustomError "not mocked"))) & withMockMap mocks
      req <- HTTP.parseRequest "http://example.com/api/users"
      result <- runService mocked req
      case result of
        Right _ -> pure ()
        Left err -> expectationFailure $ "Expected mock match, got: " ++ show err

    it "falls through to inner service on no match" $ do
      let mocks = Map.fromList
            [ ("example.com/api/users", Right fakeResponse)
            ]
          mocked = Service (\_ -> pure (Right fakeResponse)) & withMockMap mocks
      req <- HTTP.parseRequest "http://example.com/api/other"
      result <- runService mocked req
      case result of
        Right _ -> pure ()
        Left err -> expectationFailure $ show err

  describe "withRecorder" $ do
    it "records all requests" $ do
      recorder <- newIORef []
      let svc = Service (\_ -> pure (Right fakeResponse)) & withRecorder recorder
      req1 <- HTTP.parseRequest "http://example.com/a"
      req2 <- HTTP.parseRequest "http://example.com/b"
      _ <- runService svc req1
      _ <- runService svc req2
      recorded <- readIORef recorder
      length recorded `shouldBe` 2

    it "still forwards to inner service" $ do
      recorder <- newIORef []
      innerCalled <- newIORef (0 :: Int)
      let inner = Service $ \_ -> do
            modifyIORef' innerCalled (+ 1)
            pure (Right fakeResponse)
          svc = inner & withRecorder recorder
      req <- HTTP.parseRequest "http://example.com"
      _ <- runService svc req
      readIORef innerCalled >>= (`shouldBe` 1)

fakeResponse :: HttpResponse
fakeResponse = HTTP.Response
  { HTTP.responseStatus = HTTP.status200
  , HTTP.responseVersion = HTTP.http11
  , HTTP.responseHeaders = []
  , HTTP.responseBody = ""
  , HTTP.responseCookieJar = HTTP.createCookieJar []
  , HTTP.responseClose' = HTTP.ResponseClose (pure ())
  , HTTP.responseOriginalRequest = error "not used"
  , HTTP.responseEarlyHints = []
  }