Compare commits

..
Author SHA1 Message Date
typosaurus a4ee545314 feat(nadia): Update CLAUDE.md with CI configuration reference
All acceptance criteria verified:

| Criterion | Status |
|---|---|
| CLAUDE.md gains a `## CI` section | Line 33-38 |
| Documents `.gitea/workflows/ci.yml` as CI file | Line 34 |
| Documents push triggers on `main` and `master` | Line 35 |
| Documents CI runs `make verify` | Line 36 |
| All existing content preserved | All original sections intact |
| File starts with `# retoor <retoor@molodetz.nl>` header | Line 1 |
| No placeholder or TODO content | Confirmed |

Outcome: done
Changed: CLAUDE.md:1,33-38
Verified by: `make verify` — passed (61 tests, 0 failures, 0 warnings)
Findings:
- CLAUDE.md now has a `## CI` section documenting `.gitea/workflows/ci.yml`, push triggers on `main`/`master`, and `make verify` as the CI command
Open: none
Confidence: high — all acceptance criteria met, verification passes

Typosaurus-Run: c4074cd2eb1f4a0a8951adbbe1a6ec32
Typosaurus-Node: def7f24c310b4de8a068083210549c16
Typosaurus-Agent: @nadia
Refs: #26
2026-07-26 20:52:10 +00:00
typosaurus 888abecd7f feat(nadia): Create .gitea/workflows/ci.yml workflow file
Outcome: done
Changed: `.gitea/workflows/ci.yml:1-22` — new file
Verified by: `make verify` — passed (61 tests, 0 failures, 0 warnings)
Findings:
- `.gitea/workflows/ci.yml` created with `retoor` header, triggers on push to `main` and `master`, checks out repo via `actions/checkout@v4`, sets up Python 3.12 via `actions/setup-python@v5`, installs dependencies with `pip install -e .`, runs `make verify` as final step
- YAML syntax validated as well-formed
- Project verification passes with zero warnings
Open: none
Confidence: high — all acceptance criteria verified, YAML is valid, project verification passes

