216 lines
7.8 KiB
Python
216 lines
7.8 KiB
Python
import base64
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import app as rollcalc_app
|
|
from machine_constraints import calculate_machine_max_product_length
|
|
|
|
|
|
class CalculationApiTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp_dir = tempfile.TemporaryDirectory()
|
|
self.original_log_file = rollcalc_app.LOG_FILE
|
|
rollcalc_app.LOG_FILE = os.path.join(self.temp_dir.name, "access.json")
|
|
rollcalc_app.app.config.update(TESTING=True)
|
|
self.client = rollcalc_app.app.test_client()
|
|
credentials = base64.b64encode(b"mtazl:rollcalc").decode("ascii")
|
|
self.headers = {"Authorization": f"Basic {credentials}"}
|
|
|
|
def tearDown(self):
|
|
rollcalc_app.LOG_FILE = self.original_log_file
|
|
self.temp_dir.cleanup()
|
|
|
|
def test_calculation_endpoint_returns_machine_readable_authoritative_result(self):
|
|
response = self.client.post(
|
|
"/api/calculations/roll",
|
|
json={
|
|
"article_number": "180205",
|
|
"roll_length_m": 65,
|
|
"core_diameter_mm": 150,
|
|
},
|
|
headers=self.headers,
|
|
)
|
|
result = response.get_json()
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(result["status"], "success")
|
|
self.assertEqual(result["resolved_article"]["number"], "180205")
|
|
self.assertEqual(
|
|
result["provenance"]["calculator"],
|
|
"roll_calculation.calculate_roll",
|
|
)
|
|
|
|
def test_modification_endpoint_changes_state_and_recalculates(self):
|
|
response = self.client.post(
|
|
"/api/calculations/roll/modify",
|
|
json={
|
|
"state": {
|
|
"request": {
|
|
"article_number": "180205",
|
|
"roll_length_m": 65,
|
|
"core_diameter_mm": 150,
|
|
}
|
|
},
|
|
"changes": {"roll_length_m": 80},
|
|
},
|
|
headers=self.headers,
|
|
)
|
|
result = response.get_json()
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(result["status"], "success")
|
|
self.assertEqual(result["state"]["request"]["roll_length_m"], 80.0)
|
|
self.assertEqual(result["calculation"]["effective_roll_length_m"], 80.0)
|
|
|
|
def test_product_length_endpoint_uses_the_domain_calculation(self):
|
|
response = self.client.post(
|
|
"/api/calculations/product-length",
|
|
json={
|
|
"target_roll_diameter_mm": 600,
|
|
"core_diameter_mm": 140,
|
|
"thickness_mm": 4.311611,
|
|
"thickness_stddev_mm": 0.249999,
|
|
"width_m": 6.0,
|
|
"area_weight_g_m2": 495.395833,
|
|
},
|
|
headers=self.headers,
|
|
)
|
|
result = response.get_json()
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(result["status"], "success")
|
|
self.assertAlmostEqual(
|
|
result["calculation"]["average_product_length_m"], 62.0068774341
|
|
)
|
|
|
|
def test_transport_endpoint_delegates_to_shared_domain_service(self):
|
|
payload = {
|
|
"length_m": 10,
|
|
"width_m": 5,
|
|
"height_m": 5,
|
|
"max_weight_kg": 10000,
|
|
"roll_diameter_mm": 1000,
|
|
"core_diameter_mm": 150,
|
|
"roll_width_m": 1,
|
|
"roll_weight_kg": 1000,
|
|
}
|
|
with patch.object(
|
|
rollcalc_app,
|
|
"analyze_transport",
|
|
return_value={"status": "success", "analysis": {"final_rolls": 10}},
|
|
) as analyze:
|
|
response = self.client.post(
|
|
"/api/calculations/transport",
|
|
json=payload,
|
|
headers=self.headers,
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.get_json()["analysis"]["final_rolls"], 10)
|
|
analyze.assert_called_once_with(payload)
|
|
|
|
def test_transport_endpoint_returns_structured_domain_validation(self):
|
|
response = self.client.post(
|
|
"/api/calculations/transport",
|
|
json={"length_m": 0},
|
|
headers=self.headers,
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.get_json()["status"], "invalid_parameter")
|
|
|
|
def test_production_feasibility_endpoint_uses_shared_domain(self):
|
|
response = self.client.post(
|
|
"/api/calculations/production-feasibility",
|
|
json={
|
|
"machine": "B1",
|
|
"roll_weight_kg": 2500,
|
|
"average_diameter_mm": 876,
|
|
"maximum_diameter_mm": 877,
|
|
"core_diameter_mm": 168,
|
|
"product_width_m": 5,
|
|
},
|
|
headers=self.headers,
|
|
)
|
|
result = response.get_json()
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(result["feasibility"], "feasible_with_warnings")
|
|
self.assertEqual(result["warning_constraints"], ["roll_diameter_mm"])
|
|
|
|
def test_machine_maximum_length_endpoint_is_authenticated_and_structured(self):
|
|
payload = {
|
|
"machine": "K7",
|
|
"core_diameter_mm": 133,
|
|
"thickness_mm": 4.311611,
|
|
"thickness_stddev_mm": 0.249999,
|
|
"product_width_m": 6,
|
|
"area_weight_g_m2": 495.395833,
|
|
}
|
|
response = self.client.post(
|
|
"/api/calculations/machine-max-product-length",
|
|
json=payload,
|
|
headers=self.headers,
|
|
)
|
|
result = response.get_json()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(result["status"], "success")
|
|
self.assertEqual(result, calculate_machine_max_product_length(**payload))
|
|
self.assertIn("final_feasibility", result)
|
|
self.assertEqual(
|
|
result["production_maximums"]["nominal_governing_constraints"],
|
|
["roll_diameter_nominal"],
|
|
)
|
|
|
|
def test_machine_maximum_length_endpoint_does_not_accept_mcp_article_convenience(self):
|
|
response = self.client.post(
|
|
"/api/calculations/machine-max-product-length",
|
|
json={
|
|
"article_number": "218500",
|
|
"machine": "K7",
|
|
"core_diameter_mm": 133,
|
|
},
|
|
headers=self.headers,
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.get_json(), {
|
|
"status": "invalid_parameter",
|
|
"invalid": [{
|
|
"field": "request",
|
|
"message": "unknown request fields: article_number",
|
|
}],
|
|
})
|
|
|
|
def test_machine_maximum_length_endpoint_preserves_domain_outcomes(self):
|
|
incompatible = self.client.post(
|
|
"/api/calculations/machine-max-product-length",
|
|
json={"machine": "K7", "core_diameter_mm": 194, "thickness_mm": 4},
|
|
headers=self.headers,
|
|
)
|
|
missing = self.client.post(
|
|
"/api/calculations/machine-max-product-length",
|
|
json={"machine": "K7", "core_diameter_mm": 133, "thickness_mm": 4},
|
|
headers=self.headers,
|
|
)
|
|
invalid = self.client.post(
|
|
"/api/calculations/machine-max-product-length",
|
|
json={"machine": "K7", "unexpected": 1},
|
|
headers=self.headers,
|
|
)
|
|
missing_required = self.client.post(
|
|
"/api/calculations/machine-max-product-length",
|
|
json={"machine": "K7", "core_diameter_mm": 133},
|
|
headers=self.headers,
|
|
)
|
|
self.assertEqual(incompatible.get_json()["status"], "incompatible")
|
|
self.assertEqual(missing.get_json()["status"], "needs_clarification")
|
|
self.assertEqual(invalid.get_json()["status"], "invalid_parameter")
|
|
self.assertEqual(missing_required.get_json()["status"], "invalid_parameter")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|