From 09d8c4b9123cec83ced4a5598d8033e3aa8d4b6c Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sat, 29 Oct 2022 15:41:07 -0700 Subject: [PATCH] MultiServer: fix case sensitivity in server commands (#1156) * fix case sensitivity in server commands * improve ambiguous match breakout * worried about accidentally swapping team and slot * Remove now unused import --- MultiServer.py | 63 ++++++++++++++++++++------------ test/programs/TestMultiServer.py | 40 ++++++++++++++++++++ 2 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 test/programs/TestMultiServer.py diff --git a/MultiServer.py b/MultiServer.py index 1a672afa..8ccd50e7 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -1802,14 +1802,32 @@ class ServerCommandProcessor(CommonCommandProcessor): self.output(response) return False + def resolve_player(self, input_name: str) -> typing.Optional[typing.Tuple[int, int, str]]: + """ returns (team, slot, player name) """ + # first match case + for (team, slot), name in self.ctx.player_names.items(): + if name == input_name: + return team, slot, name + + # if no case-sensitive match, then match without case only if there's only 1 match + input_lower = input_name.lower() + match: typing.Optional[typing.Tuple[int, int, str]] = None + for (team, slot), name in self.ctx.player_names.items(): + lowered = name.lower() + if lowered == input_lower: + if match: + return None # ambiguous input_name + match = (team, slot, name) + return match + @mark_raw def _cmd_collect(self, player_name: str) -> bool: """Send out the remaining items to player.""" - seeked_player = player_name.lower() - for (team, slot), name in self.ctx.player_names.items(): - if name.lower() == seeked_player: - collect_player(self.ctx, team, slot) - return True + player = self.resolve_player(player_name) + if player: + team, slot, _ = player + collect_player(self.ctx, team, slot) + return True self.output(f"Could not find player {player_name} to collect") return False @@ -1822,11 +1840,11 @@ class ServerCommandProcessor(CommonCommandProcessor): @mark_raw def _cmd_forfeit(self, player_name: str) -> bool: """Send out the remaining items from a player to their intended recipients.""" - seeked_player = player_name.lower() - for (team, slot), name in self.ctx.player_names.items(): - if name.lower() == seeked_player: - forfeit_player(self.ctx, team, slot) - return True + player = self.resolve_player(player_name) + if player: + team, slot, _ = player + forfeit_player(self.ctx, team, slot) + return True self.output(f"Could not find player {player_name} to release") return False @@ -1834,12 +1852,12 @@ class ServerCommandProcessor(CommonCommandProcessor): @mark_raw def _cmd_allow_forfeit(self, player_name: str) -> bool: """Allow the specified player to use the !release command.""" - seeked_player = player_name.lower() - for (team, slot), name in self.ctx.player_names.items(): - if name.lower() == seeked_player: - self.ctx.allow_forfeits[(team, slot)] = True - self.output(f"Player {player_name} is now allowed to use the !release command at any time.") - return True + player = self.resolve_player(player_name) + if player: + team, slot, name = player + self.ctx.allow_forfeits[(team, slot)] = True + self.output(f"Player {name} is now allowed to use the !release command at any time.") + return True self.output(f"Could not find player {player_name} to allow the !release command for.") return False @@ -1847,13 +1865,12 @@ class ServerCommandProcessor(CommonCommandProcessor): @mark_raw def _cmd_forbid_forfeit(self, player_name: str) -> bool: """"Disallow the specified player from using the !release command.""" - seeked_player = player_name.lower() - for (team, slot), name in self.ctx.player_names.items(): - if name.lower() == seeked_player: - self.ctx.allow_forfeits[(team, slot)] = False - self.output( - f"Player {player_name} has to follow the server restrictions on use of the !release command.") - return True + player = self.resolve_player(player_name) + if player: + team, slot, name = player + self.ctx.allow_forfeits[(team, slot)] = False + self.output(f"Player {name} has to follow the server restrictions on use of the !release command.") + return True self.output(f"Could not find player {player_name} to forbid the !release command for.") return False diff --git a/test/programs/TestMultiServer.py b/test/programs/TestMultiServer.py new file mode 100644 index 00000000..6ea66e33 --- /dev/null +++ b/test/programs/TestMultiServer.py @@ -0,0 +1,40 @@ +import unittest +from MultiServer import Context, ServerCommandProcessor + + +class TestResolvePlayerName(unittest.TestCase): + def test_resolve(self) -> None: + p = ServerCommandProcessor(Context("", 0, "", "", 0, 0, False)) + p.ctx.player_names = { + (1, 1): "AAA", + (1, 2): "aBc", + (1, 3): "abC", + } + assert not p.resolve_player("abc"), "ambiguous name entry shouldn't resolve to player" + assert not p.resolve_player("Abc"), "ambiguous name entry shouldn't resolve to player" + assert p.resolve_player("aBc") == (1, 2, "aBc"), "matching case resolve" + assert p.resolve_player("abC") == (1, 3, "abC"), "matching case resolve" + assert not p.resolve_player("aB"), "partial name shouldn't resolve to player" + assert not p.resolve_player("abCD"), "incorrect name shouldn't resolve to player" + + p.ctx.player_names = { + (1, 1): "aaa", + (1, 2): "abc", + (1, 3): "abC", + } + assert p.resolve_player("abc") == (1, 2, "abc"), "matching case resolve" + assert not p.resolve_player("Abc"), "ambiguous name entry shouldn't resolve to player" + assert not p.resolve_player("aBc"), "ambiguous name entry shouldn't resolve to player" + assert p.resolve_player("abC") == (1, 3, "abC"), "matching case resolve" + + p.ctx.player_names = { + (1, 1): "AbcdE", + (1, 2): "abc", + (1, 3): "abCD", + } + assert p.resolve_player("abc") == (1, 2, "abc"), "matching case resolve" + assert p.resolve_player("abC") == (1, 2, "abc"), "case insensitive resolves when 1 match" + assert p.resolve_player("Abc") == (1, 2, "abc"), "case insensitive resolves when 1 match" + assert p.resolve_player("ABC") == (1, 2, "abc"), "case insensitive resolves when 1 match" + assert p.resolve_player("abcd") == (1, 3, "abCD"), "case insensitive resolves when 1 match" + assert not p.resolve_player("aB"), "partial name shouldn't resolve to player"