Merge pull request 'feat: Expose the calculator over HTTP' (#17) from typosaurus/15-expose-the-calculator-over-http into main
Reviewed-on: #17
This commit is contained in:
commit
0b77019f6c
14
CLAUDE.md
14
CLAUDE.md
@ -7,6 +7,20 @@ 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
|
||||
|
||||
```
|
||||
|
||||
7
Makefile
7
Makefile
@ -1,5 +1,8 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
verify:
|
||||
@python3.13 -m compileall -q src tests app && python3.13 -m unittest discover -s tests -q && echo "verification passed"
|
||||
@PYTHONPATH=src python3 -m compileall -q src tests && PYTHONPATH=src python3 -m unittest discover -s tests -q && echo "verification passed"
|
||||
|
||||
run:
|
||||
FLASK_APP=app flask run
|
||||
@PYTHONPATH=src python3 -m typosaurus_sandbox
|
||||
|
||||
|
||||
122
README.md
122
README.md
@ -1,11 +1,123 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
# 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.
|
||||
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.
|
||||
|
||||
16
pyproject.toml
Normal file
16
pyproject.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[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"]
|
||||
5
src/typosaurus_sandbox/__init__.py
Normal file
5
src/typosaurus_sandbox/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from typosaurus_sandbox.app import App
|
||||
|
||||
__all__ = ["App"]
|
||||
7
src/typosaurus_sandbox/__main__.py
Normal file
7
src/typosaurus_sandbox/__main__.py
Normal file
@ -0,0 +1,7 @@
|
||||
# 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")
|
||||
15
src/typosaurus_sandbox/app.py
Normal file
15
src/typosaurus_sandbox/app.py
Normal file
@ -0,0 +1,15 @@
|
||||
# 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
src/typosaurus_sandbox/domain/__init__.py
Normal file
1
src/typosaurus_sandbox/domain/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
6
src/typosaurus_sandbox/domain/calculator/__init__.py
Normal file
6
src/typosaurus_sandbox/domain/calculator/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from typosaurus_sandbox.domain.calculator.operations import add, clamp, clamp_to_byte, subtract, variance
|
||||
|
||||
__all__ = ["add", "subtract", "clamp", "clamp_to_byte", "variance"]
|
||||
|
||||
33
src/typosaurus_sandbox/domain/calculator/operations.py
Normal file
33
src/typosaurus_sandbox/domain/calculator/operations.py
Normal file
@ -0,0 +1,33 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
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 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
src/typosaurus_sandbox/presentation/__init__.py
Normal file
1
src/typosaurus_sandbox/presentation/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
1
src/typosaurus_sandbox/presentation/api/__init__.py
Normal file
1
src/typosaurus_sandbox/presentation/api/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
1
src/typosaurus_sandbox/presentation/api/v1/__init__.py
Normal file
1
src/typosaurus_sandbox/presentation/api/v1/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
60
src/typosaurus_sandbox/presentation/api/v1/calculator.py
Normal file
60
src/typosaurus_sandbox/presentation/api/v1/calculator.py
Normal file
@ -0,0 +1,60 @@
|
||||
# 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,87 +1,120 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from app import app
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from typosaurus_sandbox.app import App
|
||||
|
||||
client = TestClient(App)
|
||||
|
||||
|
||||
class TestCalculatorAPI(unittest.TestCase):
|
||||
class TestHealthEndpoint(unittest.TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.client = app.test_client()
|
||||
|
||||
def test_add_success(self) -> None:
|
||||
response = self.client.get('/add?left=3&right=5')
|
||||
def test_health_returns_ok(self) -> None:
|
||||
response = client.get("/health")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json, {'result': 8})
|
||||
self.assertEqual(response.json(), {"status": "ok"})
|
||||
|
||||
def test_add_missing_param_returns_400(self) -> None:
|
||||
response = self.client.get('/add?left=3')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
|
||||
def test_add_invalid_type_returns_400(self) -> None:
|
||||
response = self.client.get('/add?left=abc&right=5')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
class TestCalculatorAddEndpoint(unittest.TestCase):
|
||||
|
||||
def test_subtract_success(self) -> None:
|
||||
response = self.client.get('/subtract?left=10&right=3')
|
||||
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': 7})
|
||||
self.assertEqual(response.json(), {"result": 8})
|
||||
|
||||
def test_subtract_missing_param_returns_400(self) -> None:
|
||||
response = self.client.get('/subtract?right=3')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
|
||||
def test_subtract_invalid_type_returns_400(self) -> None:
|
||||
response = self.client.get('/subtract?left=10&right=xyz')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
|
||||
def test_clamp_success(self) -> None:
|
||||
response = self.client.get('/clamp?value=5&low=0&high=10')
|
||||
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': 5})
|
||||
self.assertEqual(response.json(), {"result": -8})
|
||||
|
||||
def test_clamp_value_below_low_returns_low(self) -> None:
|
||||
response = self.client.get('/clamp?value=-5&low=0&high=10')
|
||||
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': 0})
|
||||
self.assertEqual(response.json(), {"result": 7})
|
||||
|
||||
def test_clamp_missing_param_returns_400(self) -> None:
|
||||
response = self.client.get('/clamp?value=5&low=0')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
|
||||
def test_clamp_invalid_type_returns_400(self) -> None:
|
||||
response = self.client.get('/clamp?value=abc&low=0&high=10')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
|
||||
def test_clamp_low_greater_than_high_returns_400(self) -> None:
|
||||
response = self.client.get('/clamp?value=5&low=10&high=0')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
|
||||
def test_clamp_to_byte_success(self) -> None:
|
||||
response = self.client.get('/clamp_to_byte?value=100')
|
||||
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': 100})
|
||||
self.assertEqual(response.json(), {"result": -7})
|
||||
|
||||
def test_clamp_to_byte_above_255_returns_255(self) -> None:
|
||||
response = self.client.get('/clamp_to_byte?value=300')
|
||||
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': 255})
|
||||
self.assertEqual(response.json(), {"result": 0})
|
||||
|
||||
def test_clamp_to_byte_missing_param_returns_400(self) -> None:
|
||||
response = self.client.get('/clamp_to_byte')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
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)
|
||||
|
||||
def test_clamp_to_byte_invalid_type_returns_400(self) -> None:
|
||||
response = self.client.get('/clamp_to_byte?value=abc')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', response.json)
|
||||
|
||||
@ -3,7 +3,47 @@
|
||||
import math
|
||||
import unittest
|
||||
|
||||
from src.calculator import clamp, percentage, variance
|
||||
from typosaurus_sandbox.domain.calculator import add, clamp, clamp_to_byte, 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 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)
|
||||
|
||||
|
||||
class TestClampFunction(unittest.TestCase):
|
||||
@ -63,31 +103,6 @@ class TestClampFunction(unittest.TestCase):
|
||||
self.assertTrue(math.isnan(result))
|
||||
|
||||
|
||||
class TestPercentageFunction(unittest.TestCase):
|
||||
|
||||
def test_valid_percentage(self) -> None:
|
||||
self.assertEqual(percentage(50, 200), 25.0)
|
||||
|
||||
def test_zero_value(self) -> None:
|
||||
self.assertEqual(percentage(0, 100), 0.0)
|
||||
|
||||
def test_full_value(self) -> None:
|
||||
self.assertEqual(percentage(200, 100), 200.0)
|
||||
|
||||
def test_fractional(self) -> None:
|
||||
self.assertAlmostEqual(percentage(1, 3), 33.333333333333336)
|
||||
|
||||
def test_total_zero(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
percentage(50, 0)
|
||||
|
||||
def test_float_arguments(self) -> None:
|
||||
self.assertEqual(percentage(25.5, 100.0), 25.5)
|
||||
|
||||
def test_negative_value(self) -> None:
|
||||
self.assertEqual(percentage(-50, 200), -25.0)
|
||||
|
||||
|
||||
class TestVarianceFunction(unittest.TestCase):
|
||||
|
||||
def test_empty_list_raises_value_error(self) -> None:
|
||||
@ -118,5 +133,3 @@ class TestVarianceFunction(unittest.TestCase):
|
||||
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