skip to content

A sign-up screen limits how often its confirmation email can be re-requested; what should an automated case assert when the limit trips?

level: juniorimportance: should knowfreq 38%

answer

  1. A refusal the user can act on
  2. Silence is a defect, not a limit
  3. Check the mailbox gains nothing
  4. Prove recovery after the stated window
  5. Threshold from configuration, not a guess

basics

~20 s

Assert three things: the request is refused visibly rather than silently dropped, the screen says when the user may try again, and no extra message reaches the mailbox. Checking only that a button greyed out proves none of them.

solid answer

~40 s

A resend limit is a product behaviour with a user-visible contract, so the case asserts the contract rather than the blocked control. Drive the resend up to the configured threshold and assert each of those requests succeeds, then make one more and assert four things: the refusal is shown to the user in plain words, it states when the request becomes available again, no additional message arrives in the recipient mailbox, and the link already delivered is unchanged and still usable. Finish by proving recovery - after the stated window the resend succeeds again - otherwise the case cannot tell a cooldown from a permanent lockout. Take the threshold from the deployment's configured value rather than a hard-coded guess, and keep this separate from any throttling applied further down the delivery path.

code

pseudocode · 14 lines
pseudocode
threshold = configured_resend_limit()

for i in 1..threshold:
    assert request_confirmation_email().accepted
    assert mailbox.message_count() == i

blocked = request_confirmation_email()
assert blocked.refused_visibly
assert blocked.states_retry_time
assert mailbox.message_count() == threshold          # nothing extra was sent
assert open_link(link_from(mailbox.first())).accepted # delivered link untouched

advance_clock_past(blocked.retry_after)
assert request_confirmation_email().accepted

go deeper

for a junior

Be ready to list what you would check when a resend is refused: a message the user can read, a clear statement of when to try again, and no new message in the mailbox. Say plainly that a silent no-op is a defect rather than a limit.

for a middle

Explain how the case reaches the limit deliberately - the configured number of accepted requests, then one more - and why it asserts the refusal and the unchanged message count rather than the disabled control on the screen.

for a senior

Show that you prove recovery after the stated window, keep the product's own limit separate from throttling applied further down the delivery path, and confirm that a refused request leaves the already-delivered link usable instead of resetting it.

for a principal

Own the tradeoff in the limit itself. A tight window frustrates users whose first message was slow to arrive; a loose one turns the send into an amplifier aimed at any address someone types. Say what signal would move you and how you would watch for it.

A resend limit is not a disabled button. It is a product behaviour with a user-visible contract: after some number of requests inside some window, the flow refuses, tells the user when they may ask again, and stops sending. A case that checks only the greyed-out control has tested the convenience and none of the contract. ## The four assertions When the request that exceeds the limit is made, four things must be true, and each catches a different real defect: 1. **The refusal is visible.** The user is told the request was declined. A silent no-op, where the screen behaves as though the message is on its way, is worse than an error: the user waits, asks again, then abandons the flow. 2. **The refusal is actionable.** It states when the request becomes available again, in terms the user can act on. "Too many requests" on its own converts one problem into a support ticket. 3. **Nothing extra was sent.** The number of messages in the recipient mailbox is unchanged by the refused request. The screen and the send path can disagree: a flow can display a refusal and still queue the message, or block the visible control while the request behind it keeps sending. 4. **The link already delivered still works.** A refused request issues nothing, so it has no business invalidating what the user is holding. A limit that strands the user is a denial of the whole flow wearing the costume of a rate limit. | What a weak case checks | What the contract needs | | --- | --- | | the control is disabled | the request itself is refused | | an error appeared | the error says when to retry | | the run did not crash | the message count is unchanged | | something was blocked | the delivered link is still usable | ## Recovery is half the behaviour A limit that never lifts is a lockout. The case is not finished until it proves the counter resets: advance past the window the refusal named, request once more, and assert that it succeeds and a message arrives. That one step is what turns "the product blocked me" into "the product told me the truth about when I could try again", and it is the assertion that catches a window configured in the wrong unit or a counter that never decrements. Two shapes of limit exist, and the recovery step is what tells them apart. A **fixed window** resets on a boundary, so the whole allowance returns at once. A **rolling window** ages the oldest request out continuously, so waiting exactly the stated interval restores exactly one request rather than the full allowance. A case that expects the whole allowance back is asserting the wrong model, and it will fail intermittently in a way that looks like flakiness rather than a mismatch. ## Where the threshold comes from Hard-coding the number is the standard mistake. The case then fails on the first deployment configured differently, and the fix applied under time pressure is to raise the number until it passes, which quietly deletes the assertion. - Read the threshold from the configuration the target deployment is running with, and drive the loop from that. - Assert that each request up to the threshold **succeeds** and produces exactly one message. The refusal only means something if the allowed requests genuinely worked. - Never assert a value observed in a previous run. Looping until the request fails and recording the count is a case that cannot fail; it will absorb a change from five allowed requests to fifty without a word. - Where a number has to appear in the reasoning, define it in the case itself as a scenario rather than treating it as how such limits generally behave. ## Keep the boundary clean The control under test is the **product's own** limit on the resend action. Refusals that arise further down the delivery path, where a receiving system slows or rejects a burst, are a different subject with different symptoms and a different owner, and folding them into this case produces a test that fails for reasons the product cannot fix. Tell them apart by where the refusal appears: the product's own limit is visible immediately in the response the user gets, while a downstream problem shows up as a message that was accepted and then did not arrive. The last thing worth asserting is that the limit is scoped the way the product intends, whether that is per account, per recipient address, or per originating client. A case that only ever exercises one account cannot tell you which, and a limit scoped more broadly than intended will refuse a request that a second, unrelated user made.

  • Why assert anything about the mailbox when the screen already showed a refusal?
    The screen and the send path can disagree. A flow can display a refusal and still queue the message, or block the visible control while the request behind it keeps sending. Counting the messages that arrived proves the limit stopped the send, not merely the button.
  • What does the case lose if it never advances past the cooldown?
    It cannot distinguish a temporary limit from a permanent lockout, which is the difference between a working control and a support ticket. Asserting one successful request after the stated window proves the counter resets and that the message the user was shown was truthful.

saying these in an interview costs you the question

  • Asserts only that the resend button became disabled
  • Accepts a silent no-op as a working rate limit
  • Hard-codes the threshold instead of reading configuration
  • Never checks that no extra message was sent
  • Assumes a refused resend invalidates the delivered link
  • Skips the recovery check after the stated window