diff options
| -rw-r--r-- | geldschieberbot.py | 80 | ||||
| -rwxr-xr-x | test.py | 96 |
2 files changed, 175 insertions, 1 deletions
diff --git a/geldschieberbot.py b/geldschieberbot.py index 0a912c3..79cb643 100644 --- a/geldschieberbot.py +++ b/geldschieberbot.py @@ -69,6 +69,7 @@ class Geldschieberbot: "cars": {}, "scheduled_cmds": {}, "changes": {}, + "aliases": {}, } self.balance = self.state["balance"] @@ -77,6 +78,7 @@ class Geldschieberbot: self.available_cars = self.state["cars"] self.scheduled_cmds = self.state["scheduled_cmds"] self.changes = self.state["changes"] + self.aliases = self.state.setdefault("aliases", {}) def save_state(self, state_path=STATE_FILE): """Load state from disk""" @@ -168,6 +170,33 @@ class Geldschieberbot: for m in self.balance: del self.balance[m][name] + def expand_aliases(self, + users: list[str], + exclude_users=None) -> list[str]: + """Expand any alias + + Any user name in exclude_users will not be expanded. This is usefull + when using aliases with commands implicitly adding a user like !split. + """ + ret = [] + for user in users: + if not user in self.aliases and user != 'all': + ret.append(user) + continue + + expanded_users = [] + if user in self.aliases: + expanded_users += self.aliases[user] + else: + expanded_users += list(self.name2num.keys()) + + ret.extend([ + u for u in expanded_users + if not exclude_users or u not in exclude_users + ]) + + return ret + @classmethod def create_help(cls) -> str: return """ @@ -179,6 +208,10 @@ class Geldschieberbot: sum [name] - print summary of specific users full-sum - print summary of all users + alias - list registered aliases + alias name person [persons] - create an alias for one or multiple persons + alias remove name - remove an alias + split amount person [persons] - split amount between the sender and persons teil amount person [persons] - split amount between the sender and persons @@ -247,7 +280,7 @@ class Geldschieberbot: return {'msg': f'Summary:\n{self.create_summary(name)}'} msg = "Summary:\n" - for name in args[1:]: + for name in self.expand_aliases(args[1:]): if name in self.name2num or name in self.available_cars: msg += self.create_summary(name) + "\n" else: @@ -293,6 +326,8 @@ class Geldschieberbot: return {'err': 'amount must be a positive number'} recipient = self.num2name[sender] + # exclude the implicit recipient from alias expension + persons = self.expand_aliases(persons, exclude_users=[recipient]) # persons + sender npersons = len(persons) + 1 amount_per_person = int(amount / npersons) @@ -821,6 +856,48 @@ class Geldschieberbot: return {'msg': msg} + def alias(self, sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument + """List or create aliases""" + + if len(args) == 1: + msg = '' + for alias, users in self.aliases.items(): + msg += f'\n\t{alias}: {" ".join(users)}' + return {'msg': f'Aliases:\n\tall: {" ".join(self.name2num)}{msg}'} + + if len(args) == 2: + return {'err': 'Not a valid alias command'} + + if args[1] == 'remove': + alias = args[2] + if alias not in self.aliases: + return {'err': f'Alias "{alias}" not registered'} + + del self.aliases[alias] + return {'msg': f'Alias "{alias}" removed'} + + # create a new alias + alias = args[1] + if alias in self.aliases or alias == 'all': + return {'err': f'Alias "{alias}" is already registered'} + + if alias in self.name2num: + return {'err': f'A user "{alias}" is already registered'} + + try: + to_cent(alias) + return {'err': 'Pure numerical aliases are not allowed'} + except (ValueError, TypeError): + pass + + users = args[2:] + for user in users: + if user not in self.name2num: + return {'err': f'User {user} is not registered'} + + self.aliases[alias] = users + return {'msg': f'New alias "{alias}" registered'} + def __init__(self, dry_run=False): self.dry_run = dry_run # Run without changing the stored state self.load_state() @@ -831,6 +908,7 @@ class Geldschieberbot: self.cmds = { 'reg': self.register, 'register': self.register, + 'alias': self.alias, 'sum': self.summary, 'summary': self.summary, 'full-sum': self.full_summary, @@ -419,6 +419,102 @@ alice: self.assertEqual(res.stdout, msg) +class TestAliasAdd(unittest.TestCase): + + def setUp(self): + reset_state("test/state_3users.json") + + def test_add_success(self): + res = run_bot(self, num[alice], f"!alias foo {alice} {bob}") + self.assertEqual(res.stdout, 'New alias "foo" registered') + + res = run_bot(self, num[alice], f"!alias bar {charlie}") + self.assertEqual(res.stdout, 'New alias "bar" registered') + + def test_add_invalid_aliases(self): + res = run_bot(self, num[alice], f"!alias foo {alice} {bob}") + self.assertEqual(res.stdout, 'New alias "foo" registered') + res = run_bot(self, num[alice], f"!alias foo {alice} {bob}") + self.assertEqual(res.stdout, + 'ERROR: Alias "foo" is already registered') + + res = run_bot(self, num[alice], f"!alias {alice} {alice} {bob}") + self.assertEqual(res.stdout, + f'ERROR: A user "{alice}" is already registered') + + res = run_bot(self, num[alice], f"!alias 13.23 {charlie}") + self.assertEqual(res.stdout, + 'ERROR: Pure numerical aliases are not allowed') + + res = run_bot(self, num[alice], "!alias bar ingo") + self.assertEqual(res.stdout, 'ERROR: User ingo is not registered') + + res = run_bot(self, num[alice], f"!alias all {alice}") + self.assertEqual(res.stdout, + 'ERROR: Alias "all" is already registered') + + +class TestAlias(unittest.TestCase): + + def setUp(self): + reset_state("test/state_3users.json") + + def test_list_aliases_empty_state(self): + res = run_bot(self, num[alice], "!alias") + self.assertEqual(res.stdout, f"""Aliases: +\tall: {alice} {bob} {charlie}""") + + def test_list_aliases(self): + run_bot(self, num[alice], f"!alias alob {alice} {bob}") + res = run_bot(self, num[alice], "!alias") + self.assertEqual( + res.stdout, f"""Aliases: +\tall: {alice} {bob} {charlie} +\talob: {alice} {bob}""") + + def test_add_invalid_aliases(self): + run_bot(self, num[alice], f"!alias foo {alice} {bob}") + res = run_bot(self, num[alice], f"!alias foo {alice} {bob}") + self.assertEqual(res.stdout, + 'ERROR: Alias "foo" is already registered') + + res = run_bot(self, num[alice], f"!alias {alice} {alice} {bob}") + self.assertEqual(res.stdout, + f'ERROR: A user "{alice}" is already registered') + + res = run_bot(self, num[alice], f"!alias 13.23 {charlie}") + self.assertEqual(res.stdout, + 'ERROR: Pure numerical aliases are not allowed') + + res = run_bot(self, num[alice], "!alias bar ingo") + self.assertEqual(res.stdout, 'ERROR: User ingo is not registered') + + res = run_bot(self, num[alice], f"!alias all {alice}") + self.assertEqual(res.stdout, + 'ERROR: Alias "all" is already registered') + + def test_split_all(self): + res = run_bot(self, num[alice], "!split 9 all") + self.assertEqual(res.stdout, \ +"""Split 9.00 between 3 -> 3.00 each +New Balance: +alice: +\t<- bob 3.00 +\t<- charlie 3.00 +\tBalance: 6.00""") + + def test_split_alias(self): + run_bot(self, num[alice], f"!alias alob {alice} {bob}") + res = run_bot(self, num[charlie], "!split 9 alob") + self.assertEqual(res.stdout, \ +"""Split 9.00 between 3 -> 3.00 each +New Balance: +charlie: +\t<- alice 3.00 +\t<- bob 3.00 +\tBalance: 6.00""") + + class TestCarsAdd(unittest.TestCase): def setUp(self): |
