operatore: immagine gia' analizzata all'ingresso, analisi impulsiva o in linea, Registra (Refs #97)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
"""gitea:Adriano/TieMeasureFlow#97 - operator: image already analysed on entry.
|
||||
|
||||
The backend (POST /api/vision/check, task.acquisition_json) is built elsewhere:
|
||||
api_client and requests are mocked here with the contract's shapes.
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from tests.conftest import api_get_router
|
||||
from tests.test_measure_camera_grab import (
|
||||
PNG, RECIPE, STATION, TASK, _fake, _station,
|
||||
)
|
||||
|
||||
ACQ = {"camera_url": "http://acq-host:9000", "camera_source": "camera:601020", "modo": "in_linea"}
|
||||
|
||||
|
||||
def _task_with(acq):
|
||||
return {**TASK, "acquisition_json": acq}
|
||||
|
||||
|
||||
# --- grab: the camera of the task, else the station's -------------------------
|
||||
def test_grab_with_task_id_uses_task_camera(logged_in_client, mock_api_client, monkeypatch):
|
||||
_station(mock_api_client, monkeypatch, STATION)
|
||||
mock_api_client.get.side_effect = api_get_router({"/api/tasks/12": _task_with(ACQ)})
|
||||
with patch("blueprints.measure.http_requests.post", return_value=_fake()) as post:
|
||||
resp = logged_in_client.post("/measure/api/vision/grab", data={"task_id": "12"})
|
||||
assert resp.status_code == 200 and resp.data == PNG
|
||||
args, kwargs = post.call_args
|
||||
assert args[0] == "http://acq-host:9000/camera/grab"
|
||||
assert kwargs["json"]["source"] == "camera:601020"
|
||||
mock_api_client.get_station_by_code.assert_not_called()
|
||||
|
||||
|
||||
def test_grab_task_id_in_json_works_too(logged_in_client, mock_api_client, monkeypatch):
|
||||
_station(mock_api_client, monkeypatch, STATION)
|
||||
mock_api_client.get.side_effect = api_get_router({"/api/tasks/12": _task_with(ACQ)})
|
||||
with patch("blueprints.measure.http_requests.post", return_value=_fake()) as post:
|
||||
resp = logged_in_client.post("/measure/api/vision/grab", json={"task_id": 12})
|
||||
assert resp.status_code == 200
|
||||
assert post.call_args[0][0] == "http://acq-host:9000/camera/grab"
|
||||
|
||||
|
||||
def test_grab_task_without_acquisition_uses_station(logged_in_client, mock_api_client, monkeypatch):
|
||||
_station(mock_api_client, monkeypatch, STATION)
|
||||
mock_api_client.get.side_effect = api_get_router({"/api/tasks/12": _task_with(None)})
|
||||
with patch("blueprints.measure.http_requests.post", return_value=_fake()) as post:
|
||||
resp = logged_in_client.post("/measure/api/vision/grab", data={"task_id": "12"})
|
||||
assert resp.status_code == 200
|
||||
args, kwargs = post.call_args
|
||||
assert args[0] == "http://host.docker.internal:8765/camera/grab"
|
||||
assert kwargs["json"]["source"] == "camera:folder"
|
||||
|
||||
|
||||
def test_grab_task_camera_wins_even_if_station_has_none(logged_in_client, mock_api_client, monkeypatch):
|
||||
_station(mock_api_client, monkeypatch, {**STATION, "camera_url": None})
|
||||
mock_api_client.get.side_effect = api_get_router({"/api/tasks/12": _task_with(ACQ)})
|
||||
with patch("blueprints.measure.http_requests.post", return_value=_fake()) as post:
|
||||
resp = logged_in_client.post("/measure/api/vision/grab", data={"task_id": "12"})
|
||||
assert resp.status_code == 200
|
||||
assert post.call_args[0][0] == "http://acq-host:9000/camera/grab"
|
||||
|
||||
|
||||
def test_grab_task_lookup_error_falls_back_to_station(logged_in_client, mock_api_client, monkeypatch):
|
||||
_station(mock_api_client, monkeypatch, STATION)
|
||||
mock_api_client.get.side_effect = api_get_router(
|
||||
{"/api/tasks/12": {"error": True, "status_code": 404, "detail": "x"}})
|
||||
with patch("blueprints.measure.http_requests.post", return_value=_fake()) as post:
|
||||
resp = logged_in_client.post("/measure/api/vision/grab", data={"task_id": "12"})
|
||||
assert resp.status_code == 200
|
||||
assert post.call_args[0][0] == "http://host.docker.internal:8765/camera/grab"
|
||||
|
||||
|
||||
def test_grab_acquisition_as_json_string(logged_in_client, mock_api_client, monkeypatch):
|
||||
_station(mock_api_client, monkeypatch, {**STATION, "camera_url": None})
|
||||
mock_api_client.get.side_effect = api_get_router(
|
||||
{"/api/tasks/12": _task_with(json.dumps(ACQ))})
|
||||
with patch("blueprints.measure.http_requests.post", return_value=_fake()) as post:
|
||||
resp = logged_in_client.post("/measure/api/vision/grab", data={"task_id": "12"})
|
||||
assert resp.status_code == 200
|
||||
assert post.call_args[0][0] == "http://acq-host:9000/camera/grab"
|
||||
|
||||
|
||||
# --- check proxy ---------------------------------------------------------------
|
||||
CHECK_OK = {
|
||||
"outcome": "ok", "overlay": [], "failures": [], "calibration": "ok",
|
||||
"measurements": [{"subtask_id": 1, "value": 9.9, "pass_fail": "pass"}],
|
||||
}
|
||||
|
||||
|
||||
def test_check_proxy_forwards_multipart(logged_in_client, mock_api_client):
|
||||
mock_api_client.post.return_value = CHECK_OK
|
||||
resp = logged_in_client.post(
|
||||
"/measure/api/vision/check",
|
||||
data={
|
||||
"task_id": "12", "production_run_id": "4",
|
||||
"image": (io.BytesIO(b"\xff\xd8\xff fake"), "shot.jpg", "image/jpeg"),
|
||||
},
|
||||
content_type="multipart/form-data",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.get_json()["measurements"][0]["pass_fail"] == "pass"
|
||||
args, kwargs = mock_api_client.post.call_args
|
||||
assert args[0] == "/api/vision/check"
|
||||
assert kwargs["data"] == {"task_id": "12", "production_run_id": "4"}
|
||||
name, content, mime = kwargs["files"]["image"]
|
||||
assert name == "shot.jpg" and mime == "image/jpeg" and content.startswith(b"\xff\xd8")
|
||||
|
||||
|
||||
def test_check_proxy_without_run_leaves_it_out(logged_in_client, mock_api_client):
|
||||
mock_api_client.post.return_value = CHECK_OK
|
||||
logged_in_client.post(
|
||||
"/measure/api/vision/check",
|
||||
data={"task_id": "12", "image": (io.BytesIO(b"x"), "a.jpg", "image/jpeg")},
|
||||
content_type="multipart/form-data",
|
||||
)
|
||||
assert mock_api_client.post.call_args[1]["data"] == {"task_id": "12"}
|
||||
|
||||
|
||||
def test_check_proxy_requires_image(logged_in_client, mock_api_client):
|
||||
resp = logged_in_client.post("/measure/api/vision/check", data={"task_id": "12"})
|
||||
assert resp.status_code == 400
|
||||
mock_api_client.post.assert_not_called()
|
||||
|
||||
|
||||
def test_check_proxy_relays_backend_error(logged_in_client, mock_api_client):
|
||||
mock_api_client.post.return_value = {"error": True, "status_code": 422, "detail": "nessun grafo"}
|
||||
resp = logged_in_client.post(
|
||||
"/measure/api/vision/check",
|
||||
data={"task_id": "12", "image": (io.BytesIO(b"x"), "a.jpg", "image/jpeg")},
|
||||
content_type="multipart/form-data",
|
||||
)
|
||||
assert resp.status_code == 422
|
||||
assert resp.get_json()["detail"] == "nessun grafo"
|
||||
|
||||
|
||||
def test_check_requires_login(client):
|
||||
resp = client.post("/measure/api/vision/check")
|
||||
assert resp.status_code in (302, 401)
|
||||
|
||||
|
||||
# --- execute page: buttons and config -------------------------------------------
|
||||
def _page(logged_in_client, mock_api_client, monkeypatch, station, task):
|
||||
_station(mock_api_client, monkeypatch, station)
|
||||
mock_api_client.get_station_recipes.return_value = {"items": [{"id": 3}]}
|
||||
mock_api_client.get.side_effect = api_get_router({
|
||||
"/api/tasks/12": task,
|
||||
"/api/recipes/3/tasks": [{"id": 12, "order_index": 0}],
|
||||
"/api/recipes/3": RECIPE,
|
||||
})
|
||||
return logged_in_client.get("/measure/execute/12").get_data(as_text=True)
|
||||
|
||||
|
||||
def test_page_impulsive_has_acquire_and_record(logged_in_client, mock_api_client, monkeypatch):
|
||||
html = _page(logged_in_client, mock_api_client, monkeypatch, STATION, TASK)
|
||||
assert 'cameraAvailable: true' in html
|
||||
assert 'modo: "impulsiva"' in html
|
||||
assert "/measure/api/vision/check" in html
|
||||
assert "grab()" in html and "record()" in html
|
||||
assert "togglePause()" not in html
|
||||
assert "Anteprima, non registrata" in html
|
||||
|
||||
|
||||
def test_page_in_line_has_pause_and_record(logged_in_client, mock_api_client, monkeypatch):
|
||||
html = _page(logged_in_client, mock_api_client, monkeypatch, STATION, _task_with(ACQ))
|
||||
assert 'modo: "in_linea"' in html
|
||||
assert "togglePause()" in html and "record()" in html
|
||||
assert "grab()" not in html # the loop grabs by itself
|
||||
|
||||
|
||||
def test_page_task_camera_enables_it_without_station_camera(logged_in_client, mock_api_client, monkeypatch):
|
||||
html = _page(logged_in_client, mock_api_client, monkeypatch,
|
||||
{**STATION, "camera_url": None}, _task_with({**ACQ, "modo": "impulsiva"}))
|
||||
assert 'cameraAvailable: true' in html
|
||||
assert "grab()" in html
|
||||
|
||||
|
||||
def test_page_without_camera_is_as_before(logged_in_client, mock_api_client, monkeypatch):
|
||||
html = _page(logged_in_client, mock_api_client, monkeypatch,
|
||||
{**STATION, "camera_url": None}, TASK)
|
||||
assert 'cameraAvailable: false' in html
|
||||
assert "record()" not in html and "togglePause()" not in html and "grab()" not in html
|
||||
Reference in New Issue
Block a user