Repository: aymanizz/smalld-click

Issue:
Messages with empty content throw an error
For bots running with a prefix and name, messages with only a prefix would also cause an error.

Comments:

Patch:
diff --git a/smalld_click/smalld_click.py b/smalld_click/smalld_click.py
index dbf3fa6..cbc56ac 100644
--- a/smalld_click/smalld_click.py
+++ b/smalld_click/smalld_click.py
@@ -26,10 +26,14 @@ class SmallDCliRunner:
         create_message=None,
         executor=None,
     ):
+        self.prefix = prefix.strip()
+        self.name = name.strip() if name is not None else cli.name or ""
+
+        if not self.prefix and not self.name:
+            raise ValueError("either prefix or name must be non empty")
+
         self.smalld = smalld
         self.cli = cli
-        self.prefix = prefix
-        self.name = name if name is not None else cli.name or ""
         self.timeout = timeout
         self.create_message = create_message if create_message else plain_message
         self.executor = executor if executor else ThreadPoolExecutor()
@@ -45,18 +49,18 @@ class SmallDCliRunner:
         self.executor.__exit__(*args)
 
     def on_message(self, msg):
-        content = msg["content"]
+        content = msg.get("content") or ""
         user_id = msg["author"]["id"]
         channel_id = msg["channel_id"]
 
         handle = self.pending.pop((user_id, channel_id), None)
         if handle is not None:
             handle.complete_with(msg)
-            return
+            return None
 
         args = parse_command(self.prefix, self.name, content)
         if args is None:
-            return
+            return None
 
         return self.executor.submit(self.handle_command, msg, args)
 
@@ -90,14 +94,16 @@ def plain_message(msg):
 
 def parse_command(prefix, name, message):
     if not message.startswith(prefix):
-        return
+        return None
+
     cmd = message[len(prefix) :].lstrip()
     if not name:
         return cmd
-    cmd_name, *rest = cmd.split(maxsplit=1)
-    if cmd_name != name:
-        return
-    return "".join(rest)
+    elif not cmd:
+        return None
+
+    cmd_name, *args = cmd.split(maxsplit=1)
+    return "".join(args) if cmd_name == name else None
 
 
 def split_args(command):


Test patch:
diff --git a/test/test_smalld_click.py b/test/test_smalld_click.py
index 83f512a..34121ac 100644
--- a/test/test_smalld_click.py
+++ b/test/test_smalld_click.py
@@ -55,6 +55,15 @@ def make_subject(request, smalld):
     return factory
 
 
+def test_raises_error_for_empty_prefix_and_name(make_subject):
+    @click.command()
+    def command():
+        pass
+
+    with pytest.raises(ValueError) as exc_info:
+        make_subject(command, prefix="", name="")
+
+
 def test_exposes_correct_context(make_subject):
     conversation = None
 
@@ -110,10 +119,10 @@ def test_parses_multicommands(make_subject):
     create_command(1)
 
     cli_collection = click.CommandCollection(sources=[cli])
-    subject = make_subject(cli_collection)
+    subject = make_subject(cli_collection, prefix="$")
 
-    f1 = subject.on_message(make_message("cmd0 --opt"))
-    f2 = subject.on_message(make_message("cmd1 --opt"))
+    f1 = subject.on_message(make_message("$cmd0 --opt"))
+    f2 = subject.on_message(make_message("$cmd1 --opt"))
     assert_completes([f1, f2])
     assert all(slots)
 
@@ -121,38 +130,42 @@ def test_parses_multicommands(make_subject):
 @pytest.mark.parametrize(
     "prefix, name, message, expected",
     [
-        ("", "", "command", True),
-        ("++", "", "++command", True),
-        ("++", "invoke", "++invoke command", True),
-        ("++", "", "++  command", True),
-        ("++", "", "++--opt command", True),
-        ("", "invoke", "invokecommand", False),
-        ("", "invoke", "invoke--opt command", False),
-        ("", "invoke", "invoke command", True),
+        ("++", "", "", False),
+        ("++", "", "++", True),
+        ("++", "", "++arg", True),
+        ("++", "", "++  arg", True),
+        ("++", "", "++--opt arg", True),
+        ("", "invoke", "", False),
+        ("", "invoke", "invoke", True),
+        ("", "invoke", "invoke arg", True),
+        ("", "invoke", "invokearg", False),
+        ("", "invoke", "invoke --opt", True),
+        ("", "invoke", "invoke--opt arg", False),
+        ("++", "invoke", "", False),
+        ("++", "invoke", "++", False),
+        ("++", "invoke", "++invoke", True),
+        ("++", "invoke", "++  invoke", True),
+        ("++", "invoke", "++invoke arg", True),
+        ("++", "invoke", None, False),
     ],
 )
 def test_parses_name_and_prefix_correctly(
     make_subject, prefix, name, message, expected
 ):
-    cli_called = False
-    command_called = False
+    called = False
 
-    @click.group()
+    @click.command()
+    @click.argument("arg", required=False)
     @click.option("--opt", is_flag=True)
-    def cli(opt):
-        nonlocal cli_called
-        cli_called = True
-
-    @cli.command()
-    def command():
-        nonlocal command_called
-        command_called = True
+    def cli(arg, opt):
+        nonlocal called
+        called = True
 
     subject = make_subject(cli, prefix=prefix, name=name)
     f = subject.on_message(make_message(message))
 
-    assert_completes(f) if expected else time.sleep(0.5)
-    assert cli_called is command_called is expected
+    assert_completes(f) if expected else time.sleep(0.2)
+    assert called is expected
 
 
 def test_handles_echo(make_subject, smalld):