"""PATCH /entities/{id} — edit mutable fields without cloning (sprint P1).""" import pytest from fastapi.testclient import TestClient from atocore.engineering.service import ( create_entity, get_entity, init_engineering_schema, update_entity, ) from atocore.main import app from atocore.models.database import init_db @pytest.fixture def env(tmp_data_dir, tmp_path, monkeypatch): registry_path = tmp_path / "test-registry.json" registry_path.write_text('{"projects": []}', encoding="utf-8") monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) from atocore import config config.settings = config.Settings() init_db() init_engineering_schema() yield tmp_data_dir def test_update_entity_description(env): e = create_entity(entity_type="component", name="t", description="old desc") updated = update_entity(e.id, description="new desc") assert updated.description == "new desc" assert get_entity(e.id).description == "new desc" def test_update_entity_properties_merge(env): e = create_entity( entity_type="component", name="t2", properties={"color": "red", "kg": 5}, ) updated = update_entity( e.id, properties_patch={"color": "blue", "material": "invar"}, ) assert updated.properties == {"color": "blue", "kg": 5, "material": "invar"} def test_update_entity_properties_null_deletes_key(env): e = create_entity( entity_type="component", name="t3", properties={"color": "red", "kg": 5}, ) updated = update_entity(e.id, properties_patch={"color": None}) assert "color" not in updated.properties assert updated.properties.get("kg") == 5 def test_update_entity_confidence_bounds(env): e = create_entity(entity_type="component", name="t4", confidence=0.5) with pytest.raises(ValueError): update_entity(e.id, confidence=1.5) with pytest.raises(ValueError): update_entity(e.id, confidence=-0.1) def test_update_entity_source_refs_append_dedup(env): e = create_entity( entity_type="component", name="t5", source_refs=["session:a", "session:b"], ) updated = update_entity( e.id, append_source_refs=["session:b", "session:c"], ) assert updated.source_refs == ["session:a", "session:b", "session:c"] def test_update_entity_returns_none_for_unknown(env): assert update_entity("nonexistent", description="x") is None def test_api_patch_happy_path(env): e = create_entity( entity_type="component", name="tower", description="old", properties={"material": "steel"}, confidence=0.6, ) client = TestClient(app) r = client.patch( f"/entities/{e.id}", json={ "description": "three-stage tower", "properties": {"material": "invar", "height_mm": 1200}, "confidence": 0.9, "source_refs": ["session:s1"], "note": "from voice session", }, ) assert r.status_code == 200, r.text body = r.json() assert body["description"] == "three-stage tower" assert body["properties"]["material"] == "invar" assert body["properties"]["height_mm"] == 1200 assert body["confidence"] == 0.9 assert "session:s1" in body["source_refs"] def test_api_patch_omitted_fields_unchanged(env): e = create_entity( entity_type="component", name="keep-desc", description="keep me", ) client = TestClient(app) r = client.patch( f"/entities/{e.id}", json={"confidence": 0.7}, ) assert r.status_code == 200 assert r.json()["description"] == "keep me" def test_api_patch_404_on_missing(env): client = TestClient(app) r = client.patch("/entities/does-not-exist", json={"description": "x"}) assert r.status_code == 404 def test_api_patch_rejects_bad_confidence(env): e = create_entity(entity_type="component", name="bad-conf") client = TestClient(app) r = client.patch(f"/entities/{e.id}", json={"confidence": 2.0}) assert r.status_code == 400 def test_api_patch_aliased_under_v1(env): e = create_entity(entity_type="component", name="v1-patch") client = TestClient(app) r = client.patch( f"/v1/entities/{e.id}", json={"description": "via v1"}, ) assert r.status_code == 200 assert get_entity(e.id).description == "via v1" def test_api_patch_audit_row_written(env): from atocore.engineering.service import get_entity_audit e = create_entity(entity_type="component", name="audit-check") client = TestClient(app) client.patch( f"/entities/{e.id}", json={"description": "new", "note": "manual edit"}, ) audit = get_entity_audit(e.id) actions = [a["action"] for a in audit] assert "updated" in actions