]> asedeno.scripts.mit.edu Git - bluechips.git/commitdiff
Merge remote branch 'storborg/master'
authorEvan Broder <broder@mit.edu>
Sun, 8 Nov 2009 21:35:25 +0000 (16:35 -0500)
committerEvan Broder <broder@mit.edu>
Sun, 8 Nov 2009 21:35:25 +0000 (16:35 -0500)
18 files changed:
bluechips/config/environment.py
bluechips/controllers/spend.py
bluechips/controllers/user.py
bluechips/lib/app_globals.py
bluechips/lib/permissions.py
bluechips/lib/totals.py
bluechips/model/expenditure.py
bluechips/model/subitem.py
bluechips/model/types.py
bluechips/tests/__init__.py
bluechips/tests/functional/test_spend.py
bluechips/tests/functional/test_transfer.py
bluechips/tests/functional/test_user.py [new file with mode: 0644]
bluechips/tests/model/test_currency.py
bluechips/tests/model/test_currency_validator.py [new file with mode: 0644]
bluechips/tests/model/test_expenditure.py
bluechips/tests/model/test_split.py [new file with mode: 0644]
test.ini

index c6ba9f0c0eb9cb30b400900d333ef7dbcac42786..75122f1c54f5ecde21210c2920b89ea8cb623f91 100644 (file)
@@ -45,6 +45,3 @@ def load_environment(global_conf, app_conf):
     # any Pylons config options)
     config['pylons.app_globals'].mailer = Mailer(config.get('mailer.host',
                                                             '127.0.0.1'))
-    if 'mailer.user' in config:
-        config['pylons.app_globals'].mailer.login(config['mailer.user'],
-                                                  config['mailer.password'])
index 8d2dc4a526e182f8f80430babb5be0452b53da78..aceb6ade6a839519217703bfe5d8c647afcd0a05 100644 (file)
@@ -65,10 +65,9 @@ class SpendController(BaseController):
             c.values = {}
             for ii, user_row in enumerate(c.users):
                 user_id, user = user_row
+                val = 0
                 if user.resident:
                     val = Decimal(100) / Decimal(num_residents)
-                else:
-                    val = 0
                 c.values['shares-%d.amount' % ii] = val
         else:
             c.title = 'Edit an Expenditure'
@@ -78,17 +77,15 @@ class SpendController(BaseController):
             c.values = {}
             for ii, user_row in enumerate(c.users):
                 user_id, user = user_row
-                try:
-                    share = [s.share for s in c.expenditure.splits
-                             if s.user == user][0]
-                    if c.expenditure.amount == 0:
-                        percent = 0
-                    else:
-                        percent = (Decimal(100) * Decimal(int(share)) /
-                                   Decimal(int(c.expenditure.amount))).\
-                                quantize(Decimal("0.001"))
-                except IndexError:
+                shares_by_user = dict(((sp.user, sp.share) for sp
+                                       in c.expenditure.splits))
+                share = shares_by_user.get(user, 0)
+                if c.expenditure.amount == 0:
                     percent = 0
+                else:
+                    percent = (Decimal(100) * Decimal(int(share)) /
+                               Decimal(int(c.expenditure.amount))).\
+                            quantize(Decimal("0.001"))
                 c.values['shares-%d.amount' % ii] = percent
 
         return render('/spend/index.mako')
index c586643755c2bd8df618e3063b144465303ecd78..df8253e83811e570ccb7eef23d7da31feb16b339 100644 (file)
@@ -25,13 +25,12 @@ class EmailSchema(Schema):
 
 class UserController(BaseController):
     def index(self):
+        c.title = 'User Settings'
         return render('/user/index.mako')
 
     @validate(schema=EmailSchema(), form='index')
     def update(self):
         new_email = self.form_result['new_email']
-        if new_email == '':
-            new_email = None
         request.environ['user'].email = new_email
         meta.Session.commit()
         if new_email is None:
index 016a9e1eaf0bf360544da778e9f893ebe323bd65..edf6aa5bc4ae3c586265a25a3c7d4a44b5992883 100644 (file)
@@ -33,7 +33,7 @@ class Globals(object):
             log.info("From: %s\nTo: %s\nSubject: %s\n\n%s",
                      msg.From, msg.To, msg.Subject, msg.Body)
         else:
