fix(measure): filtro stazione anche su barcode, avvio sequenza e task_execute (#24)

_recipe_allowed_at_station() riusa get_station_recipes() (stessa fonte di
/measure/select) per lookup_barcode, start_sequence e task_execute: prima
solo l'elenco filtrava, il barcode e i link diretti lasciavano misurare
ricette non assegnate alla stazione. Ricetta non assegnata -> abort(403)
con messaggio "ricetta non assegnata a questa stazione" (pagine, come già
fa role_required) o JSON 403 (lookup-barcode). Aggiornati i test esistenti
che ora richiedono get_station_recipes/STATION_CODE mockati.
Backend (ruoli sui run, StationRecipeAssignment su open_run) resta al
lotto A1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 22:47:30 +02:00
parent f739f963c7
commit befdce5e6a
6 changed files with 163 additions and 20 deletions
@@ -170,7 +170,15 @@ class TestTaskList:
class TestStartSequence:
"""GET /measure/start/<recipe_id> — the operator's way in."""
@pytest.fixture(autouse=True)
def _station_configured(self, monkeypatch):
# start_sequence() checks recipe-station assignment (#24); STATION_CODE
# defaults to unset in the test env, so every test in this class needs it.
monkeypatch.setattr("blueprints.measure.Config.STATION_CODE", "ST-TEST")
def test_start_goes_to_the_first_task(self, logged_in_client, mock_api_client):
# start_sequence() only accepts a recipe assigned to this station (#24).
mock_api_client.get_station_recipes.return_value = [{"id": 1}]
mock_api_client.get.side_effect = api_get_router({
"/api/recipes/1/tasks": [
{"id": 31, "title": "Nota", "order_index": 1},
@@ -186,6 +194,7 @@ class TestStartSequence:
def test_start_remembers_the_traceability(self, logged_in_client, mock_api_client):
"""The list used to be where lot and serial were stored; the sequence
skips it, so the storing has to happen on the way in."""
mock_api_client.get_station_recipes.return_value = [{"id": 1}]
mock_api_client.get.side_effect = api_get_router({
"/api/recipes/1/tasks": [{"id": 30, "title": "Prima", "order_index": 0}],
"/api/recipes/1": {"id": 1, "code": "REC-001", "name": "Test Recipe"},
@@ -200,6 +209,7 @@ class TestStartSequence:
self, logged_in_client, mock_api_client,
):
"""Point 8 holds on this door too: it is a way into the measurement."""
mock_api_client.get_station_recipes.return_value = [{"id": 1}]
mock_api_client.get.side_effect = api_get_router({
"/api/recipes/1/tasks": [{"id": 30, "title": "Prima", "order_index": 0}],
"/api/recipes/1": {
@@ -215,6 +225,7 @@ class TestStartSequence:
def test_start_without_tasks_falls_back_to_the_list(
self, logged_in_client, mock_api_client,
):
mock_api_client.get_station_recipes.return_value = [{"id": 1}]
mock_api_client.get.side_effect = api_get_router({
"/api/recipes/1/tasks": [],
"/api/recipes/1": {"id": 1, "code": "REC-001", "name": "Test Recipe"},
@@ -224,6 +235,18 @@ class TestStartSequence:
assert resp.status_code == 302
assert resp.headers["Location"].endswith("/measure/tasks/1")
def test_start_refuses_recipe_not_assigned_to_station(
self, logged_in_client, mock_api_client,
):
"""#24: a recipe not on this station's list cannot be started via a direct link."""
mock_api_client.get_station_recipes.return_value = [{"id": 99}]
mock_api_client.get.side_effect = api_get_router({
"/api/recipes/1": {"id": 1, "code": "REC-001", "name": "Test Recipe"},
})
resp = logged_in_client.get("/measure/start/1")
assert resp.status_code == 403
class TestTaskComplete:
"""GET /measure/complete/<recipe_id> tests."""