packages feed

warp-s2n-tls-0.1.0.0: test/IntegrationTests.hs

{-# LANGUAGE NumericUnderscores #-}

module IntegrationTests (integrationSpec) where

import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (replicateConcurrently)
import Control.Exception (SomeException, try)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Network.Connection qualified as Conn
import S2nTls (S2nTls (..))
import Test.Hspec (SpecWith, describe, expectationFailure, it, shouldBe, shouldSatisfy)

import Network.Wai.Handler.WarpS2N (tlsSettings)

import TestUtils

integrationSpec :: SpecWith S2nTls
integrationSpec = do
    describe "Basic TLS roundtrip" basicRoundtripSpec
    describe "Multiple requests" multipleRequestsSpec
    describe "Concurrent connections" concurrentSpec
    describe "Large payloads" largePayloadSpec

basicRoundtripSpec :: SpecWith S2nTls
basicRoundtripSpec = do
    it "establishes TLS connection and receives response" $ \tls -> do
        let settings = tlsSettings testCertPath testKeyPath
        withTestServer tls settings $ \port -> do
            result <- makeSecureRequest port
            case result of
                Left err -> expectationFailure $ "Request failed: " ++ err
                Right response -> do
                    let responseStr = LBS.toStrict response
                    responseStr `shouldSatisfy` BS.isInfixOf "HTTP"
                    responseStr `shouldSatisfy` BS.isInfixOf "200"
                    responseStr `shouldSatisfy` BS.isInfixOf "blarg"

    it "handles connection close gracefully" $ \tls -> do
        let settings = tlsSettings testCertPath testKeyPath
        withTestServer tls settings $ \port -> do
            result <- makeSecureRequest port
            case result of
                Left err -> expectationFailure $ "Request failed: " ++ err
                Right _ -> pure ()
            result2 <- makeSecureRequest port
            case result2 of
                Left err -> expectationFailure $ "Second request failed: " ++ err
                Right _ -> pure ()

multipleRequestsSpec :: SpecWith S2nTls
multipleRequestsSpec = do
    it "handles 10 sequential requests" $ \tls -> do
        let settings = tlsSettings testCertPath testKeyPath
        withTestServer tls settings $ \port -> do
            results <- mapM (\_ -> makeSecureRequest port) [1 .. 10 :: Int]
            let failures = [e | Left e <- results]
            failures `shouldBe` []

    it "handles requests with small delays" $ \tls -> do
        let settings = tlsSettings testCertPath testKeyPath
        withTestServer tls settings $ \port -> do
            results <-
                mapM
                    ( \_ -> do
                        threadDelay 50_000
                        makeSecureRequest port
                    )
                    [1 .. 5 :: Int]
            let failures = [e | Left e <- results]
            failures `shouldBe` []

concurrentSpec :: SpecWith S2nTls
concurrentSpec = do
    it "handles 10 concurrent connections" $ \tls -> do
        let settings = tlsSettings testCertPath testKeyPath
        withTestServer tls settings $ \port -> do
            results <- replicateConcurrently 10 (makeSecureRequest port)
            let successes = length [() | Right _ <- results]
            successes `shouldBe` 10

    it "handles 25 concurrent connections" $ \tls -> do
        let settings = tlsSettings testCertPath testKeyPath
        withTestServer tls settings $ \port -> do
            results <- replicateConcurrently 25 (makeSecureRequest port)
            let successes = length [() | Right _ <- results]
            successes `shouldSatisfy` (>= 23)

largePayloadSpec :: SpecWith S2nTls
largePayloadSpec = do
    it "client can send larger request" $ \tls -> do
        let settings = tlsSettings testCertPath testKeyPath
        withTestServer tls settings $ \port -> do
            result <- try @SomeException $ do
                ctx <- Conn.initConnectionContext
                conn <-
                    Conn.connectTo
                        ctx
                        Conn.ConnectionParams
                            { Conn.connectionHostname = "localhost"
                            , Conn.connectionPort = fromIntegral port
                            , Conn.connectionUseSecure =
                                Just
                                    Conn.TLSSettingsSimple
                                        { Conn.settingDisableCertificateValidation = True
                                        , Conn.settingDisableSession = False
                                        , Conn.settingUseServerName = True
                                        }
                            , Conn.connectionUseSocks = Nothing
                            }

                let body = BS.replicate 1_000_000 0x41
                    request =
                        BS.concat
                            [ "POST / HTTP/1.1\r\n"
                            , "Host: localhost\r\n"
                            , "Content-Length: 1000000\r\n"
                            , "Connection: close\r\n"
                            , "\r\n"
                            , body
                            ]
                Conn.connectionPut conn request

                response <- readAll conn
                Conn.connectionClose conn
                pure response

            case result of
                Left e -> expectationFailure $ "Large request failed: " ++ show e
                Right response ->
                    BS.length response `shouldSatisfy` (> 0)
  where
    readAll conn = do
        chunk <- Conn.connectionGetChunk conn
        if BS.null chunk
            then pure BS.empty
            else do
                rest <- readAll conn
                pure (chunk <> rest)