import unittest from unittest.mock import patch, MagicMock from app import app class TestWebApp(unittest.TestCase): def setUp(self): # Configure app for testing app.config['TESTING'] = True self.client = app.test_client() @patch('app.get_members_with_fees') def test_index_page(self, mock_get_members): """Test that / returns the refresh meta tag""" response = self.client.get('/') self.assertEqual(response.status_code, 200) self.assertIn(b'url=/fees', response.data) @patch('app.get_members_with_fees') def test_fees_route(self, mock_get_members): """Test that /fees returns 200 and renders the dashboard""" # Mock attendance data mock_get_members.return_value = ( [('Test Member', 'A', {'2026-01': (750, 4)})], ['2026-01'] ) response = self.client.get('/fees') self.assertEqual(response.status_code, 200) self.assertIn(b'FUJ Fees Dashboard', response.data) self.assertIn(b'Test Member', response.data) @patch('app.get_junior_members_with_fees') def test_fees_juniors_route(self, mock_get_junior_members): """Test that /fees-juniors returns 200 and renders the junior dashboard""" # Mock attendance data: one with string symbol '?', one with integer mock_get_junior_members.return_value = ( [ ('Test Junior 1', 'J', {'2026-01': ('?', 1, 0, 1)}), ('Test Junior 2', 'J', {'2026-01': (500, 4, 1, 3)}) ], ['2026-01'] ) response = self.client.get('/fees-juniors') self.assertEqual(response.status_code, 200) self.assertIn(b'FUJ Junior Fees Dashboard', response.data) self.assertIn(b'Test Junior 1', response.data) self.assertIn(b'? / 1 (J)', response.data) self.assertIn(b'500 CZK / 4 (1A+3J)', response.data) @patch('app.fetch_sheet_data') @patch('app.get_members_with_fees') def test_reconcile_route(self, mock_get_members, mock_fetch_sheet): """Test that /reconcile returns 200 and shows matches""" # Mock attendance data mock_get_members.return_value = ( [('Test Member', 'A', {'2026-01': (750, 4)})], ['2026-01'] ) # Mock sheet data - include all keys required by reconcile mock_fetch_sheet.return_value = [{ 'date': '2026-01-01', 'amount': 750, 'person': 'Test Member', 'purpose': '2026-01', 'message': 'test payment', 'sender': 'External Bank User', 'inferred_amount': 750 }] response = self.client.get('/reconcile') self.assertEqual(response.status_code, 200) self.assertIn(b'Payment Reconciliation', response.data) self.assertIn(b'Test Member', response.data) self.assertIn(b'OK', response.data) @patch('app.fetch_sheet_data') def test_payments_route(self, mock_fetch_sheet): """Test that /payments returns 200 and groups transactions""" # Mock sheet data mock_fetch_sheet.return_value = [{ 'date': '2026-01-01', 'amount': 750, 'person': 'Test Member', 'purpose': '2026-01', 'message': 'Direct Member Payment', 'sender': 'External Bank User' }] response = self.client.get('/payments') self.assertEqual(response.status_code, 200) self.assertIn(b'Payments Ledger', response.data) self.assertIn(b'Test Member', response.data) self.assertIn(b'Direct Member Payment', response.data) @patch('app.fetch_sheet_data') @patch('app.fetch_exceptions') @patch('app.get_junior_members_with_fees') def test_reconcile_juniors_route(self, mock_get_junior, mock_exceptions, mock_transactions): """Test that /reconcile-juniors correctly computes balances for juniors.""" mock_get_junior.return_value = ( [ ('Junior One', 'J', {'2026-01': (500, 4, 2, 2)}), ('Junior Two', 'X', {'2026-01': ('?', 1, 0, 1)}) ], ['2026-01'] ) mock_exceptions.return_value = {} mock_transactions.return_value = [{ 'date': '2026-01-15', 'amount': 500, 'person': 'Junior One', 'purpose': '2026-01', 'message': '', 'sender': 'Parent', 'inferred_amount': 500 }] response = self.client.get('/reconcile-juniors') self.assertEqual(response.status_code, 200) self.assertIn(b'Junior Payment Reconciliation', response.data) self.assertIn(b'Junior One', response.data) self.assertIn(b'Junior Two', response.data) self.assertIn(b'OK', response.data) self.assertIn(b'?', response.data) if __name__ == '__main__': unittest.main()