feat(nadia): Expose missing calculator operations as API endpoints
Outcome: done Changed: src/typosaurus_sandbox/presentation/api/v1/calculator.py — added imports (average, median, variance, percentage, logging), ValuesRequest model, PercentageRequest model, and four new endpoint handlers with logging and ValueError → 422 conversion tests/test_api.py — added 26 test cases across four new test classes Verified by: `make verify` — 93 tests, 0 failures, 0 new warnings Findings: - `ValuesRequest` model accepts `values: list[float]` and serves /average, /median, /variance - `PercentageRequest` model accepts `value: float, total: float` for /percentage - All four endpoints log at DEBUG level on invocation - Empty list (average, median, variance) and zero total (percentage) produce HTTP 422 with descriptive detail Open: none Confidence: high — 26 new tests pass, zero regressions, pattern matches existing endpoint conventions Typosaurus-Run: 34b5946ec981488091bee588eb919ff2 Typosaurus-Node: 644d226aefca431c81cf8be8a46d2070 Typosaurus-Agent: @nadia Refs: #28
This commit is contained in:
@@ -118,3 +118,133 @@ class TestCalculatorClampToByteEndpoint(unittest.TestCase):
|
||||
response = client.post("/api/v1/calculator/clamp-to-byte", json={"value": "abc"})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
|
||||
class TestCalculatorAverageEndpoint(unittest.TestCase):
|
||||
|
||||
def test_average_positive_values(self) -> None:
|
||||
response = client.post("/api/v1/calculator/average", json={"values": [1, 2, 3, 4, 5]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 3.0})
|
||||
|
||||
def test_average_single_value(self) -> None:
|
||||
response = client.post("/api/v1/calculator/average", json={"values": [5]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 5.0})
|
||||
|
||||
def test_average_negative_values(self) -> None:
|
||||
response = client.post("/api/v1/calculator/average", json={"values": [-10, -20, -30]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": -20.0})
|
||||
|
||||
def test_average_empty_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/average", json={"values": []})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_average_invalid_input_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/average", json={"values": ["a", "b"]})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_average_missing_field_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/average", json={})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
|
||||
class TestCalculatorMedianEndpoint(unittest.TestCase):
|
||||
|
||||
def test_median_odd_length(self) -> None:
|
||||
response = client.post("/api/v1/calculator/median", json={"values": [1, 3, 5]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 3.0})
|
||||
|
||||
def test_median_even_length(self) -> None:
|
||||
response = client.post("/api/v1/calculator/median", json={"values": [1, 2, 3, 4]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 2.5})
|
||||
|
||||
def test_median_single_element(self) -> None:
|
||||
response = client.post("/api/v1/calculator/median", json={"values": [7]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 7.0})
|
||||
|
||||
def test_median_unsorted_input(self) -> None:
|
||||
response = client.post("/api/v1/calculator/median", json={"values": [3, 1, 2]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 2.0})
|
||||
|
||||
def test_median_empty_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/median", json={"values": []})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_median_invalid_input_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/median", json={"values": ["a"]})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_median_missing_field_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/median", json={})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
|
||||
class TestCalculatorVarianceEndpoint(unittest.TestCase):
|
||||
|
||||
def test_variance_known_set(self) -> None:
|
||||
response = client.post("/api/v1/calculator/variance", json={"values": [1, 2, 3, 4, 5]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 2.0})
|
||||
|
||||
def test_variance_constant_values(self) -> None:
|
||||
response = client.post("/api/v1/calculator/variance", json={"values": [1.0, 1.0, 1.0]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 0.0})
|
||||
|
||||
def test_variance_single_element(self) -> None:
|
||||
response = client.post("/api/v1/calculator/variance", json={"values": [42.0]})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 0.0})
|
||||
|
||||
def test_variance_empty_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/variance", json={"values": []})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_variance_invalid_input_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/variance", json={"values": ["a", "b", "c"]})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_variance_missing_field_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/variance", json={})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
|
||||
class TestCalculatorPercentageEndpoint(unittest.TestCase):
|
||||
|
||||
def test_percentage_half(self) -> None:
|
||||
response = client.post("/api/v1/calculator/percentage", json={"value": 50, "total": 100})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 50.0})
|
||||
|
||||
def test_percentage_quarter(self) -> None:
|
||||
response = client.post("/api/v1/calculator/percentage", json={"value": 25, "total": 100})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 25.0})
|
||||
|
||||
def test_percentage_zero_value(self) -> None:
|
||||
response = client.post("/api/v1/calculator/percentage", json={"value": 0, "total": 100})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 0.0})
|
||||
|
||||
def test_percentage_total_zero_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/percentage", json={"value": 50, "total": 0})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_percentage_invalid_input_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/percentage", json={"value": "abc", "total": 100})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_percentage_missing_value_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/percentage", json={"total": 100})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_percentage_missing_total_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/percentage", json={"value": 50})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user