Continuing unittest work

This commit is contained in:
Griatch 2021-04-19 09:19:15 +02:00
parent be8b4b82de
commit aa5a07f6d0
4 changed files with 401 additions and 42 deletions

View file

@ -59,6 +59,7 @@ class AccountCmdSet(CmdSet):
self.add(admin.CmdNewPassword())
# Comm commands
self.add(comms.CmdChannel())
self.add(comms.CmdAddCom())
self.add(comms.CmdDelCom())
self.add(comms.CmdAllCom())

View file

@ -30,7 +30,7 @@ __all__ = (
"CmdAddCom",
"CmdDelCom",
"CmdAllCom",
"CmdChannels",
#"CmdChannels",
"CmdCdestroy",
"CmdCBoot",
"CmdCemit",
@ -55,25 +55,27 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
Talk on and manage in-game channels.
Usage:
channel channelname [= <msg>]
channel
channel/list
channel/all
channel/history channelname [= index]
channel/sub channelname [= alias]
channel/unsub channelname[,channelname, ...]
channel/alias channelname = alias
channel/unalias channelname = alias
channel/mute channelname[,channelname,...]
channel/unmute channelname[,channelname,...]
channel/create channelname;alias;alias:typeclass [= description]
channel/destroy channelname [= reason]
channel/lock channelname = lockstring
channel/desc channelname = description
channel/boot[/quiet] channelname = subscribername [: reason]
channel/ban channelname
channel/ban[/quiet] channelname = subscribername [: reason]
channel/who channelname
channel channelname [= <msg>]
channel
channel/list
channel/all
channel/history channelname [= index]
channel/sub channelname [= alias]
channel/unsub channelname[,channelname, ...]
channel/alias channelname = alias
channel/unalias channelname = alias
channel/mute channelname[,channelname,...]
channel/unmute channelname[,channelname,...]
channel/create channelname;alias;alias:typeclass [= description]
channel/destroy channelname [= reason]
channel/desc channelname = description
channel/lock channelname = lockstring
channel/unlock channelname = lockstring
channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]
channel/ban channelname (list bans)
channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]
channel/unban[/quiet] channelname[, channelname, ...] = subscribername
channel/who channelname
This handles all operations on channels.
@ -82,8 +84,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
aliases = ["chan", "channels"]
locks = "cmd: not pperm(channel_banned)"
switch_options = (
"history", "sub", "unsub", "mute", "alias", "unalias", "create",
"destroy", "desc", "boot", "who")
"list", "all", "history", "sub", "unsub", "mute", "unmute", "alias", "unalias",
"create", "destroy", "desc", "lock", "unlock", "boot", "ban", "unban", "who",)
def search_channel(self, channelname, exact=False):
"""
@ -152,7 +154,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
caller = self.caller
log_file = channel.attributes.get(
"log_file", default=channel.log_file.format(channelkey=channel.key))
"log_file", default=channel.log_to_file.format(channel_key=channel.key))
def send_msg(lines):
return caller.msg(
@ -336,7 +338,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
def set_lock(self, channel, lockstring):
"""
Set a lockstring on a channel.
Set a lockstring on a channel. Permissions must have been
checked before this call.
Args:
channel (Channel): The channel to operate on.
@ -353,6 +356,26 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
return False, err
return True, ""
def unset_lock(self, channel, lockstring):
"""
Remove locks in a lockstring on a channel. Permissions must have been
checked before this call.
Args:
channel (Channel): The channel to operate on.
lockstring (str): A lockstring on the form 'type:lockfunc();...'
Returns:
bool, str: True, None if setting lock was successful. If False,
the second part is an error string.
"""
try:
channel.locks.remove(lockstring)
except LockException as err:
return False, err
return True, ""
def set_desc(self, channel, description):
"""
Set a channel description. This is shown in listings etc.
@ -443,6 +466,19 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
return True, ""
return False, f"{target} was not previously banned from this channel."
def channel_list_bans(self, channel):
"""
Show a channel's bans.
Args:
channel (Channel): The channel to operate on.
Returns:
list: A list of strings, each the name of a banned user.
"""
return [banned.key for banned in channel.banlist]
def channel_list_who(self, channel):
"""
Show a list of online people is subscribing to a channel. This will check
@ -492,7 +528,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
"""
caller = self.caller
subscribed_channels = channelcls.objects.get_subscriptions(caller)
subscribed_channels = list(channelcls.objects.get_subscriptions(caller))
unsubscribed_available_channels = [
chan
for chan in channelcls.objects.get_all_channels()
@ -531,6 +567,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
",".join(nick.db_key for nick in make_iter(nicks)
if nick and nick.value[3].lower() == clower),
chan.db.desc))
return comtable
def display_all_channels(self, subscribed, available):
"""
@ -583,10 +620,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
"""
Main functionality of command.
"""
# from evennia import set_trace;set_trace()
caller = self.caller
switches = self.switches
channel_names = self.lhslist
channel_names = [name for name in self.lhslist if name]
if not channel_names:
if 'all' in switches:
@ -604,9 +642,13 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
table = self.display_subbed_channels(subscribed)
self.msg("\n|wChannel subscriptions|n "
f"(use |w/all|n to see all available)\n{table}")
f"(use |w/all|n to see all available):\n{table}")
return
if not self.switches and not self.args:
caller.msg("Usage[/switches]: channel [= message]")
return
if 'create' in switches:
# create a new channel
config = self.lhs
@ -636,19 +678,28 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
self.msg("Multiple possible channel matches/alias for "
"'{channel_name}':\n" + ", ".join(chan.key for chan in channel))
return
channels.append(channel)
channels.extend(channel)
# we have at least one channel at this point
channel = channels[0]
if not switches:
# send a message to channel(s)
message = self.rhs
if not message:
self.msg("To send: channel <channel-name-or-alias> = <message>")
return
for chan in channels:
self.msg_channel(chan, message)
if self.rhs:
# send message to channel
self.msg_channel(channel, self.rhs.strip())
else:
# inspect a given channel
subscribed, available = self.list_channels()
if channel in subscribed:
table = self.display_subbed_channels[channel]
self.msg(
"\n|wSubscribed to Channel|n "
f"(use |w/all|n to see all available)\n{table}")
elif channel in available:
table = self.display_all_channels([], [channel])
self.msg(
"\n|wNot subscribed Channel|n (use /list to "
f"show all subscriptions)\n{table}")
return
if 'history' in switches or 'hist' in switches:
@ -734,8 +785,151 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
self.msg("You can only delete channels you control.")
return
def _perform_delete(caller, prompt, result):
def _perform_delete(caller, *args, **kwargs):
self.destroy_channel(channel, message=reason)
caller.msg(f"Channel {channel.key} was successfully deleted.")
ask_yes_no(
caller,
f"Are you sure you want to delete channel '{channel.key}'"
"(make sure name is correct!)? This will disconnect and "
"remove all users' aliases. {yesno}?",
_perform_delete,
"Aborted."
)
if 'lock' in switches:
# add a lockstring to channel
lockstring = self.rhs.strip()
if not lockstring:
self.msg("Usage: channel/lock channelname = lockstring")
return
if not channel.access(caller, "control"):
self.msg("You need 'control'-access to change locks on this channel.")
return
success, err = self.set_lock(channel, self.rhs)
if success:
caller.msg("Added/updated lock on channel.")
else:
caller.msg(f"Could not add/update lock: {err}")
return
if 'unlock' in switches:
# remove/update lockstring from channel
lockstring = self.rhs.strip()
if not lockstring:
self.msg("Usage: channel/unlock channelname = lockstring")
return
if not channel.access(caller, "control"):
self.msg("You need 'control'-access to change locks on this channel.")
return
success, err = self.set_lock(channel, self.rhs)
if success:
caller.msg("Removed lock on channel.")
else:
caller.msg(f"Could not remove lock: {err}")
return
if 'boot' in switches:
# boot a user from channel(s)
if not self.rhs:
caller.msg("Usage: channel/boot channel[,channel,...] = username [:reason]")
return
target_str, *reason = self.rhs.rsplit(":", 1)
reason = reason[0].strip() if reason else ""
for chan in channels:
if not chan.access(caller, "admin"):
self.msg("You need 'control'-access to boot a user from {chan.key}.")
return
# the target must be a member of all given channels
target = self.search(target_str, candidates=chan.subscriptions.all())
if not target:
caller.msg(f"Cannot boot '{target_str}' - not in channel {chan.key}.")
return
def _boot_user(caller, *args, **kwargs):
for chan in channels:
success, err = self.boot_user(chan, target, quiet=False, reason=reason)
if success:
caller.msg(f"Booted {target.key} from channel {chan.key}.")
else:
caller.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}")
channames = ", ".join(chan.key for chan in channels)
ask_yes_no(
caller,
f"Are you sure you want to boot user {target.key} from "
f"channel(s) {channames} (make sure name/channels are correct) "
"{yesno}?",
_boot_user,
"Aborted.",
default="Y"
)
return
if 'ban' in switches:
# ban a user from channel(s)
if not self.rhs:
# view bans for channels
if not channel.access(caller, "control"):
self.msg("You need 'control'-access to view bans on channel {channel.key}")
return
bans = ["Channel bans "
"(to ban, use channel/ban channel[,channel,...] = username [:reason]"]
bans.expand(self.channel_list_bans(channel))
self.msg("\n".join(bans))
return
target_str, *reason = self.rhs.rsplit(":", 1)
reason = reason[0].strip() if reason else ""
for chan in channels:
# the target must be a member of all given channels
target = self.search(target_str, candidates=chan.subscriptions.all())
if not target:
caller.msg(f"Cannot ban '{target_str}' - not in channel {chan.key}.")
return
def _ban_user(caller, *args, **kwargs):
for chan in channels:
success, err = self.ban_user(chan, target, quiet=False, reason=reason)
if success:
caller.msg(f"Banned {target.key} from channel {chan.key}.")
else:
caller.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}")
channames = ", ".join(chan.key for chan in channels)
ask_yes_no(
caller,
f"Are you sure you want to ban user {target.key} from "
f"channel(s) {channames} (make sure name/channels are correct) "
"{yesno}?",
_ban_user,
"Aborted.",
)
return
if "who" in switches:
# view who's a member of a channel
who_list = [f"Subscribed to {channel.key}:"]
who_list.expand(self.channel_list_who(channel))
caller.msg("\n".join(who_list))
return
class CmdAddCom(COMMAND_DEFAULT_CLASS):

