ihatemoney/ihatemoney/tests/import_test.py
2024-03-30 19:26:53 +01:00

701 lines
25 KiB
Python

import copy
import json
import pytest
from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
from ihatemoney.utils import list_of_dicts2csv, list_of_dicts2json
@pytest.fixture
def import_data(request: pytest.FixtureRequest):
data = [
{
"date": "2017-01-01",
"what": "refund",
"amount": 13.33,
"payer_name": "tata",
"payer_weight": 1.0,
"bill_type": "Expense",
"owers": ["jeanne"],
},
{
"date": "2016-12-31",
"what": "red wine",
"bill_type": "Expense",
"amount": 200.0,
"payer_name": "jeanne",
"payer_weight": 1.0,
"owers": ["zorglub", "tata"],
},
{
"date": "2016-12-31",
"bill_type": "Expense",
"what": "fromage a raclette",
"amount": 10.0,
"payer_name": "zorglub",
"payer_weight": 2.0,
"owers": ["zorglub", "jeanne", "tata", "pepe"],
},
]
request.cls.data = data
yield data
class CommonTestCase(object):
@pytest.mark.usefixtures("import_data")
class Import(IhatemoneyTestCase):
def setUp(self):
super().setUp()
self.data = [
{
"date": "2017-01-01",
"what": "refund",
"bill_type": "Expense",
"amount": 13.33,
"payer_name": "tata",
"payer_weight": 1.0,
"owers": ["jeanne"],
},
{
"date": "2016-12-31",
"what": "red wine",
"bill_type": "Expense",
"amount": 200.0,
"payer_name": "jeanne",
"payer_weight": 1.0,
"owers": ["zorglub", "tata"],
},
{
"date": "2016-12-31",
"what": "a raclette",
"bill_type": "Expense",
"amount": 10.0,
"payer_name": "zorglub",
"payer_weight": 2.0,
"owers": ["zorglub", "jeanne", "tata", "pepe"],
},
]
def populate_data_with_currencies(self, currencies):
for d in range(len(self.data)):
self.data[d]["currency"] = currencies[d]
def test_import_currencies_in_empty_project_with_currency(self):
# Import JSON with currencies in an empty project with a default currency
self.post_project("raclette", default_currency="EUR")
self.login("raclette")
project = self.get_project("raclette")
self.populate_data_with_currencies(["EUR", "CAD", "EUR"])
self.import_project("raclette", self.generate_form_data(self.data))
bills = project.get_pretty_bills()
# Check if all bills have been added
assert len(bills) == len(self.data)
# Check if name of bills are ok
b = [e["what"] for e in bills]
b.sort()
ref = [e["what"] for e in self.data]
ref.sort()
assert b == ref
# Check if other informations in bill are ok
for d in self.data:
for b in bills:
if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"]
assert b["amount"] == d["amount"]
assert b["currency"] == d["currency"]
assert b["payer_weight"] == d["payer_weight"]
assert b["date"] == d["date"]
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]]
list_project.sort()
list_json = [ower for ower in d["owers"]]
list_json.sort()
assert list_project == list_json
def test_import_single_currency_in_empty_project_without_currency(self):
# Import JSON with a single currency in an empty project with no
# default currency. It should work by stripping the currency from
# bills.
self.post_project("raclette")
self.login("raclette")
project = self.get_project("raclette")
self.populate_data_with_currencies(["EUR", "EUR", "EUR"])
self.import_project("raclette", self.generate_form_data(self.data))
bills = project.get_pretty_bills()
# Check if all bills have been added
assert len(bills) == len(self.data)
# Check if name of bills are ok
b = [e["what"] for e in bills]
b.sort()
ref = [e["what"] for e in self.data]
ref.sort()
assert b == ref
# Check if other informations in bill are ok
for d in self.data:
for b in bills:
if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"]
assert b["amount"] == d["amount"]
# Currency should have been stripped
assert b["currency"] == "XXX"
assert b["payer_weight"] == d["payer_weight"]
assert b["date"] == d["date"]
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]]
list_project.sort()
list_json = [ower for ower in d["owers"]]
list_json.sort()
assert list_project == list_json
def test_import_multiple_currencies_in_empty_project_without_currency(self):
# Import JSON with multiple currencies in an empty project with no
# default currency. It should fail.
self.post_project("raclette")
self.login("raclette")
project = self.get_project("raclette")
self.populate_data_with_currencies(["EUR", "CAD", "EUR"])
# Import should fail
self.import_project("raclette", self.generate_form_data(self.data), 400)
bills = project.get_pretty_bills()
# Check that there are no bills
assert len(bills) == 0
def test_import_no_currency_in_empty_project_with_currency(self):
# Import JSON without currencies (from ihatemoney < 5) in an empty
# project with a default currency.
self.post_project("raclette", default_currency="EUR")
self.login("raclette")
project = self.get_project("raclette")
self.import_project("raclette", self.generate_form_data(self.data))
bills = project.get_pretty_bills()
# Check if all bills have been added
assert len(bills) == len(self.data)
# Check if name of bills are ok
b = [e["what"] for e in bills]
b.sort()
ref = [e["what"] for e in self.data]
ref.sort()
assert b == ref
# Check if other informations in bill are ok
for d in self.data:
for b in bills:
if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"]
assert b["amount"] == d["amount"]
# All bills are converted to default project currency
assert b["currency"] == "EUR"
assert b["payer_weight"] == d["payer_weight"]
assert b["date"] == d["date"]
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]]
list_project.sort()
list_json = [ower for ower in d["owers"]]
list_json.sort()
assert list_project == list_json
def test_import_no_currency_in_empty_project_without_currency(self):
# Import JSON without currencies (from ihatemoney < 5) in an empty
# project with no default currency.
self.post_project("raclette")
self.login("raclette")
project = self.get_project("raclette")
self.import_project("raclette", self.generate_form_data(self.data))
bills = project.get_pretty_bills()
# Check if all bills have been added
assert len(bills) == len(self.data)
# Check if name of bills are ok
b = [e["what"] for e in bills]
b.sort()
ref = [e["what"] for e in self.data]
ref.sort()
assert b == ref
# Check if other informations in bill are ok
for d in self.data:
for b in bills:
if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"]
assert b["amount"] == d["amount"]
assert b["currency"] == "XXX"
assert b["payer_weight"] == d["payer_weight"]
assert b["date"] == d["date"]
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]]
list_project.sort()
list_json = [ower for ower in d["owers"]]
list_json.sort()
assert list_project == list_json
def test_import_partial_project(self):
# Import a JSON in a project with already existing data
self.post_project("raclette")
self.login("raclette")
project = self.get_project("raclette")
self.client.post(
"/raclette/members/add", data={"name": "zorglub", "weight": 2}
)
self.client.post("/raclette/members/add", data={"name": "jeanne"})
self.client.post("/raclette/members/add", data={"name": "tata"})
self.client.post(
"/raclette/add",
data={
"date": "2016-12-31",
"what": "red wine",
"bill_type": "Expense",
"payer": 2,
"payed_for": [1, 3],
"amount": "200",
},
)
self.populate_data_with_currencies(["XXX", "XXX", "XXX"])
self.import_project("raclette", self.generate_form_data(self.data))
bills = project.get_pretty_bills()
# Check if all bills have been added
assert len(bills) == len(self.data)
# Check if name of bills are ok
b = [e["what"] for e in bills]
b.sort()
ref = [e["what"] for e in self.data]
ref.sort()
assert b == ref
# Check if other informations in bill are ok
for d in self.data:
for b in bills:
if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"]
assert b["amount"] == d["amount"]
assert b["currency"] == d["currency"]
assert b["payer_weight"] == d["payer_weight"]
assert b["date"] == d["date"]
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]]
list_project.sort()
list_json = [ower for ower in d["owers"]]
list_json.sort()
assert list_project == list_json
def test_import_wrong_data(self):
self.post_project("raclette")
self.login("raclette")
data_wrong_keys = [
{
"checked": False,
"dimensions": {"width": 5, "height": 10},
"id": 1,
"name": "A green door",
"price": 12.5,
"tags": ["home", "green"],
}
]
data_amount_missing = [
{
"date": "2017-01-01",
"what": "refund",
"bill_type": "Reimbursement",
"payer_name": "tata",
"payer_weight": 1.0,
"owers": ["jeanne"],
}
]
for data in [data_wrong_keys, data_amount_missing]:
# Import should fail
self.import_project("raclette", self.generate_form_data(data), 400)
class TestExport(IhatemoneyTestCase):
def test_export(self):
# Export a simple project without currencies
self.post_project("raclette")
# add participants
self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2})
self.client.post("/raclette/members/add", data={"name": "jeanne"})
self.client.post("/raclette/members/add", data={"name": "tata"})
self.client.post("/raclette/members/add", data={"name": "pépé"})
# create bills
self.client.post(
"/raclette/add",
data={
"date": "2016-12-31",
"bill_type": "Expense",
"what": "à raclette",
"payer": 1,
"payed_for": [1, 2, 3, 4],
"amount": "10.0",
},
)
self.client.post(
"/raclette/add",
data={
"date": "2016-12-31",
"bill_type": "Expense",
"what": "red wine",
"payer": 2,
"payed_for": [1, 3],
"amount": "200",
},
)
self.client.post(
"/raclette/add",
data={
"date": "2017-01-01",
"bill_type": "Reimbursement",
"what": "refund",
"payer": 3,
"payed_for": [2],
"amount": "13.33",
},
)
# generate json export of bills
resp = self.client.get("/raclette/export/bills.json")
expected = [
{
"date": "2017-01-01",
"bill_type": "Reimbursement",
"what": "refund",
"amount": 13.33,
"currency": "XXX",
"payer_name": "tata",
"payer_weight": 1.0,
"owers": ["jeanne"],
},
{
"date": "2016-12-31",
"bill_type": "Expense",
"what": "red wine",
"amount": 200.0,
"currency": "XXX",
"payer_name": "jeanne",
"payer_weight": 1.0,
"owers": ["zorglub", "tata"],
},
{
"date": "2016-12-31",
"bill_type": "Expense",
"what": "\xe0 raclette",
"amount": 10.0,
"currency": "XXX",
"payer_name": "zorglub",
"payer_weight": 2.0,
"owers": ["zorglub", "jeanne", "tata", "p\xe9p\xe9"],
},
]
assert json.loads(resp.data.decode("utf-8")) == expected
# generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv")
expected = [
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers",
"2017-01-01,refund,Reimbursement,XXX,13.33,tata,1.0,jeanne",
'2016-12-31,red wine,Expense,XXX,200.0,jeanne,1.0,"zorglub, tata"',
'2016-12-31,à raclette,Expense,10.0,XXX,zorglub,2.0,"zorglub, jeanne, tata, pépé"',
]
received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(","))
# generate json export of transactions
resp = self.client.get("/raclette/export/transactions.json")
expected = [
{
"amount": 2.00,
"currency": "XXX",
"receiver": "jeanne",
"ower": "p\xe9p\xe9",
},
{"amount": 55.34, "currency": "XXX", "receiver": "jeanne", "ower": "tata"},
{
"amount": 127.33,
"currency": "XXX",
"receiver": "jeanne",
"ower": "zorglub",
},
]
assert json.loads(resp.data.decode("utf-8")) == expected
# generate csv export of transactions
resp = self.client.get("/raclette/export/transactions.csv")
expected = [
"amount,currency,receiver,ower",
"2.0,XXX,jeanne,pépé",
"55.34,XXX,jeanne,tata",
"127.33,XXX,jeanne,zorglub",
]
received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(","))
# wrong export_format should return a 404
resp = self.client.get("/raclette/export/transactions.wrong")
assert resp.status_code == 404
@pytest.mark.skip(reason="Currency conversion is broken")
def test_export_with_currencies(self):
self.post_project("raclette", default_currency="EUR")
# add participants
self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2})
self.client.post("/raclette/members/add", data={"name": "jeanne"})
self.client.post("/raclette/members/add", data={"name": "tata"})
self.client.post("/raclette/members/add", data={"name": "pépé"})
# create bills
self.client.post(
"/raclette/add",
data={
"date": "2016-12-31",
"what": "à raclette",
"bill_type": "Expense",
"payer": 1,
"payed_for": [1, 2, 3, 4],
"amount": "10.0",
"original_currency": "EUR",
},
)
self.client.post(
"/raclette/add",
data={
"date": "2016-12-31",
"what": "poutine from Québec",
"bill_type": "Expense",
"payer": 2,
"payed_for": [1, 3],
"amount": "100",
"original_currency": "CAD",
},
)
self.client.post(
"/raclette/add",
data={
"date": "2017-01-01",
"what": "refund",
"bill_type": "Reimbursement",
"payer": 3,
"payed_for": [2],
"amount": "13.33",
"original_currency": "EUR",
},
)
# generate json export of bills
resp = self.client.get("/raclette/export/bills.json")
expected = [
{
"date": "2017-01-01",
"what": "refund",
"bill_type": "Reimbursement",
"amount": 13.33,
"currency": "EUR",
"payer_name": "tata",
"payer_weight": 1.0,
"owers": ["jeanne"],
},
{
"date": "2016-12-31",
"what": "poutine from Qu\xe9bec",
"bill_type": "Expense",
"amount": 100.0,
"currency": "CAD",
"payer_name": "jeanne",
"payer_weight": 1.0,
"owers": ["zorglub", "tata"],
},
{
"date": "2016-12-31",
"what": "fromage \xe0 raclette",
"bill_type": "Expense",
"amount": 10.0,
"currency": "EUR",
"payer_name": "zorglub",
"payer_weight": 2.0,
"owers": ["zorglub", "jeanne", "tata", "p\xe9p\xe9"],
},
]
assert json.loads(resp.data.decode("utf-8")) == expected
# generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv")
expected = [
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers",
"2017-01-01,refund,Reimbursement,13.33,EUR,tata,1.0,jeanne",
'2016-12-31,poutine from Québec,Expense,100.0,CAD,jeanne,1.0,"zorglub, tata"',
'2016-12-31,à raclette,Expense,10.0,EUR,zorglub,2.0,"zorglub, jeanne, tata, pépé"',
]
received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(","))
# generate json export of transactions (in EUR!)
resp = self.client.get("/raclette/export/transactions.json")
expected = [
{
"amount": 2.00,
"currency": "EUR",
"receiver": "jeanne",
"ower": "p\xe9p\xe9",
},
{"amount": 10.89, "currency": "EUR", "receiver": "jeanne", "ower": "tata"},
{
"amount": 38.45,
"currency": "EUR",
"receiver": "jeanne",
"ower": "zorglub",
},
]
assert json.loads(resp.data.decode("utf-8")) == expected
# generate csv export of transactions
resp = self.client.get("/raclette/export/transactions.csv")
expected = [
"amount,currency,receiver,ower",
"2.0,EUR,jeanne,pépé",
"10.89,EUR,jeanne,tata",
"38.45,EUR,jeanne,zorglub",
]
received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(","))
# Change project currency to CAD
project = self.get_project("raclette")
project.switch_currency("CAD")
# generate json export of transactions (now in CAD!)
resp = self.client.get("/raclette/export/transactions.json")
expected = [
{
"amount": 3.00,
"currency": "CAD",
"receiver": "jeanne",
"ower": "p\xe9p\xe9",
},
{"amount": 16.34, "currency": "CAD", "receiver": "jeanne", "ower": "tata"},
{
"amount": 57.67,
"currency": "CAD",
"receiver": "jeanne",
"ower": "zorglub",
},
]
assert json.loads(resp.data.decode("utf-8")) == expected
# generate csv export of transactions
resp = self.client.get("/raclette/export/transactions.csv")
expected = [
"amount,currency,receiver,ower",
"3.0,CAD,jeanne,pépé",
"16.34,CAD,jeanne,tata",
"57.67,CAD,jeanne,zorglub",
]
received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(","))
def test_export_escape_formulae(self):
self.post_project("raclette", default_currency="EUR")
# add participants
self.client.post("/raclette/members/add", data={"name": "zorglub"})
# create bills
self.client.post(
"/raclette/add",
data={
"date": "2016-12-31",
"what": "=COS(36)",
"bill_type": "Expense",
"payer": 1,
"payed_for": [1],
"amount": "10.0",
"original_currency": "EUR",
},
)
# generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv")
expected = [
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers",
"2016-12-31,'=COS(36),Expense,10.0,EUR,zorglub,1.0,zorglub",
]
received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(","))
class TestImportJSON(CommonTestCase.Import):
def generate_form_data(self, data):
return {"file": (list_of_dicts2json(data), "test.json")}
class TestImportCSV(CommonTestCase.Import):
def generate_form_data(self, data):
formatted_data = copy.deepcopy(data)
for d in formatted_data:
d["owers"] = ", ".join([o for o in d.get("owers", [])])
return {"file": (list_of_dicts2csv(formatted_data), "test.csv")}