/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« back to all changes in this revision

Viewing changes to mandos-ctl

  • Committer: Teddy Hogeborn
  • Date: 2019-03-04 20:50:40 UTC
  • mto: (237.7.594 trunk)
  • mto: This revision was merged to the branch mainline in revision 382.
  • Revision ID: teddy@recompile.se-20190304205040-c0ecwnigjedch1j9
mandos-ctl: Add test for EnableCmd and DisableCmd

* mandos-ctl (TestEnableCmd, TestDisableCmd): New.

Show diffs side-by-side

added added

removed removed

Lines of Context:
42
42
import json
43
43
import unittest
44
44
import logging
45
 
import io
46
 
import tempfile
47
 
import contextlib
48
45
 
49
46
import dbus
50
47
 
317
314
    @value_to_set.setter
318
315
    def value_to_set(self, value):
319
316
        """When setting, convert value to a datetime.timedelta"""
320
 
        self._vts = int(round(value.total_seconds() * 1000))
 
317
        self._vts = string_to_delta(value).total_seconds() * 1000
321
318
 
322
319
# Actual (non-abstract) command classes
323
320
 
326
323
        self.verbose = verbose
327
324
 
328
325
    def output(self, clients):
329
 
        default_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
330
 
        keywords = default_keywords
331
326
        if self.verbose:
332
327
            keywords = self.all_keywords
 
328
        else:
 
329
            keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
333
330
        return str(self.TableOfClients(clients.values(), keywords))
334
331
 
335
332
    class TableOfClients(object):
478
475
    property = "Host"
479
476
 
480
477
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
481
 
    @property
482
 
    def value_to_set(self):
483
 
        return self._vts
484
 
    @value_to_set.setter
485
 
    def value_to_set(self, value):
486
 
        """When setting, read data from supplied file object"""
487
 
        self._vts = value.read()
488
 
        value.close()
489
478
    property = "Secret"
490
479
 
491
480
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
506
495
                             MillisecondsValueArgumentMixIn):
507
496
    property = "ApprovalDuration"
508
497
 
 
498
def has_actions(options):
 
499
    return any((options.enable,
 
500
                options.disable,
 
501
                options.bump_timeout,
 
502
                options.start_checker,
 
503
                options.stop_checker,
 
504
                options.is_enabled,
 
505
                options.remove,
 
506
                options.checker is not None,
 
507
                options.timeout is not None,
 
508
                options.extended_timeout is not None,
 
509
                options.interval is not None,
 
510
                options.approved_by_default is not None,
 
511
                options.approval_delay is not None,
 
512
                options.approval_duration is not None,
 
513
                options.host is not None,
 
514
                options.secret is not None,
 
515
                options.approve,
 
516
                options.deny))
 
517
 
509
518
def add_command_line_options(parser):
510
519
    parser.add_argument("--version", action="version",
511
520
                        version="%(prog)s {}".format(version),
537
546
                        help="Remove client")
538
547
    parser.add_argument("-c", "--checker",
539
548
                        help="Set checker command for client")