View file

@ -202,7 +202,7 @@ class CommandTest(EvenniaTest):
else:
# a single expected string and thus a single receiver (defaults to caller)
receiver = receiver if receiver else caller
receiver_mapping[receiver] = str(msg).strip() if msg else None
receiver_mapping[receiver] = str(msg).strip() if msg is not None else None
unmocked_msg_methods = {}
for receiver in receiver_mapping:
@ -1647,6 +1647,170 @@ class TestComms(CommandTest):
)
from evennia.utils.create import create_channel # noqa
class TestCommsChannel(CommandTest):
"""
Test the central `channel` command.
"""
def setUp(self):
super(CommandTest, self).setUp()
self.channel = create_channel(
key="testchannel",
desc="A test channel")
self.channel.connect(self.char1)
def tearDown(self):
self.channel.delete()
# test channel command
def test_channel__noarg(self):
self.call(
comms.CmdChannel(),
"",
"Usage"
)
def test_channel__msg(self):
self.channel.msg = Mock()
self.call(
comms.CmdChannel(),
"testchannel = Test message",
""
)
self.channel.msg.assert_called_with("Test message", senders=self.char1)
def test_channel__list(self):
self.call(
comms.CmdChannel(),
"/list",
"Channel subscriptions"
)
def test_channel__all(self):
self.call(
comms.CmdChannel(),
"/all",
"Available channels"
)
def test_channel__history(self):
with patch("evennia.commands.default.comms.tail_log_file") as mock_tail:
self.call(
comms.CmdChannel(),
"/history testchannel",
""
)
mock_tail.assert_called()
def test_channel__sub(self):
self.channel.disconnect(self.char1)
self.call(
comms.CmdChannel(),
"/sub testchannel",
"You are now subscribed"
)
self.assertTrue(self.char1 in self.channel.subscriptions)
def test_channel__unsub(self):
self.call(
comms.CmdChannel(),
"/unsub testchannel",
"You un-subscribed"
)
self.assertFalse(self.char1 in self.channel.subscriptions)
def test_channel__alias(self):
self.call(
comms.CmdChannel(),
"/alias testchannel = foo",
""
)
self.assertEqual(self.chan1.aliases.get('foo'), "")
def test_channel__unalias(self):
self.chan1.aliases.add("foo", "", "channel")
self.call(
comms.CmdChannel(),
"/unalias testchannel = foo",
""
)
self.assertEqual(self.chan1.aliases.get('foo'), None)
def test_channel__mute(self):
self.call(
comms.CmdChannel(),
"/mute testchannel",
""
)
self.assertTrue(self.char1 in self.channel.mutelist)
def test_channel__unmute(self):
self.channel.mute(self.char1)
self.call(
comms.CmdChannel(),
"/unmute testchannel = Char1",
""
)
self.assertFalse(self.char1 in self.channel.mutelist)
def test_channel__create(self):
self.call(
comms.CmdChannel(),
"/create testchannel2",
""
)
def test_channel__destroy(self):
self.call(
comms.CmdChannel(),
"/create testchannel2",
""
)
def test_channel__desc(self):
self.call(
comms.CmdChannel(),
"/desc testchannel = Another description",
""
)
def test_channel__lock(self):
self.call(
comms.CmdChannel(),
"/lock testchannel = foo:bar()",
""
)
self.assertEqual(self.channel.locks.all(), [])
def test_channel__unlock(self):
self.channel.locks.add("foo:bar()")
self.call(
comms.CmdChannel(),
"/unlock testchannel = foo:bar()",
""
)
self.assertEqual(self.channel.locks.all(), [])
def test_channel__boot(self):
pass
def test_channel__ban(self):
pass
def test_channel__who(self):
self.call(
comms.CmdChannel(),
"/who testchannel",
""
)
class TestBatchProcess(CommandTest):
@patch("evennia.contrib.tutorial_examples.red_button.repeat")

