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:
@@ -1,6 +1,6 @@
|
|||||||
"""MeasurementTec blueprint - recipe selection and measurement execution."""
|
"""MeasurementTec blueprint - recipe selection and measurement execution."""
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint, flash, jsonify, redirect, render_template,
|
Blueprint, abort, flash, jsonify, redirect, render_template,
|
||||||
request, session, url_for,
|
request, session, url_for,
|
||||||
)
|
)
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
@@ -182,6 +182,32 @@ def _current_run_id() -> int | None:
|
|||||||
return resp.get("id")
|
return resp.get("id")
|
||||||
|
|
||||||
|
|
||||||
|
def _recipe_allowed_at_station(recipe_id: int) -> bool:
|
||||||
|
"""Is this recipe among the ones the picker shows for the current station? (#24)
|
||||||
|
|
||||||
|
The picker (/measure/select) already filters through
|
||||||
|
get_station_recipes(); barcode lookup, the guided-sequence shortcut and a
|
||||||
|
direct link into task_execute all bypass that page, so without this check
|
||||||
|
an operator could measure a recipe never assigned to this station just by
|
||||||
|
knowing (or scanning) its code. Same source of truth, same call - no
|
||||||
|
separate policy to keep in sync. Anything we cannot confirm (no station
|
||||||
|
configured, the call failing) is refused, not let through.
|
||||||
|
"""
|
||||||
|
station_code, _overridden = _current_station()
|
||||||
|
if not station_code:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
resp = api_client.get_station_recipes(station_code)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
if isinstance(resp, dict) and resp.get("error"):
|
||||||
|
return False
|
||||||
|
recipes = resp.get("items", resp) if isinstance(resp, dict) else resp
|
||||||
|
if not isinstance(recipes, list):
|
||||||
|
return False
|
||||||
|
return any(isinstance(r, dict) and r.get("id") == recipe_id for r in recipes)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Route: start the guided sequence
|
# Route: start the guided sequence
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -207,6 +233,9 @@ def start_sequence(recipe_id: int):
|
|||||||
)
|
)
|
||||||
return redirect(url_for("measure.select_recipe"))
|
return redirect(url_for("measure.select_recipe"))
|
||||||
|
|
||||||
|
if not _recipe_allowed_at_station(recipe_id):
|
||||||
|
abort(403, description=_("Ricetta non assegnata a questa stazione"))
|
||||||
|
|
||||||
missing = _missing_traceability(recipe_resp, lot_number, serial_number)
|
missing = _missing_traceability(recipe_resp, lot_number, serial_number)
|
||||||
if missing:
|
if missing:
|
||||||
flash(
|
flash(
|
||||||
@@ -281,11 +310,14 @@ def task_execute(task_id: int):
|
|||||||
)
|
)
|
||||||
return redirect(url_for("measure.select_recipe"))
|
return redirect(url_for("measure.select_recipe"))
|
||||||
|
|
||||||
|
recipe_id = task_resp.get("recipe_id")
|
||||||
|
if recipe_id and not _recipe_allowed_at_station(recipe_id):
|
||||||
|
abort(403, description=_("Ricetta non assegnata a questa stazione"))
|
||||||
|
|
||||||
lot_number = session.get("lot_number", "")
|
lot_number = session.get("lot_number", "")
|
||||||
serial_number = session.get("serial_number", "")
|
serial_number = session.get("serial_number", "")
|
||||||
|
|
||||||
# Load all task IDs for this recipe (ordered) for auto-advance
|
# Load all task IDs for this recipe (ordered) for auto-advance
|
||||||
recipe_id = task_resp.get("recipe_id")
|
|
||||||
all_task_ids = []
|
all_task_ids = []
|
||||||
measurement_interval_minutes = None
|
measurement_interval_minutes = None
|
||||||
# Allowed until the recipe says otherwise: a recipe that could not be read must
|
# Allowed until the recipe says otherwise: a recipe that could not be read must
|
||||||
@@ -405,6 +437,12 @@ def lookup_barcode():
|
|||||||
"detail": resp.get("detail", _("Ricetta non trovata")),
|
"detail": resp.get("detail", _("Ricetta non trovata")),
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
|
if not _recipe_allowed_at_station(resp.get("id")):
|
||||||
|
return jsonify({
|
||||||
|
"error": True,
|
||||||
|
"detail": _("Ricetta non assegnata a questa stazione"),
|
||||||
|
}), 403
|
||||||
|
|
||||||
return jsonify(resp)
|
return jsonify(resp)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,15 @@ class TestTaskList:
|
|||||||
class TestStartSequence:
|
class TestStartSequence:
|
||||||
"""GET /measure/start/<recipe_id> — the operator's way in."""
|
"""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):
|
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({
|
mock_api_client.get.side_effect = api_get_router({
|
||||||
"/api/recipes/1/tasks": [
|
"/api/recipes/1/tasks": [
|
||||||
{"id": 31, "title": "Nota", "order_index": 1},
|
{"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):
|
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
|
"""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."""
|
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({
|
mock_api_client.get.side_effect = api_get_router({
|
||||||
"/api/recipes/1/tasks": [{"id": 30, "title": "Prima", "order_index": 0}],
|
"/api/recipes/1/tasks": [{"id": 30, "title": "Prima", "order_index": 0}],
|
||||||
"/api/recipes/1": {"id": 1, "code": "REC-001", "name": "Test Recipe"},
|
"/api/recipes/1": {"id": 1, "code": "REC-001", "name": "Test Recipe"},
|
||||||
@@ -200,6 +209,7 @@ class TestStartSequence:
|
|||||||
self, logged_in_client, mock_api_client,
|
self, logged_in_client, mock_api_client,
|
||||||
):
|
):
|
||||||
"""Point 8 holds on this door too: it is a way into the measurement."""
|
"""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({
|
mock_api_client.get.side_effect = api_get_router({
|
||||||
"/api/recipes/1/tasks": [{"id": 30, "title": "Prima", "order_index": 0}],
|
"/api/recipes/1/tasks": [{"id": 30, "title": "Prima", "order_index": 0}],
|
||||||
"/api/recipes/1": {
|
"/api/recipes/1": {
|
||||||
@@ -215,6 +225,7 @@ class TestStartSequence:
|
|||||||
def test_start_without_tasks_falls_back_to_the_list(
|
def test_start_without_tasks_falls_back_to_the_list(
|
||||||
self, logged_in_client, mock_api_client,
|
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({
|
mock_api_client.get.side_effect = api_get_router({
|
||||||
"/api/recipes/1/tasks": [],
|
"/api/recipes/1/tasks": [],
|
||||||
"/api/recipes/1": {"id": 1, "code": "REC-001", "name": "Test Recipe"},
|
"/api/recipes/1": {"id": 1, "code": "REC-001", "name": "Test Recipe"},
|
||||||
@@ -224,6 +235,18 @@ class TestStartSequence:
|
|||||||
assert resp.status_code == 302
|
assert resp.status_code == 302
|
||||||
assert resp.headers["Location"].endswith("/measure/tasks/1")
|
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:
|
class TestTaskComplete:
|
||||||
"""GET /measure/complete/<recipe_id> tests."""
|
"""GET /measure/complete/<recipe_id> tests."""
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from unittest.mock import patch, MagicMock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from tests.conftest import api_get_router
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def _restore_measure_module():
|
def _restore_measure_module():
|
||||||
@@ -151,3 +153,66 @@ def test_station_switch_normalises_case(logged_in_client, monkeypatch):
|
|||||||
mock_api.get_station_recipes.return_value = []
|
mock_api.get_station_recipes.return_value = []
|
||||||
logged_in_client.get("/measure/select?station=st-other")
|
logged_in_client.get("/measure/select?station=st-other")
|
||||||
assert _station_calls(mock_api) == ["ST-OTHER", "ST-OTHER"]
|
assert _station_calls(mock_api) == ["ST-OTHER", "ST-OTHER"]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# gitea#24: barcode lookup, the guided-sequence shortcut and a direct link
|
||||||
|
# into task_execute must not let an operator bypass the picker's station
|
||||||
|
# filter just by knowing (or scanning) a recipe's code/id.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_lookup_barcode_rejects_recipe_not_assigned_to_station(logged_in_client, monkeypatch):
|
||||||
|
_reload_measure(monkeypatch, station_code="ST-TEST")
|
||||||
|
from blueprints import measure as measure_mod
|
||||||
|
with patch.object(measure_mod, "api_client") as mock_api:
|
||||||
|
mock_api.get.side_effect = api_get_router({
|
||||||
|
"/api/recipes/code/R-OTHER": {"id": 99, "code": "R-OTHER", "name": "Other"},
|
||||||
|
})
|
||||||
|
mock_api.get_station_recipes.return_value = [{"id": 1, "code": "R1"}]
|
||||||
|
|
||||||
|
resp = logged_in_client.post("/measure/lookup-barcode", json={"code": "R-OTHER"})
|
||||||
|
assert resp.status_code == 403
|
||||||
|
body = resp.get_json()
|
||||||
|
assert body["error"] is True
|
||||||
|
assert "assegnata" in body["detail"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_lookup_barcode_accepts_recipe_assigned_to_station(logged_in_client, monkeypatch):
|
||||||
|
_reload_measure(monkeypatch, station_code="ST-TEST")
|
||||||
|
from blueprints import measure as measure_mod
|
||||||
|
with patch.object(measure_mod, "api_client") as mock_api:
|
||||||
|
mock_api.get.side_effect = api_get_router({
|
||||||
|
"/api/recipes/code/R1": {"id": 1, "code": "R1", "name": "Assigned"},
|
||||||
|
})
|
||||||
|
mock_api.get_station_recipes.return_value = [{"id": 1, "code": "R1"}]
|
||||||
|
|
||||||
|
resp = logged_in_client.post("/measure/lookup-barcode", json={"code": "R1"})
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.get_json()["id"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_sequence_rejects_recipe_not_assigned_to_station(logged_in_client, monkeypatch):
|
||||||
|
_reload_measure(monkeypatch, station_code="ST-TEST")
|
||||||
|
from blueprints import measure as measure_mod
|
||||||
|
with patch.object(measure_mod, "api_client") as mock_api:
|
||||||
|
mock_api.get.side_effect = api_get_router({
|
||||||
|
"/api/recipes/99": {"id": 99, "code": "R-OTHER", "name": "Other"},
|
||||||
|
})
|
||||||
|
mock_api.get_station_recipes.return_value = [{"id": 1, "code": "R1"}]
|
||||||
|
|
||||||
|
resp = logged_in_client.get("/measure/start/99")
|
||||||
|
assert resp.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_execute_rejects_recipe_not_assigned_to_station(logged_in_client, monkeypatch):
|
||||||
|
_reload_measure(monkeypatch, station_code="ST-TEST")
|
||||||
|
from blueprints import measure as measure_mod
|
||||||
|
with patch.object(measure_mod, "api_client") as mock_api:
|
||||||
|
mock_api.get.side_effect = api_get_router({
|
||||||
|
"/api/tasks/10": {"id": 10, "recipe_id": 99, "subtasks": []},
|
||||||
|
})
|
||||||
|
mock_api.get_station_recipes.return_value = [{"id": 1, "code": "R1"}]
|
||||||
|
|
||||||
|
resp = logged_in_client.get("/measure/execute/10")
|
||||||
|
assert resp.status_code == 403
|
||||||
|
|||||||
@@ -47,8 +47,14 @@ def _operator_only(client):
|
|||||||
sess["user"] = user
|
sess["user"] = user
|
||||||
|
|
||||||
|
|
||||||
def _execute_page(client, mock_api_client):
|
def _execute_page(client, mock_api_client, monkeypatch):
|
||||||
_italian(client)
|
_italian(client)
|
||||||
|
# task_execute() only renders a recipe assigned to this station (#24).
|
||||||
|
# STATION_CODE defaults to unset in the test env, so it has to be forced
|
||||||
|
# here for the same reason test_the_selection_screen_starts_the_sequence
|
||||||
|
# forces it below.
|
||||||
|
monkeypatch.setattr("blueprints.measure.Config.STATION_CODE", "ST-TEST")
|
||||||
|
mock_api_client.get_station_recipes.return_value = [{"id": 3}]
|
||||||
mock_api_client.get.side_effect = api_get_router({
|
mock_api_client.get.side_effect = api_get_router({
|
||||||
"/api/tasks/11": TASK,
|
"/api/tasks/11": TASK,
|
||||||
"/api/recipes/3/tasks": [{"id": 11, "order_index": 0}],
|
"/api/recipes/3/tasks": [{"id": 11, "order_index": 0}],
|
||||||
@@ -147,9 +153,9 @@ def test_an_untouched_task_says_nothing(logged_in_client, mock_api_client):
|
|||||||
|
|
||||||
|
|
||||||
def test_the_header_offers_completato_not_the_summary(
|
def test_the_header_offers_completato_not_the_summary(
|
||||||
logged_in_client, mock_api_client,
|
logged_in_client, mock_api_client, monkeypatch,
|
||||||
):
|
):
|
||||||
html = _execute_page(logged_in_client, mock_api_client)
|
html = _execute_page(logged_in_client, mock_api_client, monkeypatch)
|
||||||
assert 'x-data="taskExecute()"' in html
|
assert 'x-data="taskExecute()"' in html
|
||||||
# The header control moves the operator on and is gated by the same rule as
|
# The header control moves the operator on and is gated by the same rule as
|
||||||
# the footer, rather than jumping out of the sequence to a recap.
|
# the footer, rather than jumping out of the sequence to a recap.
|
||||||
@@ -158,19 +164,19 @@ def test_the_header_offers_completato_not_the_summary(
|
|||||||
|
|
||||||
|
|
||||||
def test_fine_ciclo_is_shown_before_it_can_be_used(
|
def test_fine_ciclo_is_shown_before_it_can_be_used(
|
||||||
logged_in_client, mock_api_client,
|
logged_in_client, mock_api_client, monkeypatch,
|
||||||
):
|
):
|
||||||
"""It used to appear only once the task was already finished."""
|
"""It used to appear only once the task was already finished."""
|
||||||
html = _execute_page(logged_in_client, mock_api_client)
|
html = _execute_page(logged_in_client, mock_api_client, monkeypatch)
|
||||||
assert 'x-show="!cycleConfirmed"' in html
|
assert 'x-show="!cycleConfirmed"' in html
|
||||||
assert ':disabled="!isComplete"' in html
|
assert ':disabled="!isComplete"' in html
|
||||||
assert "Fine ciclo misura" in html
|
assert "Fine ciclo misura" in html
|
||||||
|
|
||||||
|
|
||||||
def test_the_missing_quotes_are_counted_out_loud(
|
def test_the_missing_quotes_are_counted_out_loud(
|
||||||
logged_in_client, mock_api_client,
|
logged_in_client, mock_api_client, monkeypatch,
|
||||||
):
|
):
|
||||||
html = _execute_page(logged_in_client, mock_api_client)
|
html = _execute_page(logged_in_client, mock_api_client, monkeypatch)
|
||||||
assert "missingQuotesLabel" in html
|
assert "missingQuotesLabel" in html
|
||||||
assert "Mancano" in html
|
assert "Mancano" in html
|
||||||
|
|
||||||
@@ -181,12 +187,12 @@ def test_the_missing_quotes_are_counted_out_loud(
|
|||||||
|
|
||||||
|
|
||||||
def test_the_operator_flow_never_links_to_the_statistics(
|
def test_the_operator_flow_never_links_to_the_statistics(
|
||||||
logged_in_client, mock_api_client,
|
logged_in_client, mock_api_client, monkeypatch,
|
||||||
):
|
):
|
||||||
_operator_only(logged_in_client)
|
_operator_only(logged_in_client)
|
||||||
pages = [
|
pages = [
|
||||||
_list_page(logged_in_client, mock_api_client),
|
_list_page(logged_in_client, mock_api_client),
|
||||||
_execute_page(logged_in_client, mock_api_client),
|
_execute_page(logged_in_client, mock_api_client, monkeypatch),
|
||||||
]
|
]
|
||||||
for html in pages:
|
for html in pages:
|
||||||
assert "/statistics" not in html
|
assert "/statistics" not in html
|
||||||
|
|||||||
@@ -71,7 +71,11 @@ TASK = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _execute_page(client, mock_api_client, allow_manual):
|
def _execute_page(client, mock_api_client, monkeypatch, allow_manual):
|
||||||
|
# task_execute() only renders a recipe assigned to this station (#24).
|
||||||
|
# STATION_CODE defaults to unset in the test env, so it has to be forced.
|
||||||
|
monkeypatch.setattr("blueprints.measure.Config.STATION_CODE", "ST-TEST")
|
||||||
|
mock_api_client.get_station_recipes.return_value = [{"id": 3}]
|
||||||
mock_api_client.get.side_effect = [
|
mock_api_client.get.side_effect = [
|
||||||
TASK,
|
TASK,
|
||||||
[{"id": 11, "order_index": 0}],
|
[{"id": 11, "order_index": 0}],
|
||||||
@@ -86,9 +90,9 @@ def _execute_page(client, mock_api_client, allow_manual):
|
|||||||
|
|
||||||
|
|
||||||
def test_keypad_digits_are_absent_when_typing_is_forbidden(
|
def test_keypad_digits_are_absent_when_typing_is_forbidden(
|
||||||
logged_in_client, mock_api_client,
|
logged_in_client, mock_api_client, monkeypatch,
|
||||||
):
|
):
|
||||||
html = _execute_page(logged_in_client, mock_api_client, allow_manual=False)
|
html = _execute_page(logged_in_client, mock_api_client, monkeypatch, allow_manual=False)
|
||||||
assert "allowManual: false" in html
|
assert "allowManual: false" in html
|
||||||
assert "addDigit('7')" not in html, "i tasti numerici non devono essere in pagina"
|
assert "addDigit('7')" not in html, "i tasti numerici non devono essere in pagina"
|
||||||
# The corrections and the confirm stay: a wrong caliper reading has to be
|
# The corrections and the confirm stay: a wrong caliper reading has to be
|
||||||
@@ -98,17 +102,19 @@ def test_keypad_digits_are_absent_when_typing_is_forbidden(
|
|||||||
|
|
||||||
|
|
||||||
def test_keypad_is_there_when_the_recipe_allows_typing(
|
def test_keypad_is_there_when_the_recipe_allows_typing(
|
||||||
logged_in_client, mock_api_client,
|
logged_in_client, mock_api_client, monkeypatch,
|
||||||
):
|
):
|
||||||
html = _execute_page(logged_in_client, mock_api_client, allow_manual=True)
|
html = _execute_page(logged_in_client, mock_api_client, monkeypatch, allow_manual=True)
|
||||||
assert "allowManual: true" in html
|
assert "allowManual: true" in html
|
||||||
assert "addDigit('7')" in html
|
assert "addDigit('7')" in html
|
||||||
|
|
||||||
|
|
||||||
def test_keypad_stays_when_the_recipe_cannot_be_read(
|
def test_keypad_stays_when_the_recipe_cannot_be_read(
|
||||||
logged_in_client, mock_api_client,
|
logged_in_client, mock_api_client, monkeypatch,
|
||||||
):
|
):
|
||||||
"""A recipe that failed to load must not silently take the keypad away."""
|
"""A recipe that failed to load must not silently take the keypad away."""
|
||||||
|
monkeypatch.setattr("blueprints.measure.Config.STATION_CODE", "ST-TEST")
|
||||||
|
mock_api_client.get_station_recipes.return_value = [{"id": 3}]
|
||||||
mock_api_client.get.side_effect = [
|
mock_api_client.get.side_effect = [
|
||||||
TASK,
|
TASK,
|
||||||
[{"id": 11, "order_index": 0}],
|
[{"id": 11, "order_index": 0}],
|
||||||
@@ -125,10 +131,10 @@ def test_keypad_stays_when_the_recipe_cannot_be_read(
|
|||||||
|
|
||||||
|
|
||||||
def test_the_operator_sees_the_description_formatted(
|
def test_the_operator_sees_the_description_formatted(
|
||||||
logged_in_client, mock_api_client,
|
logged_in_client, mock_api_client, monkeypatch,
|
||||||
):
|
):
|
||||||
"""It was written for the operator, and until now only the maker saw it."""
|
"""It was written for the operator, and until now only the maker saw it."""
|
||||||
html = _execute_page(logged_in_client, mock_api_client, allow_manual=True)
|
html = _execute_page(logged_in_client, mock_api_client, monkeypatch, allow_manual=True)
|
||||||
assert "prima riga<br><strong>grassetto</strong>" in html
|
assert "prima riga<br><strong>grassetto</strong>" in html
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ def mock_measure_api():
|
|||||||
yield mock
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
def test_task_execute_inline_js_is_valid(logged_in_client, mock_measure_api):
|
def test_task_execute_inline_js_is_valid(logged_in_client, mock_measure_api, monkeypatch):
|
||||||
"""The measurement screen carries the most inline JS in the app.
|
"""The measurement screen carries the most inline JS in the app.
|
||||||
|
|
||||||
It drives the numpad, the caliper burst detection, the supervisor modal and -
|
It drives the numpad, the caliper burst detection, the supervisor modal and -
|
||||||
@@ -235,6 +235,9 @@ def test_task_execute_inline_js_is_valid(logged_in_client, mock_measure_api):
|
|||||||
here kills every binding on the screen the operator actually works in.
|
here kills every binding on the screen the operator actually works in.
|
||||||
"""
|
"""
|
||||||
_force_italian(logged_in_client)
|
_force_italian(logged_in_client)
|
||||||
|
# STATION_CODE defaults to unset in the test env, so it has to be forced
|
||||||
|
# for task_execute()'s station-assignment check (#24).
|
||||||
|
monkeypatch.setattr("blueprints.measure.Config.STATION_CODE", "ST-TEST")
|
||||||
task = {
|
task = {
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"recipe_id": 3,
|
"recipe_id": 3,
|
||||||
@@ -250,6 +253,8 @@ def test_task_execute_inline_js_is_valid(logged_in_client, mock_measure_api):
|
|||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
# task_execute() only renders a recipe assigned to this station (#24).
|
||||||
|
mock_measure_api.get_station_recipes.return_value = [{"id": 3}]
|
||||||
mock_measure_api.get.side_effect = [
|
mock_measure_api.get.side_effect = [
|
||||||
task, # /api/tasks/11
|
task, # /api/tasks/11
|
||||||
[{"id": 11, "order_index": 0}], # /api/recipes/3/tasks
|
[{"id": 11, "order_index": 0}], # /api/recipes/3/tasks
|
||||||
|
|||||||
Reference in New Issue
Block a user