#!/usr/bin/env python3 from datetime import datetime, date, timedelta import json import os from shutil import copyfile from string import Template import subprocess import unittest alice, bob, charlie = "alice", "bob", "charlie" num = {alice: "+49123456", bob: "+49654321", charlie: "+49615243"} os.environ["GSB_GROUP_ID"] = "test" os.environ["GSB_STATE_FILE"] = "test/state.json" os.environ["GSB_SEND_CMD"] = "cat" os.environ["GSB_SEND_GROUP"] = "cat" os.environ["GSB_SEND_USER"] = "cat" os.environ["GSB_MODULES"] = "geldschiebing.py" now = datetime.now().date() msg_template = Template(""" {"envelope": {"source":"$sender", "sourceDevice":2, "relay":null, "timestamp":1544101248419, "isReceipt":false, "dataMessage": {"timestamp":1544101248419, "message":"$msg", "expiresInSeconds":0, "attachments":[], "groupInfo": {"groupId":"test", "members":null, "name":null, "type":"DELIVER"} }, "syncMessage":null, "callMessage":null} }""".replace("\n", "")) scheduled_state_template = Template(""" {"balance": {"alice": {"bob": -100, "charlie": -100}, "bob": {"alice": 100, "charlie": 0}, "charlie": {"alice": 100, "bob": 0}}, "name2num": {"alice": "+49123456", "bob": "+49654321", "charlie": "+49615243"}, "num2name": {"+49123456": "alice", "+49654321": "bob", "+49615243": "charlie"}, "cars": {}, "scheduled_cmds": {"stuff": {"schedule": "$schedule", "last_time": "$last_time", "sender": "+49123456", "cmd": ["split", "3", "bob", "charlie"]}}, "changes": {"alice": [], "bob": [], "charlie": []}}""") def _run_bot(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, check=False, # res = subprocess.run(["python3", "./bot.py"], text=True, capture_output=True, input=msg) if res.returncode != 0: print(res.stdout) print(res.stderr) return res def run_bot(test, sender, cmd): res = _run_bot(sender, cmd) test.assertEqual(res.returncode, 0) 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"]) else: state = os.environ["GSB_STATE_FILE"] 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) def compare_state(comp_state): with open(comp_state, "r") as csf, \ open(os.environ["GSB_STATE_FILE"], "r") as sf: cs = csf.read() s = sf.read() return cs == s class TestRegCmd(unittest.TestCase): def setUp(self): reset_state() def test_correct_reg(self): 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) self.assertEqual(res.stdout, 'Happy geldschiebing {}!'.format(alice)) 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") self.assertEqual(res.stdout, 'ERROR: not in form "!reg name"') def test_numerical_reg(self): res = run_bot(self, num[alice], "!reg 9") 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') def test_additional_reg(self): res = run_bot(self, num[alice], "!reg " + alice) self.assertEqual(res.stdout, 'Happy geldschiebing {}!'.format(alice)) res = run_bot(self, num[bob], "!reg " + bob) self.assertEqual(res.stdout, 'Happy geldschiebing {}!'.format(bob)) 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() def setUp(self): 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[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[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], "!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) self.assertEqual(res.stdout, 'ERROR: amount must be a positive number') 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[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[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[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[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], "!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[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') 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') 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' ) def test_summary_invalide_single_user(self): reset_state() res = run_bot(self, num[alice], "!sum " + alice) self.assertEqual(res.stdout, 'ERROR: name "alice" not registered') def test_summary_double_user(self): reset_state("test/state.json_transactions1") res = run_bot(self, num[alice], "!sum {} {}".format(alice, bob)) summary = \ """Summary: alice: \t-> bob 0.01 \t-> charlie 1.00 \tBalance: -1.01 bob: \t<- alice 0.01 \t-> charlie 42.00 \tBalance: -41.99 """ self.assertEqual(res.stdout, summary) def test_summary(self): reset_state("test/state.json_transactions1") res = run_bot(self, num[alice], "!sum") self.assertEqual( res.stdout, 'Summary:\nalice:\n\t-> bob 0.01\n\t-> charlie 1.00\n\tBalance: -1.01' ) def test_full_summary(self): reset_state("test/state.json_transactions1") res = run_bot(self, num[alice], "!full-sum") summary = \ """Summary: alice: \t-> bob 0.01 \t-> charlie 1.00 \tBalance: -1.01 bob: \t<- alice 0.01 \t-> charlie 42.00 \tBalance: -41.99 charlie: \t<- alice 1.00 \t<- bob 42.00 \tBalance: 43.00""" self.assertEqual(res.stdout, summary) def test_full_summary_with_args(self): reset_state("test/state.json_transactions1") res = run_bot(self, num[alice], "!full-sum foo") self.assertEqual(res.stdout, 'ERROR: full-sum takes no arguments') 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.") def test_no_command(self): res = run_bot(self, num[alice], "Hi, how are you?") self.assertEqual(res.stdout, "") 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]) 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]) self.assertEqual(res.stdout, msg) class TestSplitCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") def test_split_unregistered(self): res = run_bot(self, "+4971576357", "!split") self.assertEqual(res.stdout, 'ERROR: you must register first') def test_split_invalid_args(self): res = run_bot(self, num[alice], "!split") 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]+"') def test_split_one_unknown_user(self): res = run_bot(self, num[alice], "!split 30 " + "foo" + " " + charlie) msg = \ """Split 30.00 between 3 -> 10.00 each foo not known. Please take care manually New Balance: alice: \t<- charlie 10.00 \tBalance: 10.00""" self.assertEqual(res.stdout, msg) def test_split_with_sender_in_user_list(self): res = run_bot(self, num[alice], f"!split 30 {charlie} {alice}") msg = \ """Split 30.00 between 3 -> 10.00 each alice, you will be charged multiple times. This may not be what you want New Balance: alice: \t<- charlie 10.00 \tBalance: 10.00""" self.assertEqual(res.stdout, msg) def test_split_with_double_user(self): res = run_bot(self, num[alice], f"!split 30 {charlie} {charlie}") msg = \ """Split 30.00 between 3 -> 10.00 each New Balance: 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 = \ """Split 30.00 between 3 -> 10.00 each New Balance: alice: \t<- bob 10.00 \t<- charlie 10.00 \tBalance: 20.00""" self.assertEqual(res.stdout, msg) def test_split_single_user_amount_swap(self): res = run_bot(self, num[alice], f"!split {bob} 10") msg = \ """Split 10.00 between 2 -> 5.00 each New Balance: alice: \t<- bob 5.00 \tBalance: 5.00""" self.assertEqual(res.stdout, msg) def test_split_whitespace(self): res = run_bot(self, num[alice], "!split 30 " + bob + " " + charlie + " ") msg = \ """Split 30.00 between 3 -> 10.00 each New Balance: alice: \t<- bob 10.00 \t<- charlie 10.00 \tBalance: 20.00""" self.assertEqual(res.stdout, msg) class TestCarsAdd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") def test_add_success(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 bar 0.02" res = run_bot(self, num[alice], i) o = 'added "bar" as an available car' self.assertEqual(res.stdout, o) save_state("test/state.json_2cars") def test_add_invalid_service_charge(self): i = "!cars add foo 0.04hut" 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") def test_schieb_car(self): i = "!schieb foo 20" res = run_bot(self, num[alice], i) 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") def test_sum_car(self): res = run_bot(self, num[alice], "!sum foo") o =\ """Summary: foo: \tAll fine :) """ self.assertEqual(res.stdout, o) class TestCarsRemove(unittest.TestCase): def setUp(self): reset_state("test/state.json_2cars") def test_schieb_car(self): i = "!cars remove foo" res = run_bot(self, num[alice], i) o = 'removed "foo" from the available cars' self.assertEqual(res.stdout, o) i = "!sum foo" res = run_bot(self, num[alice], i) o = 'ERROR: name "foo" not registered' self.assertEqual(res.stdout, o) class TestCarPayCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_2cars") def test_alice_pays_exact(self): run_bot(self, num[bob], "!zieh foo 20") run_bot(self, num[charlie], "!zieh foo 10") i = "!cars pay foo 30" res = run_bot(self, num[alice], i) o = \ """alice payed 30.00 Transferring 100.00% of everybody's charges Transfer 20.00 from bob to alice Transfer 10.00 from charlie to alice New Balances: alice: \t<- bob 20.00 \t<- charlie 10.00 \tBalance: 30.00 foo: \tAll fine :)""" self.assertEqual(res.stdout, o) def test_alice_pays_more(self): run_bot(self, num[bob], "!zieh foo 20") run_bot(self, num[charlie], "!zieh foo 10") i = "!cars pay foo 40" res = run_bot(self, num[alice], i) o = \ """alice payed 40.00 Transferring 100.00% of everybody's charges Transfer 20.00 from bob to alice Transfer 10.00 from charlie to alice New Balances: alice: \t<- bob 20.00 \t<- charlie 10.00 \tBalance: 30.00 \tCars: \t<- foo 10.00 \tLiability: 10.00 foo: \t-> alice 10.00 \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") i = "!cars pay foo 15" res = run_bot(self, num[alice], i) o = \ """alice payed 15.00 Transferring 50.00% of everybody's charges Transfer 10.00 from bob to alice Transfer 5.00 from charlie to alice New Balances: alice: \t<- bob 10.00 \t<- charlie 5.00 \tBalance: 15.00 foo: \t<- bob 10.00 \t<- charlie 5.00 \tBalance: 15.00""" self.assertEqual(res.stdout, o) class TestCarsListCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_2cars") def test_no_cars(self): reset_state() i = "!cars" res = run_bot(self, num[alice], i) o = "No cars registered yet." self.assertEqual(res.stdout, o) def test_implicit_call(self): i = "!cars" res = run_bot(self, num[alice], i) o = \ """foo - service charge 4ct/km foo: \tAll fine :) bar - service charge 2ct/km bar: \tAll fine :)""" self.assertEqual(res.stdout, o) def test_list_all(self): i = "!cars ls" res = run_bot(self, num[alice], i) o = \ """foo - service charge 4ct/km foo: \tAll fine :) bar - service charge 2ct/km bar: \tAll fine :)""" self.assertEqual(res.stdout, o) i = "!cars list" res = run_bot(self, num[alice], i) o = \ """foo - service charge 4ct/km foo: \tAll fine :) 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) o = \ """foo - service charge 4ct/km foo: \tAll fine :)""" self.assertEqual(res.stdout, o) def test_list_invalid_explicit_car(self): i = "!cars ls alice" res = run_bot(self, num[alice], i) o = 'ERROR: "alice" is no available car\n' self.assertEqual(res.stdout, o) class TestTankenCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") def test_tanken_3users(self): i = \ """!tanken 10 alice 20 alice bob 10 charlie""" res = run_bot(self, num[alice], i) o = \ """alice: 20km = fuel: 3.33, service charge: 0.00 bob: 20km = fuel: 3.33, service charge: 0.00 charlie: 10km = fuel: 3.33, service charge: 0.00 New Balance: alice: \t<- bob 3.33 \t<- charlie 3.33 \tBalance: 6.66""" self.assertEqual(res.stdout, o) def test_tanken_unknown_user(self): i = \ """!tanken 10 alice 20 alice dieter 10 charlie""" res = run_bot(self, num[alice], i) o = \ """alice: 20km = fuel: 3.33, service charge: 0.00 dieter: 20km = fuel: 3.33, service charge: 0.00 dieter not known. Please collect fuel cost manually charlie: 10km = fuel: 3.33, service charge: 0.00 New Balance: alice: \t<- charlie 3.33 \tBalance: 3.33""" self.assertEqual(res.stdout, o) def test_tanken_3users_with_car(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 = \ """!tanken 10 alice foo 20 alice bob 10 charlie""" res = run_bot(self, num[alice], i) o = \ """alice: 20km = fuel: 3.33, service charge: 0.40 bob: 20km = fuel: 3.33, service charge: 0.40 charlie: 10km = fuel: 3.33, service charge: 0.40 New Balance: alice: \t<- bob 3.33 \t<- charlie 3.33 \tBalance: 6.66 \tCars: \t-> foo 0.40 \tLiability: -0.40 Car foo: \t<- alice 0.40 \t<- bob 0.40 \t<- charlie 0.40 \tBalance: 1.20""" self.assertEqual(res.stdout, o) def test_tanken_unknown_user_with_car(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 = \ """!tanken 10 alice foo 20 alice bob 10 dieter""" res = run_bot(self, num[alice], i) o = \ """alice: 20km = fuel: 3.33, service charge: 0.40 bob: 20km = fuel: 3.33, service charge: 0.40 dieter: 10km = fuel: 3.33, service charge: 0.40 dieter not known. alice held accountable for service charge. Please collect fuel cost manually New Balance: alice: \t<- bob 3.33 \tBalance: 3.33 \tCars: \t-> foo 0.80 \tLiability: -0.80 Car foo: \t<- alice 0.80 \t<- bob 0.40 \tBalance: 1.20""" self.assertEqual(res.stdout, o) class TestTransferCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") def test_transfer(self): i = '!transfer 5 bob charlie' res = run_bot(self, num[alice], i) o = \ """New Balance: alice -> 5.00 bob New Balance: alice <- 5.00 charlie New Balance: bob -> 5.00 charlie """ self.assertEqual(res.stdout, o) def test_transfer_credit(self): res = run_bot(self, num[alice], "!schieb bob 5") i = '!transfer 5 bob charlie' res = run_bot(self, num[alice], i) o = \ """New Balance: alice <- 0.00 bob New Balance: alice <- 5.00 charlie New Balance: bob -> 5.00 charlie """ self.assertEqual(res.stdout, o) def test_transfer_change_dept(self): res = run_bot(self, num[alice], "!schieb bob 5") i = '!transfer 5 bob charlie' res = run_bot(self, num[alice], i) o = \ """New Balance: alice <- 0.00 bob New Balance: alice <- 5.00 charlie New Balance: bob -> 5.00 charlie """ self.assertEqual(res.stdout, o) #TODO: tanken, transfer, cars pay class TestListChangesCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") def test_list_changes_unregistered(self): res = run_bot(self, "+4971576357", "!list-changes") self.assertEqual(res.stdout, 'ERROR: you must register first') def test_list_changes_single_change(self): run_bot(self, num[alice], f"!schieb {bob} 10") res = run_bot(self, num[alice], "!list-changes") msg = \ """Changes from alice 1-1 Change 1: \t!schieb bob 10 \talice <- 10.00 bob """ self.assertEqual(res.stdout, msg) def test_list_changes_single_change_invalid_amount(self): run_bot(self, num[alice], f"!schieb {bob} 10") res = run_bot(self, num[alice], "!list-changes foo") msg = \ """ERROR: the amount of changes to list must be a number""" self.assertEqual(res.stdout, msg) def test_list_changes_single_change_invalid_offset(self): run_bot(self, num[alice], f"!schieb {bob} 10") res = run_bot(self, num[alice], "!list-changes 1 foo") msg = \ """ERROR: the first change to list must be a number""" self.assertEqual(res.stdout, msg) res = run_bot(self, num[alice], "!list-changes 1 20") msg = \ """ERROR: the first change to list is bigger than there are changes""" self.assertEqual(res.stdout, msg) def test_list_changes_two_changes(self): run_bot(self, num[alice], f"!schieb {bob} 10") run_bot(self, num[alice], f"!zieh {charlie} 5") res = run_bot(self, num[alice], "!list-changes") msg = \ """Changes from alice 1-2 Change 1: \t!schieb bob 10 \talice <- 10.00 bob Change 2: \t!zieh charlie 5 \talice -> 5.00 charlie """ self.assertEqual(res.stdout, msg) def test_list_changes_one_of_two(self): run_bot(self, num[alice], f"!schieb {bob} 10") run_bot(self, num[alice], f"!zieh {charlie} 5") res = run_bot(self, num[alice], "!list-changes 1") msg = \ """Changes from alice 2-2 Change 2: \t!zieh charlie 5 \talice -> 5.00 charlie """ self.assertEqual(res.stdout, msg) def test_list_changes_only_second(self): run_bot(self, num[alice], f"!schieb {bob} 10") run_bot(self, num[alice], f"!zieh {charlie} 5") run_bot(self, num[alice], f"!split 9 {bob} {charlie}") res = run_bot(self, num[alice], "!list-changes 1 2") msg = \ """Changes from alice 2-2 Change 2: \t!zieh charlie 5 \talice -> 5.00 charlie """ self.assertEqual(res.stdout, msg) class TestFuckCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") def test_fuck_unregistered(self): res = run_bot(self, "+4971576357", "!fuck") self.assertEqual(res.stdout, 'ERROR: you must register first') def test_fuck_nothing(self): res = run_bot(self, num[alice], "!fuck") self.assertEqual(res.stdout, 'Nothing to rewind') def test_fuck_invalid_change(self): run_bot(self, num[alice], f"!schieb {bob} 10") res = run_bot(self, num[alice], "!fuck foo") msg = "ERROR: change to rewind must be a number" self.assertEqual(res.stdout, msg) res = run_bot(self, num[alice], "!fuck 20") msg = "ERROR: change to rewind is bigger than there are changes" self.assertEqual(res.stdout, msg) def test_fuck_transaction(self): for cmd in ["fuck", "undo", "rewind"]: run_bot(self, num[alice], f"!schieb {bob} 10") res = run_bot(self, num[alice], f"!{cmd}") msg = \ """alice: sorry I fucked up! Rewinding: !schieb bob 10 alice <- 10.00 bob """ self.assertEqual(res.stdout, msg) compare_state("test/state.json_3users") def test_fuck_transfer(self): run_bot(self, num[alice], f"!transfer 5 {bob} {charlie}") res = run_bot(self, num[alice], "!fuck") msg = \ """alice: sorry I fucked up! Rewinding: !transfer 5 bob charlie alice -> 5.00 bob alice <- 5.00 charlie charlie <- 5.00 bob """ self.assertEqual(res.stdout, msg) compare_state("test/state.json_3users") def test_rewind_first(self): run_bot(self, num[alice], f"!split 3 {bob} {charlie}") run_bot(self, num[alice], f"!schieb {bob} 10") run_bot(self, num[alice], f"!nimm {charlie} 10") res = run_bot(self, num[alice], "!fuck 1") msg = ""\ """alice: sorry I fucked up! Rewinding: !split 3 bob charlie alice <- 1.00 bob alice <- 1.00 charlie """ self.assertEqual(res.stdout, msg) def test_rewind_second(self): run_bot(self, num[alice], f"!split 10 {bob} {charlie}") run_bot(self, num[alice], f"!schieb {bob} 10") run_bot(self, num[alice], f"!nimm {charlie} 10") res = run_bot(self, num[alice], "!fuck 2") msg = ""\ """alice: sorry I fucked up! Rewinding: !schieb bob 10 alice <- 10.00 bob """ self.assertEqual(res.stdout, msg) def test_fuck_split(self): run_bot(self, num[alice], "!split 3 bob charlie") res = run_bot(self, num[alice], "!fuck") msg = \ """alice: sorry I fucked up! Rewinding: !split 3 bob charlie alice <- 1.00 bob alice <- 1.00 charlie """ self.assertEqual(res.stdout, msg) compare_state("test/state.json_3users") class TestScheduleCmd(unittest.TestCase): def setUp(self): reset_state("test/state.json_3users") def test_weekly(self): res = run_bot(self, num[alice], "!weekly stuff split 3 bob charlie") msg = \ """Recorded the weekly command "split 3 bob charlie" as "stuff" Running weekly command stuff for alice initially Split 3.00 between 3 -> 1.00 each New Balance: alice: \t<- bob 1.00 \t<- charlie 1.00 \tBalance: 2.00""" self.assertEqual(res.stdout, msg) save_state("test/state.json_schedule_weekly") res = run_bot(self, num[alice], "!fuck") msg = \ """alice: sorry I fucked up! Rewinding: split 3 bob charlie alice <- 1.00 bob alice <- 1.00 charlie 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")) res = run_bot(self, num[alice], "!fuck") # There is no new line because the real bot uses two Messages msg = \ """Nothing to rewindRunning weekly command stuff for alice triggered on {} Split 3.00 between 3 -> 1.00 each New Balance: alice: \t<- bob 2.00 \t<- charlie 2.00 \tBalance: 4.00""".format(now.isoformat()) self.assertEqual(res.stdout, msg) compare_state("test/state.json_schedule_weekly") res = run_bot(self, num[alice], "!fuck") 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")) res = run_bot(self, num[alice], "!fuck") # There is no new line because the real bot uses two Messages msg = \ """Nothing to rewindRunning weekly command stuff for alice triggered on {} Split 3.00 between 3 -> 1.00 each New Balance: alice: \t<- bob 2.00 \t<- charlie 2.00 \tBalance: 4.00Running weekly command stuff for alice triggered on {} Split 3.00 between 3 -> 1.00 each New Balance: alice: \t<- bob 3.00 \t<- charlie 3.00 \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): self.maxDiff = None res = run_bot(self, num[alice], "!monthly stuff split 3 bob charlie") msg = \ """Recorded the monthly command "split 3 bob charlie" as "stuff" Running monthly command stuff for alice initially Split 3.00 between 3 -> 1.00 each New Balance: alice: \t<- bob 1.00 \t<- charlie 1.00 \tBalance: 2.00""" self.assertEqual(res.stdout, msg) save_state("test/state.json_schedule_monthly") res = run_bot(self, num[alice], "!fuck") msg = \ """alice: sorry I fucked up! Rewinding: split 3 bob charlie alice <- 1.00 bob alice <- 1.00 charlie Cancelled the monthly cmd "stuff\"""" self.assertEqual(res.stdout, msg) # Last exec one month ago if now.month > 1: 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")) res = run_bot(self, num[alice], "!fuck") # There is no new line because the real bot uses two Messages msg = \ """Nothing to rewindRunning monthly command stuff for alice triggered on {} Split 3.00 between 3 -> 1.00 each New Balance: alice: \t<- bob 2.00 \t<- charlie 2.00 \tBalance: 4.00""".format(now.isoformat()) self.assertEqual(res.stdout, msg) compare_state("test/state.json_schedule_monthly") res = run_bot(self, num[alice], "!fuck") self.assertEqual(res.stdout, 'Nothing to rewind') # Last exec two month ago if now.month > 2: 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) 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 msg = \ """Nothing to rewindRunning monthly command stuff for alice triggered on {} Split 3.00 between 3 -> 1.00 each New Balance: alice: \t<- bob 2.00 \t<- charlie 2.00 \tBalance: 4.00Running monthly command stuff for alice triggered on {} Split 3.00 between 3 -> 1.00 each New Balance: alice: \t<- bob 3.00 \t<- charlie 3.00 \tBalance: 6.00""".format(one_month_ago.isoformat(), now.isoformat()) self.assertEqual(res.stdout, msg) 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") msg = \ """Recorded the monthly command "cars pay fiat 3" as "versicherung" Running monthly command versicherung for alice initially alice payed 3.00 Transferring 100.00% of everybody's charges New Balances: alice: \tAll fine :) \tCars: \t<- fiat 3.00 \tLiability: 3.00 fiat: \t-> alice 3.00 \tBalance: -3.00""" self.assertEqual(res.stdout, msg) class TestThanks(unittest.TestCase): def test_thanks(self): res = run_bot(self, num[alice], "!thanks") self.assertEqual( res.stdout, f"You are welcome. It is a pleasure to work with you, {alice}.") def test_thanks_nick(self): res = run_bot(self, num[alice], "!thanks pussy") self.assertEqual( res.stdout, f"You are welcome. It is a pleasure to work with you, {alice}.\nBut don't call me pussy." ) if __name__ == '__main__': unittest.main()