View file

@ -1710,7 +1710,7 @@ def ask_yes_no(caller, prompt, yes_action, no_action, default=None,
Args:
prompt (str): The yes/no question to ask. This takes an optional formatting
marker `{suffix}` which will be filled with 'Y/N', [Y]/N or Y/[N]
marker `{yesno}` which will be filled with 'Y/N', [Y]/N or Y/[N]
depending on the setting of `default`. If `allow_abort`, then the
`A(bort)` will also be available.
yes_action (callable or str): If a callable, this will be called
@ -1736,9 +1736,9 @@ def ask_yes_no(caller, prompt, yes_action, no_action, default=None,
Example:
ask_yes_no(caller, "Are you happy {suffix}?",
ask_yes_no(caller, "Are you happy {yesno}?",
"you answered yes", "you answered no")
ask_yes_no(caller, "Are you sad {suffix}?",
ask_yes_no(caller, "Are you sad {yesno}?",
_callable_yes, _callable_no, allow_abort=True)
"""
@ -1760,7 +1760,7 @@ def ask_yes_no(caller, prompt, yes_action, no_action, default=None,
kwargs['no_txt'] = str(no_action)
no_action = _callable_no_txt
# prepare the prompt with suffix
# prepare the prompt with yesno suffix
suffix = "Y/N"
abort_txt = "/Abort" if allow_abort else ""
if default:
@ -1773,7 +1773,7 @@ def ask_yes_no(caller, prompt, yes_action, no_action, default=None,
allow_abort = True
abort_txt = "/[A]bort"
suffix += abort_txt
prompt = prompt.format(suffix=suffix)
prompt = prompt.format(yesno=suffix)
caller.ndb._yes_no_question = _Prompt()
caller.ndb._yes_no_question.session = session