Typosaurus-Run: c4074cd2eb1f4a0a8951adbbe1a6ec32
Typosaurus-Node: bb12c67567de40c6924cc944cb9d1c22
Typosaurus-Agent: @nadia
Refs: #26
2026-07-26 20:43:56 +00:00
typosaurus 4940dfeffd Merge pull request 'feat: Add an average function to the calculator' (#12) from typosaurus/11-add-an-average-function-to-the-calculator into main
Reviewed-on: #12
2026-07-26 22:13:17 +02:00
typosaurus c5999afcad test(sveta): Write tests for average function
Outcome: done
Changed: tests/test_calculator.py:1 (import line), tests/test_calculator.py:88-107 (new class)
Verified by: `make verify` passed (22 tests, OK); `python3 -m compileall -q tests/test_calculator.py` passed (no warnings)
Findings: TestAverageFunction class with 6 test methods added to tests/test_calculator.py:88-107 covering empty, single-element, positive, negative, mixed, and float input cases.
Open: none
Confidence: high - all 6 acceptance criteria tests exist, all pass, no comments/docstrings, conventions followed

Typosaurus-Run: 32dcefafeb39422b82cbd65f56833df7
Typosaurus-Node: cc947cb8b24b47139b0a1b6e10d6384a
Typosaurus-Agent: @sveta
Refs: #11
2026-07-26 20:12:16 +00:00
typosaurus 3ad3dc517f feat(nadia): Implement average function in src/calculator.py
Outcome: done
Changed: src/calculator.py:29-32
Verified by: `make verify` passed (16 tests, OK); `python3 -m compileall -q src/calculator.py` passed; manual assertion of all acceptance criteria passed
Findings: average(values: list[int | float]) -> float was added to src/calculator.py:29-32
Open: none
Confidence: high - all acceptance criteria met, header present, type annotations present, no comments/docstrings, compile passes, tests pass, manual verification confirms every criterion

Typosaurus-Run: 32dcefafeb39422b82cbd65f56833df7
Typosaurus-Node: 7a52f9800aff4d4396b35d10ada3aacf
Typosaurus-Agent: @nadia
Refs: #11
2026-07-26 20:09:43 +00:00
typosaurus 0b77019f6c Merge pull request 'feat: Expose the calculator over HTTP' (#17) from typosaurus/15-expose-the-calculator-over-http into main
Reviewed-on: #17
2026-07-26 21:59:04 +02:00
typosaurus a4a436c020 test(sveta): Write API integration tests for calculator endpoints
Outcome: done
Changed: tests/test_api.py:53-68,75-85,90-98 — added clamp low>high, missing-field, and subtract missing-field tests; src/typosaurus_sandbox/presentation/api/v1/calculator.py:2,51-55 — added HTTPException import and ValueError catch in calculate_clamp
Verified by: `make verify` — exit 0, 47 tests OK, zero warnings, zero compile errors
Findings:
- tests/test_api.py now contains 22 integration tests covering all five endpoints with success, validation errors (missing fields, wrong types), and the clamp low>high ValueError boundary.
- clamp low>high error is returned as 422 via HTTPException in the route handler, matching the acceptance criterion.
- All 47 tests (25 unit + 22 integration) pass with zero warnings.
Open: none
Confidence: high — all acceptance criteria addressed, all tests pass, verification gate passed

Typosaurus-Run: d4f7c095ea9d49c69663ae6d01a21513
Typosaurus-Node: 9fe1a5bb3c164f6eb359366ca55b1067
Typosaurus-Agent: @sveta
Refs: #15
2026-07-26 19:57:37 +00:00
typosaurus 1278d5c332 feat(nadia): Implement FastAPI application with calculator endpoints
```yaml
Outcome: done
Changed:
  - pyproject.toml:1-18
  - src/typosaurus_sandbox/__init__.py:1-4
  - src/typosaurus_sandbox/__main__.py:1-8
  - src/typosaurus_sandbox/app.py:1-18
  - src/typosaurus_sandbox/domain/__init__.py:1-1
  - src/typosaurus_sandbox/domain/calculator/__init__.py:1-6
  - src/typosaurus_sandbox/domain/calculator/operations.py:1-23
  - src/typosaurus_sandbox/presentation/__init__.py:1-1
  - src/typosaurus_sandbox/presentation/api/__init__.py:1-1
  - src/typosaurus_sandbox/presentation/api/v1/__init__.py:1-1
  - src/typosaurus_sandbox/presentation/api/v1/calculator.py:1-63
  - tests/__init__.py (unchanged)
  - tests/test_calculator.py:1-95
  - tests/test_api.py:1-120
  - Makefile:2
  - CLAUDE.md:4-23
Verified by: `make verify` — exit 0, 42 tests OK, zero warnings
Findings:
  - FastAPI application created at src/typosaurus_sandbox/app.py with App importable as `from typosaurus_sandbox import App`.
  - Calculator HTTP API router at src/typosaurus_sandbox/presentation/api/v1/calculator.py with endpoints: POST /api/v1/calculator/add, POST /api/v1/calculator/subtract, POST /api/v1/calculator/clamp, POST /api/v1/calculator/clamp-to-byte.
  - Health endpoint at GET /health implemented directly on App in src/typosaurus_sandbox/app.py.
  - All endpoints use Pydantic models for request validation and response serialization (AddRequest, SubtractRequest, ClampRequest, ClampToByteRequest, IntResult, FloatResult).
  - 42 tests pass (16 unit tests for calculator function

Typosaurus-Run: d4f7c095ea9d49c69663ae6d01a21513
Typosaurus-Node: d0f507214bf6453cab8f8d19b8fd2040
Typosaurus-Agent: @nadia
Refs: #15
2026-07-26 19:57:06 +00:00
typosaurus 697e926dfe Merge pull request 'feat: Make it a web application' (#10) from typosaurus/9-make-it-a-web-application into main
Reviewed-on: #10
2026-07-26 21:51:20 +02:00
typosaurus 2ee246fad6 Merge pull request 'feat: Add a percentage function to the calculator' (#18) from typosaurus/16-add-a-percentage-function-to-the-calculator into main
Reviewed-on: #18
2026-07-26 21:51:01 +02:00
typosaurus c6ff93c764 test(sveta): Write tests for percentage function
Outcome: done
Changed: tests/test_calculator.py:1 (import), tests/test_calculator.py:66-96 (new TestPercentageFunction class)
Verified by: `make verify` — exit 0, Ran 23 tests, OK
Findings: tests/test_calculator.py:66-96 — TestPercentageFunction class added with 7 test methods (valid_percentage, zero_value, full_value, fractional, total_zero, float_arguments, negative_value). All 23 tests pass.
Open: none
Confidence: high — all acceptance criteria met, verification passed

Typosaurus-Run: ff52a86851934fd293e2f4493c5a9a46
Typosaurus-Node: 7edce3239d434d36aed06535aae0d0ad
Typosaurus-Agent: @sveta
Refs: #16
2026-07-26 17:45:06 +00:00
typosaurus e11ca4b376 feat(nadia): Implement percentage function in calculator.py
Outcome: done
Changed: src/calculator.py:1 (import Union), src/calculator.py:31-34 (percentage function)
Verified by: `make verify` — exit 0, all 16 tests passed, compilation clean
Findings: src/calculator.py:31-34 — percentage(value: Union[int, float], total: Union[int, float]) -> float added, returning (value/total)*100, raising ValueError on total==0
Open: none
Confidence: high — all acceptance criteria met, verification passed

Typosaurus-Run: ff52a86851934fd293e2f4493c5a9a46
Typosaurus-Node: baf30844321745fb8a1aabe91a33823b
Typosaurus-Agent: @nadia
Refs: #16
2026-07-26 17:44:38 +00:00
typosaurus 5515714ae6 feat(nadia): Build the web frontend
Outcome: done
Changed: app/__init__.py:1-68, app/index.html:1-63
Verified by: `make verify` - passed (compileall src/tests/app, 31 unittest tests OK)
Findings:
- app/__init__.py:15-18 introduces the GET / route serving index.html via a file read with Content-Type: text/html.
- app/index.html:1 carries the # retoor header in an HTML comment.
- app/index.html:22-24 provides input fields for left/right operands and Add/Subtract buttons.
- app/index.html:28-31 provides input fields for value/low/high and a Clamp button.
- app/index.html:35-37 provides a value input field and a Clamp to Byte button.
- app/index.html:39 shows results via the #output element, updated through fetch() without page reload.
- app/index.html:57-59 displays API error responses and network errors to the user.
Open: none
Confidence: high - all 6 acceptance criteria satisfied, verification passed with 31 tests, no warnings introduced.

Typosaurus-Run: b3f34882127d437e93c240def12065ad
Typosaurus-Node: ebf1b0c5b7a24af29ee4ce91a714c4e9
Typosaurus-Agent: @nadia
Refs: #9
2026-07-26 16:09:21 +02:00
typosaurus 6f571b9e12 test(sveta): Write tests for the web API
Outcome: done
Changed: tests/test_api.py:1-96
Verified by: `make verify` — passed (compileall src/tests/app, 31 unittest tests OK)
Findings: tests/test_api.py:1-96 written — 15 tests covering all 4 endpoints (add, subtract, clamp, clamp_to_byte) with success cases, missing-param failures, invalid-type failures, and boundary conditions (clamp low>high, clamp_to_byte above 255). All use Flask test client. make verify passes with 31 tests total.
Open: none
Confidence: high — all acceptance criteria met; each endpoint has ≥1 success test and ≥1 failure test; Flask test client used without a running server; tests follow the `# retoor` header convention in tests/; `make verify` confirmed passing.

Typosaurus-Run: b3f34882127d437e93c240def12065ad
Typosaurus-Node: 045e88c491a9472ea4d6e41b0ebb7d45
Typosaurus-Agent: @sveta
Refs: #9
2026-07-26 16:09:04 +02:00
typosaurus bab9c2ec08 feat(nadia): Implement calculator API endpoints
Outcome: done
Changed: app/__init__.py:1-59 (pre-existing, meets all criteria), Makefile:3 (added app to compileall, changed python3 to python3.13)
Verified by: `make verify` - passed (compileall src/tests/app, 16 unittest tests OK)
Findings: app/__init__.py:1-59 contains the Flask app with all 4 calculator API endpoints (add, subtract, clamp, clamp_to_byte), all returning JSON with 'result' key, HTTP 200 on valid input, HTTP 400 on invalid/missing params, # retoor header present, and full type annotations.
Makefile:3 updated to use python3.13 and include app directory in compileall step for full verification coverage.
All acceptance criteria for calculator API endpoints are satisfied and verified by endpoint tests via Flask test client.
Open: none
Confidence: high - all 8 acceptance criteria verified by test client assertions, compilation passes, existing unit tests pass

Typosaurus-Run: b3f34882127d437e93c240def12065ad
Typosaurus-Node: 3fc840b7065a4c99b40a5e4130262e38
Typosaurus-Agent: @nadia
Refs: #9
2026-07-26 16:08:24 +02:00
typosaurus 63965696f4 feat(zoya): Review scaffolding against acceptance criteria
## Review Verdict — Scaffolding against acceptance criteria

### Per-criterion assessment

| # | Criterion | Evidence | Verdict |
|---|-----------|----------|---------|
| 1 | `requirements.txt` lists Flask | `requirements.txt:1` — `Flask>=3.0,<4.0` | **PASS** |
| 2 | `app/__init__.py` exists with header | `app/__init__.py:1` — `# retoor <retoor@molodetz.nl>` present | **PASS** |
| 3 | Makefile has a `run` target | `Makefile:5-6` — `run:\n\tFLASK_APP=app flask run` — uses package name `app` which Flask resolves to `app.__init__:app`, correct | **PASS** |
| 4 | `make verify` passes | Exit 0, 16 tests OK, "verification passed" printed, no stderr | **PASS** |
| 5 | `.gitignore` excludes common Flask artifacts | `.gitignore:1-2` — `__pycache__/` and `*.pyc` cover Python bytecode and cache dirs | **PASS** |
| 6 | No TODO, placeholder, or stub | Zero matches for `TODO|FIXME|placeholder|stub|XXX` across `.py`, `Makefile`, `README.md`, `requirements.txt` | **PASS** |
| 7 | No warning introduced | `make verify` produced zero stderr lines, no warnings in output | **PASS** |

### Findings

- `requirements.txt:1` — `Flask>=3.0,<4.0` added as dependency.
- `app/__init__.py:1-5` — package header, Flask import, and app instantiation all present.
- `Makefile:5-6` — run target uses `FLASK_APP=app flask run` (correct, points to `app` package).
- `make verify` — exit 0, 16 tests pass, no warnings.
- `.gitignore` — covers `__pycache__/` and `*.pyc`.
- No TODO, FIXME, placeholder, stub, or warning

Typosaurus-Run: b3f34882127d437e93c240def12065ad
Typosaurus-Node: 2ea09ca06d014fc6b74ce6c323ad78ff
Typosaurus-Agent: @zoya
Refs: #9
2026-07-26 15:10:14 +02:00
typosaurus 7fb55c03fc feat(nadia): Add run target and update project metadata
Outcome: done
Changed: Makefile:5-6; app/__init__.py:3-5; README.md:5-9
Verified by: make verify — exit 0, 16 tests OK, verification passed
Findings: Makefile run target added at ./Makefile:5-6 using FLASK_APP=app flask run
app/__init__.py updated at ./app/__init__.py:3-5 with Flask app instance
README.md updated at ./README.md:5-9 with web app run instructions
.gitignore already covers __pycache__/ and *.pyc, no changes needed
Open: none
Confidence: high - all three acceptance criteria met, verify passes, no warnings or TODOs introduced

Typosaurus-Run: b3f34882127d437e93c240def12065ad
Typosaurus-Node: e17f9f081d474231a32bd81836d2b0bc
Typosaurus-Agent: @nadia
Refs: #9
2026-07-26 15:09:49 +02:00
typosaurus bf0e3e133f feat(nadia): Add Flask dependency and create app package
Outcome: done
Changed: requirements.txt:1; app/__init__.py:1
Verified by: make verify — exit 0, 16 tests OK, verification passed
Findings: requirements.txt created at /workspace/requirements.txt, lists Flask>=3.0,<4.0
app/__init__.py created at /workspace/app/__init__.py with header '# retoor <retoor@molodetz.nl>'
make verify passes after adding Flask dependency and app package
Open: none
Confidence: high - both acceptance criteria met and verification passes

Typosaurus-Run: b3f34882127d437e93c240def12065ad
Typosaurus-Node: 70e1371eede04740955ad247415e11c6
Typosaurus-Agent: @nadia
Refs: #9
2026-07-26 15:08:49 +02:00
11 changed files with 223 additions and 5 deletions
+21
View File
@@ -0,0 +1,21 @@
# 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
+9
View File
@@ -1,3 +1,4 @@
# retoor <retoor@molodetz.nl>
# typosaurus-sandbox
A minimal Python calculator used to verify the Typosaurus agent system.
@@ -26,3 +27,11 @@ A minimal Python calculator used to verify the Typosaurus agent system.
```
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`
+4
View File
@@ -1,4 +1,8 @@
# 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
+67
View File
@@ -0,0 +1,67 @@
# 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)
+69
View File
@@ -0,0 +1,69 @@
<!-- 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
View File
@@ -0,0 +1 @@
Flask>=3.0,<4.0
+12 -1
View File
@@ -1,6 +1,6 @@
# retoor <retoor@molodetz.nl>
from typing import Sequence
from typing import Sequence, Union
def add(left: int, right: int) -> int:
@@ -25,6 +25,12 @@ 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 variance(values: Sequence[float]) -> float:
if not values:
raise ValueError
@@ -32,3 +38,8 @@ def variance(values: Sequence[float]) -> float:
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,6 +1,7 @@
# retoor <retoor@molodetz.nl>
from typosaurus_sandbox.domain.calculator.operations import add, clamp, clamp_to_byte, subtract, variance
from typosaurus_sandbox.domain.calculator.operations import add, average, clamp, clamp_to_byte, percentage, subtract, variance
__all__ = ["add", "average", "clamp", "clamp_to_byte", "percentage", "subtract", "variance"]
__all__ = ["add", "subtract", "clamp", "clamp_to_byte", "variance"]
@@ -1,6 +1,6 @@
# retoor <retoor@molodetz.nl>
from typing import Sequence
from typing import Sequence, Union
def add(left: int, right: int) -> int:
@@ -25,9 +25,21 @@ 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 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
View File
@@ -117,3 +117,4 @@ class TestCalculatorClampToByteEndpoint(unittest.TestCase):
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)
+23 -1
View File
@@ -3,7 +3,7 @@
import math
import unittest
from typosaurus_sandbox.domain.calculator import add, clamp, clamp_to_byte, subtract, variance
from typosaurus_sandbox.domain.calculator import add, average, clamp, clamp_to_byte, subtract, variance
class TestAddFunction(unittest.TestCase):
@@ -18,6 +18,28 @@ class TestAddFunction(unittest.TestCase):
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: