292
297
"LastApprovalRequest", "ApprovalDelay",
293
298
"ApprovalDuration", "Checker", "ExtendedTimeout",
294
299
"Expires", "LastCheckerStatus")
295
def run(self, mandos, clients):
296
print(self.output(clients))
300
def run(self, clients, bus=None, mandos=None):
301
print(self.output(clients.values()))
302
def output(self, clients):
303
raise NotImplementedError()
298
305
class PropertyCmd(Command):
299
306
"""Abstract class for Actions for setting one client property"""
300
307
def run_on_one_client(self, client, properties):
301
308
"""Set the Client's D-Bus property"""
302
client.Set(client_interface, self.property, self.value_to_set,
309
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", dbus_busname,
310
client.__dbus_object_path__,
311
dbus.PROPERTIES_IFACE, client_dbus_interface,
312
self.propname, self.value_to_set
313
if not isinstance(self.value_to_set, dbus.Boolean)
314
else bool(self.value_to_set))
315
client.Set(client_dbus_interface, self.propname,
303
317
dbus_interface=dbus.PROPERTIES_IFACE)
320
raise NotImplementedError()
305
class ValueArgumentMixIn(object):
306
"""Mixin class for commands taking a value as argument"""
322
class PropertyValueCmd(PropertyCmd):
323
"""Abstract class for PropertyCmd recieving a value as argument"""
307
324
def __init__(self, value):
308
325
self.value_to_set = value
310
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
311
"""Mixin class for commands taking a value argument as
327
class MillisecondsPropertyValueArgumentCmd(PropertyValueCmd):
328
"""Abstract class for PropertyValueCmd taking a value argument as
329
a datetime.timedelta() but should store it as milliseconds."""
314
331
def value_to_set(self):
316
333
@value_to_set.setter
317
334
def value_to_set(self, value):
318
"""When setting, convert value to a datetime.timedelta"""
335
"""When setting, convert value from a datetime.timedelta"""
319
336
self._vts = int(round(value.total_seconds() * 1000))
321
338
# Actual (non-abstract) command classes
423
441
class IsEnabledCmd(Command):
424
def run_on_one_client(self, client, properties):
442
def run(self, clients, bus=None, mandos=None):
443
client, properties = next(iter(clients.items()))
425
444
if self.is_enabled(client, properties):
428
447
def is_enabled(self, client, properties):
429
return bool(properties["Enabled"])
448
return properties["Enabled"]
431
450
class RemoveCmd(Command):
432
451
def run_on_one_client(self, client, properties):
452
log.debug("D-Bus: %s:%s:%s.RemoveClient(%r)", dbus_busname,
453
server_dbus_path, server_dbus_interface,
454
str(client.__dbus_object_path__))
433
455
self.mandos.RemoveClient(client.__dbus_object_path__)
435
457
class ApproveCmd(Command):
436
458
def run_on_one_client(self, client, properties):
459
log.debug("D-Bus: %s:%s:%s.Approve(True)", dbus_busname,
460
client.__dbus_object_path__, client_dbus_interface)
437
461
client.Approve(dbus.Boolean(True),
438
dbus_interface=client_interface)
462
dbus_interface=client_dbus_interface)
440
464
class DenyCmd(Command):
441
465
def run_on_one_client(self, client, properties):
466
log.debug("D-Bus: %s:%s:%s.Approve(False)", dbus_busname,
467
client.__dbus_object_path__, client_dbus_interface)
442
468
client.Approve(dbus.Boolean(False),
443
dbus_interface=client_interface)
469
dbus_interface=client_dbus_interface)
445
471
class EnableCmd(PropertyCmd):
447
473
value_to_set = dbus.Boolean(True)
449
475
class DisableCmd(PropertyCmd):
451
477
value_to_set = dbus.Boolean(False)
453
479
class BumpTimeoutCmd(PropertyCmd):
454
property = "LastCheckedOK"
480
propname = "LastCheckedOK"
455
481
value_to_set = ""
457
483
class StartCheckerCmd(PropertyCmd):
458
property = "CheckerRunning"
484
propname = "CheckerRunning"
459
485
value_to_set = dbus.Boolean(True)
461
487
class StopCheckerCmd(PropertyCmd):
462
property = "CheckerRunning"
488
propname = "CheckerRunning"
463
489
value_to_set = dbus.Boolean(False)
465
491
class ApproveByDefaultCmd(PropertyCmd):
466
property = "ApprovedByDefault"
492
propname = "ApprovedByDefault"
467
493
value_to_set = dbus.Boolean(True)
469
495
class DenyByDefaultCmd(PropertyCmd):
470
property = "ApprovedByDefault"
496
propname = "ApprovedByDefault"
471
497
value_to_set = dbus.Boolean(False)
473
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
476
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
479
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
499
class SetCheckerCmd(PropertyValueCmd):
502
class SetHostCmd(PropertyValueCmd):
505
class SetSecretCmd(PropertyValueCmd):
481
508
def value_to_set(self):
485
512
"""When setting, read data from supplied file object"""
486
513
self._vts = value.read()
490
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
493
class SetExtendedTimeoutCmd(PropertyCmd,
494
MillisecondsValueArgumentMixIn):
495
property = "ExtendedTimeout"
497
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
498
property = "Interval"
500
class SetApprovalDelayCmd(PropertyCmd,
501
MillisecondsValueArgumentMixIn):
502
property = "ApprovalDelay"
504
class SetApprovalDurationCmd(PropertyCmd,
505
MillisecondsValueArgumentMixIn):
506
property = "ApprovalDuration"
516
class SetTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
519
class SetExtendedTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
520
propname = "ExtendedTimeout"
522
class SetIntervalCmd(MillisecondsPropertyValueArgumentCmd):
523
propname = "Interval"
525
class SetApprovalDelayCmd(MillisecondsPropertyValueArgumentCmd):
526
propname = "ApprovalDelay"
528
class SetApprovalDurationCmd(MillisecondsPropertyValueArgumentCmd):
529
propname = "ApprovalDuration"
508
531
def add_command_line_options(parser):
509
532
parser.add_argument("--version", action="version",
682
704
parser.error("--all requires an action.")
683
705
if options.is_enabled and len(options.client) > 1:
684
706
parser.error("--is-enabled requires exactly one client")
708
options.remove = False
709
if has_actions(options) and not options.deny:
710
parser.error("--remove can only be combined with --deny")
711
options.remove = True
715
parser = argparse.ArgumentParser()
717
add_command_line_options(parser)
719
options = parser.parse_args()
721
check_option_syntax(parser, options)
686
723
clientnames = options.client
726
log.setLevel(logging.DEBUG)
689
729
bus = dbus.SystemBus()
690
mandos_dbus_objc = bus.get_object(busname, server_path)
730
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
731
dbus_busname, server_dbus_path)
732
mandos_dbus_objc = bus.get_object(dbus_busname,
691
734
except dbus.exceptions.DBusException:
692
735
log.critical("Could not connect to Mandos server")
695
738
mandos_serv = dbus.Interface(mandos_dbus_objc,
696
dbus_interface=server_interface)
739
dbus_interface=server_dbus_interface)
697
740
mandos_serv_object_manager = dbus.Interface(
698
741
mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
794
838
class MockClient(object):
795
839
def __init__(self, name, **attributes):
796
self.__dbus_object_path__ = "objpath_{}".format(name)
840
self.__dbus_object_path__ = "/clients/{}".format(name)
797
841
self.attributes = attributes
798
842
self.attributes["Name"] = name
800
def Set(self, interface, property, value, dbus_interface):
801
testcase.assertEqual(interface, client_interface)
802
testcase.assertEqual(dbus_interface,
803
dbus.PROPERTIES_IFACE)
804
self.attributes[property] = value
805
def Get(self, interface, property, dbus_interface):
806
testcase.assertEqual(interface, client_interface)
807
testcase.assertEqual(dbus_interface,
808
dbus.PROPERTIES_IFACE)
809
return self.attributes[property]
844
def Set(self, interface, propname, value, dbus_interface):
845
testcase.assertEqual(interface, client_dbus_interface)
846
testcase.assertEqual(dbus_interface,
847
dbus.PROPERTIES_IFACE)
848
self.attributes[propname] = value
849
def Get(self, interface, propname, dbus_interface):
850
testcase.assertEqual(interface, client_dbus_interface)
851
testcase.assertEqual(dbus_interface,
852
dbus.PROPERTIES_IFACE)
853
return self.attributes[propname]
810
854
def Approve(self, approve, dbus_interface):
811
testcase.assertEqual(dbus_interface, client_interface)
855
testcase.assertEqual(dbus_interface,
856
client_dbus_interface)
812
857
self.calls.append(("Approve", (approve,
813
858
dbus_interface)))
814
859
self.client = MockClient(
861
906
LastCheckerStatus=-2)
862
907
self.clients = collections.OrderedDict(
864
(self.client, self.client.attributes),
865
(self.other_client, self.other_client.attributes),
909
("/clients/foo", self.client.attributes),
910
("/clients/barbar", self.other_client.attributes),
867
self.one_client = {self.client: self.client.attributes}
912
self.one_client = {"/clients/foo": self.client.attributes}
917
def get_object(client_bus_name, path):
918
self.assertEqual(client_bus_name, dbus_busname)
920
"/clients/foo": self.client,
921
"/clients/barbar": self.other_client,
869
925
class TestPrintTableCmd(TestCmd):
870
926
def test_normal(self):
871
output = PrintTableCmd().output(self.clients)
872
expected_output = """
873
Name Enabled Timeout Last Successful Check
874
foo Yes 00:05:00 2019-02-03T00:00:00
875
barbar Yes 00:05:00 2019-02-04T00:00:00
927
output = PrintTableCmd().output(self.clients.values())
928
expected_output = "\n".join((
929
"Name Enabled Timeout Last Successful Check",
930
"foo Yes 00:05:00 2019-02-03T00:00:00 ",
931
"barbar Yes 00:05:00 2019-02-04T00:00:00 ",
877
933
self.assertEqual(output, expected_output)
878
934
def test_verbose(self):
879
output = PrintTableCmd(verbose=True).output(self.clients)
880
expected_output = """
881
Name Enabled Timeout Last Successful Check Created Interval Host Key ID Fingerprint Check Is Running Last Enabled Approval Is Pending Approved By Default Last Approval Request Approval Delay Approval Duration Checker Extended Timeout Expires Last Checker Status
882
foo Yes 00:05:00 2019-02-03T00:00:00 2019-01-02T00:00:00 00:02:00 foo.example.org 92ed150794387c03ce684574b1139a6594a34f895daaaf09fd8ea90a27cddb12 778827225BA7DE539C5A7CFA59CFF7CDBD9A5920 No 2019-01-03T00:00:00 No Yes 00:00:00 00:00:01 fping -q -- %(host)s 00:15:00 2019-02-04T00:00:00 0
883
barbar Yes 00:05:00 2019-02-04T00:00:00 2019-01-03T00:00:00 00:02:00 192.0.2.3 0558568eedd67d622f5c83b35a115f796ab612cff5ad227247e46c2b020f441c 3E393AEAEFB84C7E89E2F547B3A107558FCA3A27 Yes 2019-01-04T00:00:00 No No 2019-01-03T00:00:00 00:00:30 00:00:01 : 00:15:00 2019-02-05T00:00:00 -2
935
output = PrintTableCmd(verbose=True).output(
936
self.clients.values())
937
expected_output = "\n".join((
938
# First line (headers)
939
"Name Enabled Timeout Last Successful Check Created "
940
" Interval Host Key ID "
942
" Check Is Running Last Enabl"
943
"ed Approval Is Pending Approved By Default Last A"
944
"pproval Request Approval Delay Approval Duration Checker"
945
" Extended Timeout Expires Last "
947
# Second line (client "foo")
948
"foo Yes 00:05:00 2019-02-03T00:00:00 2019-01-02"
949
"T00:00:00 00:02:00 foo.example.org 92ed150794387c03ce684"
950
"574b1139a6594a34f895daaaf09fd8ea90a27cddb12 778827225BA7"
951
"DE539C5A7CFA59CFF7CDBD9A5920 No 2019-01-03"
953
" 00:00:00 00:00:01 fping -"
954
"q -- %(host)s 00:15:00 2019-02-04T00:00:00 0 "
956
# Third line (client "barbar")
957
"barbar Yes 00:05:00 2019-02-04T00:00:00 2019-01-03"
958
"T00:00:00 00:02:00 192.0.2.3 0558568eedd67d622f5c8"
959
"3b35a115f796ab612cff5ad227247e46c2b020f441c 3E393AEAEFB8"
960
"4C7E89E2F547B3A107558FCA3A27 Yes 2019-01-04"
961
"T00:00:00 No No 2019-0"
962
"1-03T00:00:00 00:00:30 00:00:01 : "
963
" 00:15:00 2019-02-05T00:00:00 -2 "
885
966
self.assertEqual(output, expected_output)
886
967
def test_one_client(self):
887
output = PrintTableCmd().output(self.one_client)
968
output = PrintTableCmd().output(self.one_client.values())
888
969
expected_output = """
889
970
Name Enabled Timeout Last Successful Check
890
971
foo Yes 00:05:00 2019-02-03T00:00:00
982
1065
self.calls.append(("RemoveClient", (dbus_path,)))
983
1066
mandos = MockMandos()
984
1067
super(TestRemoveCmd, self).setUp()
985
RemoveCmd().run(mandos, self.clients)
1068
RemoveCmd().run(self.clients, self.bus, mandos)
986
1069
self.assertEqual(len(mandos.calls), 2)
987
for client in self.clients:
988
self.assertIn(("RemoveClient",
989
(client.__dbus_object_path__,)),
1070
for clientpath in self.clients:
1071
self.assertIn(("RemoveClient", (clientpath,)),
992
1074
class TestApproveCmd(TestCmd):
993
1075
def test_approve(self):
994
ApproveCmd().run(None, self.clients)
995
for client in self.clients:
996
self.assertIn(("Approve", (True, client_interface)),
1076
ApproveCmd().run(self.clients, self.bus)
1077
for clientpath in self.clients:
1078
client = self.bus.get_object(dbus_busname, clientpath)
1079
self.assertIn(("Approve", (True, client_dbus_interface)),
999
1082
class TestDenyCmd(TestCmd):
1000
1083
def test_deny(self):
1001
DenyCmd().run(None, self.clients)
1002
for client in self.clients:
1003
self.assertIn(("Approve", (False, client_interface)),
1084
DenyCmd().run(self.clients, self.bus)
1085
for clientpath in self.clients:
1086
client = self.bus.get_object(dbus_busname, clientpath)
1087
self.assertIn(("Approve", (False, client_dbus_interface)),
1006
1090
class TestEnableCmd(TestCmd):
1007
1091
def test_enable(self):
1008
for client in self.clients:
1092
for clientpath in self.clients:
1093
client = self.bus.get_object(dbus_busname, clientpath)
1009
1094
client.attributes["Enabled"] = False
1011
EnableCmd().run(None, self.clients)
1096
EnableCmd().run(self.clients, self.bus)
1013
for client in self.clients:
1098
for clientpath in self.clients:
1099
client = self.bus.get_object(dbus_busname, clientpath)
1014
1100
self.assertTrue(client.attributes["Enabled"])
1016
1102
class TestDisableCmd(TestCmd):
1017
1103
def test_disable(self):
1018
DisableCmd().run(None, self.clients)
1020
for client in self.clients:
1104
DisableCmd().run(self.clients, self.bus)
1105
for clientpath in self.clients:
1106
client = self.bus.get_object(dbus_busname, clientpath)
1021
1107
self.assertFalse(client.attributes["Enabled"])
1023
1109
class Unique(object):
1033
1119
self.values_to_set)
1034
1120
for value_to_set, value_to_get in zip(self.values_to_set,
1035
1121
values_to_get):
1036
for client in self.clients:
1037
old_value = client.attributes[self.property]
1122
for clientpath in self.clients:
1123
client = self.bus.get_object(dbus_busname, clientpath)
1124
old_value = client.attributes[self.propname]
1038
1125
self.assertNotIsInstance(old_value, Unique)
1039
client.attributes[self.property] = Unique()
1126
client.attributes[self.propname] = Unique()
1040
1127
self.run_command(value_to_set, self.clients)
1041
for client in self.clients:
1042
value = client.attributes[self.property]
1128
for clientpath in self.clients:
1129
client = self.bus.get_object(dbus_busname, clientpath)
1130
value = client.attributes[self.propname]
1043
1131
self.assertNotIsInstance(value, Unique)
1044
1132
self.assertEqual(value, value_to_get)
1045
1133
def run_command(self, value, clients):
1046
self.command().run(None, clients)
1134
self.command().run(clients, self.bus)
1048
1136
class TestBumpTimeoutCmd(TestPropertyCmd):
1049
1137
command = BumpTimeoutCmd
1050
property = "LastCheckedOK"
1138
propname = "LastCheckedOK"
1051
1139
values_to_set = [""]
1053
1141
class TestStartCheckerCmd(TestPropertyCmd):
1054
1142
command = StartCheckerCmd
1055
property = "CheckerRunning"
1143
propname = "CheckerRunning"
1056
1144
values_to_set = [dbus.Boolean(True)]
1058
1146
class TestStopCheckerCmd(TestPropertyCmd):
1059
1147
command = StopCheckerCmd
1060
property = "CheckerRunning"
1148
propname = "CheckerRunning"
1061
1149
values_to_set = [dbus.Boolean(False)]
1063
1151
class TestApproveByDefaultCmd(TestPropertyCmd):
1064
1152
command = ApproveByDefaultCmd
1065
property = "ApprovedByDefault"
1153
propname = "ApprovedByDefault"
1066
1154
values_to_set = [dbus.Boolean(True)]
1068
1156
class TestDenyByDefaultCmd(TestPropertyCmd):
1069
1157
command = DenyByDefaultCmd
1070
property = "ApprovedByDefault"
1158
propname = "ApprovedByDefault"
1071
1159
values_to_set = [dbus.Boolean(False)]
1073
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1074
"""Abstract class for tests of PropertyCmd classes using the
1075
ValueArgumentMixIn"""
1161
class TestPropertyValueCmd(TestPropertyCmd):
1162
"""Abstract class for tests of PropertyValueCmd classes"""
1076
1163
def runTest(self):
1077
if type(self) is TestValueArgumentPropertyCmd:
1164
if type(self) is TestPropertyValueCmd:
1079
return super(TestValueArgumentPropertyCmd, self).runTest()
1166
return super(TestPropertyValueCmd, self).runTest()
1080
1167
def run_command(self, value, clients):
1081
self.command(value).run(None, clients)
1168
self.command(value).run(clients, self.bus)
1083
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1170
class TestSetCheckerCmd(TestPropertyValueCmd):
1084
1171
command = SetCheckerCmd
1085
property = "Checker"
1172
propname = "Checker"
1086
1173
values_to_set = ["", ":", "fping -q -- %s"]
1088
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1175
class TestSetHostCmd(TestPropertyValueCmd):
1089
1176
command = SetHostCmd
1091
1178
values_to_set = ["192.0.2.3", "foo.example.org"]
1093
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1180
class TestSetSecretCmd(TestPropertyValueCmd):
1094
1181
command = SetSecretCmd
1096
values_to_set = [open("/dev/null", "rb"),
1183
values_to_set = [io.BytesIO(b""),
1097
1184
io.BytesIO(b"secret\0xyzzy\nbar")]
1098
1185
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1100
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1187
class TestSetTimeoutCmd(TestPropertyValueCmd):
1101
1188
command = SetTimeoutCmd
1102
property = "Timeout"
1189
propname = "Timeout"
1103
1190
values_to_set = [datetime.timedelta(),
1104
1191
datetime.timedelta(minutes=5),
1105
1192
datetime.timedelta(seconds=1),
1324
1413
def test_is_enabled_short(self):
1325
1414
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1416
def test_deny_before_remove(self):
1417
options = self.parser.parse_args(["--deny", "--remove",
1419
check_option_syntax(self.parser, options)
1420
commands = commands_from_options(options)
1421
self.assertEqual(len(commands), 2)
1422
self.assertIsInstance(commands[0], DenyCmd)
1423
self.assertIsInstance(commands[1], RemoveCmd)
1425
def test_deny_before_remove_reversed(self):
1426
options = self.parser.parse_args(["--remove", "--deny",
1428
check_option_syntax(self.parser, options)
1429
commands = commands_from_options(options)
1430
self.assertEqual(len(commands), 2)
1431
self.assertIsInstance(commands[0], DenyCmd)
1432
self.assertIsInstance(commands[1], RemoveCmd)
1435
class Test_check_option_syntax(unittest.TestCase):
1436
# This mostly corresponds to the definition from has_actions() in
1437
# check_option_syntax()
1439
# The actual values set here are not that important, but we do
1440
# at least stick to the correct types, even though they are
1444
"bump_timeout": True,
1445
"start_checker": True,
1446
"stop_checker": True,
1450
"timeout": datetime.timedelta(),
1451
"extended_timeout": datetime.timedelta(),
1452
"interval": datetime.timedelta(),
1453
"approved_by_default": True,
1454
"approval_delay": datetime.timedelta(),
1455
"approval_duration": datetime.timedelta(),
1457
"secret": io.BytesIO(b"x"),
1463
self.parser = argparse.ArgumentParser()
1464
add_command_line_options(self.parser)
1466
@contextlib.contextmanager
1467
def assertParseError(self):
1468
with self.assertRaises(SystemExit) as e:
1469
with self.temporarily_suppress_stderr():
1471
# Exit code from argparse is guaranteed to be "2". Reference:
1472
# https://docs.python.org/3/library
1473
# /argparse.html#exiting-methods
1474
self.assertEqual(e.exception.code, 2)
1477
@contextlib.contextmanager
1478
def temporarily_suppress_stderr():
1479
null = os.open(os.path.devnull, os.O_RDWR)
1480
stderrcopy = os.dup(sys.stderr.fileno())
1481
os.dup2(null, sys.stderr.fileno())
1487
os.dup2(stderrcopy, sys.stderr.fileno())
1488
os.close(stderrcopy)
1490
def check_option_syntax(self, options):
1491
check_option_syntax(self.parser, options)
1493
def test_actions_requires_client_or_all(self):
1494
for action, value in self.actions.items():
1495
options = self.parser.parse_args()
1496
setattr(options, action, value)
1497
with self.assertParseError():
1498
self.check_option_syntax(options)
1500
def test_actions_conflicts_with_verbose(self):
1501
for action, value in self.actions.items():
1502
options = self.parser.parse_args()
1503
setattr(options, action, value)
1504
options.verbose = True
1505
with self.assertParseError():
1506
self.check_option_syntax(options)
1508
def test_dump_json_conflicts_with_verbose(self):
1509
options = self.parser.parse_args()
1510
options.dump_json = True
1511
options.verbose = True
1512
with self.assertParseError():
1513
self.check_option_syntax(options)
1515
def test_dump_json_conflicts_with_action(self):
1516
for action, value in self.actions.items():
1517
options = self.parser.parse_args()
1518
setattr(options, action, value)
1519
options.dump_json = True
1520
with self.assertParseError():
1521
self.check_option_syntax(options)
1523
def test_all_can_not_be_alone(self):
1524
options = self.parser.parse_args()
1526
with self.assertParseError():
1527
self.check_option_syntax(options)
1529
def test_all_is_ok_with_any_action(self):
1530
for action, value in self.actions.items():
1531
options = self.parser.parse_args()
1532
setattr(options, action, value)
1534
self.check_option_syntax(options)
1536
def test_is_enabled_fails_without_client(self):
1537
options = self.parser.parse_args()
1538
options.is_enabled = True
1539
with self.assertParseError():
1540
self.check_option_syntax(options)
1542
def test_is_enabled_works_with_one_client(self):
1543
options = self.parser.parse_args()
1544
options.is_enabled = True
1545
options.client = ["foo"]
1546
self.check_option_syntax(options)
1548
def test_is_enabled_fails_with_two_clients(self):
1549
options = self.parser.parse_args()
1550
options.is_enabled = True
1551
options.client = ["foo", "barbar"]
1552
with self.assertParseError():
1553
self.check_option_syntax(options)
1555
def test_remove_can_only_be_combined_with_action_deny(self):
1556
for action, value in self.actions.items():
1557
if action in {"remove", "deny"}:
1559
options = self.parser.parse_args()
1560
setattr(options, action, value)
1562
options.remove = True
1563
with self.assertParseError():
1564
self.check_option_syntax(options)
1329
1568
def should_only_run_tests():