Files
ATOCore/tests/test_inbox_crossproject.py

202 lines
6.2 KiB
Python
Raw Normal View History

"""Issue C — inbox pseudo-project + cross-project (project="") entities."""
import pytest
from fastapi.testclient import TestClient
from atocore.engineering.service import (
create_entity,
get_entities,
init_engineering_schema,
promote_entity,
)
from atocore.main import app
from atocore.projects.registry import (
GLOBAL_PROJECT,
INBOX_PROJECT,
is_reserved_project,
register_project,
resolve_project_name,
update_project,
)
@pytest.fixture
def seeded_db(tmp_data_dir, tmp_path, monkeypatch):
# Isolate the project registry so "p05" etc. don't canonicalize
# to aliases inherited from the host registry.
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_engineering_schema()
# Audit table lives in the memory schema — bring it up so audit rows
# don't spam warnings during retargeting tests.
from atocore.models.database import init_db
init_db()
yield tmp_data_dir
def test_inbox_is_reserved():
assert is_reserved_project("inbox") is True
assert is_reserved_project("INBOX") is True
assert is_reserved_project("p05-interferometer") is False
assert is_reserved_project("") is False
def test_resolve_project_name_preserves_inbox():
assert resolve_project_name("inbox") == "inbox"
assert resolve_project_name("INBOX") == "inbox"
assert resolve_project_name("") == ""
def test_cannot_register_inbox(tmp_path, monkeypatch):
monkeypatch.setenv(
"ATOCORE_PROJECT_REGISTRY_PATH",
str(tmp_path / "registry.json"),
)
from atocore import config
config.settings = config.Settings()
with pytest.raises(ValueError, match="reserved"):
register_project(
project_id="inbox",
ingest_roots=[{"source": "vault", "subpath": "incoming/inbox"}],
)
def test_cannot_update_inbox(tmp_path, monkeypatch):
monkeypatch.setenv(
"ATOCORE_PROJECT_REGISTRY_PATH",
str(tmp_path / "registry.json"),
)
from atocore import config
config.settings = config.Settings()
with pytest.raises(ValueError, match="reserved"):
update_project(project_name="inbox", description="hijack attempt")
def test_create_entity_with_empty_project_is_global(seeded_db):
e = create_entity(entity_type="material", name="Invar", project="")
assert e.project == ""
def test_create_entity_in_inbox(seeded_db):
e = create_entity(entity_type="vendor", name="Zygo", project="inbox")
assert e.project == "inbox"
def test_get_entities_inbox_scope(seeded_db):
create_entity(entity_type="vendor", name="Zygo", project="inbox")
create_entity(entity_type="material", name="Invar", project="")
create_entity(entity_type="component", name="Mirror", project="p05")
inbox = get_entities(project=INBOX_PROJECT, scope_only=True)
assert {e.name for e in inbox} == {"Zygo"}
def test_get_entities_global_scope(seeded_db):
create_entity(entity_type="vendor", name="Zygo", project="inbox")
create_entity(entity_type="material", name="Invar", project="")
create_entity(entity_type="component", name="Mirror", project="p05")
globals_ = get_entities(project=GLOBAL_PROJECT, scope_only=True)
assert {e.name for e in globals_} == {"Invar"}
def test_real_project_includes_global_by_default(seeded_db):
create_entity(entity_type="material", name="Invar", project="")
create_entity(entity_type="component", name="Mirror", project="p05")
create_entity(entity_type="component", name="Other", project="p06")
p05 = get_entities(project="p05")
names = {e.name for e in p05}
assert "Mirror" in names
assert "Invar" in names, "cross-project material should bleed in by default"
assert "Other" not in names
def test_real_project_scope_only_excludes_global(seeded_db):
create_entity(entity_type="material", name="Invar", project="")
create_entity(entity_type="component", name="Mirror", project="p05")
p05 = get_entities(project="p05", scope_only=True)
assert {e.name for e in p05} == {"Mirror"}
def test_api_post_entity_with_null_project_stores_global(seeded_db):
client = TestClient(app)
r = client.post("/entities", json={
"entity_type": "material",
"name": "Titanium",
"project": None,
})
assert r.status_code == 200
globals_ = get_entities(project=GLOBAL_PROJECT, scope_only=True)
assert any(e.name == "Titanium" for e in globals_)
def test_api_get_entities_scope_only(seeded_db):
create_entity(entity_type="material", name="Invar", project="")
create_entity(entity_type="component", name="Mirror", project="p05")
client = TestClient(app)
mixed = client.get("/entities?project=p05").json()
scoped = client.get("/entities?project=p05&scope_only=true").json()
assert mixed["count"] == 2
assert scoped["count"] == 1
def test_promote_with_target_project_retargets(seeded_db):
e = create_entity(
entity_type="vendor",
name="ZygoLead",
project="inbox",
status="candidate",
)
ok = promote_entity(e.id, target_project="p05")
assert ok is True
from atocore.engineering.service import get_entity
promoted = get_entity(e.id)
assert promoted.status == "active"
assert promoted.project == "p05"
def test_promote_without_target_project_keeps_project(seeded_db):
e = create_entity(
entity_type="vendor",
name="ZygoStay",
project="inbox",
status="candidate",
)
ok = promote_entity(e.id)
assert ok is True
from atocore.engineering.service import get_entity
promoted = get_entity(e.id)
assert promoted.status == "active"
assert promoted.project == "inbox"
def test_api_promote_with_target_project(seeded_db):
e = create_entity(
entity_type="vendor",
name="ZygoApi",
project="inbox",
status="candidate",
)
client = TestClient(app)
r = client.post(
f"/entities/{e.id}/promote",
json={"target_project": "p05"},
)
assert r.status_code == 200
body = r.json()
assert body["status"] == "promoted"
assert body["target_project"] == "p05"