/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos-ctl

  • Committer: Teddy Hogeborn
  • Date: 2019-03-03 22:09:38 UTC
  • Revision ID: teddy@recompile.se-20190303220938-92e8q07rtjdnpjh6
mandos-ctl: Refactor

* mandos-ctl (commands_and_clients_from_options): Take options as an
                                                  argument.  Move
                                                  parser setup into
                                                  new function and
                                                  move extra parse
                                                  checking into main.
  (add_command_line_options): New.
  (main): Create parser here and call add_command_line_options() to
          add options to it.

Show diffs side-by-side

added added

removed removed

Lines of Context:
278
278
        commands which want to operate on all clients at the same time
279
279
        can override this run() method instead."""
280
280
        self.mandos = mandos
281
 
        for client, properties in clients.items():
282
 
            self.run_on_one_client(client, properties)
 
281
        for client in clients:
 
282
            self.run_on_one_client(client)
283
283
 
284
284
class PrintCmd(Command):
285
285
    """Abstract class for commands printing client details"""
295
295
 
296
296
class PropertyCmd(Command):
297
297
    """Abstract class for Actions for setting one client property"""
298
 
    def run_on_one_client(self, client, properties):
 
298
    def run_on_one_client(self, client):
299
299
        """Set the Client's D-Bus property"""
300
300
        client.Set(client_interface, self.property, self.value_to_set,
301
301
                   dbus_interface=dbus.PROPERTIES_IFACE)
323
323
        self.verbose = verbose
324
324
 
325
325
    def output(self, clients):
326
 
        default_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
327
 
        keywords = default_keywords
328
326
        if self.verbose:
329
327
            keywords = self.all_keywords
 
328
        else:
 
329
            keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
330
330
        return str(self.TableOfClients(clients.values(), keywords))
331
331
 
332
332
    class TableOfClients(object):
419
419
        return value
420
420
 
421
421
class IsEnabledCmd(Command):
422
 
    def run_on_one_client(self, client, properties):
423
 
        if self.is_enabled(client, properties):
 
422
    def run_on_one_client(self, client):
 
423
        if self.is_enabled(client):
424
424
            sys.exit(0)
425
425
        sys.exit(1)
426
 
    def is_enabled(self, client, properties):
427
 
        return bool(properties["Enabled"])
 
426
    def is_enabled(self, client):
 
427
        return client.Get(client_interface, "Enabled",
 
428
                          dbus_interface=dbus.PROPERTIES_IFACE)
428
429
 
429
430
class RemoveCmd(Command):
430
 
    def run_on_one_client(self, client, properties):
 
431
    def run_on_one_client(self, client):
431
432
        self.mandos.RemoveClient(client.__dbus_object_path__)
432
433
 
433
434
class ApproveCmd(Command):
434
 
    def run_on_one_client(self, client, properties):
 
435
    def run_on_one_client(self, client):
435
436
        client.Approve(dbus.Boolean(True),
436
437
                       dbus_interface=client_interface)
437
438
 
438
439
class DenyCmd(Command):
439
 
    def run_on_one_client(self, client, properties):
 
440
    def run_on_one_client(self, client):
440
441
        client.Approve(dbus.Boolean(False),
441
442
                       dbus_interface=client_interface)
442
443
 
580
581
    parser.add_argument("client", nargs="*", help="Client name")
581
582
 
582
583
 
583
 
def commands_from_options(options):
 
584
def commands_and_clients_from_options(options):
584
585
 
585
586
    commands = []
586
587
 
594
595
        commands.append(DisableCmd())
595
596
 
596
597
    if options.bump_timeout:
597
 
        commands.append(BumpTimeoutCmd())
 
598
        commands.append(BumpTimeoutCmd(options.bump_timeout))
598
599
 
599
600
    if options.start_checker:
600
601
        commands.append(StartCheckerCmd())
651
652
    if not commands:
652
653
        commands.append(PrintTableCmd(verbose=options.verbose))
653
654
 
654
 
    return commands
 
655
    return commands, options.client
655
656
 
656
657
 
657
658
def main():
673
674
    if options.is_enabled and len(options.client) > 1:
674
675
        parser.error("--is-enabled requires exactly one client")
675
676
 
