diff --git a/api/tax_calculator/controllers.py b/api/tax_calculator/controllers.py index 15d667b..0e4ad93 100644 --- a/api/tax_calculator/controllers.py +++ b/api/tax_calculator/controllers.py @@ -7,14 +7,32 @@ def get_reliable_brackets(): return get_tax_brackets() -def get_unreliable_brackets(tax_year): - naptime() +def calculate_tax(salary, tax_year): + brackets = get_tax_brackets(str(tax_year)) + total_tax = 0 + tax_bands = [] - # be evil - roulette = random.randint(1, 4) - print(f'Database roulette {roulette}') - if roulette == 3: - raise Exception("Database not found!") + for bracket in brackets: + min_income = bracket['min'] + max_income = bracket.get('max', None) + rate = bracket['rate'] - return get_tax_brackets(tax_year) - \ No newline at end of file + if max_income is not None and salary > max_income: + tax_amount = (max_income - min_income) * rate + else: + tax_amount = (salary - min_income) * rate + + total_tax += tax_amount + tax_bands.append({ + 'min_income': min_income, + 'max_income': max_income, + 'tax_rate': rate, + 'tax_amount': tax_amount + }) + + if max_income is not None and salary > max_income: + break + + effective_rate = total_tax / salary * 100 if salary > 0 else 0 + + return total_tax, tax_bands, effective_rate diff --git a/api/tax_calculator/routes.py b/api/tax_calculator/routes.py index b6259bb..d5f4692 100644 --- a/api/tax_calculator/routes.py +++ b/api/tax_calculator/routes.py @@ -1,10 +1,16 @@ -from flask import jsonify, render_template, redirect +import logging + +from flask import jsonify, redirect, request from app import app -from . import controllers +from . import controllers, error_handlers + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) @app.route('/tax-calculator/') def tax_calculator_instructions(): + """Provide instructions for tax calculator API.""" return jsonify({ 'tax_brackets': controllers.get_reliable_brackets() }) @@ -12,11 +18,63 @@ def tax_calculator_instructions(): @app.route('/tax-calculator/tax-year') def default_brackets(): + """Redirect to default brackets.""" return redirect('/') -@app.route('/tax-calculator/tax-year/') -def tax_year_brackets(tax_year): - return jsonify({ - 'tax_brackets': controllers.get_unreliable_brackets(tax_year) - }) +@app.route('/tax-calculator', methods=['GET']) +def tax_calculator(): + """ + Calculate total tax based on salary and tax year. + + Returns: + jsonify: JSON response containing total_tax, tax_bands, and effective_rate. + """ + try: + salary = float(request.args.get('salary')) + tax_year = int(request.args.get('tax_year')) + + if tax_year not in [2019, 2020, 2021, 2022]: + error_message = 'Invalid tax year. Only years 2019, 2020, 2021, and 2022 are supported.' + logger.error(error_message) + return jsonify({ + 'errors': error_handlers.format_error( + error_message, + field='tax_year' + ) + }), 400 + + if salary <= 0: + error_message = 'Salary must be a positive value.' + logger.error(error_message) + return jsonify({ + 'errors': error_handlers.format_error( + error_message, + field='salary' + ) + }), 400 + + total_tax, tax_bands, effective_rate = controllers.calculate_tax(salary, tax_year) + + return jsonify({ + 'total_tax': total_tax, + 'tax_bands': tax_bands, + 'effective_rate': effective_rate + }), 200 + + except ValueError: + error_message = 'Invalid input types. Salary and tax_year must be numeric values.' + logger.error(error_message) + return jsonify({ + 'errors': error_handlers.format_error( + error_message, + ) + }), 400 + except Exception as e: + error_message = str(e) + logger.error(error_message) + return jsonify({ + 'errors': error_handlers.format_error( + error_message, + ) + }), 500 diff --git a/api/tax_calculator/test_tax_calculator_api.py b/api/tax_calculator/test_tax_calculator_api.py index 2373980..53bc9da 100644 --- a/api/tax_calculator/test_tax_calculator_api.py +++ b/api/tax_calculator/test_tax_calculator_api.py @@ -1,10 +1,20 @@ import os import json + brackets_dir = os.path.join(os.path.dirname(__file__), 'fixtures') + def _get_brackets(tax_year): - + """ + Get tax brackets for a specific year. + + Parameters: + tax_year (str): The tax year for which to retrieve the brackets. + + Returns: + dict: A dictionary containing the tax brackets for the specified year. + """ filename = f'tax-brackets--{tax_year}.json' file_with_path = os.path.join(brackets_dir, filename) @@ -15,7 +25,140 @@ def _get_brackets(tax_year): return json_contents -def test_basic_route(client): - resp = client.get('/tax-calculator/') - brackets = _get_brackets('2022') - assert resp.json == {'tax_brackets': brackets} +def test_basic_route(client): + """ + Test the basic route for the tax calculator API. + + Parameters: + client: The Flask test client. + + Returns: + None + """ + resp = client.get('/tax-calculator/') + brackets = _get_brackets('2022') + assert resp.json == {'tax_brackets': brackets} + + +def test_valid_input(client): + """ + Test valid input for the tax calculator API. + + Parameters: + client: The Flask test client. + + Returns: + None + """ + resp = client.get('/tax-calculator/?salary=100000&tax_year=2021') + assert resp.status_code == 200 + data = resp.json + assert 'total_tax' in data + assert 'tax_bands' in data + assert 'effective_rate' in data + + +def test_invalid_tax_year(client): + """ + Test invalid tax year input for the tax calculator API. + + Parameters: + client: The Flask test client. + + Returns: + None + """ + resp = client.get('/tax-calculator/?salary=100000&tax_year=2018') + assert resp.status_code == 400 + error = resp.json['errors'][0] + assert error['code'] == '' + assert error['field'] == 'tax_year' + assert 'Invalid tax year' in error['message'] + + +def test_negative_salary(client): + """ + Test negative salary input for the tax calculator API. + + Parameters: + client: The Flask test client. + + Returns: + None + """ + resp = client.get('/tax-calculator/?salary=-10000&tax_year=2022') + assert resp.status_code == 400 + error = resp.json['errors'][0] + assert error['code'] == '' + assert error['field'] == 'salary' + assert 'positive value' in error['message'] + + +def test_invalid_input_types(client): + """ + Test invalid input types for the tax calculator API. + + Parameters: + client: The Flask test client. + + Returns: + None + """ + resp = client.get('/tax-calculator/?salary=abc&tax_year=2022') + assert resp.status_code == 400 + error = resp.json['errors'][0] + assert error['code'] == '' + assert 'Invalid input types' in error['message'] + + +def test_no_params(client): + """ + Test no parameters provided for the tax calculator API. + + Parameters: + client: The Flask test client. + + Returns: + None + """ + resp = client.get('/tax-calculator/') + assert resp.status_code == 400 + error = resp.json['errors'][0] + assert error['code'] == '' + assert error['message'] == 'Both salary and tax_year parameters are required.' + + +def test_missing_salary(client): + """ + Test missing salary parameter for the tax calculator API. + + Parameters: + client: The Flask test client. + + Returns: + None + """ + resp = client.get('/tax-calculator/?tax_year=2022') + assert resp.status_code == 400 + error = resp.json['errors'][0] + assert error['code'] == '' + assert error['field'] == 'salary' + assert 'required' in error['message'] + + +def test_missing_tax_year(client): + """ + Test missing tax year parameter for the tax calculator API. + + Parameters: + client: The Flask test client. + + Returns: + None + """ + resp = client.get('/tax-calculator/?salary=50000') + assert resp.status_code == 400 + error = resp.json['errors'][0] + assert error['code'] == '' + assert error['field'] == 'tax_year' + assert 'required' in error