packages feed

typesafe-hs-0.1.0.0: test/TypeSafe/V1Test.hs

module TypeSafe.V1Test (tests) where

import Control.Exception (SomeException, try)
import Data.Aeson
import Data.ByteString.Char8 qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Data.Foldable (toList)
import Data.IORef
import Data.Proxy (Proxy (..))
import Data.Text qualified as Text
import Data.Vector qualified as Vector
import GHC.TypeLits (symbolVal)
import Network.HTTP.Types
import Servant.Client qualified as Client
import Test.Tasty
import Test.Tasty.HUnit
import TypeSafe.Test.MockServer
import TypeSafe.V1
import TypeSafe.Wire

modelsFixture :: ModelMetadataList
modelsFixture = ModelMetadataList (Vector.singleton (ModelMetadata "jev-latest" "Test model" "2026-09-15"))

withClient :: (Methods -> IORef Reply -> IO Captured -> IO ()) -> IO ()
withClient action = do
  response <- either fail pure =<< eitherDecodeFileStrict "test/golden/baikai-response.json"
  reply <- newIORef (ReplySuccess response)
  withMockServer reply modelsFixture $ \port readCapture -> do
    env <- getClientEnv (Text.pack ("http://127.0.0.1:" <> show port))
    let capture = readCapture >>= maybe (fail "no request captured") pure
    action (makeMethods env "test-key") reply capture

requestFixture :: IO SystemOneRequest
requestFixture = either fail pure =<< eitherDecodeFileStrict "test/golden/baikai-request.json"

tests :: TestTree
tests =
  testGroup
    "TypeSafe.V1"
    [ testCase "loopback bytes, bearer token and response" $ withClient $ \methods _ capture -> do
        request <- requestFixture
        expected <- either fail pure =<< eitherDecodeFileStrict "test/golden/baikai-response.json"
        methods.createSystemOne request >>= (@?= expected)
        seen <- capture
        seen.method @?= "POST"
        seen.path @?= "/v1/systemone"
        seen.body @?= encodeSystemOneRequest request
        lookup hAuthorization seen.headers @?= Just "Bearer test-key"
        assertBool "JSON content type" (maybe False (BS.isPrefixOf "application/json") (lookup hContentType seen.headers))
        lookup dangerZoneImagesHeader seen.headers @?= Nothing
        lookup dangerZoneLongContextHeader seen.headers @?= Nothing,
      testCase "FailureResponse preserves status, headers and exact body" $ withClient $ \methods reply _ -> do
        bytes <- LBS.readFile "test/golden/validation-error-422.json"
        writeIORef reply (ReplyError status422 [(hContentType, "application/json"), (requestIdHeader, "req_test")] bytes)
        request <- requestFixture
        result <- try @ClientError (methods.createSystemOne request)
        case result of
          Left (FailureResponse _ response) -> do
            Client.responseStatusCode response @?= status422
            lookup requestIdHeader (toList (Client.responseHeaders response)) @?= Just "req_test"
            Client.responseBody response @?= bytes
            case parseErrorBody (Client.responseBody response) of
              ErrorBodyValidation body -> case body.detail of
                Just entries -> (Vector.head entries).msg @?= "Field required"
                Nothing -> assertFailure "detail missing"
              other -> assertFailure (show other)
          other -> assertFailure (show other),
      testCase "danger-zone flags emit only requested headers" $ withClient $ \methods _ capture -> do
        request <- requestFixture
        _ <- methods.createSystemOneWith (DangerZone True False) request
        first <- capture
        lookup dangerZoneImagesHeader first.headers @?= Just "true"
        lookup dangerZoneLongContextHeader first.headers @?= Nothing
        _ <- methods.createSystemOneWith (DangerZone False True) request
        second <- capture
        lookup dangerZoneImagesHeader second.headers @?= Nothing
        lookup dangerZoneLongContextHeader second.headers @?= Just "true",
      testCase "listModels route" $ withClient $ \methods _ capture -> do
        methods.listModels >>= (@?= modelsFixture)
        seen <- capture
        seen.method @?= "GET"
        seen.path @?= "/v1/models"
        lookup hAuthorization seen.headers @?= Just "Bearer test-key",
      testCase "getClientEnv parses default URL" $ do
        env <- getClientEnv defaultBaseUrl
        Client.baseUrl env @?= Client.BaseUrl Client.Https "api.typesafe.ai" 443 "",
      testCase "getClientEnv rejects malformed URL" $ do
        result <- try @SomeException (getClientEnv "not a url")
        case result of
          Left _ -> pure ()
          Right _ -> assertFailure "invalid URL accepted",
      testCase "invalid successful response stays DecodeFailure" $ withClient $ \methods reply _ -> do
        writeIORef reply (ReplyRaw status200 [(hContentType, "application/json")] "{\"model\":\"m\",\"answers\":{}}")
        request <- requestFixture
        result <- try @ClientError (methods.createSystemOne request)
        case result of
          Left (DecodeFailure _ _) -> pure ()
          other -> assertFailure (show other),
      testCase "type-level header names match constants" $ do
        (BS.pack (symbolVal (Proxy @"TypeSafe-Danger-Zone-Images"))) @?= "TypeSafe-Danger-Zone-Images"
        (BS.pack (symbolVal (Proxy @"TypeSafe-Danger-Zone-Long-Context"))) @?= "TypeSafe-Danger-Zone-Long-Context"
        dangerZoneImagesHeader @?= "TypeSafe-Danger-Zone-Images"
        dangerZoneLongContextHeader @?= "TypeSafe-Danger-Zone-Long-Context"
    ]