676
 
    clientnames = options.client
 
677
    commands, clientnames = commands_and_clients_from_options(options)
677
678
 
678
679
    try:
679
680
        bus = dbus.SystemBus()
693
694
        def filter(self, record):
694
695
            return False
695
696
    dbus_filter = NullFilter()
 
697
    dbus_logger.addFilter(dbus_filter)
696
698
    try:
697
 
        dbus_logger.addFilter(dbus_filter)
698
 
        mandos_clients = {path: ifs_and_props[client_interface]
699
 
                          for path, ifs_and_props in
700
 
                          mandos_serv_object_manager
701
 
                          .GetManagedObjects().items()
702
 
                          if client_interface in ifs_and_props}
 
699
        try:
 
700
            mandos_clients = {path: ifs_and_props[client_interface]
 
701
                              for path, ifs_and_props in
 
702
                              mandos_serv_object_manager
 
703
                              .GetManagedObjects().items()
 
704
                              if client_interface in ifs_and_props}
 
705
        finally:
 
706
            # restore dbus logger
 
707
            dbus_logger.removeFilter(dbus_filter)
703
708
    except dbus.exceptions.DBusException as e:
704
709
        log.critical("Failed to access Mandos server through D-Bus:"
705
710
                     "\n%s", e)
706
711
        sys.exit(1)
707
 
    finally:
708
 
        # restore dbus logger
709
 
        dbus_logger.removeFilter(dbus_filter)
710
712
 
711
713
    # Compile dict of (clients: properties) to process
712
714
    clients = {}
726
728
                sys.exit(1)
727
729
 
728
730
    # Run all commands on clients
729
 
    commands = commands_from_options(options)
730
731
    for command in commands:
731
732
        command.run(mandos_serv, clients)
732
733
 
746
747
 
747
748
class Test_string_to_delta(unittest.TestCase):
748
749
    def test_handles_basic_rfc3339(self):
749
 
        self.assertEqual(string_to_delta("PT0S"),
750
 
                         datetime.timedelta())
751
 
        self.assertEqual(string_to_delta("P0D"),
752
 
                         datetime.timedelta())
753
 
        self.assertEqual(string_to_delta("PT1S"),
754
 
                         datetime.timedelta(0, 1))
755
750
        self.assertEqual(string_to_delta("PT2H"),
756
751
                         datetime.timedelta(0, 7200))
757
752
    def test_falls_back_to_pre_1_6_1_with_warning(self):
792
787
                testcase.assertEqual(dbus_interface,
793
788
                                     dbus.PROPERTIES_IFACE)
794
789
                self.attributes[property] = value
 
790
                self.calls.append(("Set", (interface, property, value,
 
791
                                           dbus_interface)))
795
792
            def Get(self, interface, property, dbus_interface):
796
793
                testcase.assertEqual(interface, client_interface)
797
794
                testcase.assertEqual(dbus_interface,
798
795
                                     dbus.PROPERTIES_IFACE)
 
796
                self.calls.append(("Get", (interface, property,
 
797
                                           dbus_interface)))
799
798
                return self.attributes[property]
800
 
            def Approve(self, approve, dbus_interface):
801
 
                testcase.assertEqual(dbus_interface, client_interface)
802
 
                self.calls.append(("Approve", (approve,
803
 
                                               dbus_interface)))
804
 
        self.client = MockClient(
805
 
            "foo",
806
 
            KeyID=("92ed150794387c03ce684574b1139a65"
807
 
                   "94a34f895daaaf09fd8ea90a27cddb12"),
808
 
            Secret=b"secret",
809
 
            Host="foo.example.org",
810
 
            Enabled=dbus.Boolean(True),
811
 
            Timeout=300000,
812
 
            LastCheckedOK="2019-02-03T00:00:00",
813
 
            Created="2019-01-02T00:00:00",
814
 
            Interval=120000,
815
 
            Fingerprint=("778827225BA7DE539C5A"
816
 
                         "7CFA59CFF7CDBD9A5920"),
817
 
            CheckerRunning=dbus.Boolean(False),
818
 
            LastEnabled="2019-01-03T00:00:00",
819
 
            ApprovalPending=dbus.Boolean(False),
820
 
            ApprovedByDefault=dbus.Boolean(True),
821
 
            LastApprovalRequest="",
822
 
            ApprovalDelay=0,
823
 
            ApprovalDuration=1000,
824
 
            Checker="fping -q -- %(host)s",
825
 
            ExtendedTimeout=900000,
826
 
            Expires="2019-02-04T00:00:00",
827
 
            LastCheckerStatus=0)
