277
276
# Abstract classes first
278
277
class Command(object):
279
278
"""Abstract class for commands"""
280
def run(self, clients, bus=None, mandos=None):
279
def run(self, mandos, clients):
281
280
"""Normal commands should implement run_on_one_client(), but
282
281
commands which want to operate on all clients at the same time
283
282
can override this run() method instead."""
284
283
self.mandos = mandos
285
for clientpath, properties in clients.items():
286
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
287
dbus_busname, str(clientpath))
288
client = bus.get_object(dbus_busname, clientpath)
284
for client, properties in clients.items():
289
285
self.run_on_one_client(client, properties)
291
287
class PrintCmd(Command):
297
293
"LastApprovalRequest", "ApprovalDelay",
298
294
"ApprovalDuration", "Checker", "ExtendedTimeout",
299
295
"Expires", "LastCheckerStatus")
300
def run(self, clients, bus=None, mandos=None):
301
print(self.output(clients.values()))
302
def output(self, clients):
303
raise NotImplementedError()
296
def run(self, mandos, clients):
297
print(self.output(clients))
305
299
class PropertyCmd(Command):
306
300
"""Abstract class for Actions for setting one client property"""
307
301
def run_on_one_client(self, client, properties):
308
302
"""Set the Client's D-Bus property"""
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
client.Set(client_interface, self.property, self.value_to_set,
317
304
dbus_interface=dbus.PROPERTIES_IFACE)
320
raise NotImplementedError()
322
class PropertyValueCmd(PropertyCmd):
323
"""Abstract class for PropertyCmd recieving a value as argument"""
306
class ValueArgumentMixIn(object):
307
"""Mixin class for commands taking a value as argument"""
324
308
def __init__(self, value):
325
309
self.value_to_set = value
327
class MillisecondsPropertyValueArgumentCmd(PropertyValueCmd):
328
"""Abstract class for PropertyValueCmd taking a value argument as
329
a datetime.timedelta() but should store it as milliseconds."""
311
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
312
"""Mixin class for commands taking a value argument as
331
315
def value_to_set(self):
333
317
@value_to_set.setter
334
318
def value_to_set(self, value):
335
"""When setting, convert value from a datetime.timedelta"""
319
"""When setting, convert value to a datetime.timedelta"""
336
320
self._vts = int(round(value.total_seconds() * 1000))
338
322
# Actual (non-abstract) command classes
441
424
class IsEnabledCmd(Command):
442
def run(self, clients, bus=None, mandos=None):
443
client, properties = next(iter(clients.items()))
425
def run_on_one_client(self, client, properties):
444
426
if self.is_enabled(client, properties):
447
429
def is_enabled(self, client, properties):
448
return properties["Enabled"]
430
return bool(properties["Enabled"])
450
432
class RemoveCmd(Command):
451
433
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__))
455
434
self.mandos.RemoveClient(client.__dbus_object_path__)
457
436
class ApproveCmd(Command):
458
437
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)
461
438
client.Approve(dbus.Boolean(True),
462
dbus_interface=client_dbus_interface)
439
dbus_interface=client_interface)
464
441
class DenyCmd(Command):
465
442
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)
468
443
client.Approve(dbus.Boolean(False),
469
dbus_interface=client_dbus_interface)
444
dbus_interface=client_interface)
471
446
class EnableCmd(PropertyCmd):
473
448
value_to_set = dbus.Boolean(True)
475
450
class DisableCmd(PropertyCmd):
477
452
value_to_set = dbus.Boolean(False)
479
454
class BumpTimeoutCmd(PropertyCmd):
480
propname = "LastCheckedOK"
455
property = "LastCheckedOK"
481
456
value_to_set = ""
483
458
class StartCheckerCmd(PropertyCmd):
484
propname = "CheckerRunning"
459
property = "CheckerRunning"
485
460
value_to_set = dbus.Boolean(True)
487
462
class StopCheckerCmd(PropertyCmd):
488
propname = "CheckerRunning"
463
property = "CheckerRunning"
489
464
value_to_set = dbus.Boolean(False)
491
466
class ApproveByDefaultCmd(PropertyCmd):
492
propname = "ApprovedByDefault"
467
property = "ApprovedByDefault"
493
468
value_to_set = dbus.Boolean(True)
495
470
class DenyByDefaultCmd(PropertyCmd):
496
propname = "ApprovedByDefault"
471
property = "ApprovedByDefault"
497
472
value_to_set = dbus.Boolean(False)
499
class SetCheckerCmd(PropertyValueCmd):
502
class SetHostCmd(PropertyValueCmd):
505
class SetSecretCmd(PropertyValueCmd):
474
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
477
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
480
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
508
482
def value_to_set(self):
512
486
"""When setting, read data from supplied file object"""
513
487
self._vts = value.read()
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"
491
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
494
class SetExtendedTimeoutCmd(PropertyCmd,
495
MillisecondsValueArgumentMixIn):
496
property = "ExtendedTimeout"
498
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
499
property = "Interval"
501
class SetApprovalDelayCmd(PropertyCmd,
502
MillisecondsValueArgumentMixIn):
503
property = "ApprovalDelay"
505
class SetApprovalDurationCmd(PropertyCmd,
506
MillisecondsValueArgumentMixIn):
507
property = "ApprovalDuration"
531
509
def add_command_line_options(parser):
532
510
parser.add_argument("--version", action="version",
723
694
clientnames = options.client
726
log.setLevel(logging.DEBUG)
729
697
bus = dbus.SystemBus()
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,
698
mandos_dbus_objc = bus.get_object(busname, server_path)
734
699
except dbus.exceptions.DBusException:
735
700
log.critical("Could not connect to Mandos server")
738
703
mandos_serv = dbus.Interface(mandos_dbus_objc,
739
dbus_interface=server_dbus_interface)
704
dbus_interface=server_interface)
740
705
mandos_serv_object_manager = dbus.Interface(
741
706
mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
748
713
dbus_filter = NullFilter()
750
715
dbus_logger.addFilter(dbus_filter)
751
log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", dbus_busname,
752
server_dbus_path, dbus.OBJECT_MANAGER_IFACE)
753
mandos_clients = {path: ifs_and_props[client_dbus_interface]
716
mandos_clients = {path: ifs_and_props[client_interface]
754
717
for path, ifs_and_props in
755
718
mandos_serv_object_manager
756
719
.GetManagedObjects().items()
757
if client_dbus_interface in ifs_and_props}
720
if client_interface in ifs_and_props}
758
721
except dbus.exceptions.DBusException as e:
759
722
log.critical("Failed to access Mandos server through D-Bus:"
769
732
if not clientnames:
770
clients = {objpath: properties
771
for objpath, properties in mandos_clients.items()}
733
clients = {bus.get_object(busname, path): properties
734
for path, properties in mandos_clients.items()}
773
736
for name in clientnames:
774
for objpath, properties in mandos_clients.items():
775
if properties["Name"] == name:
776
clients[objpath] = properties
737
for path, client in mandos_clients.items():
738
if client["Name"] == name:
739
client_objc = bus.get_object(busname, path)
740
clients[client_objc] = client
779
743
log.critical("Client not found on server: %r", name)
838
802
class MockClient(object):
839
803
def __init__(self, name, **attributes):
840
self.__dbus_object_path__ = "/clients/{}".format(name)
804
self.__dbus_object_path__ = "objpath_{}".format(name)
841
805
self.attributes = attributes
842
806
self.attributes["Name"] = name
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]
808
def Set(self, interface, property, value, dbus_interface):
809
testcase.assertEqual(interface, client_interface)
810
testcase.assertEqual(dbus_interface,
811
dbus.PROPERTIES_IFACE)
812
self.attributes[property] = value
813
def Get(self, interface, property, dbus_interface):
814
testcase.assertEqual(interface, client_interface)
815
testcase.assertEqual(dbus_interface,
816
dbus.PROPERTIES_IFACE)
817
return self.attributes[property]
854
818
def Approve(self, approve, dbus_interface):
855
testcase.assertEqual(dbus_interface,
856
client_dbus_interface)
819
testcase.assertEqual(dbus_interface, client_interface)
857
820
self.calls.append(("Approve", (approve,
858
821
dbus_interface)))
859
822
self.client = MockClient(
906
869
LastCheckerStatus=-2)
907
870
self.clients = collections.OrderedDict(
909
("/clients/foo", self.client.attributes),
910
("/clients/barbar", self.other_client.attributes),
872
(self.client, self.client.attributes),
873
(self.other_client, self.other_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,
875
self.one_client = {self.client: self.client.attributes}
925
877
class TestPrintTableCmd(TestCmd):
926
878
def test_normal(self):
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 ",
879
output = PrintTableCmd().output(self.clients)
880
expected_output = """
881
Name Enabled Timeout Last Successful Check
882
foo Yes 00:05:00 2019-02-03T00:00:00
883
barbar Yes 00:05:00 2019-02-04T00:00:00
933
885
self.assertEqual(output, expected_output)
934
886
def test_verbose(self):
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 "
887
output = PrintTableCmd(verbose=True).output(self.clients)
888
expected_output = """
889
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
890
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
891
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
966
893
self.assertEqual(output, expected_output)
967
894
def test_one_client(self):
968
output = PrintTableCmd().output(self.one_client.values())
895
output = PrintTableCmd().output(self.one_client)
969
896
expected_output = """
970
897
Name Enabled Timeout Last Successful Check
971
898
foo Yes 00:05:00 2019-02-03T00:00:00
1037
964
class TestIsEnabledCmd(TestCmd):
1038
965
def test_is_enabled(self):
1039
self.assertTrue(all(IsEnabledCmd().is_enabled(client,
1041
for client, properties
1042
in self.clients.items()))
966
self.assertTrue(all(IsEnabledCmd().is_enabled(client, properties)
967
for client, properties in self.clients.items()))
1043
968
def test_is_enabled_run_exits_successfully(self):
1044
969
with self.assertRaises(SystemExit) as e:
1045
IsEnabledCmd().run(self.one_client)
970
IsEnabledCmd().run(None, self.one_client)
1046
971
if e.exception.code is not None:
1047
972
self.assertEqual(e.exception.code, 0)
1065
990
self.calls.append(("RemoveClient", (dbus_path,)))
1066
991
mandos = MockMandos()
1067
992
super(TestRemoveCmd, self).setUp()
1068
RemoveCmd().run(self.clients, self.bus, mandos)
993
RemoveCmd().run(mandos, self.clients)
1069
994
self.assertEqual(len(mandos.calls), 2)
1070
for clientpath in self.clients:
1071
self.assertIn(("RemoveClient", (clientpath,)),
995
for client in self.clients:
996
self.assertIn(("RemoveClient",
997
(client.__dbus_object_path__,)),
1074
1000
class TestApproveCmd(TestCmd):
1075
1001
def test_approve(self):
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)),
1002
ApproveCmd().run(None, self.clients)
1003
for client in self.clients:
1004
self.assertIn(("Approve", (True, client_interface)),
1082
1007
class TestDenyCmd(TestCmd):
1083
1008
def test_deny(self):
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)),
1009
DenyCmd().run(None, self.clients)
1010
for client in self.clients:
1011
self.assertIn(("Approve", (False, client_interface)),
1090
1014
class TestEnableCmd(TestCmd):
1091
1015
def test_enable(self):
1092
for clientpath in self.clients:
1093
client = self.bus.get_object(dbus_busname, clientpath)
1016
for client in self.clients:
1094
1017
client.attributes["Enabled"] = False
1096
EnableCmd().run(self.clients, self.bus)
1019
EnableCmd().run(None, self.clients)
1098
for clientpath in self.clients:
1099
client = self.bus.get_object(dbus_busname, clientpath)
1021
for client in self.clients:
1100
1022
self.assertTrue(client.attributes["Enabled"])
1102
1024
class TestDisableCmd(TestCmd):
1103
1025
def test_disable(self):
1104
DisableCmd().run(self.clients, self.bus)
1105
for clientpath in self.clients:
1106
client = self.bus.get_object(dbus_busname, clientpath)
1026
DisableCmd().run(None, self.clients)
1028
for client in self.clients:
1107
1029
self.assertFalse(client.attributes["Enabled"])
1109
1031
class Unique(object):
1119
1041
self.values_to_set)
1120
1042
for value_to_set, value_to_get in zip(self.values_to_set,
1121
1043
values_to_get):
1122
for clientpath in self.clients:
1123
client = self.bus.get_object(dbus_busname, clientpath)
1124
old_value = client.attributes[self.propname]
1044
for client in self.clients:
1045
old_value = client.attributes[self.property]
1125
1046
self.assertNotIsInstance(old_value, Unique)
1126
client.attributes[self.propname] = Unique()
1047
client.attributes[self.property] = Unique()
1127
1048
self.run_command(value_to_set, self.clients)
1128
for clientpath in self.clients:
1129
client = self.bus.get_object(dbus_busname, clientpath)
1130
value = client.attributes[self.propname]
1049
for client in self.clients:
1050
value = client.attributes[self.property]
1131
1051
self.assertNotIsInstance(value, Unique)
1132
1052
self.assertEqual(value, value_to_get)
1133
1053
def run_command(self, value, clients):
1134
self.command().run(clients, self.bus)
1054
self.command().run(None, clients)
1136
1056
class TestBumpTimeoutCmd(TestPropertyCmd):
1137
1057
command = BumpTimeoutCmd
1138
propname = "LastCheckedOK"
1058
property = "LastCheckedOK"
1139
1059
values_to_set = [""]
1141
1061
class TestStartCheckerCmd(TestPropertyCmd):
1142
1062
command = StartCheckerCmd
1143
propname = "CheckerRunning"
1063
property = "CheckerRunning"
1144
1064
values_to_set = [dbus.Boolean(True)]
1146
1066
class TestStopCheckerCmd(TestPropertyCmd):
1147
1067
command = StopCheckerCmd
1148
propname = "CheckerRunning"
1068
property = "CheckerRunning"
1149
1069
values_to_set = [dbus.Boolean(False)]
1151
1071
class TestApproveByDefaultCmd(TestPropertyCmd):
1152
1072
command = ApproveByDefaultCmd
1153
propname = "ApprovedByDefault"
1073
property = "ApprovedByDefault"
1154
1074
values_to_set = [dbus.Boolean(True)]
1156
1076
class TestDenyByDefaultCmd(TestPropertyCmd):
1157
1077
command = DenyByDefaultCmd
1158
propname = "ApprovedByDefault"
1078
property = "ApprovedByDefault"
1159
1079
values_to_set = [dbus.Boolean(False)]
1161
class TestPropertyValueCmd(TestPropertyCmd):
1162
"""Abstract class for tests of PropertyValueCmd classes"""
1081
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1082
"""Abstract class for tests of PropertyCmd classes using the
1083
ValueArgumentMixIn"""
1163
1084
def runTest(self):
1164
if type(self) is TestPropertyValueCmd:
1085
if type(self) is TestValueArgumentPropertyCmd:
1166
return super(TestPropertyValueCmd, self).runTest()
1087
return super(TestValueArgumentPropertyCmd, self).runTest()
1167
1088
def run_command(self, value, clients):
1168
self.command(value).run(clients, self.bus)
1089
self.command(value).run(None, clients)
1170
class TestSetCheckerCmd(TestPropertyValueCmd):
1091
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1171
1092
command = SetCheckerCmd
1172
propname = "Checker"
1093
property = "Checker"
1173
1094
values_to_set = ["", ":", "fping -q -- %s"]
1175
class TestSetHostCmd(TestPropertyValueCmd):
1096
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1176
1097
command = SetHostCmd
1178
1099
values_to_set = ["192.0.2.3", "foo.example.org"]
1180
class TestSetSecretCmd(TestPropertyValueCmd):
1101
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1181
1102
command = SetSecretCmd
1183
1104
values_to_set = [io.BytesIO(b""),
1184
1105
io.BytesIO(b"secret\0xyzzy\nbar")]
1185
1106
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1187
class TestSetTimeoutCmd(TestPropertyValueCmd):
1108
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1188
1109
command = SetTimeoutCmd
1189
propname = "Timeout"
1110
property = "Timeout"
1190
1111
values_to_set = [datetime.timedelta(),
1191
1112
datetime.timedelta(minutes=5),
1192
1113
datetime.timedelta(seconds=1),
1194
1115
datetime.timedelta(weeks=52)]
1195
1116
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1197
class TestSetExtendedTimeoutCmd(TestPropertyValueCmd):
1118
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
1198
1119
command = SetExtendedTimeoutCmd
1199
propname = "ExtendedTimeout"
1120
property = "ExtendedTimeout"
1200
1121
values_to_set = [datetime.timedelta(),
1201
1122
datetime.timedelta(minutes=5),
1202
1123
datetime.timedelta(seconds=1),
1214
1135
datetime.timedelta(weeks=52)]
1215
1136
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1217
class TestSetApprovalDelayCmd(TestPropertyValueCmd):
1138
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
1218
1139
command = SetApprovalDelayCmd
1219
propname = "ApprovalDelay"
1140
property = "ApprovalDelay"
1220
1141
values_to_set = [datetime.timedelta(),
1221
1142
datetime.timedelta(minutes=5),
1222
1143
datetime.timedelta(seconds=1),
1224
1145
datetime.timedelta(weeks=52)]
1225
1146
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1227
class TestSetApprovalDurationCmd(TestPropertyValueCmd):
1148
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
1228
1149
command = SetApprovalDurationCmd
1229
propname = "ApprovalDuration"
1150
property = "ApprovalDuration"
1230
1151
values_to_set = [datetime.timedelta(),
1231
1152
datetime.timedelta(minutes=5),
1232
1153
datetime.timedelta(seconds=1),
1413
1333
def test_is_enabled_short(self):
1414
1334
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
1337
class Test_check_option_syntax(unittest.TestCase):
1436
1338
# This mostly corresponds to the definition from has_actions() in