Add tests for join/invite errors
authorKirill Isakov <bootctl@gmail.com>
Thu, 26 May 2022 08:20:34 +0000 (14:20 +0600)
committerKirill Isakov <bootctl@gmail.com>
Fri, 27 May 2022 17:56:50 +0000 (23:56 +0600)
src/invitation.c
test/integration/cmd_join.py [new file with mode: 0755]
test/integration/meson.build
test/integration/testlib/proc.py

index 151f701..a95a324 100644 (file)
@@ -333,6 +333,11 @@ int cmd_invite(int argc, char *argv[]) {
                return 1;
        }
 
+       if(argc > 2) {
+               fprintf(stderr, "Too many arguments!\n");
+               return 1;
+       }
+
        // Check validity of the new node's name
        if(!check_id(argv[1])) {
                fprintf(stderr, "Invalid name for node.\n");
@@ -1243,7 +1248,7 @@ int cmd_join(int argc, char *argv[]) {
 
                if(!fgets(line, sizeof(line), stdin)) {
                        fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
-                       return false;
+                       return 1;
                }
 
                invitation = line;
diff --git a/test/integration/cmd_join.py b/test/integration/cmd_join.py
new file mode 100755 (executable)
index 0000000..585afee
--- /dev/null
@@ -0,0 +1,139 @@
+#!/usr/bin/env python3
+
+"""Test invite/join error conditions."""
+
+import os
+import shutil
+
+from testlib import check, util
+from testlib.log import log
+from testlib.proc import Tinc
+from testlib.test import Test
+
+FAKE_INVITE = "localhost:65535/pVOZMJGm3MqTvTu0UnhMGb2cfuqygiu79MdnERnGYdga5v8C"
+
+
+def init(ctx: Test) -> Tinc:
+    """Initialize a node."""
+
+    node = ctx.node()
+    stdin = f"""
+        init {node}
+        set Port 0
+        set Address localhost
+        set DeviceType dummy
+    """
+    node.cmd(stdin=stdin)
+    return node
+
+
+def test_invite(foo: Tinc) -> None:
+    """Test successful 'invite'."""
+
+    foo.cmd("set", "Mode", "switch")
+    foo.cmd("set", "Broadcast", "mst")
+    foo.start()
+
+    log.info("test successful invitation")
+    out, _ = foo.cmd("invite", "quux")
+    check.is_in(f"localhost:{foo.port}/", out)
+
+    for filename in os.listdir(foo.sub("invitations")):
+        content = util.read_text(foo.sub(f"invitations/{filename}"))
+        if filename == "ed25519_key.priv":
+            check.is_in("-----BEGIN ED25519 PRIVATE KEY-----", content)
+        else:
+            check.is_in("Broadcast = mst", content)
+            check.is_in("Mode = switch", content)
+            check.is_in("Address = localhost", content)
+            check.is_in("Name = quux", content)
+            check.is_in(f"NetName = {foo}", content)
+            check.is_in(f"ConnectTo = {foo}", content)
+
+
+def test_invite_errors(foo: Tinc) -> None:
+    """Test invite error conditions."""
+
+    log.info("invite node with tincd stopped")
+    _, err = foo.cmd("invite", "foobar", code=1)
+    check.is_in("Could not open pid file", err)
+
+    log.info("start node %s", foo)
+    foo.start()
+
+    log.info("invite without arguments")
+    _, err = foo.cmd("invite", code=1)
+    check.is_in("Not enough arguments", err)
+
+    log.info("invite with too many arguments")
+    _, err = foo.cmd("invite", "foo", "bar", code=1)
+    check.is_in("Too many arguments", err)
+
+    log.info("invite with invalid name")
+    _, err = foo.cmd("invite", "!@#", code=1)
+    check.is_in("Invalid name for node", err)
+
+    log.info("invite existing node")
+    _, err = foo.cmd("invite", foo.name, code=1)
+    check.is_in("already exists", err)
+
+    if os.name != "nt":
+        invites = foo.sub("invitations")
+        os.chmod(invites, 0)
+        _, err = foo.cmd("invite", "foobar", code=1)
+        check.is_in("Could not read directory", err)
+        os.chmod(invites, 0o750)
+
+        log.info("block creating invitations directory")
+        shutil.rmtree(foo.sub("invitations"))
+        os.chmod(foo.work_dir, 0o500)
+        _, err = foo.cmd("invite", "foobar", code=1)
+        check.is_in("Could not create directory", err)
+        os.chmod(foo.work_dir, 0o750)
+
+        log.info("fully block access to configuration directory")
+        work_dir = foo.sub("test_no_access")
+        os.mkdir(work_dir, mode=0)
+        _, err = foo.cmd("-c", work_dir, "invite", "foobar", code=1)
+        check.is_in("Could not open", err)
+
+
+def test_join_errors(foo: Tinc) -> None:
+    """Test join error conditions."""
+
+    log.info("try joining with redundant arguments")
+    _, err = foo.cmd("join", "bar", "quux", code=1)
+    check.is_in("Too many arguments", err)
+
+    log.info("try joining with existing configuration")
+    _, err = foo.cmd("join", FAKE_INVITE, code=1)
+    check.is_in("already exists", err)
+
+    log.info("try running without an invite URL")
+    work_dir = foo.sub("test_no_invite")
+    join = foo.tinc("-c", work_dir, "join")
+    _, err = join.communicate(input="")
+    check.equals(1, join.returncode)
+    check.is_in("Error while reading", err)
+
+    log.info("try using an invalid invite")
+    work_dir = foo.sub("test_invalid_invite")
+    _, err = foo.cmd("-c", work_dir, "join", FAKE_INVITE, code=1)
+    check.is_in("Could not connect to", err)
+
+    if os.name != "nt":
+        log.info("test working without access to configuration directory")
+        work_dir = foo.sub("wd_access_test")
+        os.mkdir(work_dir, mode=400)
+        _, err = foo.cmd("-c", work_dir, "join", FAKE_INVITE, code=1)
+        check.is_in("No permission to write", err)
+
+
+with Test("run invite success tests") as context:
+    test_invite(init(context))
+
+with Test("run invite error tests") as context:
+    test_invite_errors(init(context))
+
+with Test("run join tests") as context:
+    test_join_errors(init(context))
index 392d86f..0fd03dd 100644 (file)
@@ -2,6 +2,7 @@ tests = [
   'basic.py',
   'cmd_dump.py',
   'cmd_fsck.py',
+  'cmd_join.py',
   'cmd_keys.py',
   'cmd_misc.py',
   'cmd_net.py',
index d5cbee4..ffa0a5f 100755 (executable)
@@ -149,6 +149,11 @@ class Tinc:
         """Return path to a subdirectory within the working dir for this node."""
         return os.path.join(self._work_dir, *paths)
 
+    @property
+    def work_dir(self):
+        """Node's working directory."""
+        return self._work_dir
+
     @property
     def script_up(self) -> str:
         """Name of the hosts/XXX-up script for this node."""