828
 
        self.other_client = MockClient(
829
 
            "barbar",
830
 
            KeyID=("0558568eedd67d622f5c83b35a115f79"
831
 
                   "6ab612cff5ad227247e46c2b020f441c"),
832
 
            Secret=b"secretbar",
833
 
            Host="192.0.2.3",
834
 
            Enabled=dbus.Boolean(True),
835
 
            Timeout=300000,
836
 
            LastCheckedOK="2019-02-04T00:00:00",
837
 
            Created="2019-01-03T00:00:00",
838
 
            Interval=120000,
839
 
            Fingerprint=("3E393AEAEFB84C7E89E2"
840
 
                         "F547B3A107558FCA3A27"),
841
 
            CheckerRunning=dbus.Boolean(True),
842
 
            LastEnabled="2019-01-04T00:00:00",
843
 
            ApprovalPending=dbus.Boolean(False),
844
 
            ApprovedByDefault=dbus.Boolean(False),
845
 
            LastApprovalRequest="2019-01-03T00:00:00",
846
 
            ApprovalDelay=30000,
847
 
            ApprovalDuration=1000,
848
 
            Checker=":",
849
 
            ExtendedTimeout=900000,
850
 
            Expires="2019-02-05T00:00:00",
851
 
            LastCheckerStatus=-2)
