"""models.py — compact result shapes (plan §6: ids, titles, quantities, statuses).""" import pytest from pydantic import ValidationError from mcp_server.models import ( AllocationPolicy, CompleteStepResult, Consumable, HandoffContext, NextStep, ProtocolAnnotation, StepInfo, ) def test_consumable_defaults_to_fifo_and_required(): consumable = Consumable(resource_key="ethanol_absolute", quantity=2.0, unit="mL") assert consumable.allocation is AllocationPolicy.FIFO assert consumable.optional is False assert consumable.container_id is None def test_consumable_quantity_may_be_null_for_prompted_steps(): consumable = Consumable(resource_key="x", quantity=None, unit="mL") assert consumable.quantity is None def test_consumable_rejects_missing_required_fields(): with pytest.raises(ValidationError): Consumable(resource_key="x") # quantity and unit missing def test_annotation_defaults(): annotation = ProtocolAnnotation() assert annotation.consumables == [] assert annotation.prompt_quantity is False def test_step_info_defaults_unfinished(): step = StepInfo(id=9, body="Add ethanol") assert step.finished is False assert step.position is None def test_complete_step_result_status_is_restricted(): for status in ("completed", "protocol_complete", "reverted", "partial_failure"): CompleteStepResult( experiment_id=123, status=status, step=StepInfo(id=9, body="b"), ) with pytest.raises(ValidationError): CompleteStepResult( experiment_id=123, status="exploded", step=StepInfo(id=9, body="b"), ) def test_next_step_carries_consumables_and_stock(): next_step = NextStep(experiment_id=123, step=StepInfo(id=9, body="Add ethanol")) assert next_step.consumables == [] assert next_step.stock == [] def test_handoff_context_is_id_only_and_rest_optional(): """Plan §8: the app forwards only ids; device/location are optional extras.""" handoff = HandoffContext(experiment_id=123, user_id=2) assert handoff.step_id is None assert handoff.device_id is None assert handoff.location_id is None