Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5515714ae6 | ||
|
|
6f571b9e12 | ||
|
|
bab9c2ec08 | ||
|
|
63965696f4 | ||
|
|
7fb55c03fc | ||
|
|
bf0e3e133f | ||
|
|
078766216d | ||
|
|
8605a8c596 | ||
|
|
c7f0712d12 | ||
|
|
65ce7f1994 | ||
|
|
f5363514bf | ||
|
|
ff830c6fb8 | ||
|
|
cb4c5516ad | ||
|
|
d5861fb2e3 | ||
|
|
8f7f09f44a | ||
|
|
3f2a5c8a07 |
@@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -1,2 +1,5 @@
|
||||
verify:
|
||||
@python3 -m compileall -q src && echo "verification passed"
|
||||
@python3.13 -m compileall -q src tests app && python3.13 -m unittest discover -s tests -q && echo "verification passed"
|
||||
|
||||
run:
|
||||
FLASK_APP=app flask run
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# typosaurus-sandbox
|
||||
|
||||
Sandbox for Typosaurus end-to-end verification
|
||||
Sandbox for Typosaurus end-to-end verification
|
||||
|
||||
## Running the web app
|
||||
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
The development server starts at http://127.0.0.1:5000.
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
Flask>=3.0,<4.0
|
||||
Binary file not shown.
+12
-2
@@ -8,5 +8,15 @@ def subtract(left: int, right: int) -> int:
|
||||
return left - right
|
||||
|
||||
|
||||
def power(base: int, exponent: int) -> int:
|
||||
return base ** exponent
|
||||
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))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
@@ -0,0 +1,87 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from app import app
|
||||
|
||||
|
||||
class TestCalculatorAPI(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')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json, {'result': 8})
|
||||
|
||||
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)
|
||||
|
||||
def test_subtract_success(self) -> None:
|
||||
response = self.client.get('/subtract?left=10&right=3')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json, {'result': 7})
|
||||
|
||||
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')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json, {'result': 5})
|
||||
|
||||
def test_clamp_value_below_low_returns_low(self) -> None:
|
||||
response = self.client.get('/clamp?value=-5&low=0&high=10')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json, {'result': 0})
|
||||
|
||||
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')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json, {'result': 100})
|
||||
|
||||
def test_clamp_to_byte_above_255_returns_255(self) -> None:
|
||||
response = self.client.get('/clamp_to_byte?value=300')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json, {'result': 255})
|
||||
|
||||
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_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)
|
||||
@@ -0,0 +1,63 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import math
|
||||
import unittest
|
||||
|
||||
from src.calculator import clamp
|
||||
|
||||
|
||||
class TestClampFunction(unittest.TestCase):
|
||||
|
||||
def test_value_below_low_returns_low(self) -> None:
|
||||
self.assertEqual(clamp(-5, 0, 10), 0)
|
||||
|
||||
def test_value_above_high_returns_high(self) -> None:
|
||||
self.assertEqual(clamp(15, 0, 10), 10)
|
||||
|
||||
def test_value_in_range_returns_value(self) -> None:
|
||||
self.assertEqual(clamp(5, 0, 10), 5)
|
||||
|
||||
def test_value_equals_low_returns_low(self) -> None:
|
||||
self.assertEqual(clamp(0, 0, 10), 0)
|
||||
|
||||
def test_value_equals_high_returns_high(self) -> None:
|
||||
self.assertEqual(clamp(10, 0, 10), 10)
|
||||
|
||||
def test_low_greater_than_high_raises_value_error(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
clamp(5, 10, 0)
|
||||
|
||||
def test_all_negative_values(self) -> None:
|
||||
self.assertEqual(clamp(-10, -5, -1), -5)
|
||||
|
||||
def test_float_value_below_low_returns_low_as_int(self) -> None:
|
||||
self.assertEqual(clamp(-1.0, 0, 10), 0)
|
||||
|
||||
def test_float_value_above_high_returns_high_as_int(self) -> None:
|
||||
self.assertEqual(clamp(15.0, 0, 10), 10)
|
||||
|
||||
def test_float_value_in_range_returns_float(self) -> None:
|
||||
result = clamp(5.0, 0, 10)
|
||||
self.assertIsInstance(result, float)
|
||||
self.assertEqual(result, 5.0)
|
||||
|
||||
def test_float_value_equals_boundary_returns_boundary(self) -> None:
|
||||
self.assertEqual(clamp(0.0, 0, 10), 0)
|
||||
self.assertEqual(clamp(10.0, 0, 10), 10)
|
||||
|
||||
def test_float_low_greater_than_float_high_raises_value_error(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
clamp(5.0, 10.0, 0.0)
|
||||
|
||||
def test_large_values(self) -> None:
|
||||
self.assertEqual(clamp(10**9, 0, 10**6), 10**6)
|
||||
|
||||
def test_negative_infinity_not_clamped_by_default(self) -> None:
|
||||
self.assertEqual(clamp(-math.inf, 0, 10), 0)
|
||||
|
||||
def test_positive_infinity_not_clamped_by_default(self) -> None:
|
||||
self.assertEqual(clamp(math.inf, 0, 10), 10)
|
||||
|
||||
def test_nan_returns_nan(self) -> None:
|
||||
result = clamp(math.nan, 0, 10)
|
||||
self.assertTrue(math.isnan(result))
|
||||
Reference in New Issue
Block a user