-            self.mailer.send(msg)
+            self.mailer.send(msg) # pragma: nocover
 
     def handle_notification(self, users, subject, body):
         "Send a notification email."
index 63c9c5c0241a495990bdd9ca4abe6aeba4391b36..4a30fdc3e0c66b59ab27bc7b866aa1b6eb276ca4 100644 (file)
@@ -12,12 +12,12 @@ from bluechips.model import meta
 class BlueChipUser(RequestPermission):
     def check(self, app, environ, start_response):
         if 'REMOTE_USER' not in environ:
-            raise NotAuthenticatedError('Not Authenticated')
+            raise NotAuthenticatedError('Not Authenticated') # pragma: nocover
         environ['user'] = meta.Session.query(model.User).\
             filter_by(username=unicode(environ['REMOTE_USER'])).\
             first()
         if environ['user'] == None:
-            raise NotAuthorizedError('You are not allowed access.')
+            raise NotAuthorizedError('You are not allowed access.') # pragma: nocover
         return app(environ, start_response)
 
 class DummyAuthenticate(AddDictToEnviron):
index e0c455967d1566c0815c462e9bf22677884fc008..730ca648ea1b228c3f10ab712d0b77868858cd29 100644 (file)
@@ -94,9 +94,9 @@ def settle(debts_dict):
         settle_list.append((owes['who'], owed['who'], val))
     
     if len(owes_list) > 0:
-        raise DirtyBooks, ("People still owe money", owes_list)
+        raise DirtyBooks, ("People still owe money", owes_list) #pragma:nocover
     if len(owed_list) > 0:
-        raise DirtyBooks, ("People are still owed money", owed_list)
+        raise DirtyBooks, ("People are still owed money", owed_list) #pragma:nocover
     
     return settle_list
 
index 4f9dfb6bac430b9908c2d9aedee65be18caf2430..21172b9bcb046185e36e7c9ebf369651585623ac 100644 (file)
@@ -28,16 +28,6 @@ class Expenditure(object):
         split_percentage = Decimal(100) / Decimal(residents.count())
         self.split(dict((resident, split_percentage) for resident in residents))
     
-    def update_split(self):
-        """
-        Re-split an expenditure using the same percentages as what is
-        currently in the database
-        """
-        
-        old_splits = meta.Session.query(Split).filter(Split.expenditure==self)
-        split_dict = dict((s.user, Decimal(int(s.share))) for s in old_splits)
-        self.split(split_dict)
-    
     def split(self, split_dict):
         """
         Split up an expenditure.
index 81cadfaf02ab5a729cd975d00546408de2316958..2bbbf9b997f0779349251a4c2d7fe311f680a60c 100644 (file)
@@ -2,13 +2,12 @@ from types import Currency
 
 class Subitem(object):
     def __init__(self, expenditure=None, user=None, amount=Currency(0)):
-        self.expenditure = expenditure
-        self.user = user
-        self.share = share
+        self.expenditure = expenditure # pragma: nocover
+        self.user = user # pragma: nocover
+        self.share = share # pragma: nocover
         
     def __repr__(self):
-        return '<Subitem: expense: %s user: %s cost: %s>' % (self.expense,
-                                                             self.user,
-                                                             self.amount)
+        return ('<Subitem: expense: %s user: %s cost: %s>' %
+                (self.expense, self.user, self.amount)) # pragma: nocover
 
 __all__ = ['Subitem']
index 5423de9a2ea47188aa8fe02806953a9215414634..8cea5e8b107d781fe716e26f572f4eda3694bdc5 100644 (file)
@@ -123,12 +123,6 @@ class Currency(object):
         """
         return self.__mul__(other)
     
-    def __str_no_dollar__(self):
-        """
-        Get to the formatted string without the dollar sign
-        """
-        return str(self).replace('$', '')
-    
     def __repr__(self):
         return '%s("%s")' % (self.__class__.__name__, str(self))
     def __str__(self):
index f31ec7dff32eada2d5edc7aee772be4e3301ebb8..8ce9e3dacbaef9d6656f29ca37e374d04bc12f16 100644 (file)
@@ -31,11 +31,14 @@ def setUpPackage():
     # Invoke websetup with the current config file
     SetupCommand('setup-app').run([config['__file__']])
     
