diff --git a/bank_api/app.py b/bank_api/app.py index 948b973..db02122 100644 --- a/bank_api/app.py +++ b/bank_api/app.py @@ -2,6 +2,7 @@ from flask_restx import Api, Resource, reqparse, fields, abort from bank_api.bank import Bank +from bank_api.bank_report import BankReport def create_app(): @@ -11,6 +12,7 @@ def create_app(): api = Api(app, title='My Banking API', description='A simple banking API for learning Test-Driven-Development') bank = Bank() + bank_report = BankReport(bank) # Custom API documentation add_money = api.model("Add", { @@ -31,7 +33,8 @@ def post(self, name): def get(self, name): """Get an Account""" try: - return bank.get_account(name).to_dict() + account = bank.get_account(name) + return {**account.to_dict(), 'balance': bank_report.get_balance(name)} except Exception: abort(404, 'Account not found') diff --git a/bank_api/bank.py b/bank_api/bank.py index afe6353..2e003af 100644 --- a/bank_api/bank.py +++ b/bank_api/bank.py @@ -45,6 +45,8 @@ def get_account(self, name: str) -> Account: def add_funds(self, name: str, amount: int) -> None: """Add funds to the named account""" + if amount <= 0: + raise ValueError("Amount must be a positive integer") account = self.get_account(name) now = datetime.now() self.transactions.append(Transaction(account, now, amount)) diff --git a/bank_api/bank_report.py b/bank_api/bank_report.py new file mode 100644 index 0000000..380ed96 --- /dev/null +++ b/bank_api/bank_report.py @@ -0,0 +1,10 @@ +from bank_api.bank import Bank + + +class BankReport: + def __init__(self, bank: Bank): + self.bank = bank + + def get_balance(self, name: str) -> int: + account = self.bank.get_account(name) + return sum(t.amount for t in self.bank.transactions if t.account == account) diff --git a/tests/test_app.py b/tests/test_app.py index e05b062..aa0e107 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -16,8 +16,28 @@ def client(): def test_account_creation(client: FlaskClient): - # Use the client to make requests to the Flask app. - # response = client.get('/example/route') - # Or use client.post to make a POST request - # https://flask.palletsprojects.com/en/1.1.x/testing/ - pass + post_response = client.post('/accounts/Alice') + + assert post_response.status_code == 200 + assert post_response.get_json() == {"name": "Alice"} + + get_response = client.get('/accounts/Alice') + + assert get_response.status_code == 200 + assert get_response.get_json() == {"name": "Alice", "balance": 0} + + +def test_get_account_returns_404_if_not_found(client: FlaskClient): + response = client.get('/accounts/Nobody') + + assert response.status_code == 404 + + +def test_get_account_includes_balance(client: FlaskClient): + client.post('/accounts/Alice') + client.post('/money', json={'name': 'Alice', 'amount': 1000}) + + response = client.get('/accounts/Alice') + + assert response.status_code == 200 + assert response.get_json()['balance'] == 1000 diff --git a/tests/test_bank.py b/tests/test_bank.py index 0f106f0..170c7db 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -32,5 +32,33 @@ def test_get_account_raises_error_if_no_account_matches(bank: Bank): with pytest.raises(ValueError): bank.get_account('Name 2') -# TODO: Add unit tests for bank.add_funds() +# --- add_funds() tests --- + +def test_add_funds_creates_transaction(bank: Bank): + bank.create_account('Alice') + bank.add_funds('Alice', 1000) + + assert len(bank.transactions) == 1 + +def test_add_funds_transaction_has_correct_amount(bank: Bank): + bank.create_account('Alice') + bank.add_funds('Alice', 500) + + assert bank.transactions[0].amount == 500 + +def test_add_funds_raises_error_if_account_not_found(bank: Bank): + with pytest.raises(ValueError): + bank.add_funds('Nobody', 100) + +def test_add_funds_raises_error_if_amount_is_negative(bank: Bank): + bank.create_account('Alice') + + with pytest.raises(ValueError): + bank.add_funds('Alice', -50) + +def test_add_funds_raises_error_if_amount_is_zero(bank: Bank): + bank.create_account('Alice') + + with pytest.raises(ValueError): + bank.add_funds('Alice', 0) diff --git a/tests/test_bank_report.py b/tests/test_bank_report.py new file mode 100644 index 0000000..bd396e7 --- /dev/null +++ b/tests/test_bank_report.py @@ -0,0 +1,47 @@ +"""Unit tests for bank_report.py""" + +from datetime import datetime + +import pytest + +from bank_api.bank import Account, Bank, Transaction +from bank_api.bank_report import BankReport + + +@pytest.fixture +def bank() -> Bank: + return Bank() + + +def test_balance_is_zero_with_no_transactions(bank: Bank, monkeypatch): + bank_report = BankReport(bank) + account = Account('Alice') + monkeypatch.setattr(bank, 'get_account', lambda name: account) + monkeypatch.setattr(bank, 'transactions', []) + + assert bank_report.get_balance('Alice') == 0 + + +def test_balance_sums_transactions(bank: Bank, monkeypatch): + bank_report = BankReport(bank) + account = Account('Alice') + monkeypatch.setattr(bank, 'get_account', lambda name: account) + monkeypatch.setattr(bank, 'transactions', [ + Transaction(account, datetime.now(), 500), + Transaction(account, datetime.now(), 300), + ]) + + assert bank_report.get_balance('Alice') == 800 + + +def test_balance_ignores_other_accounts_transactions(bank: Bank, monkeypatch): + bank_report = BankReport(bank) + alice = Account('Alice') + bob = Account('Bob') + monkeypatch.setattr(bank, 'get_account', lambda name: alice) + monkeypatch.setattr(bank, 'transactions', [ + Transaction(alice, datetime.now(), 1000), + Transaction(bob, datetime.now(), 200), + ]) + + assert bank_report.get_balance('Alice') == 1000