Type ordinary integration probe HTTP traffic

This commit is contained in:
ms2k 2026-07-20 23:06:01 +09:00
commit cc5c47f2ce

View file

@ -6,6 +6,7 @@ import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as B8
import Nuthatch.Probe.Assert
import Nuthatch.Probe.Curl
import Nuthatch.Probe.Http
import Nuthatch.Probe.Oidc
import Nuthatch.Probe.ProductClient
import System.Exit (ExitCode (..))
@ -25,7 +26,7 @@ import System.Process
main :: IO ()
main =
withSystemTempDirectory "nuthatch-idempotency-probe" $ \directory -> do
context <- newProbeContext directory
context <- newProbeContext
user <- requireEnv "NUTHATCH_OIDC_USER_A"
session <- login context False user "idempotency"
lane <- requireEnv "NUTHATCH_FIXTURE_LANE"
@ -53,16 +54,16 @@ verifyEpochs context session lane ipA ipB = do
conflict <- targetedUpload context session lane ipB operationId "conflict"
assertContains "conflicting reuse"
"\"result\":\"conflict\""
(curlBody conflict)
(probeBody conflict)
threadDelay 3000000
(responseA, responseB) <- concurrently
(targetedUpload context session lane ipA operationId "second-epoch-a")
(targetedUpload context session lane ipB operationId "second-epoch-b")
let outcomes = [responseA, responseB]
succeeded response =
"\"result\":\"succeeded\"" `BS.isInfixOf` curlBody response
"\"result\":\"succeeded\"" `BS.isInfixOf` probeBody response
conflicted response =
"\"result\":\"conflict\"" `BS.isInfixOf` curlBody response
"\"result\":\"conflict\"" `BS.isInfixOf` probeBody response
assertEqual "new epoch winner count" 1 (length (filter succeeded outcomes))
assertEqual "new epoch conflict count" 1 (length (filter conflicted outcomes))
let (winningBody, replayIp) =
@ -72,13 +73,9 @@ verifyEpochs context session lane ipA ipB = do
secondReplay <-
targetedUpload context session lane replayIp operationId winningBody
requireSucceededWindow "new epoch replay" secondReplay
downloaded <- downloadFrom
(probeBase context)
(resolveArgs lane ipA)
session
"personal"
"idempotency.txt"
assertEqual "new epoch object body" winningBody (curlBody downloaded)
downloaded <-
targetedDownload context session lane ipA "personal" "idempotency.txt"
assertEqual "new epoch object body" winningBody (probeBody downloaded)
verifyActiveIdentity
:: ProbeContext
@ -106,7 +103,7 @@ verifyActiveIdentity context session lane ipA ipB directory = do
"first-epoch"
assertContains "active replay after terminal window"
"\"result\":\"pending\""
(curlBody pending)
(probeBody pending)
locked <- targetedUploadAt
context
session
@ -115,14 +112,14 @@ verifyActiveIdentity context session lane ipA ipB directory = do
"active-window.bin"
"73000000-0000-4000-8000-000000000003"
"first-epoch"
assertEqual "active key lock status" 200 (curlStatus locked)
assertEqual "active key lock status" 200 (probeStatus locked)
assertContains "active key lock"
"\"result\":\"rejected\""
(curlBody locked)
(probeBody locked)
completed <- wait active
assertContains "active request completion"
"\"result\":\"succeeded\""
(curlBody completed)
(probeBody completed)
targetedUpload
:: ProbeContext
@ -131,7 +128,7 @@ targetedUpload
-> String
-> String
-> BS.ByteString
-> IO CurlResponse
-> IO ProbeResponse
targetedUpload context session lane ip =
targetedUploadAt context session lane ip "idempotency.txt"
@ -143,15 +140,39 @@ targetedUploadAt
-> String
-> String
-> BS.ByteString
-> IO CurlResponse
targetedUploadAt context session lane ip path operationId =
uploadTo
(probeBase context)
(resolveArgs lane ip)
-> IO ProbeResponse
targetedUploadAt context session lane ip path operationId body =
uploadDeclared
context
session
"personal"
lane
ip
path
operationId
(BS.length body)
(sha256Hex body)
body
targetedDownload
:: ProbeContext
-> Session
-> String
-> String
-> String
-> String
-> IO ProbeResponse
targetedDownload context session lane ip root path = do
cookies <- cookieHeader (sessionCookies session)
curl
( resolveArgs lane ip
<> [ "--header", "Cookie: " <> B8.unpack cookies
, probeBase context
<> "/api/download?root="
<> root
<> "&path="
<> path
]
)
uploadDeclared
:: ProbeContext
@ -163,14 +184,15 @@ uploadDeclared
-> Int
-> BS.ByteString
-> BS.ByteString
-> IO CurlResponse
-> IO ProbeResponse
uploadDeclared context session lane ip path operationId size digest body =
withSystemTempFile "nuthatch-declared-upload" $ \bodyPath handle -> do
BS.hPut handle body
hClose handle
cookies <- cookieHeader (sessionCookies session)
curl
( resolveArgs lane ip
<> [ "--cookie", sessionCookies session
<> [ "--header", "Cookie: " <> B8.unpack cookies
, "--header", "Content-Type: application/octet-stream"
, "--header", "X-Declared-Size: " <> show size
, "--header", "X-Content-SHA256: " <> B8.unpack digest
@ -193,8 +215,9 @@ slowUpload
-> FilePath
-> String
-> BS.ByteString
-> IO CurlResponse
-> IO ProbeResponse
slowUpload context session lane ip directory operationId body = do
cookies <- cookieHeader (sessionCookies session)
let outputPath = directory <> "/active-upload-response"
statusPath = directory <> "/active-upload-status"
firstChunk = BS.take 65536 body
@ -204,7 +227,7 @@ slowUpload context session lane ip directory operationId body = do
, "--silent"
, "--show-error"
, "--resolve", "nuthatch-" <> lane <> ":8080:" <> ip
, "--cookie", sessionCookies session
, "--header", "Cookie: " <> B8.unpack cookies
, "--header", "Content-Type: application/octet-stream"
, "--header", "X-Declared-Size: " <> show (BS.length body)
, "--header", "X-Content-SHA256: " <> B8.unpack (sha256Hex body)
@ -240,10 +263,10 @@ slowUpload context session lane ip directory operationId body = do
status <- case reads (B8.unpack statusBytes) of
[(value, "")] -> pure value
_ -> fail "slow upload returned invalid HTTP status"
pure CurlResponse
{ curlStatus = status
, curlHeaders = []
, curlBody = responseBody
pure ProbeResponse
{ probeStatus = status
, probeHeaders = []
, probeBody = responseBody
}
resolveArgs :: String -> String -> [String]
@ -255,13 +278,13 @@ resolveArgs lane ip =
, "--resolve", "nuthatch-" <> lane <> ":8080:" <> ip
]
requireSucceededWindow :: String -> CurlResponse -> IO ()
requireSucceededWindow :: String -> ProbeResponse -> IO ()
requireSucceededWindow label response = do
assertEqual (label <> " status") 200 (curlStatus response)
assertContains label "\"result\":\"succeeded\"" (curlBody response)
assertEqual (label <> " status") 200 (probeStatus response)
assertContains label "\"result\":\"succeeded\"" (probeBody response)
assertContains (label <> " window")
"\"idempotency_window_seconds\":2"
(curlBody response)
(probeBody response)
emitRefinementTrace :: IO ()
emitRefinementTrace =