-    meta.Session.add(bluechips.model.User(u'root', u'Charlie Root', True))
-    meta.Session.add(bluechips.model.User(u'ben', u'Ben Bitdiddle', True))
-    meta.Session.add(bluechips.model.User(u'gotta', u'Gotta Lisp', True))
-    meta.Session.add(bluechips.model.User(u'rich', u'Rich Scheme', True))
+    u1 = bluechips.model.User(u'root', u'Charlie Root', True)
+    u1.email = u'charlie@example.com'
+    u2 = bluechips.model.User(u'ben', u'Ben Bitdiddle', True)
+    u3 = bluechips.model.User(u'gotta', u'Gotta Lisp', True)
+    u4 = bluechips.model.User(u'rich', u'Rich Scheme', True)
 
+    for u in (u1, u2, u3, u4):
+        meta.Session.add(u)
     meta.Session.commit()
 
 def tearDownPackage():
index 7c510f91968f182746dd6dd7ef9b7313649c98ee..801c5808b88588f952779167fa632dddb61daf02 100644 (file)
@@ -1,8 +1,13 @@
 from datetime import date
+from formencode import Invalid
+
 from bluechips.tests import *
 
 from bluechips import model
 from bluechips.model import meta
+from bluechips.model.types import Currency
+
+from bluechips.controllers.spend import ExpenditureSchema
 
 class TestSpendController(TestController):
 
@@ -16,15 +21,17 @@ class TestSpendController(TestController):
                 filter_by(name=u'Charlie Root').one()
         
         form['spender_id'] = user.id
-        form['amount'] = '66.78'
+        form['amount'] = '74.04'
         # Make sure date is today.
         today = date.today()
         assert form['date'].value == today.strftime('%m/%d/%Y')
         form['description'] = 'A test expenditure'
         form['shares-0.amount'] = '1'
         form['shares-1.amount'] = '2'
-        form['shares-2.amount'] = '3'
-        form['shares-3.amount'] = '4'
+        form['shares-2.amount'] = '2'
+        form['shares-3.amount'] = '1'
+        for ii in range(4):
+            assert int(form['shares-%d.user_id' % ii].value) == ii + 1
 
         response = form.submit()
         response = response.follow()
@@ -33,11 +40,17 @@ class TestSpendController(TestController):
         e = meta.Session.query(model.Expenditure).\
                 order_by(model.Expenditure.id.desc()).first()
         assert e.spender.name == u'Charlie Root'
-        assert e.amount == 6678
+        assert e.amount == 7404
         assert e.date == today
         assert e.description == u'A test expenditure'
 
         # Test the split.
+        shares = dict(((sp.user_id, sp.share)
+                       for sp in e.splits))
+        assert shares[1] == Currency('12.34')
+        assert shares[2] == Currency('24.68')
+        assert shares[3] == Currency('24.68')
+        assert shares[4] == Currency('12.34')
 
 
     def test_edit(self):
@@ -71,6 +84,64 @@ class TestSpendController(TestController):
                 order_by(model.Expenditure.id.desc()).first()
         assert e.description == u'Updated bundt cake'
 
+    def test_edit_zero_value(self):
+        user = meta.Session.query(model.User).\
+                filter_by(name=u'Charlie Root').one()
+        e = model.Expenditure(user, 0, u'A zero value expenditure', None)
+        e.even_split()
+        meta.Session.add(e)
+        meta.Session.commit()
+
+        response = self.app.get(url_for(controller='spend',
+                                        action='edit',
+                                        id=e.id))
+        response.mustcontain('Edit an Expenditure')
+        form = response.form
+
+        assert int(form['spender_id'].value) == user.id
+        assert form['amount'].value == '0.00'
+        assert form['date'].value == date.today().strftime('%m/%d/%Y')
+        assert form['description'].value == u'A zero value expenditure'
+        for ii in range(4):
+            assert form['shares-%d.amount' % ii].value == '0'
+
+    def test_edit_nonexistent(self):
+        response = self.app.get(url_for(controller='spend',
+                                        action='edit',
+                                        id=124234), status=404)
+
+    def test_update_nonexistent(self):
+        response = self.app.post(url_for(controller='spend',
+                                         action='update',
+                                         id=14234), 
+                                 params=self.sample_post,
+                                 status=404)
+
+    def test_all_zero_shares_fails(self):
+        params = self.sample_post.copy()
+        for ii in range(4):
+            params['shares-%d.amount' % ii] = '0'
+        v = ExpenditureSchema()
+        try:
+            v.to_python(params)
+        except Invalid:
+            pass
+
+    def setUp(self):
+        self.sample_post = {
+            'spender_id': '1',
+            'amount': '44.12',
+            'date': '10/5/2008',
+            'description': 'Example expenditure post data.',
+            'shares-0.user_id': '1',
+            'shares-0.amount': '1',
+            'shares-1.user_id': '2',
+            'shares-1.amount': '1',
+            'shares-2.user_id': '3',
+            'shares-2.amount': '1',
+            'shares-3.user_id': '4',
+            'shares-3.amount': '1'}
+
     def tearDown(self):
         expenditures = meta.Session.query(model.Expenditure).all()
         for e in expenditures:
index d9032a45401e83dc107d582b87e1f3349d81bdbd..9e01e6a455725f8d82fdb6dd375dff4a165aa858 100644 (file)
@@ -70,6 +70,33 @@ class TestTransferController(TestController):
                 order_by(model.Transfer.id.desc()).first()
         assert t.description == u'A new description'
 
+    def test_edit_nonexistent(self):
+        response = self.app.get(url_for(controller='transfer',
+                                        action='edit',
+                                        id=21424), status=404)
+
+    def test_update_nonexistent(self):
+        response = self.app.post(url_for(controller='transfer',
+                                         action='update',
+                                         id=21424),
+                                 params=self.sample_params,
+                                 status=404)
+
+    def test_update_get_redirects(self):
+        response = self.app.get(url_for(controller='transfer',
+                                        action='update'),
+                                status=302)
+        assert (dict(response.headers)['location'] ==
+                url_for(controller='transfer', action='edit', qualified=True))
+
+    def setUp(self):
+        self.sample_params = {
+            'debtor_id': '1',
+            'creditor_id': '2',
+            'amount': '33.98',
+            'date': '4/1/2007',
+            'description': 'Example transfer params.'}
+
     def tearDown(self):
         transfers = meta.Session.query(model.Transfer).all()
         for t in transfers:
diff --git a/bluechips/tests/functional/test_user.py b/bluechips/tests/functional/test_user.py
new file mode 100644 (file)
index 0000000..b5c9136
--- /dev/null
@@ -0,0 +1,33 @@
+from pylons import config
+
+from bluechips.tests import *
+from bluechips import model
+from bluechips.model import meta
+
+class TestUserController(TestController):
+
+    def test_index(self):
+        response = self.app.get(url_for(controller='user'))
+        # Test response...
+        response.mustcontain('Email Notifications', 'User Settings')
+        form = response.form
+        form['new_email'] = 'test@example.com'
+        response = form.submit().follow()
+        response.mustcontain('Updated email address')
+
+        user = meta.Session.query(model.User).\
+                filter_by(username=unicode(config['fake_username'])).one()
+        assert user.email == 'test@example.com'
+
+    def test_clear_email(self):
+        response = self.app.get(url_for(controller='user'))
+        form = response.form
+        form['new_email'] = ''
+        response = form.submit().follow()
+        response.mustcontain('Removed email address')
+
+        user = meta.Session.query(model.User).\
+                filter_by(username=unicode(config['fake_username'])).one()
+        assert user.email == None
+
+
index a689778eea64c7c9f32d3d45bf73c31f6dd01b78..4b8bdb419474f1a3cdbc7a319f76fcbd97a7fc09 100644 (file)
@@ -1,76 +1,19 @@
 from unittest import TestCase
-from bluechips.tests import *
-from bluechips.model.types import Currency
-from decimal import Decimal
+from bluechips.model import types
 
 class TestCurrency(TestCase):