540
 
    parser.add_argument("-t", "--timeout", type=string_to_delta,
 
549
    parser.add_argument("-t", "--timeout",
541
550
                        help="Set timeout for client")
542
 
    parser.add_argument("--extended-timeout", type=string_to_delta,
 
551
    parser.add_argument("--extended-timeout",
543
552
                        help="Set extended timeout for client")
544
 
    parser.add_argument("-i", "--interval", type=string_to_delta,
 
553
    parser.add_argument("-i", "--interval",
545
554
                        help="Set checker interval for client")
546
555
    approve_deny_default = parser.add_mutually_exclusive_group()
547
556
    approve_deny_default.add_argument(
552
561
        "--deny-by-default", action="store_false",
553
562
        dest="approved_by_default",
554
563
        help="Set client to be denied by default")
555
 
    parser.add_argument("--approval-delay", type=string_to_delta,
 
564
    parser.add_argument("--approval-delay",
556
565
                        help="Set delay before client approve/deny")
557
 
    parser.add_argument("--approval-duration", type=string_to_delta,
 
566
    parser.add_argument("--approval-duration",
558
567
                        help="Set duration of one client approval")
559
568
    parser.add_argument("-H", "--host", help="Set host for client")
560
569
    parser.add_argument("-s", "--secret",
571
580
    parser.add_argument("client", nargs="*", help="Client name")
572
581
 
573
582
 
574
 
def commands_from_options(options):
 
583
def commands_and_clients_from_options(options):
575
584
 
576
585
    commands = []
577
586
 
585
594
        commands.append(DisableCmd())
586
595
 
587
596
    if options.bump_timeout:
588
 
        commands.append(BumpTimeoutCmd())
 
597
        commands.append(BumpTimeoutCmd(options.bump_timeout))
589
598
 
590
599
    if options.start_checker:
591
600
        commands.append(StartCheckerCmd())
600
609
        commands.append(RemoveCmd())
601
610
 
602
611
    if options.checker is not None:
603
 
        commands.append(SetCheckerCmd(options.checker))
 
612
        commands.append(SetCheckerCmd())
604
613
 
605
614
    if options.timeout is not None:
606
615
        commands.append(SetTimeoutCmd(options.timeout))
610
619
            SetExtendedTimeoutCmd(options.extended_timeout))
611
620
 
612
621
    if options.interval is not None:
613
 
        commands.append(SetIntervalCmd(options.interval))
 
622
        command.append(SetIntervalCmd(options.interval))
614
623
 
615
624
    if options.approved_by_default is not None:
616
625
        if options.approved_by_default:
617
 
            commands.append(ApproveByDefaultCmd())
 
626
            command.append(ApproveByDefaultCmd())
618
627
        else:
619
 
            commands.append(DenyByDefaultCmd())
 
628
            command.append(DenyByDefaultCmd())
620
629
 
621
630
    if options.approval_delay is not None:
622
 
        commands.append(SetApprovalDelayCmd(options.approval_delay))
 
631
        command.append(SetApprovalDelayCmd(options.approval_delay))
623
632
 
624
633
    if options.approval_duration is not None:
625
 
        commands.append(
 
634
        command.append(
626
635
            SetApprovalDurationCmd(options.approval_duration))
627
636
 
628
637
    if options.host is not None:
629
 
        commands.append(SetHostCmd(options.host))
 
638
        command.append(SetHostCmd(options.host))
630
639
 
631
640
    if options.secret is not None:
632
 
        commands.append(SetSecretCmd(options.secret))
 
641
        command.append(SetSecretCmd(options.secret))
633
642
 
634
643
    if options.approve:
635
644
        commands.append(ApproveCmd())
642
651
    if not commands:
643
652
        commands.append(PrintTableCmd(verbose=options.verbose))
644
653
 
645
 
    return commands
646
 
 
647
 
 
648
 
def check_option_syntax(parser, options):
649
 
    """Apply additional restrictions on options, not expressible in
650
 
argparse"""
651
 
 
652
 
    def has_actions(options):
653
 
        return any((options.enable,
654
 
                    options.disable,
655
 
                    options.bump_timeout,
656
 
                    options.start_checker,
657
 
                    options.stop_checker,
658
 
                    options.is_enabled,
659
 
                    options.remove,
660
 
                    options.checker is not None,
661
 
                    options.timeout is not None,
662
 
                    options.extended_timeout is not None,
663
 
                    options.interval is not None,
664
 
                    options.approved_by_default is not None,
665
 
                    options.approval_delay is not None,
666
 
                    options.approval_duration is not None,
667
 
                    options.host is not None,
668
 
                    options.secret is not None,
669
 
                    options.approve,
670
 
                    options.deny))
 
654
    return commands, options.client
 
655
 
 
656
 
 
657
def main():
 
658
    parser = argparse.ArgumentParser()
 
659
 
 
660
    add_command_line_options(parser)
 
661
 
 
662
    options = parser.parse_args()
671
663
 
672
664
    if has_actions(options) and not (options.client or options.all):
673
665
        parser.error("Options require clients names or --all.")
681
673
    if options.is_enabled and len(options.client) > 1:
682
674
        parser.error("--is-enabled requires exactly one client")
683
675
 
684
 
 
685
 
def main():
686
 
    parser = argparse.ArgumentParser()
687
 
 
688
 
    add_command_line_options(parser)
689
 
 
690
 
    options = parser.parse_args()
691
 
 
692
 
    check_option_syntax(parser, options)
693
 
 
694
 
    clientnames = options.client
 
676
    commands, clientnames = commands_and_clients_from_options(options)
695
677
 
696
678
    try:
697
679
        bus = dbus.SystemBus()
744
726
                sys.exit(1)
745
727
 
746
728
    # Run all commands on clients
747
 
    commands = commands_from_options(options)
748
729
    for command in commands:
749
730
        command.run(mandos_serv, clients)
750
731
 
764
745
 
765
746
class Test_string_to_delta(unittest.TestCase):
766
747
    def test_handles_basic_rfc3339(self):
767
 
        self.assertEqual(string_to_delta("PT0S"),
768
 
                         datetime.timedelta())
769
 
        self.assertEqual(string_to_delta("P0D"),
770
 
                         datetime.timedelta())
771
 
        self.assertEqual(string_to_delta("PT1S"),
772
 
                         datetime.timedelta(0, 1))
773
748
        self.assertEqual(string_to_delta("PT2H"),
774
749
                         datetime.timedelta(0, 7200))
775
750
    def test_falls_back_to_pre_1_6_1_with_warning(self):
810
785
                testcase.assertEqual(dbus_interface,
811
786
                                     dbus.PROPERTIES_IFACE)
812
787
                self.attributes[property] = value
 
788
                self.calls.append(("Set", (interface, property, value,
 
789
                                           dbus_interface)))
813
790
            def Get(self, interface, property, dbus_interface):
814
791
                testcase.assertEqual(interface, client_interface)
815
792
                testcase.assertEqual(dbus_interface,
816
793
                                     dbus.PROPERTIES_IFACE)
 
794
                self.calls.append(("Get", (interface, property,
 
795
                                           dbus_interface)))
817
796
                return self.attributes[property]
818
797
            def Approve(self, approve, dbus_interface):
819
798
                testcase.assertEqual(dbus_interface, client_interface)
1028
1007
        for client in self.clients:
1029
1008
            self.assertFalse(client.attributes["Enabled"])
1030
1009
 
1031
 
class Unique(object):
1032
 
    """Class for objects which exist only to be unique objects, since
1033
 
unittest.mock.sentinel only exists in Python 3.3"""
1034
 
 
1035
 
class TestPropertyCmd(TestCmd):
1036
 
    """Abstract class for tests of PropertyCmd classes"""
1037
 
    def runTest(self):
1038
 
        if not hasattr(self, "command"):
1039
 
            return
1040
 
        values_to_get = getattr(self, "values_to_get",
1041
 
                                self.values_to_set)
1042
 
        for value_to_set, value_to_get in zip(self.values_to_set,
1043
 
                                              values_to_get):
1044
 
            for client in self.clients:
1045
 
                old_value = client.attributes[self.property]
1046
 
                self.assertNotIsInstance(old_value, Unique)
1047
 
                client.attributes[self.property] = Unique()
1048
 
            self.run_command(value_to_set, self.clients)
1049
 
            for client in self.clients:
1050
 
                value = client.attributes[self.property]
1051
 
                self.assertNotIsInstance(value, Unique)
1052
 
                self.assertEqual(value, value_to_get)
1053
 
    def run_command(self, value, clients):
1054
 
        self.command().run(None, clients)
1055
 
 
1056
 
class TestBumpTimeoutCmd(TestPropertyCmd):
1057
 
    command = BumpTimeoutCmd
1058
 
    property = "LastCheckedOK"
1059
 
    values_to_set = [""]
1060
 
 
1061
 
class TestStartCheckerCmd(TestPropertyCmd):
1062
 
    command = StartCheckerCmd
1063
 
    property = "CheckerRunning"
1064
 
    values_to_set = [dbus.Boolean(True)]
1065
 
 
1066
 
class TestStopCheckerCmd(TestPropertyCmd):
1067
 
    command = StopCheckerCmd
1068
 
    property = "CheckerRunning"
1069
 
    values_to_set = [dbus.Boolean(False)]
1070
 
 
1071
 
class TestApproveByDefaultCmd(TestPropertyCmd):
1072
 
    command = ApproveByDefaultCmd
1073
 
    property = "ApprovedByDefault"
1074
 
    values_to_set = [dbus.Boolean(True)]
1075
 
 
1076
 
class TestDenyByDefaultCmd(TestPropertyCmd):
1077
 
    command = DenyByDefaultCmd
1078
 
    property = "ApprovedByDefault"
1079
 
    values_to_set = [dbus.Boolean(False)]
1080
 
 
1081
 
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1082
 
    """Abstract class for tests of PropertyCmd classes using the
1083
 
ValueArgumentMixIn"""
1084
 
    def runTest(self):
1085
 
        if type(self) is TestValueArgumentPropertyCmd:
1086
 
            return
1087
 
        return super(TestValueArgumentPropertyCmd, self).runTest()
1088
 
    def run_command(self, value, clients):
1089
 
        self.command(value).run(None, clients)
1090
 
 
1091
 
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1092
 
    command = SetCheckerCmd
1093
 
    property = "Checker"
1094
 
    values_to_set = ["", ":", "fping -q -- %s"]
1095
 
 
1096
 
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1097
 
    command = SetHostCmd
1098
 
    property = "Host"
1099
 
    values_to_set = ["192.0.2.3", "foo.example.org"]
1100
 
 
1101
 
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1102
 
    command = SetSecretCmd
1103
 
    property = "Secret"
1104
 
    values_to_set = [open("/dev/null", "rb"),
1105
 
                     io.BytesIO(b"secret\0xyzzy\nbar")]
1106
 
    values_to_get = [b"", b"secret\0xyzzy\nbar"]
1107
 
 
1108
 
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1109
 
    command = SetTimeoutCmd
1110
 
    property = "Timeout"
1111
 
    values_to_set = [datetime.timedelta(),
1112
 
                     datetime.timedelta(minutes=5),
1113
 
                     datetime.timedelta(seconds=1),
1114
 
                     datetime.timedelta(weeks=1),
1115
 
                     datetime.timedelta(weeks=52)]
1116
 
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1117
 
 
1118
 
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
1119
 
    command = SetExtendedTimeoutCmd
1120
 
    property = "ExtendedTimeout"
1121
 
    values_to_set = [datetime.timedelta(),
1122
 
                     datetime.timedelta(minutes=5),
1123
 
                     datetime.timedelta(seconds=1),
1124
 
                     datetime.timedelta(weeks=1),
1125
 
                     datetime.timedelta(weeks=52)]
1126
 
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1127
 
 
1128
 
class TestSetIntervalCmd(TestValueArgumentPropertyCmd):
1129
 
    command = SetIntervalCmd
1130
 
    property = "Interval"
1131
 
    values_to_set = [datetime.timedelta(),
1132
 
                     datetime.timedelta(minutes=5),
1133
 
                     datetime.timedelta(seconds=1),
1134
 
                     datetime.timedelta(weeks=1),
1135
 
                     datetime.timedelta(weeks=52)]
1136
 
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1137
 
 
1138
 
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
1139
 
    command = SetApprovalDelayCmd
1140
 
    property = "ApprovalDelay"
1141
 
    values_to_set = [datetime.timedelta(),
1142
 
                     datetime.timedelta(minutes=5),
1143
 
                     datetime.timedelta(seconds=1),
1144
 
                     datetime.timedelta(weeks=1),
1145
 
                     datetime.timedelta(weeks=52)]
1146
 
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1147
 
 
1148
 
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
1149
 
    command = SetApprovalDurationCmd
1150
 
    property = "ApprovalDuration"
1151
 
    values_to_set = [datetime.timedelta(),
1152
 
                     datetime.timedelta(minutes=5),
1153
 
                     datetime.timedelta(seconds=1),
1154
 
                     datetime.timedelta(weeks=1),
1155
 
                     datetime.timedelta(weeks=52)]
1156
 
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1157
 
 
1158
 
class Test_command_from_options(unittest.TestCase):
1159
 
    def setUp(self):
1160
 
        self.parser = argparse.ArgumentParser()
1161
 
        add_command_line_options(self.parser)
1162
 
    def assert_command_from_args(self, args, command_cls, **cmd_attrs):
1163
 
        """Assert that parsing ARGS should result in an instance of
1164
 
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
1165
 
        options = self.parser.parse_args(args)
1166
 
        check_option_syntax(self.parser, options)
1167
 
        commands = commands_from_options(options)
1168
 
        self.assertEqual(len(commands), 1)
1169
 
        command = commands[0]
1170
 
        self.assertIsInstance(command, command_cls)
1171
 
        for key, value in cmd_attrs.items():
1172
 
            self.assertEqual(getattr(command, key), value)
1173
 
    def test_print_table(self):
1174
 
        self.assert_command_from_args([], PrintTableCmd,
1175
 
                                      verbose=False)
1176
 
 
1177
 
    def test_print_table_verbose(self):
1178
 
        self.assert_command_from_args(["--verbose"], PrintTableCmd,
1179
 
                                      verbose=True)
1180
 
 
1181
 
    def test_print_table_verbose_short(self):
1182
 
        self.assert_command_from_args(["-v"], PrintTableCmd,
1183
 
                                      verbose=True)
1184
 
 
1185
 
    def test_enable(self):
1186
 
        self.assert_command_from_args(["--enable", "foo"], EnableCmd)
1187
 
 
1188
 
    def test_enable_short(self):
1189
 
        self.assert_command_from_args(["-e", "foo"], EnableCmd)
1190
 
 
1191
 
    def test_disable(self):
1192
 
        self.assert_command_from_args(["--disable", "foo"],
1193
 
                                      DisableCmd)
1194
 
 
1195
 
    def test_disable_short(self):
1196
 
        self.assert_command_from_args(["-d", "foo"], DisableCmd)
1197
 
 
1198
 
    def test_bump_timeout(self):
1199
 
        self.assert_command_from_args(["--bump-timeout", "foo"],
1200
 
                                      BumpTimeoutCmd)
1201
 
 
1202
 
    def test_bump_timeout_short(self):
1203
 
        self.assert_command_from_args(["-b", "foo"], BumpTimeoutCmd)
1204
 
 
1205
 
    def test_start_checker(self):
1206
 
        self.assert_command_from_args(["--start-checker", "foo"],
1207
 
                                      StartCheckerCmd)
1208
 
 
1209
 
    def test_stop_checker(self):
1210
 
        self.assert_command_from_args(["--stop-checker", "foo"],
1211
 
                                      StopCheckerCmd)
1212
 
 
1213
 
    def test_remove(self):
1214
 
        self.assert_command_from_args(["--remove", "foo"],
1215
 
                                      RemoveCmd)
1216
 
 
1217
 
    def test_remove_short(self):
1218
 
        self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1219
 
 
1220
 
    def test_checker(self):
1221
 
        self.assert_command_from_args(["--checker", ":", "foo"],
1222
 
                                      SetCheckerCmd, value_to_set=":")
1223
 
 
1224
 
    def test_checker_empty(self):
1225
 
        self.assert_command_from_args(["--checker", "", "foo"],
1226
 
                                      SetCheckerCmd, value_to_set="")
1227
 
 
1228
 
    def test_checker_short(self):
1229
 
        self.assert_command_from_args(["-c", ":", "foo"],
1230
 
                                      SetCheckerCmd, value_to_set=":")
1231
 
 
1232
 
    def test_timeout(self):
1233
 
        self.assert_command_from_args(["--timeout", "PT5M", "foo"],
1234
 
                                      SetTimeoutCmd,
1235
 
                                      value_to_set=300000)
1236
 
 
1237
 
    def test_timeout_short(self):
1238
 
        self.assert_command_from_args(["-t", "PT5M", "foo"],
1239
 
                                      SetTimeoutCmd,
1240
 
                                      value_to_set=300000)
1241
 
 
1242
 
    def test_extended_timeout(self):
1243
 
        self.assert_command_from_args(["--extended-timeout", "PT15M",
1244
 
                                       "foo"],
1245
 
                                      SetExtendedTimeoutCmd,
1246
 
                                      value_to_set=900000)
1247
 
 
1248
 
    def test_interval(self):
1249
 
        self.assert_command_from_args(["--interval", "PT2M", "foo"],
1250
 
                                      SetIntervalCmd,
1251
 
                                      value_to_set=120000)
1252
 
 
1253
 
    def test_interval_short(self):
1254
 
        self.assert_command_from_args(["-i", "PT2M", "foo"],
1255
 
                                      SetIntervalCmd,
1256
 
                                      value_to_set=120000)
1257
 
 
1258
 
    def test_approve_by_default(self):
1259
 
        self.assert_command_from_args(["--approve-by-default", "foo"],
1260
 
                                      ApproveByDefaultCmd)
1261
 
 
1262
 
    def test_deny_by_default(self):
1263
 
        self.assert_command_from_args(["--deny-by-default", "foo"],
1264
 
                                      DenyByDefaultCmd)
1265
 
 
1266
 
    def test_approval_delay(self):
1267
 
        self.assert_command_from_args(["--approval-delay", "PT30S",
1268
 
                                       "foo"], SetApprovalDelayCmd,
1269
 
                                      value_to_set=30000)
1270
 
 
1271
 
    def test_approval_duration(self):
1272
 
        self.assert_command_from_args(["--approval-duration", "PT1S",
1273
 
                                       "foo"], SetApprovalDurationCmd,
1274
 
                                      value_to_set=1000)
1275
 
 
1276
 
    def test_host(self):
1277
 
        self.assert_command_from_args(["--host", "foo.example.org",
1278
 
                                       "foo"], SetHostCmd,
1279
 
                                      value_to_set="foo.example.org")
1280
 
 
1281
 
    def test_host_short(self):
1282
 
        self.assert_command_from_args(["-H", "foo.example.org",
1283
 
                                       "foo"], SetHostCmd,
1284
 
                                      value_to_set="foo.example.org")
1285
 
 
1286
 
    def test_secret_devnull(self):
1287
 
        self.assert_command_from_args(["--secret", os.path.devnull,
1288
 
                                       "foo"], SetSecretCmd,
1289
 
                                      value_to_set=b"")
1290
 
 
1291
 
    def test_secret_tempfile(self):
1292
 
        with tempfile.NamedTemporaryFile(mode="r+b") as f:
1293
 
            value = b"secret\0xyzzy\nbar"
1294
 
            f.write(value)
1295
 
            f.seek(0)
1296
 
            self.assert_command_from_args(["--secret", f.name,
1297
 
                                           "foo"], SetSecretCmd,
1298
 
                                          value_to_set=value)
1299
 
 
1300
 
    def test_secret_devnull_short(self):
1301
 
        self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1302
 
                                      SetSecretCmd, value_to_set=b"")
1303
 
 
1304
 
    def test_secret_tempfile_short(self):
1305
 
        with tempfile.NamedTemporaryFile(mode="r+b") as f:
1306
 
            value = b"secret\0xyzzy\nbar"
1307
 
            f.write(value)
1308
 
            f.seek(0)
1309
 
            self.assert_command_from_args(["-s", f.name, "foo"],
1310
 
                                          SetSecretCmd,
1311
 
                                          value_to_set=value)
1312
 
 
1313
 
    def test_approve(self):
1314
 
        self.assert_command_from_args(["--approve", "foo"],
1315
 
                                      ApproveCmd)
1316
 
 
1317
 
    def test_approve_short(self):
1318
 
        self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1319
 
 
1320
 
    def test_deny(self):
1321
 
        self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1322
 
 
1323
 
    def test_deny_short(self):
1324
 
        self.assert_command_from_args(["-D", "foo"], DenyCmd)
1325
 
 
1326
 
    def test_dump_json(self):
1327
 
        self.assert_command_from_args(["--dump-json"], DumpJSONCmd)
1328
 
 
1329
 
    def test_is_enabled(self):
1330
 
        self.assert_command_from_args(["--is-enabled", "foo"],
1331
 
                                      IsEnabledCmd)
1332
 
 
1333
 
    def test_is_enabled_short(self):
1334
 
        self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1335
 
 
1336
 
 
1337
 
class Test_check_option_syntax(unittest.TestCase):
1338
 
    # This mostly corresponds to the definition from has_actions() in
1339
 
    # check_option_syntax()
1340
 
    actions = {
1341
 
        # The actual values set here are not that important, but we do
1342
 
        # at least stick to the correct types, even though they are
1343
 
        # never used
1344
 
        "enable": True,
1345
 
        "disable": True,
1346
 
        "bump_timeout": True,
1347
 
        "start_checker": True,
1348
 
        "stop_checker": True,
1349
 
        "is_enabled": True,
1350
 
        "remove": True,
1351
 
        "checker": "x",
1352
 
        "timeout": datetime.timedelta(),
1353
 
        "extended_timeout": datetime.timedelta(),
1354
 
        "interval": datetime.timedelta(),
1355
 
        "approved_by_default": True,
1356
 
        "approval_delay": datetime.timedelta(),
1357
 
        "approval_duration": datetime.timedelta(),
1358
 
        "host": "x",
1359
 
        "secret": io.BytesIO(b"x"),
1360
 
        "approve": True,
1361
 
        "deny": True,
1362
 
    }
1363
 
 
1364
 
    def setUp(self):
1365
 
        self.parser = argparse.ArgumentParser()
1366
 
        add_command_line_options(self.parser)
1367
 
 
1368
 
    @contextlib.contextmanager
1369
 
    def assertParseError(self):
1370
 
        with self.assertRaises(SystemExit) as e:
1371
 
            with self.temporarily_suppress_stderr():
1372
 
                yield
1373
 
        # Exit code from argparse is guaranteed to be "2".  Reference:
1374
 
        # https://docs.python.org/3/library/argparse.html#exiting-methods
1375
 
        self.assertEqual(e.exception.code, 2)
1376
 
 
1377
 
    @staticmethod
1378
 
    @contextlib.contextmanager
1379
 
    def temporarily_suppress_stderr():
1380
 
        null = os.open(os.path.devnull, os.O_RDWR)
1381
 
        stderrcopy = os.dup(sys.stderr.fileno())
1382
 
        os.dup2(null, sys.stderr.fileno())
1383
 
        os.close(null)
1384
 
        try:
1385
 
            yield
1386
 
        finally:
1387
 
            # restore stderr
1388
 
            os.dup2(stderrcopy, sys.stderr.fileno())
1389
 
            os.close(stderrcopy)
1390
 
 
1391
 
    def check_option_syntax(self, options):
1392
 
        check_option_syntax(self.parser, options)
1393
 
 
1394
 
    def test_actions_requires_client_or_all(self):
1395
 
        for action, value in self.actions.items():
1396
 
            options = self.parser.parse_args()
1397
 
            setattr(options, action, value)
1398
 
            with self.assertParseError():
1399
 
                self.check_option_syntax(options)
1400
 
 
1401
 
    def test_actions_conflicts_with_verbose(self):
1402
 
        for action, value in self.actions.items():
1403
 
            options = self.parser.parse_args()
1404
 
            setattr(options, action, value)
1405
 
            options.verbose = True
1406
 
            with self.assertParseError():
1407
 
                self.check_option_syntax(options)
1408
 
 
1409
 
    def test_dump_json_conflicts_with_verbose(self):
1410
 
        options = self.parser.parse_args()
1411
 
        options.dump_json = True
1412
 
        options.verbose = True
1413
 
        with self.assertParseError():
1414
 
            self.check_option_syntax(options)
1415
 
 
1416
 
    def test_dump_json_conflicts_with_action(self):
1417
 
        for action, value in self.actions.items():
1418
 
            options = self.parser.parse_args()
1419
 
            setattr(options, action, value)
1420
 
            options.dump_json = True
1421
 
            with self.assertParseError():
1422
 
                self.check_option_syntax(options)
1423
 
 
1424
 
    def test_all_can_not_be_alone(self):
1425
 
        options = self.parser.parse_args()
1426
 
        options.all = True
1427
 
        with self.assertParseError():
1428
 
            self.check_option_syntax(options)
1429
 
 
1430
 
    def test_all_is_ok_with_any_action(self):
1431
 
        for action, value in self.actions.items():
1432
 
            options = self.parser.parse_args()
1433
 
            setattr(options, action, value)
1434
 
            options.all = True
1435
 
            self.check_option_syntax(options)
1436
 
 
1437
 
    def test_is_enabled_fails_without_client(self):
1438
 
        options = self.parser.parse_args()
1439
 
        options.is_enabled = True
1440
 
        with self.assertParseError():
1441
 
            self.check_option_syntax(options)
1442
 
 
1443
 
    def test_is_enabled_works_with_one_client(self):
1444
 
        options = self.parser.parse_args()
1445
 
        options.is_enabled = True
1446
 
        options.client = ["foo"]
1447
 
        self.check_option_syntax(options)
1448
 
 
1449
 
    def test_is_enabled_fails_with_two_clients(self):
1450
 
        options = self.parser.parse_args()
1451
 
        options.is_enabled = True
1452
 
        options.client = ["foo", "barbar"]
1453
 
        with self.assertParseError():
1454
 
            self.check_option_syntax(options)
1455
 
 
1456
1010
 
1457
1011
 
1458
1012
def should_only_run_tests():