Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Tax Calculator Feature with Error Handling and Unit Tests #15

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions api/tax_calculator/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

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
72 changes: 65 additions & 7 deletions api/tax_calculator/routes.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,80 @@
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()
})


@app.route('/tax-calculator/tax-year')
def default_brackets():
"""Redirect to default brackets."""
return redirect('/')


@app.route('/tax-calculator/tax-year/<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
153 changes: 148 additions & 5 deletions api/tax_calculator/test_tax_calculator_api.py
Original file line number Diff line number Diff line change
@@ -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)

Expand All @@ -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