test(twilio): unit test the controller, which had no spec at all - #742
Conversation
`twilio/twilio.controller.js` is the core of the SMS interface - keyword handling, consent persistence, item search, Xur, delivery callbacks - and had no spec file. Its 54.5% branch coverage came incidentally, from the routes spec exercising it over HTTP. Sixty-five tests take it to 90.0%, and the global branch figure from 80.00% to 83.61%. Covers the units the routes spec reaches only sideways: the consent worker's subscription and its watermark rules, including a retry exhausting its attempts on fake timers rather than fifteen seconds of real ones; `applyConsent` discarding a superseded intent and applying a tie; the unregistered, opted-out and already-told branches of `request`; media filtering; every mapped emoji intent and the default; the `more` keyword with and without a remembered item; item search across no match, one match, distinct matches and duplicates collapsed to one; damage type present and absent; the landline path that withholds an icon; `getXur` across failed authentication, no characters, weapon filtering, a DestinyError relayed verbatim and an unexpected error that must not be; and `statusCallback` across a missing recipient, a missing status, an unknown number, the legacy status field and the claim-check update. Thresholds are left where `autoUpdate` ratcheted them - statements to 88.63, functions to 86.15, lines to 88.78. Branches stays at 88.99, which is still above the 83.61 the suite reaches; the remaining debt is `mcp/mcp.routes.js` and `notifications/notification.routes.js` at zero and `helpers/queryBuilder.js` at 58.8%. Two notes on the tests themselves. One asserts on an item's detail rather than its name, because a no-results reply is drawn at random from a list and one of them happens to read "Does it look like a Gjallarhorn?" - matching the name would fail intermittently. And two characterise the catalyst exclusion in `queryItem` as dead code rather than as a rule: it is unreachable from `request()`, which lowercases first, and redundant besides, since no catalyst in the manifest carries an itemType the filter beside it admits. Recorded in #741 rather than changed here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iD3V7Zyugg3atXVsogTBf
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The MMS test does not yet verify the advertised non-blocking behavior.
Review effort: Lite
Findings: None
What changed in this PR
Adds comprehensive unit coverage for TwilioController and raises coverage thresholds.
Changes:
- Tests consent, messaging, searches, Xur responses, callbacks, and fallback behavior.
- Updates statement, function, and line coverage thresholds.
| File | Description |
|---|---|
vitest.config.js |
Updates coverage thresholds. |
twilio/twilio.controller.spec.js |
Adds comprehensive controller unit tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…g it The test was called "should hand images off and acknowledge without waiting" and could not see whether anything waited. `mmsService.process` is a double that settles immediately, so asserting it was called and that the reply came back passes identically when the source awaits the handoff - which is the one thing the test existed to rule out. Changing `void` to `await` in `request()` failed nothing. Split in two. The first keeps the handoff assertion under a name that claims only that. The second leaves the analysis outstanding and asks for the reply anyway, so awaiting the handoff now hangs the request and fails the test. Same shape as the ordering test on #740: a property about *when* something happens needs the dependency left pending, because a call-order assertion cannot tell awaited from merely fast. Raised by Copilot on #742. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iD3V7Zyugg3atXVsogTBf
|
Right, and worth acting on even though it came through as a summary line rather than a finding. Fixed in The test was named Split in two. The first keeps the handoff assertion under a name that claims only that. The second leaves the analysis outstanding via This is the same shape as the ordering test on #740: a claim about when something happens needs the dependency left pending, because no call-order assertion can tell awaited from merely fast. Two for two on that pattern, which is a useful thing to have learned twice. 896 passed, 1 skipped; lint and typecheck clean. |
The follow-up to #738 and #740:
twilio/twilio.controller.jsis the core of the SMS interface and had no spec file at all. Its 54.5% branch coverage came incidentally, from the routes spec exercising it over HTTP.Coverage
twilio/twilio.controller.jsbranchesSixty-five tests, over three runs to confirm they are not flaky.
What it covers
The units the routes spec reaches only sideways:
request's unregistered, opted-out and already-told branches; keyword handling without any sender lookup, which is the carrier-compliance property; the inline fallback when the queue will not take a changemorekeyword with and without a remembered itemgetXuracross failed authentication, no characters, weapon filtering, aDestinyErrorrelayed verbatim, and an unexpected error that must not bestatusCallbackacross a missing recipient, a missing status, an unknown number, the legacySmsStatusfield, and the claim-check updateThresholds
Left where
autoUpdateratcheted them: statements 82.54 → 88.63, functions 79.76 → 86.15, lines 82.54 → 88.78. Branches stays at 88.99, still above the 83.61 the suite reaches — the remaining debt ismcp/mcp.routes.jsandnotifications/notification.routes.jsat zero, andhelpers/queryBuilder.jsat 58.8%.Two notes on the tests
One assertion deliberately avoids an item's name. The no-results reply is drawn at random from a list, and one of them reads "Does it look like a Gjallarhorn?" — so a test asserting the name is absent fails intermittently. It asserts on the item's detail instead, with a comment saying why.
Two tests characterise dead code rather than a rule. The
itemName.includes('Catalyst')guard inqueryItemdoes nothing: it is unreachable fromrequest(), which lowercases the term before passing it to a case-sensitive check, and redundant anyway — every catalyst in the manifest is itemType 19, 20, 0 or 12, while the filter beside it admits only 2, 3 and 4. Both tests need a weapon-typed item named 'Catalyst', which the manifest does not contain, and that is itself the evidence. Recorded in #741 rather than changed here, since deleting it is a separate call.🤖 Generated with Claude Code
https://claude.ai/code/session_018iD3V7Zyugg3atXVsogTBf