core, tests: give the badge timeout its own message

This commit is contained in:
shum
2026-08-27 10:28:26 +00:00
parent f20856392b
commit 7e07426e69
2 changed files with 28 additions and 3 deletions
+12 -1
View File
@@ -5479,7 +5479,18 @@ sendBadgeRequest nm user signKey req =
pure $ either (badgeRequestFailed . responseFailed) id $ J.eitherDecodeStrict' respData
-- a sentence first, then the error itself: the apps render an 'internal' code by showing this
-- message (G2), and a bare 'Show' of a 'ChatError' is not something to put in front of a user
requestFailed e = "The badge service could not be reached, and the request was not delivered. Details: " <> tshow e
--
-- the agent's request timeout gets its own sentence, because it is the ONE outcome here that
-- may have been DELIVERED: the service can have received the request and consumed the code,
-- with only the reply lost. Saying "not delivered" would steer the user into re-entering the
-- same code, and 'redeemBadgeCode' mints a FRESH purchase key per call, so the retry reaches
-- the service as a different signer and is answered @code_used@ (plan §9) — burning a code
-- they paid for. Everything else this collapses is a genuine non-delivery: an unresolvable
-- target, a transport failure before the send, or an agent error other than the timeout.
requestFailed = \case
ChatErrorAgent {agentError = AGENT (A_SERVICE ASETimeout)} ->
"The badge service did not answer in time. The request may still have been delivered, so a code presented with it may already have been used, and entering it again will not help. Please contact support."
e -> "The badge service could not be reached, and the request was not delivered. Details: " <> tshow e
responseFailed e = "The badge service answered with something this app version cannot read. Details: " <> T.pack e
badgeRequestFailed e = BSPError {code = BSEInternal, message = Just e, retryAfter = Nothing}
+16 -2
View File
@@ -2862,6 +2862,12 @@ testC5StorePaymentsUnsupported ps =
-- The agent's 'serviceRequestTimeout' is shortened to two seconds for this example only: its
-- default is thirty, which is longer than any terminal assertion waits, and what is being pinned
-- is what happens AFTER the timeout, not how long the default is.
--
-- The message asserted here is the TIMEOUT one, and that is the whole point of asserting it in
-- full: nothing is listening, so the request expires as @A_SERVICE ASETimeout@, which is the one
-- outcome of 'sendBadgeRequest' that may have been delivered. It must not tell the user the
-- request was not delivered, and it must not invite a retry of the same code, which would arrive
-- as a different signer and be answered @code_used@ (plan §9).
testC5ServiceStoppedIsInternal :: HasCallStack => TestParams -> IO ()
testC5ServiceStoppedIsInternal ps =
-- the two startup phases publish the service's address and then it stays down: 'withService' is
@@ -2871,11 +2877,19 @@ testC5ServiceStoppedIsInternal ps =
let cfg = (badgeClientCfg sLink pk) {agentConfig = testAgentCfg {serviceRequestTimeout = 2}}
withNewTestChatCfg ps cfg badgeClientDbPrefix aliceProfile $ \alice -> do
alice ##> "/_badge catalog 1"
alice <##. "badge service error: internal, The badge service could not be reached, and the request was not delivered."
alice <## badgeTimeoutError
alice ##> purchaseCodeCmd "C5-SERVICE-DOWN"
alice <##. "badge service error: internal, The badge service could not be reached, and the request was not delivered."
alice <## badgeTimeoutError
noClientBadgeRows "with the service stopped" alice
-- | 'sendBadgeRequest''s timeout sentence, spelled out rather than derived from the function under
-- test: it carries no @Details:@ tail, so the whole line is compared.
badgeTimeoutError :: String
badgeTimeoutError =
"badge service error: internal, The badge service did not answer in time."
<> " The request may still have been delivered, so a code presented with it may already have been used,"
<> " and entering it again will not help. Please contact support."
-- Supersession and the slot -----------------------------------------------------
-- | A second code redeemed on the same profile takes the slot: the first purchase becomes