diff options
| -rw-r--r-- | geldschieberbot.py | 153 | ||||
| -rwxr-xr-x | test.py | 243 |
2 files changed, 244 insertions, 152 deletions
diff --git a/geldschieberbot.py b/geldschieberbot.py index 3791bd1..33fee20 100644 --- a/geldschieberbot.py +++ b/geldschieberbot.py @@ -7,7 +7,6 @@ import subprocess import sys import tanken - """Path where our data is stored persistent on disk""" state_file = os.environ["GSB_STATE_FILE"] @@ -21,12 +20,12 @@ else: scheduled_cmds - dict associating names to cmds, their schedule, and the last execution changes - dict associating users with their changes""" state = { - "balance" : {}, - "name2num" : {}, - "num2name" : {}, - "cars" : {}, - "scheduled_cmds" : {}, - "changes" : {}, + "balance": {}, + "name2num": {}, + "num2name": {}, + "cars": {}, + "scheduled_cmds": {}, + "changes": {}, } # check if a legacy file layout is present @@ -45,7 +44,7 @@ else: for num in state["changes"]: name = state["num2name"][num] state["changes"][name] = state["changes"][num] - del(state["changes"][num]) + del (state["changes"][num]) balance = state["balance"] name2num = state["name2num"] @@ -58,16 +57,14 @@ group_id = os.environ["GSB_GROUP_ID"] send_cmd = os.environ["GSB_SEND_CMD"] group_send_cmd = send_cmd + group_id - """Run without changing the stored state""" dry_run = False - """Run without sending messages""" quiet = False - """Should changes be recorded""" record_changes = True + def record(recipient, donor, amount): """Apply changes to the balance""" @@ -78,6 +75,7 @@ def record(recipient, donor, amount): balance[donor][recipient] += amount balance[recipient][donor] -= amount + def to_cent(euro): if '.' in euro: euro = euro.split('.') @@ -98,19 +96,22 @@ def to_cent(euro): raise ValueError return amount + def to_euro(cents): return f"{cents/100:.2f}" + def send(msg): if not quiet: subprocess.run(send_cmd.split(' '), input=msg.encode()) + def create_summary(user): summary = "" cars_summary = "" total = 0 cars_total = 0 - p_balances = balance[user] # failes if user is not in balance + p_balances = balance[user] # failes if user is not in balance for person in p_balances: amount = p_balances[person] if amount == 0: @@ -133,7 +134,8 @@ def create_summary(user): cars_summary += f'\tLiability: {to_euro(cars_total)}' ret_summary += f'\n\tCars:\n{cars_summary}' - return ret_summary + return ret_summary + def create_total_summary(): summary = "Summary:" @@ -150,12 +152,14 @@ def create_total_summary(): summary += f'\nCars:{cars_summary}' return summary + def create_members(): r = "" for m in name2num: r += m + ": " + name2num[m] + "\n" return r + def add_to_balance(name): nb = {} for m in balance: @@ -163,6 +167,7 @@ def add_to_balance(name): nb[m] = 0 balance[name] = nb + def create_help(): return """ Usage: send a message starting with '!' followed by a command @@ -201,8 +206,10 @@ cancel name - stop repeating cmd Happy Geldschieben! """ + cmds = {} + def register(sender, args, msg): if len(args) != 2: return None, f'not in form "{args[0]} name"' @@ -229,9 +236,11 @@ def register(sender, args, msg): changes[name] = [] return f"Happy geldschiebing {name}!", None + cmds["reg"] = register cmds["register"] = register + def summary(sender, args, msg): if len(args) == 1: return create_total_summary(), None @@ -245,21 +254,27 @@ def summary(sender, args, msg): err = f'name "{name}" not registered' return msg, err + cmds["sum"] = summary cmds["summary"] = summary + def list_users(sender, args, msg): return create_members(), None + cmds["ls"] = list_users cmds["list"] = list_users + def usage(sender, args, msg): return create_help(), None + cmds["help"] = usage cmds["usage"] = usage + def split(sender, args, msg): if not sender in num2name: return None, 'you must register first' @@ -283,7 +298,7 @@ def split(sender, args, msg): # persons + sender npersons = len(persons) + 1 - amount_per_person = int(amount/npersons) + amount_per_person = int(amount / npersons) if sender in num2name: recipient = num2name[sender] @@ -309,9 +324,11 @@ def split(sender, args, msg): output += create_summary(recipient) return output, None + cmds["split"] = split cmds["teil"] = split + def transaction(sender, args, msg): if len(args) != 3: return None, f'not in form "{args[0]} amount recipient"' @@ -346,17 +363,18 @@ def transaction(sender, args, msg): p_balance = balance[sender][recipient] - output = ("New Balance: {} {} {} {}\n".format(sender, - ("->" if p_balance > 0 else "<-"), - to_euro(abs(p_balance)), - recipient)) + output = ("New Balance: {} {} {} {}\n".format( + sender, ("->" if p_balance > 0 else "<-"), to_euro(abs(p_balance)), + recipient)) return output, None + cmds["schieb"] = transaction cmds["gib"] = transaction cmds["zieh"] = transaction cmds["nimm"] = transaction + def transfer(sender, args, msg): if len(args) < 4: return None, f'not in form "{args[0]} amount source destination"' @@ -403,7 +421,7 @@ def transfer(sender, args, msg): # Sender -> X Destination change.append((sender, destination, amount_cent)) - ret, err = transaction(source, ["!zieh", destination, amount_raw], "") + ret, err = transaction(source, ["!zieh", destination, amount_raw], "") if err: output += err + "\nThe balance may be in a inconsistent state please take care manually" return output, None @@ -418,8 +436,10 @@ def transfer(sender, args, msg): return output, None + cmds["transfer"] = transfer + def cars(sender, args, msg): # list cars if len(args) < 2 or args[1] in ["ls", "list"]: @@ -427,7 +447,7 @@ def cars(sender, args, msg): return "No cars registered yet.", None ret_msg = "" - + if len(args) > 2: cars_to_list = args[2:] else: @@ -501,7 +521,7 @@ def cars(sender, args, msg): proportion = -1 * (amount / total_available_charge) _, err = transaction(sender, f"!gib {car} {amount_euro}".split(), "") - assert(err is None) + assert (err is None) output += f"{sender_name} payed {amount_euro}\n" # transfer money @@ -512,10 +532,12 @@ def cars(sender, args, msg): to_move = int(_amount * proportion) to_move_euro = to_euro(to_move) - ret, err = transfer(sender, ["transfer", to_move_euro, car, person], "") - assert(err is None) + ret, err = transfer(sender, + ["transfer", to_move_euro, car, person], "") + assert (err is None) - output += "Transfer {} from {} to {}\n".format(to_move_euro, person, sender_name) + output += "Transfer {} from {} to {}\n".format( + to_move_euro, person, sender_name) output += "New Balances:\n" output += create_summary(sender_name) + "\n" @@ -529,11 +551,14 @@ def cars(sender, args, msg): else: return None, 'unknown car subcommand "{}".'.format(args[1]) + cmds["cars"] = cars + def _tanken(sender, args, msg): if len(args) < 2: - return None, 'not in form "{} amount [person] [car] [info]"'.format(args[0]) + return None, 'not in form "{} amount [person] [car] [info]"'.format( + args[0]) try: amount = to_cent(args[1]) except: @@ -566,7 +591,9 @@ def _tanken(sender, args, msg): output = "" change = [args] for pname, values in parts.items(): - output += pname + ": {}km = fuel: {}, service charge: {}\n".format(values["distance"], to_euro(values["cost"]), to_euro(values["service_charge"])) + output += pname + ": {}km = fuel: {}, service charge: {}\n".format( + values["distance"], to_euro(values["cost"]), + to_euro(values["service_charge"])) # record service charges if pname not in name2num: output += pname + " not known." @@ -574,7 +601,8 @@ def _tanken(sender, args, msg): person_to_charge = pname if pname not in name2num: person_to_charge = recipient - output += " {} held accountable for service charge.".format(recipient) + output += " {} held accountable for service charge.".format( + recipient) record(car, person_to_charge, values["service_charge"]) change.append([car, person_to_charge, values["service_charge"]]) @@ -599,8 +627,10 @@ def _tanken(sender, args, msg): output += create_summary(car) return output, None + cmds["tanken"] = _tanken + def fuck(sender, args, msg): if not sender in num2name: return None, "you must register first" @@ -630,9 +660,9 @@ def fuck(sender, args, msg): for change in last_changes: if not change[0] in cmds: output += "{} {} {} {}\n".format(change[0], - ("->" if change[2] < 0 else "<-"), - to_euro(abs(change[2])), - change[1]) + ("->" if change[2] < 0 else "<-"), + to_euro(abs(change[2])), + change[1]) record(change[1], change[0], change[2]) for change in last_changes: @@ -642,14 +672,16 @@ def fuck(sender, args, msg): if err: output += "ERROR: " + err else: - output += ret + output += ret return output, None + cmds["fuck"] = fuck cmds["rewind"] = fuck cmds["undo"] = fuck + def list_changes(sender, args, msg): if not sender in num2name: return None, "you must register first" @@ -663,7 +695,6 @@ def list_changes(sender, args, msg): except ValueError: return None, 'the amount of changes to list must be a number' - nchanges = len(changes[sender_name]) if nchanges == 0: return "Nothing to list", None @@ -696,17 +727,18 @@ def list_changes(sender, args, msg): msg += f'\t{" ".join(change[0])}\n' for sender, recipient, amount in change[1:]: msg += "\t{} {} {} {}\n".format(sender, - ("->" if amount < 0 else "<-"), - to_euro(abs(amount)), - recipient) + ("->" if amount < 0 else "<-"), + to_euro(abs(amount)), recipient) # prepend message header because we want to know how much changes we actually listed msg = f'Changes from {sender_name} {first_to_list + 1}-{i + 1}\n' + msg return msg, None + cmds["list-changes"] = list_changes + def schedule(sender, args, msg): if not sender in num2name: return None, "you must register first" @@ -720,7 +752,8 @@ def schedule(sender, args, msg): cmd = args[2:] if name in scheduled_cmds: - return None, 'there is already a scheduled command named "{}"'.format(name) + return None, 'there is already a scheduled command named "{}"'.format( + name) # Test the command global dry_run @@ -733,16 +766,19 @@ def schedule(sender, args, msg): if err: return None, 'the command "{}" failed and will not be recorded' - scheduled_cmd = {"schedule": args[0][1:], - "last_time": None, - "sender": sender, - "cmd": cmd} + scheduled_cmd = { + "schedule": args[0][1:], + "last_time": None, + "sender": sender, + "cmd": cmd + } scheduled_cmds[name] = scheduled_cmd - output = 'Recorded the {} command "{}" as "{}"\n'.format(args[0][1:], ' '.join(cmd), name) + output = 'Recorded the {} command "{}" as "{}"\n'.format( + args[0][1:], ' '.join(cmd), name) - output += "Running {} command {} for {} initially\n".format(scheduled_cmd["schedule"], - name, sender_name) + output += "Running {} command {} for {} initially\n".format( + scheduled_cmd["schedule"], name, sender_name) ret, err = cmds[cmd[0]](sender, cmd, "") if err: @@ -757,10 +793,12 @@ def schedule(sender, args, msg): return output, None + cmds["weekly"] = schedule cmds["monthly"] = schedule cmds["yearly"] = schedule + def cancel(sender, args, msg): cmd_name = args[1] if not cmd_name in scheduled_cmds: @@ -770,11 +808,13 @@ def cancel(sender, args, msg): if not cmd["sender"] == sender: return None, 'only the original creator can cancel this command' - del(scheduled_cmds[cmd_name]) + del (scheduled_cmds[cmd_name]) return 'Cancelled the {} cmd "{}"'.format(cmd["schedule"], cmd_name), None + cmds["cancel"] = cancel + def main(): if len(sys.argv) > 1 and sys.argv[1] in ["-d", "--dry-run"]: global dry_run @@ -790,11 +830,13 @@ def main(): continue sender_number = message["source"] - if not "dataMessage" in message or not message["dataMessage"] or not message["dataMessage"]["message"]: + if not "dataMessage" in message or not message[ + "dataMessage"] or not message["dataMessage"]["message"]: continue else: message = message["dataMessage"] - if message["groupInfo"] and message["groupInfo"]["groupId"] != group_id: + if message["groupInfo"] and message["groupInfo"][ + "groupId"] != group_id: continue body = [l.strip() for l in message["message"].lower().splitlines()] @@ -832,24 +874,24 @@ def main(): d = last_time while True: if cmd["schedule"] == "yearly": - d = date(d.year+1, d.month, d.day) + d = date(d.year + 1, d.month, d.day) elif cmd["schedule"] == "monthly": if d.day > 28: d = date(d.year, d.month, 28) if d.month == 12: - d = date(d.year+1, 1, d.day) + d = date(d.year + 1, 1, d.day) else: - d = date(d.year, d.month+1, d.day) + d = date(d.year, d.month + 1, d.day) else: d = d + timedelta(7) if d <= now: - send("Running {} command {} for {} triggered on {}\n".format(cmd["schedule"], - name, - num2name[cmd["sender"]], - d.isoformat())) + send("Running {} command {} for {} triggered on {}\n". + format(cmd["schedule"], name, num2name[cmd["sender"]], + d.isoformat())) - ret, err = cmds[cmd["cmd"][0]](cmd["sender"], cmd["cmd"], "") + ret, err = cmds[cmd["cmd"][0]](cmd["sender"], cmd["cmd"], + "") if err: send("ERROR: " + err) @@ -860,10 +902,9 @@ def main(): else: break - with open(state_file, "w") as f: json.dump(state, f) + if __name__ == "__main__": main() - @@ -52,11 +52,16 @@ scheduled_state_template = Template(""" "sender": "+49123456", "cmd": ["split", "3", "bob", "charlie"]}}, "changes": {"alice": [], "bob": [], "charlie": []}}""") + def run_bot(test, sender, cmd): - msg = msg_template.substitute(sender=sender, msg=cmd).replace("\n", "\\n") + "\n" - res = subprocess.run(["python3", "./geldschieberbot.py"], text=True, capture_output=True, - # res = subprocess.run(["python3", "./bot.py"], text=True, capture_output=True, - input=msg) + msg = msg_template.substitute(sender=sender, msg=cmd).replace("\n", + "\\n") + "\n" + res = subprocess.run( + ["python3", "./geldschieberbot.py"], + text=True, + capture_output=True, + # res = subprocess.run(["python3", "./bot.py"], text=True, capture_output=True, + input=msg) if res.returncode != 0: print(res.stdout) @@ -65,9 +70,11 @@ def run_bot(test, sender, cmd): test.assertEqual(res.stderr, "") return res + def save_state(dest): copyfile(os.environ["GSB_STATE_FILE"], dest) + def reset_state(state=None): if state: copyfile(state, os.environ["GSB_STATE_FILE"]) @@ -76,9 +83,11 @@ def reset_state(state=None): if os.path.isfile(state): os.remove(state) + def reset_state_string(string): - with open(os.environ["GSB_STATE_FILE"], "w") as f: - json.dump(json.loads(string), f) + with open(os.environ["GSB_STATE_FILE"], "w") as f: + json.dump(json.loads(string), f) + def compare_state(comp_state): with open(comp_state, "r") as csf, \ @@ -87,20 +96,20 @@ def compare_state(comp_state): s = sf.read() return cs == s -class TestRegCmd(unittest.TestCase): +class TestRegCmd(unittest.TestCase): def setUp(self): reset_state() def test_correct_reg(self): - res = run_bot(self, num[alice], "!reg "+alice) + res = run_bot(self, num[alice], "!reg " + alice) self.assertEqual(res.stdout, 'Happy geldschiebing {}!'.format(alice)) def test_double_reg(self): - res = run_bot(self, num[alice], "!reg "+alice) + res = run_bot(self, num[alice], "!reg " + alice) self.assertEqual(res.stdout, 'Happy geldschiebing {}!'.format(alice)) - res = run_bot(self, num[alice], "!reg "+alice) - self.assertEqual(res.stdout, 'ERROR: '+alice+' already registered') + res = run_bot(self, num[alice], "!reg " + alice) + self.assertEqual(res.stdout, 'ERROR: ' + alice + ' already registered') def test_invalid_reg(self): res = run_bot(self, num[alice], "!reg nase 03") @@ -108,24 +117,25 @@ class TestRegCmd(unittest.TestCase): def test_numerical_reg(self): res = run_bot(self, num[alice], "!reg 9") - self.assertEqual(res.stdout, 'ERROR: pure numerical names are not allowed') + self.assertEqual(res.stdout, + 'ERROR: pure numerical names are not allowed') res = run_bot(self, num[alice], "!reg 9.7") - self.assertEqual(res.stdout, 'ERROR: pure numerical names are not allowed') + self.assertEqual(res.stdout, + 'ERROR: pure numerical names are not allowed') def test_additional_reg(self): - res = run_bot(self, num[alice], "!reg "+alice) + res = run_bot(self, num[alice], "!reg " + alice) self.assertEqual(res.stdout, 'Happy geldschiebing {}!'.format(alice)) - res = run_bot(self, num[bob], "!reg "+bob) + res = run_bot(self, num[bob], "!reg " + bob) self.assertEqual(res.stdout, 'Happy geldschiebing {}!'.format(bob)) - res = run_bot(self, num[charlie], "!reg "+charlie) + res = run_bot(self, num[charlie], "!reg " + charlie) self.assertEqual(res.stdout, 'Happy geldschiebing {}!'.format(charlie)) self.assertTrue(compare_state("test/state.json_3users")) class TestTransactionCmd(unittest.TestCase): - @classmethod def tearDownClass(cls): reset_state() @@ -134,92 +144,118 @@ class TestTransactionCmd(unittest.TestCase): reset_state("test/state.json_3users") def test_correct_schieb(self): - res = run_bot(self, num[alice], "!schieb 10 "+bob) - self.assertEqual(res.stdout, 'New Balance: {} <- 10.00 {}\n'.format(alice, bob)) + res = run_bot(self, num[alice], "!schieb 10 " + bob) + self.assertEqual(res.stdout, + 'New Balance: {} <- 10.00 {}\n'.format(alice, bob)) - res = run_bot(self, num[bob], "!schieb 10 "+alice) - self.assertEqual(res.stdout, 'New Balance: {} <- 0.00 {}\n'.format(bob, alice)) + res = run_bot(self, num[bob], "!schieb 10 " + alice) + self.assertEqual(res.stdout, + 'New Balance: {} <- 0.00 {}\n'.format(bob, alice)) def test_correct_gib(self): - res = run_bot(self, num[alice], "!gib 10 "+bob) - self.assertEqual(res.stdout, 'New Balance: {} <- 10.00 {}\n'.format(alice, bob)) + res = run_bot(self, num[alice], "!gib 10 " + bob) + self.assertEqual(res.stdout, + 'New Balance: {} <- 10.00 {}\n'.format(alice, bob)) - res = run_bot(self, num[bob], "!gib 10 "+alice) - self.assertEqual(res.stdout, 'New Balance: {} <- 0.00 {}\n'.format(bob, alice)) + res = run_bot(self, num[bob], "!gib 10 " + alice) + self.assertEqual(res.stdout, + 'New Balance: {} <- 0.00 {}\n'.format(bob, alice)) def test_correct_amount(self): - res = run_bot(self, num[bob], "!schieb 1.1 "+alice) - self.assertEqual(res.stdout, 'New Balance: {} <- 1.10 {}\n'.format(bob, alice)) + res = run_bot(self, num[bob], "!schieb 1.1 " + alice) + self.assertEqual(res.stdout, + 'New Balance: {} <- 1.10 {}\n'.format(bob, alice)) - res = run_bot(self, num[bob], "!gib 1,1 "+alice) - self.assertEqual(res.stdout, 'New Balance: {} <- 2.20 {}\n'.format(bob, alice)) + res = run_bot(self, num[bob], "!gib 1,1 " + alice) + self.assertEqual(res.stdout, + 'New Balance: {} <- 2.20 {}\n'.format(bob, alice)) def test_invalid_amount(self): - res = run_bot(self, num[bob], "!schieb 1b1 "+alice) + res = run_bot(self, num[bob], "!schieb 1b1 " + alice) self.assertEqual(res.stdout, 'ERROR: amount must be a positive number') - res = run_bot(self, num[bob], "!schieb ä€ "+alice) + res = run_bot(self, num[bob], "!schieb ä€ " + alice) self.assertEqual(res.stdout, 'ERROR: amount must be a positive number') def test_correct_schieb_name_before_amount(self): - res = run_bot(self, num[alice], "!schieb "+bob+ " 10") - self.assertEqual(res.stdout, 'New Balance: {} <- 10.00 {}\n'.format(alice, bob)) + res = run_bot(self, num[alice], "!schieb " + bob + " 10") + self.assertEqual(res.stdout, + 'New Balance: {} <- 10.00 {}\n'.format(alice, bob)) - res = run_bot(self, num[bob], "!schieb "+alice+ " 10") - self.assertEqual(res.stdout, 'New Balance: {} <- 0.00 {}\n'.format(bob, alice)) + res = run_bot(self, num[bob], "!schieb " + alice + " 10") + self.assertEqual(res.stdout, + 'New Balance: {} <- 0.00 {}\n'.format(bob, alice)) def test_correct_gib_name_before_amount(self): - res = run_bot(self, num[alice], "!gib "+charlie+ " 10") - self.assertEqual(res.stdout, 'New Balance: {} <- 10.00 {}\n'.format(alice, charlie)) + res = run_bot(self, num[alice], "!gib " + charlie + " 10") + self.assertEqual( + res.stdout, 'New Balance: {} <- 10.00 {}\n'.format(alice, charlie)) - res = run_bot(self, num[charlie], "!gib "+alice+ " 10") - self.assertEqual(res.stdout, 'New Balance: {} <- 0.00 {}\n'.format(charlie, alice)) + res = run_bot(self, num[charlie], "!gib " + alice + " 10") + self.assertEqual(res.stdout, + 'New Balance: {} <- 0.00 {}\n'.format(charlie, alice)) def test_correct_nimm(self): - res = run_bot(self, num[alice], "!nimm 10 "+bob) - self.assertEqual(res.stdout, 'New Balance: {} -> 10.00 {}\n'.format(alice, bob)) + res = run_bot(self, num[alice], "!nimm 10 " + bob) + self.assertEqual(res.stdout, + 'New Balance: {} -> 10.00 {}\n'.format(alice, bob)) - res = run_bot(self, num[bob], "!nimm 10 "+alice) - self.assertEqual(res.stdout, 'New Balance: {} <- 0.00 {}\n'.format(bob, alice)) + res = run_bot(self, num[bob], "!nimm 10 " + alice) + self.assertEqual(res.stdout, + 'New Balance: {} <- 0.00 {}\n'.format(bob, alice)) def test_correct_zieh_name_before_amount(self): - res = run_bot(self, num[alice], "!zieh "+charlie+ " 10") - self.assertEqual(res.stdout, 'New Balance: {} -> 10.00 {}\n'.format(alice, charlie)) + res = run_bot(self, num[alice], "!zieh " + charlie + " 10") + self.assertEqual( + res.stdout, 'New Balance: {} -> 10.00 {}\n'.format(alice, charlie)) - res = run_bot(self, num[charlie], "!zieh "+alice+ " 10") - self.assertEqual(res.stdout, 'New Balance: {} <- 0.00 {}\n'.format(charlie, alice)) + res = run_bot(self, num[charlie], "!zieh " + alice + " 10") + self.assertEqual(res.stdout, + 'New Balance: {} <- 0.00 {}\n'.format(charlie, alice)) def test_transactions_complex(self): - res = run_bot(self, num[alice], "!schieb "+charlie+ " 1,1") - self.assertEqual(res.stdout, 'New Balance: {} <- 1.10 {}\n'.format(alice, charlie)) + res = run_bot(self, num[alice], "!schieb " + charlie + " 1,1") + self.assertEqual(res.stdout, + 'New Balance: {} <- 1.10 {}\n'.format(alice, charlie)) - res = run_bot(self, num[alice], "!zieh "+charlie+ " 2.1") - self.assertEqual(res.stdout, 'New Balance: {} -> 1.00 {}\n'.format(alice, charlie)) + res = run_bot(self, num[alice], "!zieh " + charlie + " 2.1") + self.assertEqual(res.stdout, + 'New Balance: {} -> 1.00 {}\n'.format(alice, charlie)) - res = run_bot(self, num[charlie], "!schieb "+bob+ " 42") - self.assertEqual(res.stdout, 'New Balance: {} <- 42.00 {}\n'.format(charlie, bob)) + res = run_bot(self, num[charlie], "!schieb " + bob + " 42") + self.assertEqual(res.stdout, + 'New Balance: {} <- 42.00 {}\n'.format(charlie, bob)) - res = run_bot(self, num[alice], "!zieh "+bob+ " 0.01") - self.assertEqual(res.stdout, 'New Balance: {} -> 0.01 {}\n'.format(alice, bob)) + res = run_bot(self, num[alice], "!zieh " + bob + " 0.01") + self.assertEqual(res.stdout, + 'New Balance: {} -> 0.01 {}\n'.format(alice, bob)) compare_state("test/state.json_transactions1") def test_transactions_with_myself(self): res = run_bot(self, num[alice], f"!schieb {alice} 1,1") - self.assertEqual(res.stdout, 'ERROR: you can not transfer money to or from yourself') + self.assertEqual( + res.stdout, + 'ERROR: you can not transfer money to or from yourself') res = run_bot(self, num[alice], f"!zieh {alice} 2.1") - self.assertEqual(res.stdout, 'ERROR: you can not transfer money to or from yourself') + self.assertEqual( + res.stdout, + 'ERROR: you can not transfer money to or from yourself') + class TestSumCmd(unittest.TestCase): def test_summary_single_user(self): reset_state("test/state.json_transactions1") - res = run_bot(self, num[alice], "!sum "+alice) - self.assertEqual(res.stdout, 'Summary:\nalice:\n\t-> bob 0.01\n\t-> charlie 1.00\n\tBalance: -1.01\n') + res = run_bot(self, num[alice], "!sum " + alice) + self.assertEqual( + res.stdout, + 'Summary:\nalice:\n\t-> bob 0.01\n\t-> charlie 1.00\n\tBalance: -1.01\n' + ) def test_summary_invalide_single_user(self): reset_state() - res = run_bot(self, num[alice], "!sum "+alice) + res = run_bot(self, num[alice], "!sum " + alice) self.assertEqual(res.stdout, 'ERROR: name "alice" not registered') def test_summary_double_user(self): @@ -257,11 +293,13 @@ charlie: \tBalance: 43.00""" self.assertEqual(res.stdout, summary) -class TestMisc(unittest.TestCase): +class TestMisc(unittest.TestCase): def test_unknown_command(self): res = run_bot(self, num[alice], "!foo") - self.assertEqual(res.stdout, "ERROR: unknown cmd. Enter !help for a list of commands.") + self.assertEqual( + res.stdout, + "ERROR: unknown cmd. Enter !help for a list of commands.") def test_no_command(self): res = run_bot(self, num[alice], "Hi, how are you?") @@ -269,22 +307,23 @@ class TestMisc(unittest.TestCase): class TestListCmd(unittest.TestCase): - def setUp(self): reset_state("test/state.json_3users") def test_ls(self): res = run_bot(self, num[alice], "!ls") - msg = "alice: {}\nbob: {}\ncharlie: {}\n".format(num[alice], num[bob], num[charlie]) + msg = "alice: {}\nbob: {}\ncharlie: {}\n".format( + num[alice], num[bob], num[charlie]) self.assertEqual(res.stdout, msg) def test_list(self): res = run_bot(self, num[bob], "!list") - msg = "alice: {}\nbob: {}\ncharlie: {}\n".format(num[alice], num[bob], num[charlie]) + msg = "alice: {}\nbob: {}\ncharlie: {}\n".format( + num[alice], num[bob], num[charlie]) self.assertEqual(res.stdout, msg) -class TestSplitCmd(unittest.TestCase): +class TestSplitCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") @@ -294,10 +333,12 @@ class TestSplitCmd(unittest.TestCase): def test_split_invalid_args(self): res = run_bot(self, num[alice], "!split") - self.assertEqual(res.stdout, 'ERROR: not in form "!split amount [name]+"') + self.assertEqual(res.stdout, + 'ERROR: not in form "!split amount [name]+"') res = run_bot(self, num[alice], "!split 10") - self.assertEqual(res.stdout, 'ERROR: not in form "!split amount [name]+"') + self.assertEqual(res.stdout, + 'ERROR: not in form "!split amount [name]+"') def test_split_one_unknown_user(self): res = run_bot(self, num[alice], "!split 30 " + "foo" + " " + charlie) @@ -330,7 +371,7 @@ alice: \t<- charlie 20.00 \tBalance: 20.00""" self.assertEqual(res.stdout, msg) - + def test_split(self): res = run_bot(self, num[alice], "!split 30 " + bob + " " + charlie) msg = \ @@ -353,7 +394,8 @@ alice: self.assertEqual(res.stdout, msg) def test_split_whitespace(self): - res = run_bot(self, num[alice], "!split 30 " + bob + " " + charlie + " ") + res = run_bot(self, num[alice], + "!split 30 " + bob + " " + charlie + " ") msg = \ """Split 30.00 between 3 -> 10.00 each New Balance: @@ -363,6 +405,7 @@ alice: \tBalance: 20.00""" self.assertEqual(res.stdout, msg) + class TestCarsAddCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") @@ -372,7 +415,7 @@ class TestCarsAddCmd(unittest.TestCase): res = run_bot(self, num[alice], i) o = 'added "foo" as an available car' self.assertEqual(res.stdout, o) - + i = "!cars new bar 0.02" res = run_bot(self, num[alice], i) o = 'added "bar" as an available car' @@ -385,23 +428,24 @@ class TestCarsAddCmd(unittest.TestCase): res = run_bot(self, num[alice], i) o = "ERROR: service-charge must be a positive number" self.assertEqual(res.stdout, o) - + i = "!cars new bar -5" res = run_bot(self, num[alice], i) o = "ERROR: service-charge must be a positive number" self.assertEqual(res.stdout, o) - + def test_add_name_conflict(self): i = "!cars add foo 0.04" res = run_bot(self, num[alice], i) o = 'added "foo" as an available car' self.assertEqual(res.stdout, o) - + i = "!cars new alice 0.02" res = run_bot(self, num[alice], i) o = 'ERROR: A user named "alice" already exists. Please use a different name for this car' self.assertEqual(res.stdout, o) + class TestCarsTransactions(unittest.TestCase): def setUp(self): reset_state("test/state.json_2cars") @@ -412,6 +456,7 @@ class TestCarsTransactions(unittest.TestCase): o = "New Balance: alice <- 20.00 foo\n" self.assertEqual(res.stdout, o) + class TestCarsSum(unittest.TestCase): def setUp(self): reset_state("test/state.json_2cars") @@ -425,6 +470,7 @@ foo: """ self.assertEqual(res.stdout, o) + class TestCarPayCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_2cars") @@ -473,7 +519,6 @@ foo: \tBalance: -10.00""" self.assertEqual(res.stdout, o) - def test_alice_pays_half(self): run_bot(self, num[bob], "!zieh foo 20") run_bot(self, num[charlie], "!zieh foo 10") @@ -496,6 +541,7 @@ foo: \tBalance: 15.00""" self.assertEqual(res.stdout, o) + class TestCarsListCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_2cars") @@ -541,7 +587,7 @@ bar - service charge 2ct/km bar: \tAll fine :)""" self.assertEqual(res.stdout, o) - + def test_list_explicit_car(self): i = "!cars ls foo" res = run_bot(self, num[alice], i) @@ -557,8 +603,8 @@ foo: o = 'ERROR: "alice" is no available car\n' self.assertEqual(res.stdout, o) -class TestTankenCmd(unittest.TestCase): +class TestTankenCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") @@ -687,7 +733,6 @@ Car foo: class TestTransferCmd(unittest.TestCase): - def setUp(self): reset_state("test/state.json_3users") @@ -728,7 +773,6 @@ New Balance: bob -> 5.00 charlie #TODO: tanken, transfer, cars pay class TestListChangesCmd(unittest.TestCase): - def setUp(self): reset_state("test/state.json_3users") @@ -806,8 +850,8 @@ Change 2: """ self.assertEqual(res.stdout, msg) -class TestFuckCmd(unittest.TestCase): +class TestFuckCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") @@ -897,7 +941,7 @@ alice <- 1.00 charlie compare_state("test/state.json_3users") def test_fuck_transaction(self): - run_bot(self, num[alice], "!schieb "+bob+ " 10") + run_bot(self, num[alice], "!schieb " + bob + " 10") res = run_bot(self, num[alice], "!fuck") msg = \ """alice: sorry I fucked up! @@ -908,8 +952,8 @@ alice <- 10.00 bob self.assertEqual(res.stdout, msg) compare_state("test/state.json_3users") -class TestScheduleCmd(unittest.TestCase): +class TestScheduleCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") @@ -939,7 +983,9 @@ Cancelled the weekly cmd "stuff\"""" self.assertEqual(res.stdout, msg) # Last exec onw week ago - reset_state_string(scheduled_state_template.substitute(last_time=now-timedelta(7),schedule="weekly")) + reset_state_string( + scheduled_state_template.substitute(last_time=now - timedelta(7), + schedule="weekly")) res = run_bot(self, num[alice], "!fuck") # There is no new line because the real bot uses two Messages @@ -959,7 +1005,9 @@ alice: self.assertEqual(res.stdout, 'Nothing to rewind') # Last exec two week ago - reset_state_string(scheduled_state_template.substitute(last_time=now-timedelta(14),schedule="weekly")) + reset_state_string( + scheduled_state_template.substitute(last_time=now - timedelta(14), + schedule="weekly")) res = run_bot(self, num[alice], "!fuck") # There is no new line because the real bot uses two Messages @@ -979,7 +1027,7 @@ alice: \tBalance: 6.00""".format((now - timedelta(7)).isoformat(), now.isoformat()) self.assertEqual(res.stdout, msg) - + os.remove("test/state.json_schedule_weekly") def test_monthly(self): @@ -1010,11 +1058,12 @@ Cancelled the monthly cmd "stuff\"""" # Last exec one month ago if now.month > 1: - one_month_ago = date(now.year, now.month-1, now.day) + one_month_ago = date(now.year, now.month - 1, now.day) else: - one_month_ago = date(now.year-1, 12, now.day) - reset_state_string(scheduled_state_template.substitute(last_time=one_month_ago, - schedule="monthly")) + one_month_ago = date(now.year - 1, 12, now.day) + reset_state_string( + scheduled_state_template.substitute(last_time=one_month_ago, + schedule="monthly")) res = run_bot(self, num[alice], "!fuck") # There is no new line because the real bot uses two Messages @@ -1035,12 +1084,13 @@ alice: # Last exec two month ago if now.month > 2: - two_months_ago = date(now.year, now.month-2, now.day) + two_months_ago = date(now.year, now.month - 2, now.day) else: - two_months_ago = date(now.year-1, 12-now.month+1, now.day) + two_months_ago = date(now.year - 1, 12 - now.month + 1, now.day) - reset_state_string(scheduled_state_template.substitute(last_time=two_months_ago, - schedule="monthly")) + reset_state_string( + scheduled_state_template.substitute(last_time=two_months_ago, + schedule="monthly")) res = run_bot(self, num[alice], "!fuck") # There is no new line because the real bot uses to Messages @@ -1060,13 +1110,13 @@ alice: \tBalance: 6.00""".format(one_month_ago.isoformat(), now.isoformat()) self.assertEqual(res.stdout, msg) - - os.remove("test/state.json_schedule_monthly") + os.remove("test/state.json_schedule_monthly") def test_monthly_car(self): run_bot(self, num[alice], "!cars add fiat 0.5") - res = run_bot(self, num[alice], "!monthly versicherung cars pay fiat 3") + res = run_bot(self, num[alice], + "!monthly versicherung cars pay fiat 3") msg = \ """Recorded the monthly command "cars pay fiat 3" as "versicherung" Running monthly command versicherung for alice initially @@ -1083,5 +1133,6 @@ fiat: \tBalance: -3.00""" self.assertEqual(res.stdout, msg) + if __name__ == '__main__': unittest.main() |