852
 
        self.clients =  collections.OrderedDict(
853
 
            [
854
 
                (self.client, self.client.attributes),
855
 
                (self.other_client, self.other_client.attributes),
 
799
            def __getitem__(self, key):
 
800
                return self.attributes[key]
 
801
            def __setitem__(self, key, value):
 
802
                self.attributes[key] = value
 
803
        self.clients = collections.OrderedDict([
 
804
            ("foo",
 
805
             MockClient(
 
806
                 "foo",
 
807
                 KeyID=("92ed150794387c03ce684574b1139a65"
 
808
                        "94a34f895daaaf09fd8ea90a27cddb12"),
 
809
                 Secret=b"secret",
 
810
                 Host="foo.example.org",
 
811
                 Enabled=dbus.Boolean(True),
 
812
                 Timeout=300000,
 
813
                 LastCheckedOK="2019-02-03T00:00:00",
 
814
                 Created="2019-01-02T00:00:00",
 
815
                 Interval=120000,
 
816
                 Fingerprint=("778827225BA7DE539C5A"
 
817
                              "7CFA59CFF7CDBD9A5920"),
 
818
                 CheckerRunning=dbus.Boolean(False),
 
819
                 LastEnabled="2019-01-03T00:00:00",
 
820
                 ApprovalPending=dbus.Boolean(False),
 
821
                 ApprovedByDefault=dbus.Boolean(True),
 
822
                 LastApprovalRequest="",
 
823
                 ApprovalDelay=0,
 
824
                 ApprovalDuration=1000,
 
825
                 Checker="fping -q -- %(host)s",
 
826
                 ExtendedTimeout=900000,
 
827
                 Expires="2019-02-04T00:00:00",
 
828
                 LastCheckerStatus=0)),
 
829
            ("barbar",
 
830
             MockClient(
 
831
                 "barbar",
 
832
                 KeyID=("0558568eedd67d622f5c83b35a115f79"
 
833
                        "6ab612cff5ad227247e46c2b020f441c"),
 
834
                 Secret=b"secretbar",
 
835
                 Host="192.0.2.3",
 
836
                 Enabled=dbus.Boolean(True),
 
837
                 Timeout=300000,
 
838
                 LastCheckedOK="2019-02-04T00:00:00",
 
839
                 Created="2019-01-03T00:00:00",
 
840
                 Interval=120000,
 
841
                 Fingerprint=("3E393AEAEFB84C7E89E2"
 
842
                              "F547B3A107558FCA3A27"),
 
843
                 CheckerRunning=dbus.Boolean(True),
 
844
                 LastEnabled="2019-01-04T00:00:00",
 
845
                 ApprovalPending=dbus.Boolean(False),
 
846
                 ApprovedByDefault=dbus.Boolean(False),
 
847
                 LastApprovalRequest="2019-01-03T00:00:00",
 
848
                 ApprovalDelay=30000,
 
849
                 ApprovalDuration=1000,
 
850
                 Checker=":",
 
851
                 ExtendedTimeout=900000,
 
852
                 Expires="2019-02-05T00:00:00",
 
853
                 LastCheckerStatus=-2)),
856
854
            ])
857
 
        self.one_client = {self.client: self.client.attributes}
858
855
 
859
856
class TestPrintTableCmd(TestCmd):
860
857
    def test_normal(self):
874
871
"""[1:-1]
875
872
        self.assertEqual(output, expected_output)
876
873
    def test_one_client(self):
877
 
        output = PrintTableCmd().output(self.one_client)
 
874
        output = PrintTableCmd().output({"foo": self.clients["foo"]})
878
875
        expected_output = """
879
876
Name Enabled Timeout  Last Successful Check
880
877
foo  Yes     00:05:00 2019-02-03T00:00:00  
938
935
        json_data = json.loads(DumpJSONCmd().output(self.clients))
939
936
        self.assertDictEqual(json_data, self.expected_json)
940
937
    def test_one_client(self):
941
 
        clients = self.one_client
 
938
        clients = {"foo": self.clients["foo"]}
942
939
        json_data = json.loads(DumpJSONCmd().output(clients))
943
940
        expected_json = {"foo": self.expected_json["foo"]}
944
941
        self.assertDictEqual(json_data, expected_json)
945
942
 
946
943
class TestIsEnabledCmd(TestCmd):
947
944
    def test_is_enabled(self):
948
 
        self.assertTrue(all(IsEnabledCmd().is_enabled(client, properties)
949
 
                            for client, properties in self.clients.items()))
 
945
        self.assertTrue(all(IsEnabledCmd().is_enabled(client)
 
946
                            for client in self.clients.values()))
 
947
    def test_is_enabled_does_get_attribute(self):
 
948
        client = self.clients["foo"]
 
949
        self.assertTrue(IsEnabledCmd().is_enabled(client))
 
950
        self.assertListEqual(client.calls,
 
951
                             [("Get",
 
952
                               ("se.recompile.Mandos.Client",
 
953
                                "Enabled",
 
954
                                "org.freedesktop.DBus.Properties"))])
950
955
    def test_is_enabled_run_exits_successfully(self):
 
956
        client = self.clients["foo"]
951
957
        with self.assertRaises(SystemExit) as e:
952
 
            IsEnabledCmd().run(None, self.one_client)
 
958
            IsEnabledCmd().run_on_one_client(client)
953
959
        if e.exception.code is not None:
954
960
            self.assertEqual(e.exception.code, 0)
955
961
        else:
956
962
            self.assertIsNone(e.exception.code)
957
963
    def test_is_enabled_run_exits_with_failure(self):
958
 
        self.client.attributes["Enabled"] = dbus.Boolean(False)
 
964
        client = self.clients["foo"]
 
965
        client["Enabled"] = dbus.Boolean(False)
959
966
        with self.assertRaises(SystemExit) as e:
960
 
            IsEnabledCmd().run(None, self.one_client)
 
967
            IsEnabledCmd().run_on_one_client(client)
961
968
        if isinstance(e.exception.code, int):
962
969
            self.assertNotEqual(e.exception.code, 0)
963
970
        else:
964
971
            self.assertIsNotNone(e.exception.code)
965
972
 
966
 
class TestRemoveCmd(TestCmd):
967
 
    def test_remove(self):
968
 
        class MockMandos(object):
969
 
            def __init__(self):
970
 
                self.calls = []
971
 
            def RemoveClient(self, dbus_path):
972
 
                self.calls.append(("RemoveClient", (dbus_path,)))
973
 
        mandos = MockMandos()
974
 
        super(TestRemoveCmd, self).setUp()
975
 
        RemoveCmd().run(mandos, self.clients)
976
 
        self.assertEqual(len(mandos.calls), 2)
977
 
        for client in self.clients:
978
 
            self.assertIn(("RemoveClient",
979
 
                           (client.__dbus_object_path__,)),
980
 
                          mandos.calls)
981
 
 
982
 
class TestApproveCmd(TestCmd):
983
 
    def test_approve(self):
984
 
        ApproveCmd().run(None, self.clients)
985
 
        for client in self.clients:
986
 
            self.assertIn(("Approve", (True, client_interface)),
987
 
                          client.calls)
988
 
 
989
 
class TestDenyCmd(TestCmd):
990
 
    def test_deny(self):
991
 
        DenyCmd().run(None, self.clients)
992
 
        for client in self.clients:
993
 
            self.assertIn(("Approve", (False, client_interface)),
994
 
                          client.calls)
995
 
 
996
 
class TestEnableCmd(TestCmd):
997
 
    def test_enable(self):
998
 
        for client in self.clients:
999
 
            client.attributes["Enabled"] = False
1000
 
 
1001
 
        EnableCmd().run(None, self.clients)
1002
 
 
1003
 
        for client in self.clients:
1004
 
            self.assertTrue(client.attributes["Enabled"])
1005
 
 
1006
 
class TestDisableCmd(TestCmd):
1007
 
    def test_disable(self):
1008
 
        DisableCmd().run(None, self.clients)
1009
 
 
1010
 
        for client in self.clients:
1011
 
            self.assertFalse(client.attributes["Enabled"])
1012
 
 
1013
 
class Unique(object):
1014
 
    """Class for objects which exist only to be unique objects, since
1015
 
unittest.mock.sentinel only exists in Python 3.3"""
1016
 
 
1017
 
class TestPropertyCmd(TestCmd):
1018
 
    """Abstract class for tests of PropertyCmd classes"""
1019
 
    def runTest(self):
1020
 
        if not hasattr(self, "command"):
1021
 
            return
1022
 
        values_to_get = getattr(self, "values_to_get",
1023
 
                                self.values_to_set)
1024
 
        for value_to_set, value_to_get in zip(self.values_to_set,
1025
 
                                              values_to_get):
1026
 
            for client in self.clients:
1027
 
                old_value = client.attributes[self.property]
1028
 
                self.assertNotIsInstance(old_value, Unique)
1029
 
                client.attributes[self.property] = Unique()
1030
 
            self.run_command(value_to_set, self.clients)
1031
 
            for client in self.clients:
1032
 
                value = client.attributes[self.property]
1033
 
                self.assertNotIsInstance(value, Unique)
1034
 
                self.assertEqual(value, value_to_get)
1035
 
    def run_command(self, value, clients):
1036
 
        self.command().run(None, clients)
1037
 
 
1038
 
class TestBumpTimeoutCmd(TestPropertyCmd):
1039
 
    command = BumpTimeoutCmd
1040
 
    property = "LastCheckedOK"
1041
 
    values_to_set = [""]
1042
 
 
1043
 
class TestStartCheckerCmd(TestPropertyCmd):
1044
 
    command = StartCheckerCmd
1045
 
    property = "CheckerRunning"
1046
 
    values_to_set = [dbus.Boolean(True)]
1047
 
 
1048
 
class TestStopCheckerCmd(TestPropertyCmd):
1049
 
    command = StopCheckerCmd
1050
 
    property = "CheckerRunning"
1051
 
    values_to_set = [dbus.Boolean(False)]
1052
 
 
1053
 
class TestApproveByDefaultCmd(TestPropertyCmd):
1054
 
    command = ApproveByDefaultCmd
1055
 
    property = "ApprovedByDefault"
1056
 
    values_to_set = [dbus.Boolean(True)]
1057
 
 
1058
 
class TestDenyByDefaultCmd(TestPropertyCmd):
1059
 
    command = DenyByDefaultCmd
1060
 
    property = "ApprovedByDefault"
1061
 
    values_to_set = [dbus.Boolean(False)]
1062
 
 
1063
 
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1064
 
    """Abstract class for tests of PropertyCmd classes using the
1065
 
ValueArgumentMixIn"""
1066
 
    def runTest(self):
1067
 
        if type(self) is TestValueArgumentPropertyCmd:
1068
 
            return
1069
 
        return super(TestValueArgumentPropertyCmd, self).runTest()
1070
 
    def run_command(self, value, clients):
1071
 
        self.command(value).run(None, clients)
1072
 
 
1073
 
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1074
 
    command = SetCheckerCmd
1075
 
    property = "Checker"
1076
 
    values_to_set = ["", ":", "fping -q -- %s"]
1077
 
 
1078
 
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1079
 
    command = SetHostCmd
1080
 
    property = "Host"
1081
 
    values_to_set = ["192.0.2.3", "foo.example.org"]
1082
 
 
1083
 
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1084
 
    command = SetSecretCmd
1085
 
    property = "Secret"
1086
 
    values_to_set = [b"", b"secret"]
1087
 
 
1088
 
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1089
 
    command = SetTimeoutCmd
1090
 
    property = "Timeout"
1091
 
    values_to_set = ["P0D", "PT5M", "PT1S", "PT120S", "P1Y"]
1092
 
    values_to_get = [0, 300000, 1000, 120000, 31449600000]
1093
 
 
1094
 
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
1095
 
    command = SetExtendedTimeoutCmd
1096
 
    property = "ExtendedTimeout"
1097
 
    values_to_set = ["P0D", "PT5M", "PT1S", "PT120S", "P1Y"]
1098
 
    values_to_get = [0, 300000, 1000, 120000, 31449600000]
1099
 
 
1100
 
class TestSetIntervalCmd(TestValueArgumentPropertyCmd):
1101
 
    command = SetIntervalCmd
1102
 
    property = "Interval"
1103
 
    values_to_set = ["P0D", "PT5M", "PT1S", "PT120S", "P1Y"]
1104
 
    values_to_get = [0, 300000, 1000, 120000, 31449600000]
1105
 
 
1106
 
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
1107
 
    command = SetApprovalDelayCmd
1108
 
    property = "ApprovalDelay"
1109
 
    values_to_set = ["P0D", "PT5M", "PT1S", "PT120S", "P1Y"]
1110
 
    values_to_get = [0, 300000, 1000, 120000, 31449600000]
1111
 
 
1112
 
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
1113
 
    command = SetApprovalDurationCmd
1114
 
    property = "ApprovalDuration"
1115
 
    values_to_set = ["P0D", "PT5M", "PT1S", "PT120S", "P1Y"]
1116
 
    values_to_get = [0, 300000, 1000, 120000, 31449600000]
1117
 
 
1118
 
class TestOptions(unittest.TestCase):
1119
 
    def setUp(self):
1120
 
        self.parser = argparse.ArgumentParser()
1121
 
        add_command_line_options(self.parser)
1122
 
    def commands_from_args(self, args):
1123
 
        self.options = self.parser.parse_args(args)
1124
 
        return commands_from_options(self.options)
1125
 
    def test_default_is_show_table(self):
1126
 
        commands = self.commands_from_args([])
1127
 
        self.assertEqual(len(commands), 1)
1128
 
        command = commands[0]
1129
 
        self.assertIsInstance(command, PrintTableCmd)
1130
 
        self.assertEqual(command.verbose, False)
1131
 
    def test_show_table_verbose(self):
1132
 
        commands = self.commands_from_args(["--verbose"])
1133
 
        self.assertEqual(len(commands), 1)
1134
 
        command = commands[0]
1135
 
        self.assertIsInstance(command, PrintTableCmd)
1136
 
        self.assertEqual(command.verbose, True)
1137
 
    def test_enable(self):
1138
 
        commands = self.commands_from_args(["--enable", "foo"])
1139
 
        self.assertEqual(len(commands), 1)
1140
 
        command = commands[0]
1141
 
        self.assertIsInstance(command, EnableCmd)
1142
 
        self.assertEqual(self.options.client, ["foo"])
1143
 
    def test_disable(self):
1144
 
        commands = self.commands_from_args(["--disable", "foo"])
1145
 
        self.assertEqual(len(commands), 1)
1146
 
        command = commands[0]
1147
 
        self.assertIsInstance(command, DisableCmd)
1148
 
        self.assertEqual(self.options.client, ["foo"])
1149
 
 
1150
973
 
1151
974
 
1152
975
def should_only_run_tests():