1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/ihatemoney_ynh.git synced 2024-09-03 19:26:15 +02:00
ihatemoney_ynh/budget/tests.py

820 lines
28 KiB
Python

# -*- coding: utf-8 -*-
try:
import unittest2 as unittest
except ImportError:
import unittest # NOQA
import base64
import json
from collections import defaultdict
from flask import session
import run
import models
class TestCase(unittest.TestCase):
def setUp(self):
run.app.config['TESTING'] = True
run.app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///memory"
run.app.config['CSRF_ENABLED'] = False # simplify the tests
self.app = run.app.test_client()
try:
models.db.init_app(run.app)
run.mail.init_app(run.app)
except:
pass
models.db.app = run.app
models.db.create_all()
def tearDown(self):
# clean after testing
models.db.session.remove()
models.db.drop_all()
def login(self, project, password=None, test_client=None):
password = password or project
return self.app.post('/authenticate', data=dict(
id=project, password=password), follow_redirects=True)
def post_project(self, name):
"""Create a fake project"""
# create the project
self.app.post("/create", data={
'name': name,
'id': name,
'password': name,
'contact_email': '%s@notmyidea.org' % name
})
def create_project(self, name):
models.db.session.add(models.Project(id=name, name=unicode(name),
password=name, contact_email="%s@notmyidea.org" % name))
models.db.session.commit()
class BudgetTestCase(TestCase):
def test_notifications(self):
"""Test that the notifications are sent, and that email adresses
are checked properly.
"""
# sending a message to one person
with run.mail.record_messages() as outbox:
# create a project
self.login("raclette")
self.post_project("raclette")
self.app.post("/raclette/invite",
data={"emails": 'alexis@notmyidea.org'})
self.assertEqual(len(outbox), 2)
self.assertEqual(outbox[0].recipients, ["raclette@notmyidea.org"])
self.assertEqual(outbox[1].recipients, ["alexis@notmyidea.org"])
# sending a message to multiple persons
with run.mail.record_messages() as outbox:
self.app.post("/raclette/invite",
data={"emails": 'alexis@notmyidea.org, toto@notmyidea.org'})
# only one message is sent to multiple persons
self.assertEqual(len(outbox), 1)
self.assertEqual(outbox[0].recipients,
["alexis@notmyidea.org", "toto@notmyidea.org"])
# mail address checking
with run.mail.record_messages() as outbox:
response = self.app.post("/raclette/invite",
data={"emails": "toto"})
self.assertEqual(len(outbox), 0) # no message sent
self.assertIn("The email toto is not valid", response.data)
# mixing good and wrong adresses shouldn't send any messages
with run.mail.record_messages() as outbox:
self.app.post("/raclette/invite",
data={"emails": 'alexis@notmyidea.org, alexis'}) # not valid
# only one message is sent to multiple persons
self.assertEqual(len(outbox), 0)
def test_password_reminder(self):
# test that it is possible to have an email cotaining the password of a
# project in case people forget it (and it happens!)
self.create_project("raclette")
with run.mail.record_messages() as outbox:
# a nonexisting project should not send an email
self.app.post("/password-reminder", data={"id": "unexisting"})
self.assertEqual(len(outbox), 0)
# a mail should be sent when a project exists
self.app.post("/password-reminder", data={"id": "raclette"})
self.assertEqual(len(outbox), 1)
self.assertIn("raclette", outbox[0].body)
self.assertIn("raclette@notmyidea.org", outbox[0].recipients)
def test_project_creation(self):
with run.app.test_client() as c:
# add a valid project
c.post("/create", data={
'name': 'The fabulous raclette party',
'id': 'raclette',
'password': 'party',
'contact_email': 'raclette@notmyidea.org'
})
# session is updated
self.assertEqual(session['raclette'], 'party')
# project is created
self.assertEqual(len(models.Project.query.all()), 1)
# Add a second project with the same id
models.Project.query.get('raclette')
c.post("/create", data={
'name': 'Another raclette party',
'id': 'raclette', # already used !
'password': 'party',
'contact_email': 'raclette@notmyidea.org'
})
# no new project added
self.assertEqual(len(models.Project.query.all()), 1)
def test_project_deletion(self):
with run.app.test_client() as c:
c.post("/create", data={
'name': 'raclette party',
'id': 'raclette',
'password': 'party',
'contact_email': 'raclette@notmyidea.org'
})
# project added
self.assertEqual(len(models.Project.query.all()), 1)
c.get('/raclette/delete')
# project removed
self.assertEqual(len(models.Project.query.all()), 0)
def test_membership(self):
self.post_project("raclette")
self.login("raclette")
# adds a member to this project
self.app.post("/raclette/members/add", data={'name': 'alexis'})
self.assertEqual(len(models.Project.query.get("raclette").members), 1)
# adds him twice
result = self.app.post("/raclette/members/add",
data={'name': 'alexis'})
# should not accept him
self.assertEqual(len(models.Project.query.get("raclette").members), 1)
# add fred
self.app.post("/raclette/members/add", data={'name': 'fred'})
self.assertEqual(len(models.Project.query.get("raclette").members), 2)
# check fred is present in the bills page
result = self.app.get("/raclette/")
self.assertIn("fred", result.data)
# remove fred
self.app.post("/raclette/members/%s/delete" %
models.Project.query.get("raclette").members[-1].id)
# as fred is not bound to any bill, he is removed
self.assertEqual(len(models.Project.query.get("raclette").members), 1)
# add fred again
self.app.post("/raclette/members/add", data={'name': 'fred'})
fred_id = models.Project.query.get("raclette").members[-1].id
# bound him to a bill
result = self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': fred_id,
'payed_for': [fred_id, ],
'amount': '25',
})
# remove fred
self.app.post("/raclette/members/%s/delete" % fred_id)
# he is still in the database, but is deactivated
self.assertEqual(len(models.Project.query.get("raclette").members), 2)
self.assertEqual(
len(models.Project.query.get("raclette").active_members), 1)
# as fred is now deactivated, check that he is not listed when adding
# a bill or displaying the balance
result = self.app.get("/raclette/")
self.assertNotIn("/raclette/members/%s/delete" % fred_id, result.data)
result = self.app.get("/raclette/add")
self.assertNotIn("fred", result.data)
# adding him again should reactivate him
self.app.post("/raclette/members/add", data={'name': 'fred'})
self.assertEqual(
len(models.Project.query.get("raclette").active_members), 2)
# adding an user with the same name as another user from a different
# project should not cause any troubles
self.post_project("randomid")
self.login("randomid")
self.app.post("/randomid/members/add", data={'name': 'fred'})
self.assertEqual(
len(models.Project.query.get("randomid").active_members), 1)
def test_person_model(self):
self.post_project("raclette")
self.login("raclette")
# adds a member to this project
self.app.post("/raclette/members/add", data={'name': 'alexis'})
alexis = models.Project.query.get("raclette").members[-1]
# should not have any bills
self.assertFalse(alexis.has_bills())
# bound him to a bill
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': alexis.id,
'payed_for': [alexis.id, ],
'amount': '25',
})
# should have a bill now
alexis = models.Project.query.get("raclette").members[-1]
self.assertTrue(alexis.has_bills())
def test_member_delete_method(self):
self.post_project("raclette")
self.login("raclette")
# adds a member to this project
self.app.post("/raclette/members/add", data={'name': 'alexis'})
# try to remove the member using GET method
response = self.app.get("/raclette/members/1/delete")
self.assertEqual(response.status_code, 405)
#delete user using POST method
self.app.post("/raclette/members/1/delete")
self.assertEqual(
len(models.Project.query.get("raclette").active_members), 0)
#try to delete an user already deleted
self.app.post("/raclette/members/1/delete")
def test_demo(self):
# test that a demo project is created if none is defined
self.assertEqual([], models.Project.query.all())
self.app.get("/demo")
self.assertTrue(models.Project.query.get("demo") is not None)
def test_authentication(self):
# raclette that the login / logout process works
self.create_project("raclette")
# try to see the project while not being authenticated should redirect
# to the authentication page
resp = self.app.post("/raclette", follow_redirects=True)
self.assertIn("Authentication", resp.data)
# try to connect with wrong credentials should not work
with run.app.test_client() as c:
resp = c.post("/authenticate",
data={'id': 'raclette', 'password': 'nope'})
self.assertIn("Authentication", resp.data)
self.assertNotIn('raclette', session)
# try to connect with the right credentials should work
with run.app.test_client() as c:
resp = c.post("/authenticate",
data={'id': 'raclette', 'password': 'raclette'})
self.assertNotIn("Authentication", resp.data)
self.assertIn('raclette', session)
self.assertEqual(session['raclette'], 'raclette')
# logout should wipe the session out
c.get("/exit")
self.assertNotIn('raclette', session)
def test_manage_bills(self):
self.post_project("raclette")
# add two persons
self.app.post("/raclette/members/add", data={'name': 'alexis'})
self.app.post("/raclette/members/add", data={'name': 'fred'})
members_ids = [m.id for m in
models.Project.query.get("raclette").members]
# create a bill
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': members_ids[0],
'payed_for': members_ids,
'amount': '25',
})
models.Project.query.get("raclette")
bill = models.Bill.query.one()
self.assertEqual(bill.amount, 25)
# edit the bill
self.app.post("/raclette/edit/%s" % bill.id, data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': members_ids[0],
'payed_for': members_ids,
'amount': '10',
})
bill = models.Bill.query.one()
self.assertEqual(bill.amount, 10, "bill edition")
# delete the bill
self.app.get("/raclette/delete/%s" % bill.id)
self.assertEqual(0, len(models.Bill.query.all()), "bill deletion")
# test balance
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': members_ids[0],
'payed_for': members_ids,
'amount': '19',
})
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': members_ids[1],
'payed_for': members_ids[0],
'amount': '20',
})
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': members_ids[1],
'payed_for': members_ids,
'amount': '17',
})
balance = models.Project.query.get("raclette").balance
self.assertEqual(set(balance.values()), set([19.0, -19.0]))
#Bill with negative amount
self.app.post("/raclette/add", data={
'date': '2011-08-12',
'what': u'fromage à raclette',
'payer': members_ids[0],
'payed_for': members_ids,
# bill with a negative value should be converted to a positive
# value
'amount': '-25'
})
bill = models.Bill.query.filter(models.Bill.date == '2011-08-12')[0]
self.assertEqual(bill.amount, 25)
#add a bill with a comma
self.app.post("/raclette/add", data={
'date': '2011-08-01',
'what': u'fromage à raclette',
'payer': members_ids[0],
'payed_for': members_ids,
'amount': '25,02',
})
bill = models.Bill.query.filter(models.Bill.date == '2011-08-01')[0]
self.assertEqual(bill.amount, 25.02)
def test_rounding(self):
self.post_project("raclette")
# add members
self.app.post("/raclette/members/add", data={'name': 'alexis'})
self.app.post("/raclette/members/add", data={'name': 'fred'})
self.app.post("/raclette/members/add", data={'name': 'tata'})
# create bills
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': 1,
'payed_for': [1, 2, 3],
'amount': '24.36',
})
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'red wine',
'payer': 2,
'payed_for': [1],
'amount': '19.12',
})
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'delicatessen',
'payer': 1,
'payed_for': [1, 2],
'amount': '22',
})
balance = models.Project.query.get("raclette").balance
result = {}
result[models.Project.query.get("raclette").members[0].id] = 8.12
result[models.Project.query.get("raclette").members[1].id] = 0.0
result[models.Project.query.get("raclette").members[2].id] = -8.12
self.assertDictEqual(balance, result)
def test_edit_project(self):
# A project should be editable
self.post_project("raclette")
new_data = {
'name': 'Super raclette party!',
'contact_email': 'alexis@notmyidea.org',
'password': 'didoudida'
}
resp = self.app.post("/raclette/edit", data=new_data,
follow_redirects=True)
self.assertEqual(resp.status_code, 200)
project = models.Project.query.get("raclette")
for key, value in new_data.items():
self.assertEqual(getattr(project, key), value, key)
# Editing a project with a wrong email address should fail
new_data['contact_email'] = 'wrong_email'
resp = self.app.post("/raclette/edit", data=new_data,
follow_redirects=True)
self.assertIn("Invalid email address", resp.data)
def test_dashboard(self):
response = self.app.get("/dashboard")
self.assertEqual(response.status_code, 200)
def test_settle_page(self):
self.post_project("raclette")
response = self.app.get("/raclette/settle_bills")
self.assertEqual(response.status_code, 200)
def test_settle(self):
self.post_project("raclette")
# add members
self.app.post("/raclette/members/add", data={'name': 'alexis'})
self.app.post("/raclette/members/add", data={'name': 'fred'})
self.app.post("/raclette/members/add", data={'name': 'tata'})
#Add a member with a balance=0 :
self.app.post("/raclette/members/add", data={'name': 'toto'})
# create bills
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'fromage à raclette',
'payer': 1,
'payed_for': [1, 2, 3],
'amount': '10.0',
})
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'red wine',
'payer': 2,
'payed_for': [1],
'amount': '20',
})
self.app.post("/raclette/add", data={
'date': '2011-08-10',
'what': u'delicatessen',
'payer': 1,
'payed_for': [1, 2],
'amount': '10',
})
project = models.Project.query.get('raclette')
transactions = project.get_transactions_to_settle_bill()
members = defaultdict(int)
#We should have the same values between transactions and project balances
for t in transactions:
members[t['ower']]-=t['amount']
members[t['receiver']]+=t['amount']
balance = models.Project.query.get("raclette").balance
for m, a in members.items():
self.assertEqual(a, balance[m.id])
return
class APITestCase(TestCase):
"""Tests the API"""
def api_create(self, name, id=None, password=None, contact=None):
id = id or name
password = password or name
contact = contact or "%s@notmyidea.org" % name
return self.app.post("/api/projects", data={
'name': name,
'id': id,
'password': password,
'contact_email': contact
})
def api_add_member(self, project, name):
self.app.post("/api/projects/%s/members" % project,
data={"name": name}, headers=self.get_auth(project))
def get_auth(self, username, password=None):
password = password or username
base64string = base64.encodestring(
'%s:%s' % (username, password)).replace('\n', '')
return {"Authorization": "Basic %s" % base64string}
def assertStatus(self, expected, resp, url=""):
return self.assertEqual(expected, resp.status_code,
"%s expected %s, got %s" % (url, expected, resp.status_code))
def test_basic_auth(self):
# create a project
resp = self.api_create("raclette")
self.assertStatus(201, resp)
# try to do something on it being unauth should return a 401
resp = self.app.get("/api/projects/raclette")
self.assertStatus(401, resp)
# PUT / POST / DELETE / GET on the different resources
# should also return a 401
for verb in ('post',):
for resource in ("/raclette/members", "/raclette/bills"):
url = "/api/projects" + resource
self.assertStatus(401, getattr(self.app, verb)(url),
verb + resource)
for verb in ('get', 'delete', 'put'):
for resource in ("/raclette", "/raclette/members/1",
"/raclette/bills/1"):
url = "/api/projects" + resource
self.assertStatus(401, getattr(self.app, verb)(url),
verb + resource)
def test_project(self):
# wrong email should return an error
resp = self.app.post("/api/projects", data={
'name': "raclette",
'id': "raclette",
'password': "raclette",
'contact_email': "not-an-email"
})
self.assertTrue(400, resp.status_code)
self.assertEqual('{"contact_email": ["Invalid email address."]}',
resp.data)
# create it
resp = self.api_create("raclette")
self.assertTrue(201, resp.status_code)
# create it twice should return a 400
resp = self.api_create("raclette")
self.assertTrue(400, resp.status_code)
self.assertIn('id', json.loads(resp.data))
# get information about it
resp = self.app.get("/api/projects/raclette",
headers=self.get_auth("raclette"))
self.assertTrue(200, resp.status_code)
expected = {
"active_members": [],
"name": "raclette",
"contact_email": "raclette@notmyidea.org",
"members": [],
"password": "raclette",
"id": "raclette",
"balance": {},
}
self.assertDictEqual(json.loads(resp.data), expected)
# edit should work
resp = self.app.put("/api/projects/raclette", data={
"contact_email": "yeah@notmyidea.org",
"password": "raclette",
"name": "The raclette party",
}, headers=self.get_auth("raclette"))
self.assertEqual(200, resp.status_code)
resp = self.app.get("/api/projects/raclette",
headers=self.get_auth("raclette"))
self.assertEqual(200, resp.status_code)
expected = {
"active_members": [],
"name": "The raclette party",
"contact_email": "yeah@notmyidea.org",
"members": [],
"password": "raclette",
"id": "raclette",
"balance": {},
}
self.assertDictEqual(json.loads(resp.data), expected)
# delete should work
resp = self.app.delete("/api/projects/raclette",
headers=self.get_auth("raclette"))
self.assertEqual(200, resp.status_code)
# get should return a 401 on an unknown resource
resp = self.app.get("/api/projects/raclette",
headers=self.get_auth("raclette"))
self.assertEqual(401, resp.status_code)
def test_member(self):
# create a project
self.api_create("raclette")
# get the list of members (should be empty)
req = self.app.get("/api/projects/raclette/members",
headers=self.get_auth("raclette"))
self.assertStatus(200, req)
self.assertEqual('[]', req.data)
# add a member
req = self.app.post("/api/projects/raclette/members", data={
"name": "Alexis"
}, headers=self.get_auth("raclette"))
# the id of the new member should be returned
self.assertStatus(201, req)
self.assertEqual("1", req.data)
# the list of members should contain one member
req = self.app.get("/api/projects/raclette/members",
headers=self.get_auth("raclette"))
self.assertStatus(200, req)
self.assertEqual(len(json.loads(req.data)), 1)
# edit this member
req = self.app.put("/api/projects/raclette/members/1", data={
"name": "Fred"
}, headers=self.get_auth("raclette"))
self.assertStatus(200, req)
# get should return the new name
req = self.app.get("/api/projects/raclette/members/1",
headers=self.get_auth("raclette"))
self.assertStatus(200, req)
self.assertEqual("Fred", json.loads(req.data)["name"])
# delete a member
req = self.app.delete("/api/projects/raclette/members/1",
headers=self.get_auth("raclette"))
self.assertStatus(200, req)
# the list of members should be empty
# get the list of members (should be empty)
req = self.app.get("/api/projects/raclette/members",
headers=self.get_auth("raclette"))
self.assertStatus(200, req)
self.assertEqual('[]', req.data)
def test_bills(self):
# create a project
self.api_create("raclette")
# add members
self.api_add_member("raclette", "alexis")
self.api_add_member("raclette", "fred")
self.api_add_member("raclette", "arnaud")
# get the list of bills (should be empty)
req = self.app.get("/api/projects/raclette/bills",
headers=self.get_auth("raclette"))
self.assertStatus(200, req)
self.assertEqual("[]", req.data)
# add a bill
req = self.app.post("/api/projects/raclette/bills", data={
'date': '2011-08-10',
'what': u'fromage',
'payer': "1",
'payed_for': ["1", "2"],
'amount': '25',
}, headers=self.get_auth("raclette"))
# should return the id
self.assertStatus(201, req)
self.assertEqual(req.data, "1")
# get this bill details
req = self.app.get("/api/projects/raclette/bills/1",
headers=self.get_auth("raclette"))
# compare with the added info
self.assertStatus(200, req)
expected = {
"what": "fromage",
"payer_id": 1,
"owers": [
{"activated": True, "id": 1, "name": "alexis"},
{"activated": True, "id": 2, "name": "fred"}],
"amount": 25.0,
"date": "2011-08-10",
"id": 1}
self.assertDictEqual(expected, json.loads(req.data))
# the list of bills should lenght 1
req = self.app.get("/api/projects/raclette/bills",
headers=self.get_auth("raclette"))
self.assertStatus(200, req)
self.assertEqual(1, len(json.loads(req.data)))
# edit with errors should return an error
req = self.app.put("/api/projects/raclette/bills/1", data={
'date': '201111111-08-10', # not a date
'what': u'fromage',
'payer': "1",
'payed_for': ["1", "2"],
'amount': '25',
}, headers=self.get_auth("raclette"))
self.assertStatus(400, req)
self.assertEqual('{"date": ["This field is required."]}', req.data)
# edit a bill
req = self.app.put("/api/projects/raclette/bills/1", data={
'date': '2011-09-10',
'what': u'beer',
'payer': "2",
'payed_for': ["1", "2"],
'amount': '25',
}, headers=self.get_auth("raclette"))
# check its fields
req = self.app.get("/api/projects/raclette/bills/1",
headers=self.get_auth("raclette"))
expected = {
"what": "beer",
"payer_id": 2,
"owers": [
{"activated": True, "id": 1, "name": "alexis"},
{"activated": True, "id": 2, "name": "fred"}],
"amount": 25.0,
"date": "2011-09-10",
"id": 1}
self.assertDictEqual(expected, json.loads(req.data))
# delete a bill
req = self.app.delete("/api/projects/raclette/bills/1",
headers=self.get_auth("raclette"))
self.assertStatus(200, req)
# getting it should return a 404
req = self.app.get("/api/projects/raclette/bills/1",
headers=self.get_auth("raclette"))
self.assertStatus(404, req)
if __name__ == "__main__":
unittest.main()