Initial version

This commit is contained in:
Tom Kasper
2026-08-30 20:44:00 +02:00
commit 9aa2da72b5
612 changed files with 64514 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
"""workflows/setup.py — scan_instance, map_resource, validate_protocol_template (plan §5)."""
import pytest
from fakes import FakeItem, make_item
# --- scan_instance ----------------------------------------------------------
async def test_scan_counts_containers_and_markers(setup, client):
client.items[13] = make_item(id=13, title="Water bottle", body="plain", containers=[])
result = await setup.scan_instance()
assert result.containers_seen == 2 # containers on item 12
assert result.markers_found == 1 # marker on item 12
async def test_scan_proposes_marker_matches(setup, client):
result = await setup.scan_instance()
proposal = next(p for p in result.proposals if p.resource_key == "ethanol_absolute")
assert proposal.item_id == 12
assert proposal.reason is not None
async def test_scan_proposes_exact_title_match_as_suggestion(setup, client):
"""Exact title matches are setup-time suggestions with scores (plan §5.4)."""
client.items[14] = FakeItem(id=14, title="sodium_azide", body="")
result = await setup.scan_instance()
proposal = next((p for p in result.proposals if p.item_id == 14), None)
assert proposal is not None
assert proposal.resource_key == "sodium_azide"
# --- map_resource -----------------------------------------------------------
async def test_map_resource_binds_key(setup, client, resolver_stub):
entry = await setup.map_resource("ethanol_absolute", 12)
assert entry.resource_key == "ethanol_absolute"
assert entry.item_id == 12
assert resolver_stub.mappings["ethanol_absolute"] == 12
async def test_map_resource_is_idempotent_upsert(setup, resolver_stub):
await setup.map_resource("ethanol_absolute", 12)
await setup.map_resource("ethanol_absolute", 12)
assert resolver_stub.mappings["ethanol_absolute"] == 12
entries = await resolver_stub.list_mappings()
assert len([e for e in entries if e.resource_key == "ethanol_absolute"]) == 1
async def test_map_resource_rebinding_updates(setup, resolver_stub):
await setup.map_resource("ethanol_absolute", 12)
await setup.map_resource("ethanol_absolute", 13)
assert resolver_stub.mappings["ethanol_absolute"] == 13
# --- validate_protocol_template ---------------------------------------------
def _annotated_template_client():
from fakes import FakeElabftwClient, FakeStep, make_experiment, make_item, make_template
annotated_step = FakeStep(
id=41,
body=(
"Add ethanol\n"
"<!-- labvoice:v1\n"
'{"consumables": [{"resource_key": "ethanol_absolute", "quantity": 2.0,'
' "unit": "mL"}]}\n'
"-->"
),
ordering=1,
)
broken_step = FakeStep(
id=42, body="Broken\n<!-- labvoice:v1\n{invalid json\n-->", ordering=2
)
templates = [
make_template(
id=7,
steps=[annotated_step, broken_step],
)
]
return FakeElabftwClient(
templates=templates, experiments=[make_experiment()], items=[make_item()]
)
async def test_validate_flags_unmapped_resource_key(setup, client, resolver_stub):
"""Annotation ok, but the mapping store lacks the key → blocking error."""
client.templates[7] = _annotated_template_client().templates[7]
resolver_stub.mappings.clear()
report = await setup.validate_protocol_template("experiments_templates", 7)
assert report.ok is False
errors = [i for i in report.issues if i.severity == "error"]
assert any(i.resource_key == "ethanol_absolute" for i in errors)
async def test_validate_ok_when_annotation_and_mapping_are_fine(setup, client, resolver_stub):
client.templates[7] = _annotated_template_client().templates[7]
report = await setup.validate_protocol_template("experiments_templates", 7)
assert report.ok is False # the broken json step must block
# fix the broken step, then expect a clean report
client.templates[7].steps[1].body = "Clean step without annotation"
report = await setup.validate_protocol_template("experiments_templates", 7)
assert report.ok is True
assert report.issues == []
async def test_validate_reports_annotation_error_with_step_id(setup, client):
client.templates[7] = _annotated_template_client().templates[7]
report = await setup.validate_protocol_template("experiments_templates", 7)
issues = [i for i in report.issues if i.step_id == 42]
assert issues and issues[0].severity == "error"
async def test_validate_entity_type_is_mirrored_in_report(setup, client):
client.templates[7] = _annotated_template_client().templates[7]
report = await setup.validate_protocol_template("experiments_templates", 7)
assert report.entity_type == "experiments_templates"
assert report.entity_id == 7
async def test_validate_experiment_steps_too(setup, client, resolver_stub):
"""validate works on live experiments, not only templates."""
resolver_stub.mappings.clear()
report = await setup.validate_protocol_template("experiments", 123)
assert report.entity_type == "experiments"
assert report.ok is False
async def test_validate_unknown_entity_fails_cleanly(setup):
with pytest.raises(Exception) as excinfo:
await setup.validate_protocol_template("experiments", 9999)
assert not isinstance(excinfo.value, NotImplementedError)