A case must confirm an emailed activation link works and that reusing the same link is rejected — how do you sequence and provision it?
answer
- One link, one case, one use
- The second use is the assertion
- A fresh recipient identity per case
- Assert the first use actually worked
- Shorten the lifetime, do not sleep
basics
~20 sIssue a link for this case alone, use it once and assert the account activated, then use the same link again and assert the refusal. Never share a link between cases and never depend on run order.
solid answer
~50 sSingle use means the arrange step is spent by the act step, so the order is fixed: cause the message, extract the link, use it once and **assert the effect**, then use the same link again and **assert the refusal**, then assert the first effect is unchanged. The first assertion is load-bearing. If activation silently failed, the second call becomes a first use, succeeds, and the case reports a broken one-shot rule when the real defect was that activation never happened. Provisioning must be private to the case: register a fresh recipient identity so the link belongs to nobody else. Two cases sharing one identity across parallel workers will consume each other's links and fail by scheduling order. Expiry deserves the same discipline without waiting out the real lifetime — shorten the lifetime through configuration in the target deployment, or control the clock the issuer reads.
code
pseudocode · 13 linesaddress = unique_recipient(case_id)
register(address)
msg = await_message(address, deadline = 60s)
link = activation_link_of(msg)
first = open(link)
assert first.accepted
assert account_state(address) == "active" # load-bearing
second = open(link)
assert second.refused
assert account_state(address) == "active" # not rolled backgo deeper
Know what single use means in practice: the link stops working after one successful use, so a case cannot spend it setting something up and then expect it to work again later for the real check.
Explain the ordering — use once, assert the effect, replay the same link, assert the refusal — and explain why every case needs its own freshly issued link instead of one shared across the suite.
Show how the pair stays deterministic under parallel runs, and how you prove expiry without waiting out the real lifetime by making the lifetime configurable in the target deployment or by controlling the issuing clock.
Decide where these promises are proven at all. A cheap check close to the issuing code covering every message type, plus one end-to-end case for the message that matters, usually beats proving both rules through the screen for every template.
## Two promises, and they need different proofs A single-use link carries two independent promises. **One-shot**: the first successful use consumes it and every later use is refused. **Expiry**: after some lifetime it is refused whether or not it was ever used. They fail independently — a link can be genuinely one-shot and never expire, or expire correctly while quietly permitting a second use inside the window — so a case that claims to cover "the link is safe" while exercising only one of them is claiming more than it proves. ## The ordering the one-shot promise forces Because the arrange step and the act step are the same request, the case has exactly one shape available: 1. Cause the message to be issued — register, request a reset, whatever the flow is. 2. Read the message and extract the link. 3. Use the link once and **assert the effect**: the account is active, the password form is open, whatever success means here. 4. Use the same link again and **assert the refusal**. 5. Assert the effect from step three is unchanged — the refused attempt did not undo the successful one. Step three's assertion is not decoration. Without it, a silently failed first use turns step four into a first use, which succeeds, and the case reports that the one-shot rule is broken when the real defect is that activation never worked at all. Two assertions in sequence tell you which of the two things broke; one assertion tells you only that something did. Step five is the one people leave out. A refusal that also rolls the account back is a worse defect than a missing refusal, and it costs one line to catch. ## The link has to belong to this case One-shot is a property of a shared resource, so provisioning matters more here than almost anywhere else in a suite: - **Register a fresh recipient identity per case**, derived from something unique to the run. The link is then addressed to nobody else, and there is no question about which message is yours. - **Never carry a link between cases.** A link extracted in one case and used in another is already spent by the time the second runs, and the resulting failure moves around with execution order. - **Never share one link across a parallel fan-out.** Two workers running cases against the same link means one of them fails, and which one is a scheduling detail rather than a fact about the product. - **Re-issue rather than replay on retry.** If the whole case is retried after a failure, it must go back to step one. Replaying step three with a spent link produces a refusal indistinguishable from the defect you were testing for. ## Proving expiry without paying for it The naive version waits out the real lifetime. If that lifetime is fifteen minutes the case is unaffordable, and if someone shortens the lifetime later the case passes for the wrong reason and nobody notices. | Approach | What it needs | What it costs | | --- | --- | --- | | Wait out the real lifetime | Nothing | Minutes of wall-clock every run; the worst option | | Set a short lifetime in the target deployment | The lifetime to be a setting | A deployment that differs slightly from production | | Control the clock the issuer reads | A seam that moves time | Care that the whole deployment moves together | | Issue with a backdated timestamp through the real path | An internal entry point | Skips whatever the normal path does before issuing | The middle two are usually the right trade. Whichever you pick, record in the case why the lifetime under test is not the production lifetime, because the next reader will otherwise assume the number means something. ## What the failure messages have to say Both halves of this case fail in ways that look identical from outside — "the link did not work" — so the messages have to separate them. A refusal on the first use should say explicitly that it was the first use, and should carry when the link was issued and when it was used: a first use refused after four seconds is a different bug from one refused after four minutes. A second use that unexpectedly succeeds should report the state it left behind, because that is the part a reviewer will want. ## Deciding where to prove it at all The pair above is a real end-to-end case and it is not cheap: it registers, waits for arrival, extracts, and makes two round trips. Running it for every message type the product sends is rarely the right investment. A better shape is usually: - A cheap check close to the issuing code proves one-shot and expiry for the mechanism itself, across every message type, in milliseconds. - One end-to-end case proves the mechanism is actually wired into the flow a person walks, for the message that matters most. The rule then gets covered broadly where coverage is cheap and realistically where it counts, and the suite stops paying for the same proof once per template.
- The success path already consumed the link. How do you keep the second-use assertion from depending on that first step having worked?By asserting the first step's effect before replaying. If activation silently failed, the replay is really a first use, it succeeds, and the case blames the one-shot rule for a defect that lives elsewhere. Three assertions in sequence — accepted, active after the first use, refused and still active after the second — say which of the promises broke rather than only that something did.
- How would you prove expiry without waiting out the real lifetime?Make the lifetime a setting the target deployment can carry, set it to a few seconds for that case, and wait past it. Where the lifetime is fixed, control the clock the issuer reads, or issue through the real path with a backdated timestamp. Record why the lifetime under test differs from production, or the next reader will assume the number is meaningful.
Treat the link like a turnstile ticket: walk through once and confirm you are inside, then push the same ticket back in and confirm the barrier holds.
saying these in an interview costs you the question
- Sharing one activation link across several cases to save time
- Sleeping for the full lifetime to prove a link expired
- Treating the second use as cleanup rather than asserting its refusal
- Assuming the first use succeeded without checking the account state
- Depending on case execution order for the link to still be unused