Type ordinary integration probe HTTP traffic
This commit is contained in:
parent
390434bed1
commit
cc5c47f2ce
1 changed files with 1022 additions and 661 deletions
|
|
@ -4,37 +4,35 @@ import Control.Concurrent (threadDelay)
|
|||
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.Environment (getArgs)
|
||||
import System.IO (hFlush, stdout)
|
||||
import System.IO.Temp (withSystemTempDirectory)
|
||||
import Text.Read (readMaybe)
|
||||
|
||||
main :: IO ()
|
||||
main =
|
||||
withSystemTempDirectory "nuthatch-runtime-http-probe" $ \directory -> do
|
||||
context <- newProbeContext directory
|
||||
getArgs >>= \case
|
||||
["health", expectedText] ->
|
||||
maybe
|
||||
(fail "invalid expected readiness status")
|
||||
(verifyHealth context)
|
||||
(readMaybe expectedText)
|
||||
["baseline"] -> withSession context True "runtime-baseline" $
|
||||
verifyBaseline context
|
||||
["coherence"] -> verifyCoherence context
|
||||
["redis-unavailable"] ->
|
||||
withSession context False "runtime-redis" $
|
||||
verifyRedisUnavailable context
|
||||
["database-unavailable"] ->
|
||||
withSession context False "runtime-database" $
|
||||
verifyDatabaseUnavailable context
|
||||
_ ->
|
||||
fail
|
||||
"expected health STATUS, baseline, coherence, redis-unavailable, \
|
||||
\or database-unavailable"
|
||||
main = do
|
||||
context <- newProbeContext
|
||||
getArgs >>= \case
|
||||
["health", expectedText] ->
|
||||
maybe
|
||||
(fail "invalid expected readiness status")
|
||||
(verifyHealth context)
|
||||
(readMaybe expectedText)
|
||||
["baseline"] -> withSession context True "runtime-baseline" $
|
||||
verifyBaseline context
|
||||
["coherence"] -> verifyCoherence context
|
||||
["redis-unavailable"] ->
|
||||
withSession context False "runtime-redis" $
|
||||
verifyRedisUnavailable context
|
||||
["database-unavailable"] ->
|
||||
withSession context False "runtime-database" $
|
||||
verifyDatabaseUnavailable context
|
||||
_ ->
|
||||
fail
|
||||
"expected health STATUS, baseline, coherence, redis-unavailable, \
|
||||
\or database-unavailable"
|
||||
|
||||
withSession
|
||||
:: ProbeContext
|
||||
|
|
@ -48,20 +46,20 @@ withSession context verifyBoundary suffix action = do
|
|||
|
||||
verifyHealth :: ProbeContext -> Int -> IO ()
|
||||
verifyHealth context expectedReady = do
|
||||
health <- curl [probeBase context <> "/healthz"]
|
||||
assertEqual "runtime health status" 200 (curlStatus health)
|
||||
assertContains "runtime health body" "\"ok\":true" (curlBody health)
|
||||
ready <- curl [probeBase context <> "/readyz"]
|
||||
assertEqual "runtime readiness status" expectedReady (curlStatus ready)
|
||||
_ <- curl [probeBase context <> "/app"]
|
||||
session <- curl [probeBase context <> "/api/session"]
|
||||
assertEqual "runtime unauthenticated session" 303 (curlStatus session)
|
||||
health <- get (probeClient context) (probeBase context <> "/healthz")
|
||||
assertEqual "runtime health status" 200 (probeStatus health)
|
||||
assertContains "runtime health body" "\"ok\":true" (probeBody health)
|
||||
ready <- get (probeClient context) (probeBase context <> "/readyz")
|
||||
assertEqual "runtime readiness status" expectedReady (probeStatus ready)
|
||||
_ <- get (probeClient context) (probeBase context <> "/app")
|
||||
session <- get (probeClient context) (probeBase context <> "/api/session")
|
||||
assertEqual "runtime unauthenticated session" 303 (probeStatus session)
|
||||
putStrLn "Runtime health semantics passed"
|
||||
|
||||
verifyBaseline :: ProbeContext -> Session -> IO ()
|
||||
verifyBaseline context sessionA = do
|
||||
browsed <- browse context sessionA ""
|
||||
assertEqual "runtime baseline browse" 200 (curlStatus browsed)
|
||||
assertEqual "runtime baseline browse" 200 (probeStatus browsed)
|
||||
uploaded <- upload context sessionA "personal" "cross-user.txt"
|
||||
"76000000-0000-4000-8000-000000000001" "private-a"
|
||||
requireSucceeded "runtime baseline put" uploaded
|
||||
|
|
@ -69,30 +67,34 @@ verifyBaseline context sessionA = do
|
|||
sessionB <- login context False userB "runtime-baseline-b"
|
||||
personalB <- browse context sessionB "?root=personal"
|
||||
assert "runtime personal cache crossed users" $
|
||||
not ("cross-user.txt" `BS.isInfixOf` curlBody personalB)
|
||||
wrongLogout <- curl
|
||||
[ "--cookie", sessionCookies sessionA
|
||||
, "--header"
|
||||
, "X-CSRF-Token: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
, "--request", "POST"
|
||||
, probeBase context <> "/logout"
|
||||
]
|
||||
not ("cross-user.txt" `BS.isInfixOf` probeBody personalB)
|
||||
wrongLogout <- postWith
|
||||
(probeClient context)
|
||||
defaultRequestOptions
|
||||
{ optionCookies = Just (sessionCookies sessionA)
|
||||
, optionHeaders =
|
||||
[ ( "X-CSRF-Token"
|
||||
, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
)
|
||||
]
|
||||
}
|
||||
(probeBase context <> "/logout")
|
||||
assert "runtime baseline accepted wrong logout CSRF" $
|
||||
curlStatus wrongLogout >= 400
|
||||
probeStatus wrongLogout >= 400
|
||||
deleted <- deleteObject context sessionA "personal" "cross-user.txt"
|
||||
"76000000-0000-4000-8000-000000000002"
|
||||
requireSucceeded "runtime baseline delete" deleted
|
||||
restored <- upload context sessionA "personal" "cross-user.txt"
|
||||
"76000000-0000-4000-8000-000000000003" "private-a"
|
||||
requireSucceeded "runtime unavailable fixture" restored
|
||||
loggedOut <- curl
|
||||
[ "--cookie", sessionCookies sessionA
|
||||
, "--cookie-jar", sessionCookies sessionA
|
||||
, "--header", "X-CSRF-Token: " <> B8.unpack (sessionCsrf sessionA)
|
||||
, "--request", "POST"
|
||||
, probeBase context <> "/logout"
|
||||
]
|
||||
assertEqual "runtime baseline logout" 200 (curlStatus loggedOut)
|
||||
loggedOut <- postWith
|
||||
(probeClient context)
|
||||
defaultRequestOptions
|
||||
{ optionCookies = Just (sessionCookies sessionA)
|
||||
, optionHeaders = [("X-CSRF-Token", sessionCsrf sessionA)]
|
||||
}
|
||||
(probeBase context <> "/logout")
|
||||
assertEqual "runtime baseline logout" 200 (probeStatus loggedOut)
|
||||
putStrLn "Runtime audit baseline passed"
|
||||
|
||||
verifyCoherence :: ProbeContext -> IO ()
|
||||
|
|
@ -113,33 +115,33 @@ verifyCoherence context = do
|
|||
rootListing <- browse context sessionB "?root=shared"
|
||||
assertContains "shared root cache invalidation"
|
||||
"runtime"
|
||||
(curlBody rootListing)
|
||||
(probeBody rootListing)
|
||||
prefixListing <- browse context sessionB "?root=shared&prefix=runtime"
|
||||
assertContains "shared prefix cache invalidation"
|
||||
"coherence.txt"
|
||||
(curlBody prefixListing)
|
||||
(probeBody prefixListing)
|
||||
deleted <- deleteObject context sessionA "shared" "runtime/coherence.txt"
|
||||
"74000000-0000-4000-8000-000000000002"
|
||||
requireSucceeded "shared coherence delete" deleted
|
||||
afterDelete <- browse context sessionB "?root=shared&prefix=runtime"
|
||||
assert "shared deletion remained in another replica cache" $
|
||||
not ("coherence.txt" `BS.isInfixOf` curlBody afterDelete)
|
||||
not ("coherence.txt" `BS.isInfixOf` probeBody afterDelete)
|
||||
threadDelay 2000000
|
||||
browse context sessionB "?root=shared" >>= \response ->
|
||||
assertEqual "cache TTL browse" 200 (curlStatus response)
|
||||
assertEqual "cache TTL browse" 200 (probeStatus response)
|
||||
putStrLn "Cross-user cache coherence and TTL passed"
|
||||
|
||||
verifyRedisUnavailable :: ProbeContext -> Session -> IO ()
|
||||
verifyRedisUnavailable context session = do
|
||||
listing <- browse context session "?root=personal"
|
||||
assertEqual "Redis-unavailable browse" 200 (curlStatus listing)
|
||||
assertEqual "Redis-unavailable browse" 200 (probeStatus listing)
|
||||
assertContains "Redis-unavailable browse fixture"
|
||||
"cross-user.txt"
|
||||
(curlBody listing)
|
||||
(probeBody listing)
|
||||
searched <- browse context session "?root=personal&q=CROSS-USER"
|
||||
assertContains "Redis-unavailable search"
|
||||
"cross-user.txt"
|
||||
(curlBody searched)
|
||||
(probeBody searched)
|
||||
unavailableUpload <- upload context session "personal" "unavailable.bin"
|
||||
"72000000-0000-4000-8000-000000000001" "x"
|
||||
requireError "Redis-unavailable upload"
|
||||
|
|
@ -168,22 +170,22 @@ verifyDatabaseUnavailable context session = do
|
|||
503 "mutation_rejected" unavailableDelete
|
||||
putStrLn "Database-unavailable mutation behavior passed"
|
||||
|
||||
retryPastThrottle :: Int -> IO CurlResponse -> IO CurlResponse
|
||||
retryPastThrottle :: Int -> IO ProbeResponse -> IO ProbeResponse
|
||||
retryPastThrottle attempts action = do
|
||||
response <- action
|
||||
if attempts > 1
|
||||
&& "\"error\":\"throttle_unavailable\"" `BS.isInfixOf` curlBody response
|
||||
&& "\"error\":\"throttle_unavailable\"" `BS.isInfixOf` probeBody response
|
||||
then threadDelay 250000 >> retryPastThrottle (attempts - 1) action
|
||||
else pure response
|
||||
|
||||
requireSucceeded :: String -> CurlResponse -> IO ()
|
||||
requireSucceeded :: String -> ProbeResponse -> IO ()
|
||||
requireSucceeded 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)
|
||||
|
||||
requireError :: String -> Int -> BS.ByteString -> CurlResponse -> IO ()
|
||||
requireError :: String -> Int -> BS.ByteString -> ProbeResponse -> IO ()
|
||||
requireError label status code response = do
|
||||
assertEqual (label <> " status") status (curlStatus response)
|
||||
assertEqual (label <> " status") status (probeStatus response)
|
||||
assert
|
||||
(label <> " body: " <> B8.unpack (curlBody response))
|
||||
(("\"error\":\"" <> code <> "\"") `BS.isInfixOf` curlBody response)
|
||||
(label <> " body: " <> B8.unpack (probeBody response))
|
||||
(("\"error\":\"" <> code <> "\"") `BS.isInfixOf` probeBody response)
|
||||
|
|
|
|||
Loading…
Reference in a new issue