-    def test_initInt(self):
-        """
-        Make sure the constructor for Currency works
-        """
-        self.assert_(Currency(1) is Currency(1), 
-                     "Currency objects not interned")
-    def test_initString(self):
-        """
-        Make sure the constructor for Currency works with strings
-        """
-        self.assertEqual(Currency("0.01"), Currency(1))
-        self.assert_(Currency("0.01") is Currency(1),
-                     "string and int constructors return different values")
-    
-    def test_string(self):
-        """
-        Test converting a Currency to a string
-        """
-        self.assertEqual(str(Currency(1)), "$0.01")
-        self.assertEqual(str(Currency(100)), "$1.00")
-        self.assertEqual(str(Currency(101)), "$1.01")
-        self.assertEqual(str(Currency(-1)), "-$0.01")
-        self.assertEqual(str(Currency(-100)), "-$1.00")
-        self.assertEqual(str(Currency(-101)), "-$1.01")
-    
-    def test_stringNoDollar(self):
-        """
-        Test that Currency values can be retrieved without the dollar sign
-        """
-        self.assertEqual(Currency(1).__str_no_dollar__(), "0.01")
-        self.assertEqual(Currency(100).__str_no_dollar__(), "1.00")
-        self.assertEqual(Currency(101).__str_no_dollar__(), "1.01")
-        self.assertEqual(Currency(-1).__str_no_dollar__(), "-0.01")
-        self.assertEqual(Currency(-100).__str_no_dollar__(), "-1.00")
-        self.assertEqual(Currency(-101).__str_no_dollar__(), "-1.01")
-    
-    def test_additionMath(self):
-        """
-        Confirm that addition works over currency types and ints
-        """
-        self.assertEqual(Currency(2) + 2, Currency(4))
-        self.assertEqual(2 + Currency(2), Currency(4))
-        self.assertEqual(Currency(2) + Currency(2), Currency(4))
-    
-    def test_additionType(self):
-        """
-        Adding Currencies or a Currency and an int should yield a
-        Currency
-        """
-        self.assertEqual(type(Currency(2) + 2), Currency)
-        self.assertEqual(type(2 + Currency(2)), Currency)
-        self.assertEqual(type(Currency(2) + Currency(2)), Currency)
-        
-    def test_multMath(self):
-        """
-        This test tests the same 3 things as ``test_addition``, but
-        for multiplication
-        """
-        self.assertEqual(Currency(100) * Decimal("0.25"), Currency(25))
-        self.assertEqual(Decimal("0.25") * Currency(100), Currency(25))
-        self.assertEqual(Currency(10) * Currency(10), Currency(100))
-    
-    def test_multType(self):
-        """
-        The result of multiplying a Currency with something else
-        should be a currency
-        """
-        self.assertEqual(type(Currency(100) * Decimal("0.25")), Currency)
-        self.assertEqual(type(Decimal("0.25") * Currency(100)), Currency)
-        self.assertEqual(type(Currency(100) * Currency(100)), Currency)
+    def setUp(self):
+        self.c = types.Currency('12.34')
+
+    def test_currency_float(self):
+        assert float(self.c) == 1234.
+
+    def test_currency_int(self):
+        val = int(self.c)
+        assert val == 1234
+        assert type(val) == int
+
+    def test_currency_long(self):
+        val = long(self.c)
+        assert val == 1234
+        assert type(val) == long
diff --git a/bluechips/tests/model/test_currency_validator.py b/bluechips/tests/model/test_currency_validator.py
new file mode 100644 (file)
index 0000000..bae8ca9
--- /dev/null
@@ -0,0 +1,30 @@
+from unittest import TestCase
+from formencode import Invalid
+
+from bluechips.model import types
+
+class TestCurrencyValidator(TestCase):
+    def setUp(self):
+        self.v = types.CurrencyValidator()
+
+    def test_currency_validator_good(self):
+        assert (self.v.to_python('12.34') ==
+                types.Currency('12.34'))
+
+    def test_currency_validator_nonzero(self):
+        try:
+            self.v.to_python('0')
+        except Invalid:
+            pass
+
+    def test_currency_validator_precision(self):
+        try:
+            self.v.to_python('12.345')
+        except Invalid:
+            pass
+
+    def test_currency_validator_amount(self):
+        try:
+            self.v.to_python('foo')
+        except Invalid:
+            pass
index 3af4af62e0c84db2042810c8a557749f27ffe69c..47a3cdf43df7899fcc3ac541729e1d2a94a55efd 100644 (file)
@@ -1,18 +1,79 @@
+from decimal import Decimal
+
 from unittest import TestCase
 from bluechips import model
