Compare commits
No commits in common. "main" and "typosaurus/7-add-a-clamp-function-to-the-calculator" have entirely different histories.
main
...
typosaurus
@ -1,21 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .
|
||||
- name: Run tests
|
||||
run: make verify
|
||||
|
||||
23
CLAUDE.md
23
CLAUDE.md
@ -1,4 +1,3 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
# typosaurus-sandbox
|
||||
|
||||
A minimal Python calculator used to verify the Typosaurus agent system.
|
||||
@ -8,30 +7,8 @@ A minimal Python calculator used to verify the Typosaurus agent system.
|
||||
- Full type annotations on every function signature.
|
||||
- No comments or docstrings in source files.
|
||||
|
||||
## Python backend
|
||||
|
||||
- Package manifest: `pyproject.toml`
|
||||
- Entry module: `src/typosaurus_sandbox/__main__.py`
|
||||
- Framework: FastAPI
|
||||
- Serve frontend: no
|
||||
|
||||
## Architecture
|
||||
|
||||
- Backend serves frontend: no
|
||||
- Module root: `src/typosaurus_sandbox/`
|
||||
- Calculator business logic: `src/typosaurus_sandbox/domain/calculator/operations.py`
|
||||
- HTTP API layer: `src/typosaurus_sandbox/presentation/api/v1/calculator.py`
|
||||
|
||||
## Verification
|
||||
|
||||
```
|
||||
make verify
|
||||
```
|
||||
|
||||
## CI
|
||||
|
||||
- Workflow file: `.gitea/workflows/ci.yml`
|
||||
- Trigger: push to `main` or `master` branches
|
||||
- Steps: checkout, Python 3.12 setup, dependency install, `make verify`
|
||||
|
||||
|
||||
|
||||
8
Makefile
8
Makefile
@ -1,8 +1,2 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
verify:
|
||||
@PYTHONPATH=src python3 -m compileall -q src tests && PYTHONPATH=src python3 -m unittest discover -s tests -q && echo "verification passed"
|
||||
|
||||
run:
|
||||
@PYTHONPATH=src python3 -m typosaurus_sandbox
|
||||
|
||||
@python3 -m compileall -q src tests && python3 -m unittest discover -s tests -q && echo "verification passed"
|
||||
|
||||
122
README.md
122
README.md
@ -1,123 +1,3 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
# typosaurus-sandbox
|
||||
|
||||
Sandbox for Typosaurus end-to-end verification.
|
||||
|
||||
A FastAPI application serving arithmetic operations over HTTP with JSON request/response bodies.
|
||||
|
||||
## 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
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Sandbox for Typosaurus end-to-end verification
|
||||
@ -1,67 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
from flask import jsonify
|
||||
from flask import make_response
|
||||
from flask import request
|
||||
from flask.wrappers import Response
|
||||
|
||||
from src.calculator import add
|
||||
from src.calculator import clamp
|
||||
from src.calculator import clamp_to_byte
|
||||
from src.calculator import subtract
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index() -> Response:
|
||||
index_path = os.path.join(os.path.dirname(__file__), 'index.html')
|
||||
with open(index_path) as f:
|
||||
return make_response(f.read(), 200, {'Content-Type': 'text/html'})
|
||||
|
||||
|
||||
@app.route('/add', methods=['GET'])
|
||||
def add_route() -> Response:
|
||||
try:
|
||||
left = int(request.args['left'])
|
||||
right = int(request.args['right'])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return make_response(jsonify({'error': 'Invalid or missing parameters'}), 400)
|
||||
return make_response(jsonify({'result': add(left, right)}), 200)
|
||||
|
||||
|
||||
@app.route('/subtract', methods=['GET'])
|
||||
def subtract_route() -> Response:
|
||||
try:
|
||||
left = int(request.args['left'])
|
||||
right = int(request.args['right'])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return make_response(jsonify({'error': 'Invalid or missing parameters'}), 400)
|
||||
return make_response(jsonify({'result': subtract(left, right)}), 200)
|
||||
|
||||
|
||||
@app.route('/clamp', methods=['GET'])
|
||||
def clamp_route() -> Response:
|
||||
try:
|
||||
value = int(request.args['value'])
|
||||
low = int(request.args['low'])
|
||||
high = int(request.args['high'])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return make_response(jsonify({'error': 'Invalid or missing parameters'}), 400)
|
||||
try:
|
||||
result = clamp(value, low, high)
|
||||
except ValueError:
|
||||
return make_response(jsonify({'error': 'Invalid or missing parameters'}), 400)
|
||||
return make_response(jsonify({'result': result}), 200)
|
||||
|
||||
|
||||
@app.route('/clamp_to_byte', methods=['GET'])
|
||||
def clamp_to_byte_route() -> Response:
|
||||
try:
|
||||
value = int(request.args['value'])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return make_response(jsonify({'error': 'Invalid or missing parameters'}), 400)
|
||||
return make_response(jsonify({'result': clamp_to_byte(value)}), 200)
|
||||
@ -1,69 +0,0 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Calculator</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Calculator</h1>
|
||||
|
||||
<fieldset>
|
||||
<legend>Add / Subtract</legend>
|
||||
<input type="number" id="left" placeholder="Left operand">
|
||||
<input type="number" id="right" placeholder="Right operand">
|
||||
<button onclick="calculate('add')">Add</button>
|
||||
<button onclick="calculate('subtract')">Subtract</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Clamp</legend>
|
||||
<input type="number" id="value" placeholder="Value">
|
||||
<input type="number" id="low" placeholder="Low">
|
||||
<input type="number" id="high" placeholder="High">
|
||||
<button onclick="calculate('clamp')">Clamp</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Clamp to Byte</legend>
|
||||
<input type="number" id="byte_value" placeholder="Value">
|
||||
<button onclick="calculate('clamp_to_byte')">Clamp to Byte</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="output"></p>
|
||||
|
||||
<script>
|
||||
function calculate(operation) {
|
||||
const resultEl = document.getElementById('output');
|
||||
let url;
|
||||
if (operation === 'add' || operation === 'subtract') {
|
||||
const left = document.getElementById('left').value;
|
||||
const right = document.getElementById('right').value;
|
||||
url = '/' + operation + '?left=' + encodeURIComponent(left) + '&right=' + encodeURIComponent(right);
|
||||
} else if (operation === 'clamp') {
|
||||
const value = document.getElementById('value').value;
|
||||
const low = document.getElementById('low').value;
|
||||
const high = document.getElementById('high').value;
|
||||
url = '/clamp?value=' + encodeURIComponent(value) + '&low=' + encodeURIComponent(low) + '&high=' + encodeURIComponent(high);
|
||||
} else if (operation === 'clamp_to_byte') {
|
||||
const value = document.getElementById('byte_value').value;
|
||||
url = '/clamp_to_byte?value=' + encodeURIComponent(value);
|
||||
}
|
||||
fetch(url)
|
||||
.then(function(response) {
|
||||
return response.json().then(function(data) {
|
||||
if (!response.ok) {
|
||||
resultEl.textContent = 'Error: ' + (data.error || 'Unknown error');
|
||||
} else {
|
||||
resultEl.textContent = 'Result: ' + data.result;
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function() {
|
||||
resultEl.textContent = 'Error: Network error';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,16 +0,0 @@
|
||||
[project]
|
||||
name = "typosaurus-sandbox"
|
||||
version = "0.1.0"
|
||||
description = "Sandbox for Typosaurus end-to-end verification"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"fastapi",
|
||||
"uvicorn[standard]",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
@ -1 +0,0 @@
|
||||
Flask>=3.0,<4.0
|
||||
@ -1,8 +1,5 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
|
||||
def add(left: int, right: int) -> int:
|
||||
return left + right
|
||||
|
||||
@ -23,34 +20,3 @@ def clamp(value: int, low: int, high: int) -> int:
|
||||
|
||||
def clamp_to_byte(value: int) -> int:
|
||||
return max(0, min(255, value))
|
||||
|
||||
|
||||
def average(values: list[int | float]) -> float:
|
||||
if not values:
|
||||
raise ValueError
|
||||
return sum(values) / len(values)
|
||||
|
||||
|
||||
def median(values: list[float]) -> float:
|
||||
if not values:
|
||||
raise ValueError
|
||||
sorted_values = sorted(values)
|
||||
n = len(sorted_values)
|
||||
mid = n // 2
|
||||
if n % 2 == 1:
|
||||
return sorted_values[mid]
|
||||
return (sorted_values[mid - 1] + sorted_values[mid]) / 2.0
|
||||
|
||||
|
||||
def percentage(value: Union[int, float], total: Union[int, float]) -> float:
|
||||
if total == 0:
|
||||
raise ValueError
|
||||
return (value / total) * 100
|
||||
|
||||
|
||||
def variance(values: Sequence[float]) -> float:
|
||||
if not values:
|
||||
raise ValueError
|
||||
mean = sum(values) / len(values)
|
||||
return sum((x - mean) ** 2 for x in values) / len(values)
|
||||
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from typosaurus_sandbox.app import App
|
||||
|
||||
__all__ = ["App"]
|
||||
@ -1,7 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import uvicorn
|
||||
|
||||
from typosaurus_sandbox.app import App
|
||||
|
||||
uvicorn.run(App, host="127.0.0.1", port=8000, log_level="info")
|
||||
@ -1,15 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from typosaurus_sandbox.presentation.api.v1.calculator import calculator_router
|
||||
|
||||
App = FastAPI(title="typosaurus-sandbox")
|
||||
|
||||
|
||||
@App.get("/health")
|
||||
def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
App.include_router(calculator_router)
|
||||
@ -1 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
@ -1,6 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from typosaurus_sandbox.domain.calculator.operations import add, average, clamp, clamp_to_byte, median, percentage, subtract, variance
|
||||
|
||||
__all__ = ["add", "average", "clamp", "clamp_to_byte", "median", "percentage", "subtract", "variance"]
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
|
||||
def add(left: int, right: int) -> int:
|
||||
return left + right
|
||||
|
||||
|
||||
def subtract(left: int, right: int) -> int:
|
||||
return left - right
|
||||
|
||||
|
||||
def clamp(value: int, low: int, high: int) -> int:
|
||||
if low > high:
|
||||
raise ValueError
|
||||
if value < low:
|
||||
return low
|
||||
if value > high:
|
||||
return high
|
||||
return value
|
||||
|
||||
|
||||
def clamp_to_byte(value: int) -> int:
|
||||
return max(0, min(255, value))
|
||||
|
||||
|
||||
def average(values: list[int | float]) -> float:
|
||||
if not values:
|
||||
raise ValueError
|
||||
return sum(values) / len(values)
|
||||
|
||||
|
||||
def median(values: list[float]) -> float:
|
||||
if not values:
|
||||
raise ValueError
|
||||
sorted_values = sorted(values)
|
||||
n = len(sorted_values)
|
||||
mid = n // 2
|
||||
if n % 2 == 1:
|
||||
return sorted_values[mid]
|
||||
return (sorted_values[mid - 1] + sorted_values[mid]) / 2.0
|
||||
|
||||
|
||||
def variance(values: Sequence[float]) -> float:
|
||||
if not values:
|
||||
raise ValueError
|
||||
mean = sum(values) / len(values)
|
||||
return sum((x - mean) ** 2 for x in values) / len(values)
|
||||
|
||||
|
||||
def percentage(value: Union[int, float], total: Union[int, float]) -> float:
|
||||
if total == 0:
|
||||
raise ValueError
|
||||
return (value / total) * 100
|
||||
|
||||
@ -1 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
@ -1 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
@ -1 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
@ -1,60 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from typosaurus_sandbox.domain.calculator import add, clamp, clamp_to_byte, subtract
|
||||
|
||||
calculator_router = APIRouter(prefix="/api/v1/calculator")
|
||||
|
||||
|
||||
class AddRequest(BaseModel):
|
||||
left: int
|
||||
right: int
|
||||
|
||||
|
||||
class SubtractRequest(BaseModel):
|
||||
left: int
|
||||
right: int
|
||||
|
||||
|
||||
class ClampRequest(BaseModel):
|
||||
value: float
|
||||
low: float
|
||||
high: float
|
||||
|
||||
|
||||
class ClampToByteRequest(BaseModel):
|
||||
value: int = Field(ge=-2147483648, le=2147483647)
|
||||
|
||||
|
||||
class IntResult(BaseModel):
|
||||
result: int
|
||||
|
||||
|
||||
class FloatResult(BaseModel):
|
||||
result: float
|
||||
|
||||
|
||||
@calculator_router.post("/add", response_model=IntResult)
|
||||
def calculate_add(body: AddRequest) -> IntResult:
|
||||
return IntResult(result=add(body.left, body.right))
|
||||
|
||||
|
||||
@calculator_router.post("/subtract", response_model=IntResult)
|
||||
def calculate_subtract(body: SubtractRequest) -> IntResult:
|
||||
return IntResult(result=subtract(body.left, body.right))
|
||||
|
||||
|
||||
@calculator_router.post("/clamp", response_model=FloatResult)
|
||||
def calculate_clamp(body: ClampRequest) -> FloatResult:
|
||||
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)
|
||||
def calculate_clamp_to_byte(body: ClampToByteRequest) -> IntResult:
|
||||
return IntResult(result=clamp_to_byte(body.value))
|
||||
@ -1,120 +0,0 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import unittest
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from typosaurus_sandbox.app import App
|
||||
|
||||
client = TestClient(App)
|
||||
|
||||
|
||||
class TestHealthEndpoint(unittest.TestCase):
|
||||
|
||||
def test_health_returns_ok(self) -> None:
|
||||
response = client.get("/health")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"status": "ok"})
|
||||
|
||||
|
||||
class TestCalculatorAddEndpoint(unittest.TestCase):
|
||||
|
||||
def test_add_positive_integers(self) -> None:
|
||||
response = client.post("/api/v1/calculator/add", json={"left": 3, "right": 5})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 8})
|
||||
|
||||
def test_add_negative_integers(self) -> None:
|
||||
response = client.post("/api/v1/calculator/add", json={"left": -3, "right": -5})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": -8})
|
||||
|
||||
def test_add_invalid_input_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/add", json={"left": "abc", "right": 5})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_add_missing_field_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/add", json={"left": 3})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
|
||||
class TestCalculatorSubtractEndpoint(unittest.TestCase):
|
||||
|
||||
def test_subtract_positive(self) -> None:
|
||||
response = client.post("/api/v1/calculator/subtract", json={"left": 10, "right": 3})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 7})
|
||||
|
||||
def test_subtract_negative_result(self) -> None:
|
||||
response = client.post("/api/v1/calculator/subtract", json={"left": 3, "right": 10})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": -7})
|
||||
|
||||
def test_subtract_invalid_input_returns_422(self) -> None:
|
||||
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):
|
||||
|
||||
def test_clamp_value_below_low(self) -> None:
|
||||
response = client.post("/api/v1/calculator/clamp", json={"value": -5, "low": 0, "high": 10})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 0})
|
||||
|
||||
def test_clamp_value_above_high(self) -> None:
|
||||
response = client.post("/api/v1/calculator/clamp", json={"value": 15, "low": 0, "high": 10})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 10})
|
||||
|
||||
def test_clamp_value_in_range(self) -> None:
|
||||
response = client.post("/api/v1/calculator/clamp", json={"value": 5, "low": 0, "high": 10})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 5.0})
|
||||
|
||||
def test_clamp_invalid_input_returns_422(self) -> None:
|
||||
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):
|
||||
|
||||
def test_clamp_to_byte_within_range(self) -> None:
|
||||
response = client.post("/api/v1/calculator/clamp-to-byte", json={"value": 128})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 128})
|
||||
|
||||
def test_clamp_to_byte_below_zero(self) -> None:
|
||||
response = client.post("/api/v1/calculator/clamp-to-byte", json={"value": -10})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 0})
|
||||
|
||||
def test_clamp_to_byte_above_255(self) -> None:
|
||||
response = client.post("/api/v1/calculator/clamp-to-byte", json={"value": 300})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"result": 255})
|
||||
|
||||
def test_clamp_to_byte_invalid_input_returns_422(self) -> None:
|
||||
response = client.post("/api/v1/calculator/clamp-to-byte", json={"value": "abc"})
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
@ -3,69 +3,7 @@
|
||||
import math
|
||||
import unittest
|
||||
|
||||
from typosaurus_sandbox.domain.calculator import add, average, clamp, clamp_to_byte, median, subtract, variance
|
||||
|
||||
|
||||
class TestAddFunction(unittest.TestCase):
|
||||
|
||||
def test_add_positive_integers(self) -> None:
|
||||
self.assertEqual(add(3, 5), 8)
|
||||
|
||||
def test_add_negative_integers(self) -> None:
|
||||
self.assertEqual(add(-3, -5), -8)
|
||||
|
||||
def test_add_mixed_sign(self) -> None:
|
||||
self.assertEqual(add(-3, 5), 2)
|
||||
|
||||
|
||||
class TestAverageFunction(unittest.TestCase):
|
||||
|
||||
def test_empty_sequence_raises_value_error(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
average([])
|
||||
|
||||
def test_single_element(self) -> None:
|
||||
self.assertEqual(average([5]), 5.0)
|
||||
|
||||
def test_positive_values(self) -> None:
|
||||
self.assertEqual(average([1, 2, 3, 4, 5]), 3.0)
|
||||
|
||||
def test_negative_values(self) -> None:
|
||||
self.assertEqual(average([-10, -20, -30]), -20.0)
|
||||
|
||||
def test_mixed_positive_and_negative(self) -> None:
|
||||
self.assertEqual(average([-5, 0, 5]), 0.0)
|
||||
|
||||
def test_float_values(self) -> None:
|
||||
self.assertEqual(average([1.5, 2.5, 3.0]), 7.0 / 3.0)
|
||||
|
||||
|
||||
class TestSubtractFunction(unittest.TestCase):
|
||||
|
||||
def test_subtract_positive(self) -> None:
|
||||
self.assertEqual(subtract(10, 3), 7)
|
||||
|
||||
def test_subtract_negative_result(self) -> None:
|
||||
self.assertEqual(subtract(3, 10), -7)
|
||||
|
||||
def test_subtract_negative_numbers(self) -> None:
|
||||
self.assertEqual(subtract(-5, -3), -2)
|
||||
|
||||
|
||||
class TestClampToByteFunction(unittest.TestCase):
|
||||
|
||||
def test_clamp_to_byte_within_range(self) -> None:
|
||||
self.assertEqual(clamp_to_byte(128), 128)
|
||||
|
||||
def test_clamp_to_byte_below_zero(self) -> None:
|
||||
self.assertEqual(clamp_to_byte(-10), 0)
|
||||
|
||||
def test_clamp_to_byte_above_255(self) -> None:
|
||||
self.assertEqual(clamp_to_byte(300), 255)
|
||||
|
||||
def test_clamp_to_byte_boundaries(self) -> None:
|
||||
self.assertEqual(clamp_to_byte(0), 0)
|
||||
self.assertEqual(clamp_to_byte(255), 255)
|
||||
from src.calculator import clamp
|
||||
|
||||
|
||||
class TestClampFunction(unittest.TestCase):
|
||||
@ -123,61 +61,3 @@ class TestClampFunction(unittest.TestCase):
|
||||
def test_nan_returns_nan(self) -> None:
|
||||
result = clamp(math.nan, 0, 10)
|
||||
self.assertTrue(math.isnan(result))
|
||||
|
||||
|
||||
class TestMedianFunction(unittest.TestCase):
|
||||
|
||||
def test_odd_length_returns_middle_element(self) -> None:
|
||||
self.assertEqual(median([1, 3, 5]), 3)
|
||||
|
||||
def test_even_length_returns_float_average_of_two_middle_values(self) -> None:
|
||||
result = median([1, 2, 3, 4])
|
||||
self.assertIsInstance(result, float)
|
||||
self.assertEqual(result, 2.5)
|
||||
|
||||
def test_single_element_returns_that_element(self) -> None:
|
||||
self.assertEqual(median([7]), 7)
|
||||
|
||||
def test_empty_list_raises_value_error(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
median([])
|
||||
|
||||
def test_unsorted_input_sorts_correctly(self) -> None:
|
||||
self.assertEqual(median([3, 1, 2]), 2)
|
||||
|
||||
def test_unsorted_even_length_returns_float_average(self) -> None:
|
||||
result = median([10, 30, 20, 40])
|
||||
self.assertIsInstance(result, float)
|
||||
self.assertEqual(result, 25.0)
|
||||
|
||||
|
||||
class TestVarianceFunction(unittest.TestCase):
|
||||
|
||||
def test_empty_list_raises_value_error(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
variance([])
|
||||
|
||||
def test_empty_tuple_raises_value_error(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
variance(())
|
||||
|
||||
def test_single_element_returns_zero(self) -> None:
|
||||
self.assertEqual(variance([42.0]), 0.0)
|
||||
|
||||
def test_constant_values_return_zero_variance(self) -> None:
|
||||
self.assertEqual(variance([1.0, 1.0, 1.0]), 0.0)
|
||||
|
||||
def test_population_variance_of_known_set(self) -> None:
|
||||
self.assertEqual(variance([1, 2, 3, 4, 5]), 2.0)
|
||||
|
||||
def test_two_element_variance(self) -> None:
|
||||
self.assertEqual(variance([0, 2]), 1.0)
|
||||
|
||||
def test_integer_inputs_return_float(self) -> None:
|
||||
result = variance([10, 20, 30])
|
||||
self.assertIsInstance(result, float)
|
||||
self.assertEqual(result, 200.0 / 3.0)
|
||||
|
||||
def test_tuple_input_returns_variance(self) -> None:
|
||||
self.assertEqual(variance((1, 2, 3, 4, 5)), 2.0)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user