diff --git a/README.md b/README.md index 7b68822..61eca46 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,123 @@ +# retoor + # typosaurus-sandbox -Sandbox for Typosaurus end-to-end verification +Sandbox for Typosaurus end-to-end verification. -## Running the web app +A FastAPI application serving arithmetic operations over HTTP with JSON request/response bodies. -```bash -make run +## Configuration + +The application uses a single `.env.json` file at the project root as its central point of truth +for configuration. Defaults are plug-and-play and require no setup: + +```json +{ + "host": "127.0.0.1", + "port": 8000 +} ``` -The development server starts at http://127.0.0.1:5000. \ No newline at end of file +When no `.env.json` is present, the application starts with these defaults. To customise, create +`.env.json` in the project root and populate only the keys that differ. + +## Usage + +### Start the server + +```sh +python -m typosaurus_sandbox +``` + +The server listens on `http://127.0.0.1:8000` by default. + +### Health check + +``` +GET /health +``` + +Response: + +```json +{"status": "ok"} +``` + +## API endpoints + +All calculator endpoints accept `POST` requests with a JSON body and return a JSON response. + +### POST /api/v1/calculator/add + +Add two integers. + +Request: + +```json +{"left": 3, "right": 5} +``` + +Response: + +```json +{"result": 8} +``` + +### POST /api/v1/calculator/subtract + +Subtract the right integer from the left. + +Request: + +```json +{"left": 10, "right": 3} +``` + +Response: + +```json +{"result": 7} +``` + +### POST /api/v1/calculator/clamp + +Clamp a value between a low and high bound. + +Request: + +```json +{"value": 15, "low": 0, "high": 10} +``` + +Response: + +```json +{"result": 10} +``` + +Boundaries are inclusive. A `low > high` combination produces a 422 validation response. + +### POST /api/v1/calculator/clamp-to-byte + +Clamp an integer to the byte range [0, 255]. + +Request: + +```json +{"value": 300} +``` + +Response: + +```json +{"result": 255} +``` + +## Verification + +```sh +make verify +``` + +Runs compile-all checks against all source and test files, then executes the full test suite. +Zero warnings are tolerated. diff --git a/src/typosaurus_sandbox/presentation/api/v1/calculator.py b/src/typosaurus_sandbox/presentation/api/v1/calculator.py index e8937f5..9670759 100644 --- a/src/typosaurus_sandbox/presentation/api/v1/calculator.py +++ b/src/typosaurus_sandbox/presentation/api/v1/calculator.py @@ -1,6 +1,6 @@ # retoor -from fastapi import APIRouter +from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field from typosaurus_sandbox.domain.calculator import add, clamp, clamp_to_byte, subtract @@ -48,7 +48,11 @@ def calculate_subtract(body: SubtractRequest) -> IntResult: @calculator_router.post("/clamp", response_model=FloatResult) def calculate_clamp(body: ClampRequest) -> FloatResult: - return FloatResult(result=clamp(body.value, body.low, body.high)) + try: + result = clamp(body.value, body.low, body.high) + except ValueError: + raise HTTPException(status_code=422, detail="low must not exceed high") + return FloatResult(result=result) @calculator_router.post("/clamp-to-byte", response_model=IntResult) diff --git a/tests/test_api.py b/tests/test_api.py index ee63beb..26b4256 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -54,6 +54,14 @@ class TestCalculatorSubtractEndpoint(unittest.TestCase): response = client.post("/api/v1/calculator/subtract", json={"left": 10, "right": None}) self.assertEqual(response.status_code, 422) + def test_subtract_missing_left_returns_422(self) -> None: + response = client.post("/api/v1/calculator/subtract", json={"right": 3}) + self.assertEqual(response.status_code, 422) + + def test_subtract_missing_right_returns_422(self) -> None: + response = client.post("/api/v1/calculator/subtract", json={"left": 10}) + self.assertEqual(response.status_code, 422) + class TestCalculatorClampEndpoint(unittest.TestCase): @@ -76,6 +84,18 @@ class TestCalculatorClampEndpoint(unittest.TestCase): response = client.post("/api/v1/calculator/clamp", json={"value": "x", "low": 0, "high": 10}) self.assertEqual(response.status_code, 422) + def test_clamp_low_greater_than_high_returns_422(self) -> None: + response = client.post("/api/v1/calculator/clamp", json={"value": 5, "low": 10, "high": 0}) + self.assertEqual(response.status_code, 422) + + def test_clamp_missing_low_returns_422(self) -> None: + response = client.post("/api/v1/calculator/clamp", json={"value": 5, "high": 10}) + self.assertEqual(response.status_code, 422) + + def test_clamp_missing_high_returns_422(self) -> None: + response = client.post("/api/v1/calculator/clamp", json={"value": 5, "low": 0}) + self.assertEqual(response.status_code, 422) + class TestCalculatorClampToByteEndpoint(unittest.TestCase):