Test: quota NaN chiede il capoturno, migrazione 022 conserva le righe
Refs #125 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -379,3 +379,55 @@ class TestMeasurementValueNullableMigration:
|
||||
assert value_nullable() is False
|
||||
command.upgrade(cfg, "head") # e di nuovo su
|
||||
assert value_nullable() is True
|
||||
|
||||
def test_022_keeps_rows(self, sqlite_database_url):
|
||||
"""#125: la ricostruzione della tabella conserva righe, somma dei value e indici."""
|
||||
import sqlite3
|
||||
|
||||
db_path, _ = sqlite_database_url
|
||||
cfg = _alembic_config()
|
||||
command.upgrade(cfg, "021_task_acquisition")
|
||||
|
||||
def indexes(con):
|
||||
return sorted(r[0] for r in con.execute(
|
||||
"select name from sqlite_master where type='index' and tbl_name='measurements'"
|
||||
))
|
||||
|
||||
con = sqlite3.connect(db_path)
|
||||
# Colonne NOT NULL senza default riempite per tipo (stessa idea di mig_check.py)
|
||||
vals = {}
|
||||
for _cid, name, typ, notnull, dflt, pk in con.execute("pragma table_info(measurements)").fetchall():
|
||||
if pk or not notnull or dflt is not None:
|
||||
continue
|
||||
t = typ.upper()
|
||||
vals[name] = (
|
||||
1 if "INT" in t
|
||||
else 1.5 if ("DEC" in t or "FLOAT" in t or "NUM" in t)
|
||||
else "2026-01-01 00:00:00" if ("DATE" in t or "TIME" in t)
|
||||
else "x"
|
||||
)
|
||||
vals["pass_fail"] = "pass"
|
||||
vals["value"] = 12.5
|
||||
sql = f"insert into measurements ({','.join(vals)}) values ({','.join('?' * len(vals))})"
|
||||
for _ in range(3):
|
||||
con.execute(sql, list(vals.values()))
|
||||
con.commit()
|
||||
before_idx = indexes(con)
|
||||
before = con.execute("select count(*), sum(value) from measurements").fetchone()
|
||||
con.close()
|
||||
assert before == (3, 37.5)
|
||||
|
||||
command.upgrade(cfg, "head")
|
||||
|
||||
con = sqlite3.connect(db_path)
|
||||
try:
|
||||
assert con.execute("select count(*), sum(value) from measurements").fetchone() == before
|
||||
assert indexes(con) == before_idx
|
||||
assert con.execute("pragma integrity_check").fetchone()[0] == "ok"
|
||||
vals["value"] = None
|
||||
vals["pass_fail"] = "fail"
|
||||
con.execute(sql, list(vals.values())) # una riga NaN (value NULL) ora e' accettata
|
||||
con.commit()
|
||||
assert con.execute("select count(*) from measurements where value is null").fetchone()[0] == 1
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
@@ -216,10 +216,11 @@ async def test_a_non_finite_output_is_saved_as_nan(
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_nan_quote_still_blocks_the_next_acquisition(
|
||||
async def test_a_nan_quote_needs_supervisor_authorisation(
|
||||
client, db_session, measurement_tec_user, worker_says,
|
||||
):
|
||||
"""#118: la quota NaN e' un fail come gli altri, il blocco di tolleranza resta."""
|
||||
"""#125: la quota NaN e' un fail come gli altri e chiede il capoturno (pending_authorisation);
|
||||
la prossima acquisizione non e' bloccata."""
|
||||
recipe = await create_test_recipe(db_session, measurement_tec_user.id)
|
||||
version, task, first, second = await two_vision_quotes(db_session, recipe.id)
|
||||
worker_says({"t1.radius": GOOD})
|
||||
@@ -230,6 +231,15 @@ async def test_a_nan_quote_still_blocks_the_next_acquisition(
|
||||
)
|
||||
assert blocking is not None and blocking.subtask_id == first.id and blocking.value is None
|
||||
|
||||
# #125: una seconda acquisizione sullo stesso task risponde 200, non e' bloccata
|
||||
again = await client.post(
|
||||
"/api/vision/execute",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
files={"image": ("p.png", an_image(), "image/png")},
|
||||
data={"task_id": str(task.id)},
|
||||
)
|
||||
assert again.status_code == 200, again.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_lists_a_missing_quote_as_nan(
|
||||
|
||||
Reference in New Issue
Block a user