typesafe-hs-0.1.0.0: test/TypeSafe/Conformance/OperationsTest.hs
module TypeSafe.Conformance.OperationsTest (tests) where
import Control.Exception (throwIO)
import Control.Monad (forM_)
import Data.Aeson
import Data.Aeson.Types (parseEither)
import Data.ByteString.Builder (toLazyByteString)
import Data.ByteString.Lazy qualified as LBS
import Data.Foldable (toList)
import Data.IORef
import Data.List (sort)
import Data.Sequence qualified as Seq
import Data.String (fromString)
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Network.HTTP.Types
import Servant.Client.Core
import Servant.Client.Generic (genericClient)
import Test.Tasty
import Test.Tasty.HUnit
import TypeSafe.Conformance.JsonSchema
import TypeSafe.V1 (Routes (..))
import TypeSafe.Wire
newtype Capture a = Capture (IORef [Request] -> LBS.ByteString -> IO a)
runCapture :: Capture a -> IORef [Request] -> LBS.ByteString -> IO a
runCapture (Capture action) = action
instance Functor Capture where
fmap f (Capture action) = Capture $ \ref body -> f <$> action ref body
instance Applicative Capture where
pure x = Capture $ \_ _ -> pure x
Capture f <*> Capture x = Capture $ \ref body -> f ref body <*> x ref body
instance Monad Capture where
Capture action >>= f = Capture $ \ref body -> action ref body >>= \x -> runCapture (f x) ref body
instance RunClient Capture where
runRequestAcceptStatus _ request = Capture $ \ref body -> do
modifyIORef' ref (request :)
pure Response {responseStatusCode = status200, responseHeaders = Seq.singleton (hContentType, "application/json"), responseHttpVersion = http11, responseBody = body}
throwClientError err = Capture $ \_ _ -> throwIO err
tests :: Spec -> TestTree
tests spec = testCase "every operation is bound with declared security, headers and JSON" $ do
ref <- newIORef []
let routes = genericClient @Routes @Capture
requests = [v | ("POST", "/v1/systemone", "request", "noul", v) <- operationExamples spec]
replies = [v | ("POST", "/v1/systemone", "200", "noul", v) <- operationExamples spec]
requestValue <- case requests of [v] -> pure v; _ -> fail "missing noul request example"
replyValue <- case replies of [v] -> pure v; _ -> fail "missing noul response example"
request <- either fail pure (parseEither parseJSON requestValue)
response <- runCapture (routes.systemOne "Bearer test-token" (Just "true") (Just "true") request) ref (encode replyValue)
toJSON response @?= replyValue
let models = ModelMetadataList mempty
validate spec (schemaRef "ModelMetadataList") (toJSON models) @?= []
runCapture (routes.models "Bearer test-token") ref (encode models) >>= (@?= models)
captured <- readIORef ref
let operation r = (Text.decodeUtf8 (requestMethod r), Text.decodeUtf8 (LBS.toStrict (toLazyByteString (requestPath r))))
sort (map operation captured) @?= sort (specOperations spec)
forM_ captured $ \r -> do
let (method, path) = operation r
params = [(name, location, schema) | (m, p, name, location, schema) <- operationParameters spec, m == method, p == path]
headers = toList (requestHeaders r)
names = [name | (name, _, _) <- params]
lookup hAuthorization headers @?= Just "Bearer test-token"
sort [h | (h, _) <- headers, h `notElem` [hAuthorization, hAccept]] @?= sort [fromString (Text.unpack name) | name <- names]
[security | (m, p, security) <- operationSecurity spec, m == method, p == path] @?= [toJSON [object ["HTTPBearer" .= ([] :: [Value])]]]
forM_ params $ \(name, location, schema) -> do
location @?= "header"
value <- maybe (fail ("missing header " <> Text.unpack name)) pure (lookup (fromString (Text.unpack name)) headers)
validate spec schema (String (Text.decodeUtf8 value)) @?= []
case requestBody r of
Nothing -> method @?= "GET"
Just (RequestBodyLBS bytes, media) -> do
media @?= "application/json;charset=utf-8"
eitherDecode @Value bytes @?= Right requestValue
Just _ -> assertFailure "unexpected request body representation"