1d93eded27
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
173 lines
6.4 KiB
Python
173 lines
6.4 KiB
Python
"""#40: after a reload, the quotes already measured in the cycle come back.
|
|
|
|
The cycle has no column of its own: it opens at the latest start, cycle_completed
|
|
or remeasure-of-this-task event of the run, and a task_measured of the task after
|
|
that boundary means the task is closed in this cycle.
|
|
"""
|
|
from datetime import datetime, timedelta
|
|
|
|
from httpx import AsyncClient
|
|
from sqlalchemy import select
|
|
|
|
from src.backend.models.orm.measurement import Measurement
|
|
from src.backend.models.orm.production import ProductionEvent, ProductionRun
|
|
from src.backend.models.orm.recipe import RecipeVersion
|
|
from src.backend.models.orm.station import Station
|
|
from src.backend.models.orm.task import RecipeSubtask, RecipeTask
|
|
from src.backend.tests.conftest import auth_headers, create_test_recipe
|
|
|
|
T0 = datetime(2026, 10, 1, 8, 0, 0)
|
|
|
|
|
|
async def _setup(db, user):
|
|
recipe = await create_test_recipe(db, user.id)
|
|
version = (await db.execute(
|
|
select(RecipeVersion).where(
|
|
RecipeVersion.recipe_id == recipe.id, RecipeVersion.is_current == True, # noqa: E712
|
|
)
|
|
)).scalar_one()
|
|
task = (await db.execute(
|
|
select(RecipeTask).where(RecipeTask.version_id == version.id)
|
|
)).scalars().first()
|
|
first = (await db.execute(
|
|
select(RecipeSubtask).where(RecipeSubtask.task_id == task.id)
|
|
)).scalars().first()
|
|
second = RecipeSubtask(
|
|
task_id=task.id, marker_number=2, description="Quota 2",
|
|
nominal=10.0, utl=10.5, uwl=10.3, lwl=9.7, ltl=9.5, unit="mm",
|
|
)
|
|
db.add(second)
|
|
station = Station(code="ST-CYC", name="St", active=True, created_by=user.id)
|
|
db.add(station)
|
|
await db.commit()
|
|
run = ProductionRun(
|
|
station_id=station.id, recipe_id=recipe.id, version_id=version.id,
|
|
operator_id=user.id, status="running", active_station_id=station.id,
|
|
)
|
|
db.add(run)
|
|
await db.commit()
|
|
return run, version, task, first, second
|
|
|
|
|
|
async def _event(db, run, user, kind, minutes, task_id=None):
|
|
db.add(ProductionEvent(
|
|
run_id=run.id, event_type=kind, user_id=user.id, task_id=task_id,
|
|
created_at=T0 + timedelta(minutes=minutes),
|
|
))
|
|
await db.commit()
|
|
|
|
|
|
async def _meas(db, run, version, subtask, user, minutes, value=10.0):
|
|
db.add(Measurement(
|
|
subtask_id=subtask.id, version_id=version.id, measured_by=user.id,
|
|
value=value, pass_fail="pass", production_run_id=run.id,
|
|
measured_at=T0 + timedelta(minutes=minutes),
|
|
))
|
|
await db.commit()
|
|
|
|
|
|
async def _get(client, user, run, task):
|
|
resp = await client.get(
|
|
"/api/measurements/current-cycle", headers=auth_headers(user),
|
|
params={"production_run_id": run.id, "task_id": task.id},
|
|
)
|
|
assert resp.status_code == 200
|
|
return resp.json()["measurements"]
|
|
|
|
|
|
async def test_only_second_cycle_after_cycle_completed(
|
|
client: AsyncClient, db_session, measurement_tec_user,
|
|
):
|
|
u = measurement_tec_user
|
|
run, version, task, q1, q2 = await _setup(db_session, u)
|
|
await _event(db_session, run, u, "start", 0)
|
|
await _meas(db_session, run, version, q1, u, 1, 10.1)
|
|
await _meas(db_session, run, version, q2, u, 2, 10.2)
|
|
await _event(db_session, run, u, "cycle_completed", 3)
|
|
await _meas(db_session, run, version, q1, u, 4, 10.3)
|
|
|
|
rows = await _get(client, u, run, task)
|
|
assert [(r["subtask_id"], r["value"]) for r in rows] == [(q1.id, 10.3)]
|
|
assert rows[0]["pass_fail"] == "pass"
|
|
|
|
|
|
async def test_last_reading_of_a_quote_wins(
|
|
client: AsyncClient, db_session, measurement_tec_user,
|
|
):
|
|
u = measurement_tec_user
|
|
run, version, task, q1, q2 = await _setup(db_session, u)
|
|
await _event(db_session, run, u, "start", 0)
|
|
await _meas(db_session, run, version, q1, u, 1, 10.1)
|
|
await _meas(db_session, run, version, q1, u, 2, 10.4)
|
|
|
|
rows = await _get(client, u, run, task)
|
|
assert [(r["subtask_id"], r["value"]) for r in rows] == [(q1.id, 10.4)]
|
|
|
|
|
|
async def test_remeasure_of_the_task_empties_the_list(
|
|
client: AsyncClient, db_session, measurement_tec_user,
|
|
):
|
|
u = measurement_tec_user
|
|
run, version, task, q1, q2 = await _setup(db_session, u)
|
|
await _event(db_session, run, u, "start", 0)
|
|
await _meas(db_session, run, version, q1, u, 1)
|
|
await _event(db_session, run, u, "remeasure", 2, task_id=task.id)
|
|
|
|
assert await _get(client, u, run, task) == []
|
|
|
|
|
|
async def test_remeasure_of_another_task_does_not_move_the_boundary(
|
|
client: AsyncClient, db_session, measurement_tec_user,
|
|
):
|
|
u = measurement_tec_user
|
|
run, version, task, q1, q2 = await _setup(db_session, u)
|
|
other = RecipeTask(
|
|
version_id=version.id, title="Altro", task_type=task.task_type, order_index=9,
|
|
)
|
|
db_session.add(other)
|
|
await db_session.commit()
|
|
await _event(db_session, run, u, "start", 0)
|
|
await _meas(db_session, run, version, q1, u, 1)
|
|
await _event(db_session, run, u, "remeasure", 2, task_id=other.id)
|
|
|
|
assert len(await _get(client, u, run, task)) == 1
|
|
|
|
|
|
async def test_task_measured_after_boundary_means_closed(
|
|
client: AsyncClient, db_session, measurement_tec_user,
|
|
):
|
|
u = measurement_tec_user
|
|
run, version, task, q1, q2 = await _setup(db_session, u)
|
|
await _event(db_session, run, u, "start", 0)
|
|
await _meas(db_session, run, version, q1, u, 1)
|
|
await _meas(db_session, run, version, q2, u, 2)
|
|
await _event(db_session, run, u, "task_measured", 3, task_id=task.id)
|
|
|
|
assert await _get(client, u, run, task) == []
|
|
|
|
|
|
async def test_task_measured_before_boundary_does_not_close_next_cycle(
|
|
client: AsyncClient, db_session, measurement_tec_user,
|
|
):
|
|
u = measurement_tec_user
|
|
run, version, task, q1, q2 = await _setup(db_session, u)
|
|
await _event(db_session, run, u, "start", 0)
|
|
await _event(db_session, run, u, "task_measured", 1, task_id=task.id)
|
|
await _event(db_session, run, u, "cycle_completed", 2)
|
|
await _meas(db_session, run, version, q1, u, 3)
|
|
|
|
assert len(await _get(client, u, run, task)) == 1
|
|
|
|
|
|
async def test_permissions(client: AsyncClient, db_session, measurement_tec_user, maker_user):
|
|
run, _v, task, _q1, _q2 = await _setup(db_session, measurement_tec_user)
|
|
params = {"production_run_id": run.id, "task_id": task.id}
|
|
|
|
no_key = await client.get("/api/measurements/current-cycle", params=params)
|
|
assert no_key.status_code in (401, 403)
|
|
|
|
forbidden = await client.get(
|
|
"/api/measurements/current-cycle", headers=auth_headers(maker_user), params=params,
|
|
)
|
|
assert forbidden.status_code == 403
|