+from bluechips.model import meta
 from bluechips.model.types import Currency
 
 class TestExpenditure(TestCase):
     def setUp(self):
-        self.u = model.User('chaz', u'Charles Root', False)
-        self.e = model.Expenditure(self.u, Currency('445.27'),
+        self.u = model.User(u'chaz', u'Charles Root', False)
+        self.e = model.Expenditure(self.u, Currency('444.88'),
                                    u'chaz buys lunch')
+        meta.Session.add(self.u)
+        meta.Session.add(self.e)
+        meta.Session.commit()
 
     def test_constructor(self):
         assert self.e.spender == self.u
-        assert self.e.amount == Currency('445.27')
+        assert self.e.amount == Currency('444.88')
         assert self.e.description == u'chaz buys lunch'
 
     def test_repr(self):
         assert (repr(self.e) == 
-                '<Expenditure: spender: Charles Root spent: $445.27>')
+                '<Expenditure: spender: Charles Root spent: $444.88>')
+
+    def test_even_split(self):
+        self.e.even_split()
+        meta.Session.commit()
+        for sp in self.e.splits:
+            assert sp.share == Currency('111.22')
+
+    def test_split_change_to_zero(self):
+        self.e.even_split()
+        meta.Session.commit()
+        users = meta.Session.query(model.User).all()
+        split_dict = dict((user, Decimal('0')) for user in users)
+        split_dict[self.u] = Decimal(1)
+        self.e.split(split_dict)
+
+    def _two_way_split_test(self, amount, min, max):
+        e2 = model.Expenditure(self.u, amount,
+                              u'testing splits')
+        u2 = model.User(u'bo', u'Bo Jangles', False)
+        meta.Session.add(u2)
+        meta.Session.add(e2)
+        meta.Session.commit()
+        split_dict = {}
+        split_dict[self.u] = Decimal(1)
+        split_dict[u2] = Decimal(1)
+        e2.split(split_dict)
+        assert min <= e2.share(u2) <= max
+        meta.Session.delete(e2)
+        meta.Session.delete(u2)
+        meta.Session.commit()
+
+    def test_split_rounds_down(self):
+        self._two_way_split_test(Currency('40.01'),
+                                 Currency('20.00'),
+                                 Currency('20.01'))
+
+    def test_split_rounds_up(self):
+        self._two_way_split_test(Currency('39.99'),
+                                 Currency('19.99'),
+                                 Currency('20.00'))
+
+    def test_split_small(self):
+        self._two_way_split_test(Currency('0.01'),
+                                 Currency('0.00'),
+                                 Currency('0.01'))
+
+    def test_split_small_negative(self):
+        self._two_way_split_test(Currency('-0.01'),
+                                 Currency('-0.01'),
+                                 Currency('-0.00'))
+
+    def tearDown(self):
+        meta.Session.delete(self.e)
+        meta.Session.delete(self.u)
+        meta.Session.commit()
diff --git a/bluechips/tests/model/test_split.py b/bluechips/tests/model/test_split.py
new file mode 100644 (file)
index 0000000..86c8a9b
--- /dev/null
@@ -0,0 +1,19 @@
+from unittest import TestCase
+from bluechips import model
+from bluechips.model.types import Currency
+
+class TestSplit(TestCase):
+    def setUp(self):
+        self.u = model.User('chaz', u'Charles Root', False)
+        self.e = model.Expenditure(self.u, Currency('12.34'),
+                                   u'A test expenditure')
+        self.sp = model.Split(self.e, self.u, Currency('5.55'))
+
+    def test_constructor(self):
+        assert self.sp.expenditure == self.e
+        assert self.sp.user == self.u
+        assert self.sp.share == Currency('5.55')
+
+    def test_repr(self):
+        assert (repr(self.sp) == '<Split: expense: %s user: %s share: %s>' %
+                (self.sp.expenditure, self.sp.user, self.sp.share))
index 877fca5102b34e1ad5fd8ac92663934da1f87592..478036013216e0ef850c16b1c38198a815f72af0 100644 (file)
--- a/test.ini
+++ b/test.ini
@@ -23,5 +23,6 @@ beaker.session.key = bluechips
 beaker.session.secret = somesecret
 
 fake_username = root
+testing = true
 
 sqlalchemy.url = sqlite://