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:
typosaurus
2026-07-26 22:30:18 +00:00
parent ed445eb07f
commit 41bad19a76
2 changed files with 188 additions and 1 deletions
+130
View File
@@ -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)