docs: experiment with generated documentation, let's keep it in git for
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
now
This commit is contained in:
145
docs/by-claude-opus/testing.md
Normal file
145
docs/by-claude-opus/testing.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Testing Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The project uses Python's built-in `unittest` framework with `unittest.mock` for mocking external dependencies (Google Sheets API, attendance data). Tests live in the `tests/` directory.
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
make test # Run all tests
|
||||
make test-v # Run with verbose output
|
||||
```
|
||||
|
||||
Under the hood:
|
||||
```bash
|
||||
PYTHONPATH=scripts:. python3 -m unittest discover tests
|
||||
```
|
||||
|
||||
The `PYTHONPATH` includes both `scripts/` and the project root so that test files can import from both `app.py` and `scripts/*.py`.
|
||||
|
||||
## Test Files
|
||||
|
||||
### `test_app.py` — Flask Route Tests
|
||||
|
||||
Tests the Flask web application routes using Flask's built-in test client. All external data fetching is mocked.
|
||||
|
||||
| Test | What it verifies |
|
||||
|------|-----------------|
|
||||
| `test_index_page` | `GET /` returns 200 and contains a redirect to `/fees` |
|
||||
| `test_fees_route` | `GET /fees` renders the fees dashboard with correct member names |
|
||||
| `test_reconcile_route` | `GET /reconcile` renders the reconciliation page with payment matching |
|
||||
| `test_payments_route` | `GET /payments` renders the ledger with grouped transactions |
|
||||
|
||||
**Mocking strategy**:
|
||||
|
||||
```python
|
||||
@patch('app.get_members_with_fees')
|
||||
def test_fees_route(self, mock_get_members):
|
||||
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'Test Member', response.data)
|
||||
```
|
||||
|
||||
Each test patches the data-fetching functions (`get_members_with_fees`, `fetch_sheet_data`) to return controlled test data, avoiding any network calls.
|
||||
|
||||
**Notable**: The reconcile route test also mocks `fetch_sheet_data` and verifies that the reconciliation engine correctly matches a payment against an expected fee (checking for "OK" in the response).
|
||||
|
||||
### `test_reconcile_exceptions.py` — Reconciliation Logic Tests
|
||||
|
||||
Tests the `reconcile()` function directly (unit tests for the core business logic):
|
||||
|
||||
| Test | What it verifies |
|
||||
|------|-----------------|
|
||||
| `test_reconcile_applies_exceptions` | When a fee exception exists (400 CZK instead of 750), the expected amount is overridden and balance is calculated correctly |
|
||||
| `test_reconcile_fallback_to_attendance` | When no exception exists, the attendance-based fee is used |
|
||||
|
||||
**Why these tests matter**: The exception system is critical for correctness — an incorrect override could cause members to be shown incorrect amounts owed. These tests verify that:
|
||||
- Exceptions properly override the attendance-based fee
|
||||
- The absence of an exception correctly falls back to the standard calculation
|
||||
- Balances are computed correctly against overridden amounts
|
||||
|
||||
## Test Data Patterns
|
||||
|
||||
The tests use minimal but representative data:
|
||||
|
||||
```python
|
||||
# A member with attendance-based fee
|
||||
members = [('Alice', 'A', {'2026-01': (750, 4)})]
|
||||
|
||||
# An exception reducing the fee
|
||||
exceptions = {('alice', '2026-01'): {'amount': 400, 'note': 'Test exception'}}
|
||||
|
||||
# A matching payment
|
||||
transactions = [{
|
||||
'date': '2026-01-05',
|
||||
'amount': 400,
|
||||
'person': 'Alice',
|
||||
'purpose': '2026-01',
|
||||
'inferred_amount': 400,
|
||||
'sender': 'Alice Sender',
|
||||
'message': 'fee'
|
||||
}]
|
||||
```
|
||||
|
||||
## What's Not Tested
|
||||
|
||||
| Area | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Name matching logic | ❌ Not tested | `match_members()`, `_build_name_variants()` |
|
||||
| Czech month parsing | ❌ Not tested | `parse_month_references()` |
|
||||
| Fio bank data fetching | ❌ Not tested | Both API and HTML scraping |
|
||||
| Sync deduplication | ❌ Not tested | `generate_sync_id()` |
|
||||
| QR code generation | ❌ Not tested | `/qr` route |
|
||||
| Payment inference | ❌ Not tested | `infer_payments.py` logic |
|
||||
| Multi-person payment splitting | ❌ Not tested | Even split across members/months |
|
||||
| Edge cases | ❌ Not tested | Empty sheets, malformed dates, etc. |
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
### Adding a Flask route test
|
||||
|
||||
```python
|
||||
# In tests/test_app.py
|
||||
|
||||
@patch('app.some_function')
|
||||
def test_new_route(self, mock_fn):
|
||||
mock_fn.return_value = expected_data
|
||||
response = self.client.get('/new-route')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'expected content', response.data)
|
||||
```
|
||||
|
||||
### Adding a reconciliation logic test
|
||||
|
||||
```python
|
||||
# In tests/test_reconcile_exceptions.py (or a new test file)
|
||||
|
||||
def test_multi_month_payment(self):
|
||||
members = [('Bob', 'A', {
|
||||
'2026-01': (750, 3),
|
||||
'2026-02': (750, 4)
|
||||
})]
|
||||
transactions = [{
|
||||
'date': '2026-02-01',
|
||||
'amount': 1500,
|
||||
'person': 'Bob',
|
||||
'purpose': '2026-01, 2026-02',
|
||||
'inferred_amount': 1500,
|
||||
'sender': 'Bob',
|
||||
'message': 'leden+unor'
|
||||
}]
|
||||
result = reconcile(members, ['2026-01', '2026-02'], transactions)
|
||||
bob = result['members']['Bob']
|
||||
self.assertEqual(bob['months']['2026-01']['paid'], 750)
|
||||
self.assertEqual(bob['months']['2026-02']['paid'], 750)
|
||||
self.assertEqual(bob['total_balance'], 0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Testing documentation generated from comprehensive code analysis on 2026-03-03.*
|
||||
Reference in New Issue
Block a user