Answer every wrong command invocation - #16
Merged
Conversation
Several commands produced no response at all when called with missing or badly-typed arguments. `!volume`, `!remove`, `!move`, `!move 1`, `!queue abc` and `!remove abc` were all silent, which is indistinguishable from the bot being offline. on_command_error returned early for MissingRequiredArgument on the grounds that "per-command handlers deal with these", but only `play` had one. BadArgument was not special-cased at all, so it was logged server-side and the user saw nothing. Move the handling into utils.errors, keyed on UserInputError so every subclass is covered, and reply with the command's own signature - `!move <from_pos> <to_pos>` is generated from the Command object, so it cannot drift when a parameter is renamed. Cooldowns and concurrency limits move there too, since they apply to any command rather than just `play`. The per-command @play.error and @volume.error handlers are removed. They now duplicate the central behaviour, and a private handler suppresses the central one, which is exactly how the silent-failure bug would come back. A test asserts no Music command keeps one. Splitting this out of main.py also makes it testable: importing main.py calls config.validate(), which exits the process when DISCORD_TOKEN is unset. Closes #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #7
The bug
Several commands produced no response at all when called with missing or badly-typed arguments. The user types something, the bot says nothing, and there is no way to tell whether the input was wrong or the bot is down.
!volumevolargument. Usage:!volume <vol>!removeindexargument. Usage:!remove <index>!move 1to_posargument. Usage:!move <from_pos> <to_pos>!queue abc!queue [page=1]!remove abc!remove <index>Root cause
on_command_errorbailed out onMissingRequiredArgument:The comment was wrong — only
playhad such a handler. Every other command with a required argument fell through the gap.BadArgumentwas not special-cased at all, so it hit the generic branch, got logged server-side, and the user still saw nothing.The fix
Handling moves to
utils/errors.pyand keys onUserInputError, the common base ofMissingRequiredArgument,BadArgument,TooManyArgumentsand the rest — so no subclass can slip through unanswered as new commands are added.The usage hint is generated from the
Commandobject's ownsignature, not hardcoded.<name>for required parameters,[name=default]for optional ones, so it cannot drift when a parameter is renamed.Cooldowns and
MaxConcurrencyReachedmove there too: they apply to any command, not justplay.What still stays quiet
Deliberately, two cases:
CommandNotFound— people type a prefix by accident; the bot should not nag.CheckFailure— the voice-state guards already send their own explanation.Everything else that is not user input gets logged with a traceback (
exc_info, which the old handler did not capture) and stays out of the channel, since an internal failure is the operator's problem.Removed: the per-command handlers
@play.errorand@volume.errorare deleted — they now duplicate the central behaviour. This matters beyond tidiness: a private handler suppresses the central one, so leaving one in place is exactly how the silent-failure bug comes back for that command.test_no_music_command_keeps_a_private_error_handlerguards against that (verified non-vacuous: it scans all 15Musiccommands).Why a new module rather than more code in
main.pyImporting
main.pyrunsconfig.validate(), which callssys.exit(1)whenDISCORD_TOKENis unset — so nothing in it can be unit-tested.utils/errors.pyimports cleanly, andmain.py's event handler is now a one-liner delegating to it.Verification
143 passed, 22 of them new.Beyond the mocked cases,
test_usage_matches_the_real_command_definitionsreads signatures off the actualCommandobjects and asserts the rendered hint, so these strings are checked against the real commands:Also covered: the original exception being unwrapped from
CommandInvokeError, aForbiddenon send not escalating out of the error handler, andctx.commandbeingNone.Startup was verified by booting
main.pywith a dummy token — all cogs and handlers load.