aboutsummaryrefslogtreecommitdiff
path: root/ihatemoney/tests/tests.py
diff options
context:
space:
mode:
authorAndrew Dickinson <Andrew-Dickinson@users.noreply.github.com>2020-04-20 09:30:27 -0400
committerGitHub <noreply@github.com>2020-04-20 15:30:27 +0200
commit026a0722357d74b143ed2d974ad2d871a56041b3 (patch)
tree2f23323f01e5ec1dec07ef1032acc407cba38879 /ihatemoney/tests/tests.py
parent91ef80ebb712b06b6c48336beeb7f60219b0f062 (diff)
downloadihatemoney-mirror-026a0722357d74b143ed2d974ad2d871a56041b3.zip
ihatemoney-mirror-026a0722357d74b143ed2d974ad2d871a56041b3.tar.gz
ihatemoney-mirror-026a0722357d74b143ed2d974ad2d871a56041b3.tar.bz2
Add Project History Page (#553)
Co-Authored-By: Glandos <bugs-github@antipoul.fr> All project activity can be tracked, using SQLAlchemy-continuum. IP addresses can optionally be recorded.
Diffstat (limited to 'ihatemoney/tests/tests.py')
-rw-r--r--ihatemoney/tests/tests.py648
1 files changed, 647 insertions, 1 deletions
diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py
index c4b1585..5dff64d 100644
--- a/ihatemoney/tests/tests.py
+++ b/ihatemoney/tests/tests.py
@@ -17,7 +17,8 @@ from flask_testing import TestCase
from ihatemoney.run import create_app, db, load_configuration
from ihatemoney.manage import GenerateConfig, GeneratePasswordHash, DeleteProject
-from ihatemoney import models
+from ihatemoney import models, history
+from ihatemoney.versioning import LoggingMode
from ihatemoney import utils
from sqlalchemy import orm
@@ -843,6 +844,7 @@ class BudgetTestCase(IhatemoneyTestCase):
"name": "Super raclette party!",
"contact_email": "alexis@notmyidea.org",
"password": "didoudida",
+ "logging_preference": LoggingMode.ENABLED.value,
}
resp = self.client.post("/raclette/edit", data=new_data, follow_redirects=True)
@@ -1517,6 +1519,7 @@ class APITestCase(IhatemoneyTestCase):
"name": "raclette",
"contact_email": "raclette@notmyidea.org",
"id": "raclette",
+ "logging_preference": 1,
}
decoded_resp = json.loads(resp.data.decode("utf-8"))
self.assertDictEqual(decoded_resp, expected)
@@ -1528,6 +1531,7 @@ class APITestCase(IhatemoneyTestCase):
"contact_email": "yeah@notmyidea.org",
"password": "raclette",
"name": "The raclette party",
+ "project_history": "y",
},
headers=self.get_auth("raclette"),
)
@@ -1544,6 +1548,7 @@ class APITestCase(IhatemoneyTestCase):
"contact_email": "yeah@notmyidea.org",
"members": [],
"id": "raclette",
+ "logging_preference": 1,
}
decoded_resp = json.loads(resp.data.decode("utf-8"))
self.assertDictEqual(decoded_resp, expected)
@@ -2104,12 +2109,32 @@ class APITestCase(IhatemoneyTestCase):
"contact_email": "raclette@notmyidea.org",
"id": "raclette",
"name": "raclette",
+ "logging_preference": 1,
}
self.assertStatus(200, req)
decoded_req = json.loads(req.data.decode("utf-8"))
self.assertDictEqual(decoded_req, expected)
+ def test_log_created_from_api_call(self):
+ # create a project
+ self.api_create("raclette")
+ self.login("raclette")
+
+ # add members
+ self.api_add_member("raclette", "alexis")
+
+ resp = self.client.get("/raclette/history", follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Person %s added" % em_surround("alexis"), resp.data.decode("utf-8")
+ )
+ self.assertIn(
+ "Project %s added" % em_surround("raclette"), resp.data.decode("utf-8"),
+ )
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+
class ServerTestCase(IhatemoneyTestCase):
def test_homepage(self):
@@ -2224,5 +2249,626 @@ class ModelsTestCase(IhatemoneyTestCase):
self.assertEqual(bill.pay_each(), pay_each_expected)
+def em_surround(string, regex_escape=False):
+ if regex_escape:
+ return r'<em class="font-italic">%s<\/em>' % string
+ else:
+ return '<em class="font-italic">%s</em>' % string
+
+
+class HistoryTestCase(IhatemoneyTestCase):
+ def setUp(self):
+ super().setUp()
+ self.post_project("demo")
+ self.login("demo")
+
+ def test_simple_create_logentry_no_ip(self):
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Project %s added" % em_surround("demo"), resp.data.decode("utf-8"),
+ )
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 1)
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+
+ def change_privacy_to(self, logging_preference):
+ # Change only logging_preferences
+ new_data = {
+ "name": "demo",
+ "contact_email": "demo@notmyidea.org",
+ "password": "demo",
+ }
+
+ if logging_preference != LoggingMode.DISABLED:
+ new_data["project_history"] = "y"
+ if logging_preference == LoggingMode.RECORD_IP:
+ new_data["ip_recording"] = "y"
+
+ # Disable History
+ resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+ self.assertNotIn("danger", resp.data.decode("utf-8"))
+
+ resp = self.client.get("/demo/edit")
+ self.assertEqual(resp.status_code, 200)
+ if logging_preference == LoggingMode.DISABLED:
+ self.assertIn('<input id="project_history"', resp.data.decode("utf-8"))
+ else:
+ self.assertIn(
+ '<input checked id="project_history"', resp.data.decode("utf-8")
+ )
+
+ if logging_preference == LoggingMode.RECORD_IP:
+ self.assertIn('<input checked id="ip_recording"', resp.data.decode("utf-8"))
+ else:
+ self.assertIn('<input id="ip_recording"', resp.data.decode("utf-8"))
+
+ def assert_empty_history_logging_disabled(self):
+ resp = self.client.get("/demo/history")
+ self.assertIn(
+ "This project has history disabled. New actions won't appear below. ",
+ resp.data.decode("utf-8"),
+ )
+ self.assertIn(
+ "Nothing to list", resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "The table below reflects actions recorded prior to disabling project history.",
+ resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "Some entries below contain IP addresses,", resp.data.decode("utf-8"),
+ )
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+ self.assertNotIn("<td> -- </td>", resp.data.decode("utf-8"))
+ self.assertNotIn(
+ "Project %s added" % em_surround("demo"), resp.data.decode("utf-8")
+ )
+
+ def test_project_edit(self):
+ new_data = {
+ "name": "demo2",
+ "contact_email": "demo2@notmyidea.org",
+ "password": "123456",
+ "project_history": "y",
+ }
+
+ resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Project %s added" % em_surround("demo"), resp.data.decode("utf-8")
+ )
+ self.assertIn(
+ "Project contact email changed to %s" % em_surround("demo2@notmyidea.org"),
+ resp.data.decode("utf-8"),
+ )
+ self.assertIn(
+ "Project private code changed", resp.data.decode("utf-8"),
+ )
+ self.assertIn(
+ "Project renamed to %s" % em_surround("demo2"), resp.data.decode("utf-8"),
+ )
+ self.assertLess(
+ resp.data.decode("utf-8").index("Project renamed "),
+ resp.data.decode("utf-8").index("Project contact email changed to "),
+ )
+ self.assertLess(
+ resp.data.decode("utf-8").index("Project renamed "),
+ resp.data.decode("utf-8").index("Project private code changed"),
+ )
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 4)
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+
+ def test_project_privacy_edit(self):
+ resp = self.client.get("/demo/edit")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ '<input checked id="project_history" name="project_history" type="checkbox" value="y">',
+ resp.data.decode("utf-8"),
+ )
+
+ self.change_privacy_to(LoggingMode.DISABLED)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn("Disabled Project History\n", resp.data.decode("utf-8"))
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+
+ self.change_privacy_to(LoggingMode.RECORD_IP)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Enabled Project History & IP Address Recording", resp.data.decode("utf-8")
+ )
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
+ self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 1)
+
+ self.change_privacy_to(LoggingMode.ENABLED)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn("Disabled IP Address Recording\n", resp.data.decode("utf-8"))
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
+ self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 2)
+
+ def test_project_privacy_edit2(self):
+ self.change_privacy_to(LoggingMode.RECORD_IP)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn("Enabled IP Address Recording\n", resp.data.decode("utf-8"))
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 1)
+ self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 1)
+
+ self.change_privacy_to(LoggingMode.DISABLED)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Disabled Project History & IP Address Recording", resp.data.decode("utf-8")
+ )
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 1)
+ self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 2)
+
+ self.change_privacy_to(LoggingMode.ENABLED)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn("Enabled Project History\n", resp.data.decode("utf-8"))
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
+ self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 2)
+
+ def do_misc_database_operations(self, logging_mode):
+ new_data = {
+ "name": "demo2",
+ "contact_email": "demo2@notmyidea.org",
+ "password": "123456",
+ }
+
+ # Keep privacy settings where they were
+ if logging_mode != LoggingMode.DISABLED:
+ new_data["project_history"] = "y"
+ if logging_mode == LoggingMode.RECORD_IP:
+ new_data["ip_recording"] = "y"
+
+ resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+
+ # adds a member to this project
+ resp = self.client.post(
+ "/demo/members/add", data={"name": "alexis"}, follow_redirects=True
+ )
+ self.assertEqual(resp.status_code, 200)
+
+ user_id = models.Person.query.one().id
+
+ # create a bill
+ resp = self.client.post(
+ "/demo/add",
+ data={
+ "date": "2011-08-10",
+ "what": "fromage à raclette",
+ "payer": user_id,
+ "payed_for": [user_id],
+ "amount": "25",
+ },
+ follow_redirects=True,
+ )
+ self.assertEqual(resp.status_code, 200)
+
+ bill_id = models.Bill.query.one().id
+
+ # edit the bill
+ resp = self.client.post(
+ "/demo/edit/%i" % bill_id,
+ data={
+ "date": "2011-08-10",
+ "what": "fromage à raclette",
+ "payer": user_id,
+ "payed_for": [user_id],
+ "amount": "10",
+ },
+ follow_redirects=True,
+ )
+ self.assertEqual(resp.status_code, 200)
+ # delete the bill
+ resp = self.client.get("/demo/delete/%i" % bill_id, follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+
+ # delete user using POST method
+ resp = self.client.post(
+ "/demo/members/%i/delete" % user_id, follow_redirects=True
+ )
+ self.assertEqual(resp.status_code, 200)
+
+ def test_disable_clear_no_new_records(self):
+ # Disable logging
+ self.change_privacy_to(LoggingMode.DISABLED)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "This project has history disabled. New actions won't appear below. ",
+ resp.data.decode("utf-8"),
+ )
+ self.assertIn(
+ "The table below reflects actions recorded prior to disabling project history.",
+ resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "Nothing to list", resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "Some entries below contain IP addresses,", resp.data.decode("utf-8"),
+ )
+
+ # Clear Existing Entries
+ resp = self.client.post("/demo/erase_history", follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+ self.assert_empty_history_logging_disabled()
+
+ # Do lots of database operations & check that there's still no history
+ self.do_misc_database_operations(LoggingMode.DISABLED)
+
+ self.assert_empty_history_logging_disabled()
+
+ def test_clear_ip_records(self):
+ # Enable IP Recording
+ self.change_privacy_to(LoggingMode.RECORD_IP)
+
+ # Do lots of database operations to generate IP address entries
+ self.do_misc_database_operations(LoggingMode.RECORD_IP)
+
+ # Disable IP Recording
+ self.change_privacy_to(LoggingMode.ENABLED)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertNotIn(
+ "This project has history disabled. New actions won't appear below. ",
+ resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "The table below reflects actions recorded prior to disabling project history.",
+ resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "Nothing to list", resp.data.decode("utf-8"),
+ )
+ self.assertIn(
+ "Some entries below contain IP addresses,", resp.data.decode("utf-8"),
+ )
+ self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 10)
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 1)
+
+ # Generate more operations to confirm additional IP info isn't recorded
+ self.do_misc_database_operations(LoggingMode.ENABLED)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 10)
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 6)
+
+ # Clear IP Data
+ resp = self.client.post("/demo/strip_ip_addresses", follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+ self.assertNotIn(
+ "This project has history disabled. New actions won't appear below. ",
+ resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "The table below reflects actions recorded prior to disabling project history.",
+ resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "Nothing to list", resp.data.decode("utf-8"),
+ )
+ self.assertNotIn(
+ "Some entries below contain IP addresses,", resp.data.decode("utf-8"),
+ )
+ self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 0)
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 16)
+
+ def test_logs_for_common_actions(self):
+ # adds a member to this project
+ resp = self.client.post(
+ "/demo/members/add", data={"name": "alexis"}, follow_redirects=True
+ )
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Person %s added" % em_surround("alexis"), resp.data.decode("utf-8")
+ )
+
+ # create a bill
+ resp = self.client.post(
+ "/demo/add",
+ data={
+ "date": "2011-08-10",
+ "what": "fromage à raclette",
+ "payer": 1,
+ "payed_for": [1],
+ "amount": "25",
+ },
+ follow_redirects=True,
+ )
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Bill %s added" % em_surround("25.0 for fromage à raclette"),
+ resp.data.decode("utf-8"),
+ )
+
+ # edit the bill
+ resp = self.client.post(
+ "/demo/edit/1",
+ data={
+ "date": "2011-08-10",
+ "what": "new thing",
+ "payer": 1,
+ "payed_for": [1],
+ "amount": "10",
+ },
+ follow_redirects=True,
+ )
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Bill %s added" % em_surround("25.0 for fromage à raclette"),
+ resp.data.decode("utf-8"),
+ )
+ self.assertRegex(
+ resp.data.decode("utf-8"),
+ r"Bill %s:\s* Amount changed\s* from %s\s* to %s"
+ % (
+ em_surround("25.0 for fromage à raclette", regex_escape=True),
+ em_surround("25.0", regex_escape=True),
+ em_surround("10.0", regex_escape=True),
+ ),
+ )
+ self.assertIn(
+ "Bill %s renamed to %s"
+ % (em_surround("25.0 for fromage à raclette"), em_surround("new thing"),),
+ resp.data.decode("utf-8"),
+ )
+ self.assertLess(
+ resp.data.decode("utf-8").index(
+ "Bill %s renamed to" % em_surround("25.0 for fromage à raclette")
+ ),
+ resp.data.decode("utf-8").index("Amount changed"),
+ )
+
+ # delete the bill
+ resp = self.client.get("/demo/delete/1", follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Bill %s removed" % em_surround("10.0 for new thing"),
+ resp.data.decode("utf-8"),
+ )
+
+ # edit user
+ resp = self.client.post(
+ "/demo/members/1/edit",
+ data={"weight": 2, "name": "new name"},
+ follow_redirects=True,
+ )
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertRegex(
+ resp.data.decode("utf-8"),
+ r"Person %s:\s* Weight changed\s* from %s\s* to %s"
+ % (
+ em_surround("alexis", regex_escape=True),
+ em_surround("1.0", regex_escape=True),
+ em_surround("2.0", regex_escape=True),
+ ),
+ )
+ self.assertIn(
+ "Person %s renamed to %s"
+ % (em_surround("alexis"), em_surround("new name"),),
+ resp.data.decode("utf-8"),
+ )
+ self.assertLess(
+ resp.data.decode("utf-8").index(
+ "Person %s renamed" % em_surround("alexis")
+ ),
+ resp.data.decode("utf-8").index("Weight changed"),
+ )
+
+ # delete user using POST method
+ resp = self.client.post("/demo/members/1/delete", follow_redirects=True)
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(
+ "Person %s removed" % em_surround("new name"), resp.data.decode("utf-8")
+ )
+
+ def test_double_bill_double_person_edit_second(self):
+
+ # add two members
+ self.client.post("/demo/members/add", data={"name": "User 1"})
+ self.client.post("/demo/members/add", data={"name": "User 2"})
+
+ # add two bills
+ self.client.post(
+ "/demo/add",
+ data={
+ "date": "2020-04-13",
+ "what": "Bill 1",
+ "payer": 1,
+ "payed_for": [1, 2],
+ "amount": "25",
+ },
+ )
+ self.client.post(
+ "/demo/add",
+ data={
+ "date": "2020-04-13",
+ "what": "Bill 2",
+ "payer": 1,
+ "payed_for": [1, 2],
+ "amount": "20",
+ },
+ )
+
+ # Should be 5 history entries at this point
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 5)
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+
+ # Edit ONLY the amount on the first bill
+ self.client.post(
+ "/demo/edit/1",
+ data={
+ "date": "2020-04-13",
+ "what": "Bill 1",
+ "payer": 1,
+ "payed_for": [1, 2],
+ "amount": "88",
+ },
+ )
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertRegex(
+ resp.data.decode("utf-8"),
+ r"Bill %s:\s* Amount changed\s* from %s\s* to %s"
+ % (
+ em_surround("25.0 for Bill 1", regex_escape=True),
+ em_surround("25.0", regex_escape=True),
+ em_surround("88.0", regex_escape=True),
+ ),
+ )
+
+ self.assertNotRegex(
+ resp.data.decode("utf-8"),
+ r"Removed\s* %s\s* and\s* %s\s* from\s* owers list"
+ % (
+ em_surround("User 1", regex_escape=True),
+ em_surround("User 2", regex_escape=True),
+ ),
+ resp.data.decode("utf-8"),
+ )
+
+ # Should be 6 history entries at this point
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 6)
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+
+ def test_bill_add_remove_add(self):
+ # add two members
+ self.client.post("/demo/members/add", data={"name": "User 1"})
+ self.client.post("/demo/members/add", data={"name": "User 2"})
+
+ # add 1 bill
+ self.client.post(
+ "/demo/add",
+ data={
+ "date": "2020-04-13",
+ "what": "Bill 1",
+ "payer": 1,
+ "payed_for": [1, 2],
+ "amount": "25",
+ },
+ )
+
+ # delete the bill
+ self.client.get("/demo/delete/1", follow_redirects=True)
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 5)
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+ self.assertIn(
+ "Bill %s added" % em_surround("25.0 for Bill 1"), resp.data.decode("utf-8")
+ )
+ self.assertIn(
+ "Bill %s removed" % em_surround("25.0 for Bill 1"),
+ resp.data.decode("utf-8"),
+ )
+
+ # Add a new bill
+ self.client.post(
+ "/demo/add",
+ data={
+ "date": "2020-04-13",
+ "what": "Bill 2",
+ "payer": 1,
+ "payed_for": [1, 2],
+ "amount": "20",
+ },
+ )
+
+ resp = self.client.get("/demo/history")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 6)
+ self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
+ self.assertIn(
+ "Bill %s added" % em_surround("25.0 for Bill 1"), resp.data.decode("utf-8")
+ )
+ self.assertEqual(
+ resp.data.decode("utf-8").count(
+ "Bill %s added" % em_surround("25.0 for Bill 1")
+ ),
+ 1,
+ )
+ self.assertIn(
+ "Bill %s added" % em_surround("20.0 for Bill 2"), resp.data.decode("utf-8")
+ )
+ self.assertIn(
+ "Bill %s removed" % em_surround("25.0 for Bill 1"),
+ resp.data.decode("utf-8"),
+ )
+
+ def test_double_bill_double_person_edit_second_no_web(self):
+ u1 = models.Person(project_id="demo", name="User 1")
+ u2 = models.Person(project_id="demo", name="User 1")
+
+ models.db.session.add(u1)
+ models.db.session.add(u2)
+ models.db.session.commit()
+
+ b1 = models.Bill(what="Bill 1", payer_id=u1.id, owers=[u2], amount=10,)
+ b2 = models.Bill(what="Bill 2", payer_id=u2.id, owers=[u2], amount=11,)
+
+ # This db commit exposes the "spurious owers edit" bug
+ models.db.session.add(b1)
+ models.db.session.commit()
+
+ models.db.session.add(b2)
+ models.db.session.commit()
+
+ history_list = history.get_history(models.Project.query.get("demo"))
+ self.assertEqual(len(history_list), 5)
+
+ # Change just the amount
+ b1.amount = 5
+ models.db.session.commit()
+
+ history_list = history.get_history(models.Project.query.get("demo"))
+ for entry in history_list:
+ if "prop_changed" in entry:
+ self.assertNotIn("owers", entry["prop_changed"])
+ self.assertEqual(len(history_list), 6)
+
+
if __name__ == "__main__":
unittest.main()