state-hub/tests/test_forge_dependency_edge_preview.py

158 lines
7.5 KiB
Python
Raw Normal View History

"""Dry-run preview of source-owned dependency edges (STATE-WP-0094-T05 follow-up).
``diff_against_hub`` is pure, so these tests need no database: hub state is
passed as data. They pin that the preview uses the same rules as receiving.
"""
from pathlib import Path
from api.services import forge_projection as fp
from tests.test_forge_dependency_edges import _source, _write
TGT_WP = "11111111-1111-1111-1111-111111111111"
TGT_T01 = "22222222-2222-2222-2222-222222222222"
OWNER = "forge:edge-source"
def _derive(tmp_path: Path, frontmatter_deps: str, **kw):
_write(tmp_path, _source(frontmatter_deps, **kw))
return fp.derive_from_checkout(tmp_path, "edge-source", "b" * 40)
def _hub(derived):
"""Hub already holds every derived workplan and task under derived UUIDs."""
workplans = [{"id": w.uuid, "slug": w.record_id.lower(), "status": w.status,
"flavor": w.flavor, "backing_relative_path": w.relative_path}
for w in derived.workplans]
tasks = {w.uuid: [{"id": t.uuid, "record_id": t.record_id, "title": t.title,
"status": t.status} for t in w.tasks] for w in derived.workplans}
return workplans, tasks
def _ids(derived):
wp = {w.record_id: w.uuid for w in derived.workplans}
tasks = {t.record_id: t.uuid for w in derived.workplans for t in w.tasks}
return wp, tasks
def test_preview_absent_without_hub_edges(tmp_path):
derived = _derive(tmp_path, "depends_on: [TGT-WP-0001]\n")
workplans, tasks = _hub(derived)
diff = fp.diff_against_hub(derived, workplans, tasks)
assert diff.edges is None
assert "edges" not in diff.to_dict()
def test_preview_lists_create_unresolved_and_external(tmp_path):
derived = _derive(tmp_path, "depends_on: [TGT-WP-0001, TGT-WP-0001-T01]\n")
workplans, tasks = _hub(derived)
wp, tk = _ids(derived)
# The sibling edge already exists as an externally authored (C-20/API) row.
external = {"id": "e1", "from_workplan_id": wp["SRC-WP-0001"],
"from_task_id": tk["SRC-WP-0001-T01"], "to_workplan_id": wp["SRC-WP-0002"],
"to_task_id": None, "relationship_type": "blocks", "source_owner": None}
diff = fp.diff_against_hub(
derived, workplans, tasks, hub_edges=[external],
edge_targets={"TGT-WP-0001": [TGT_WP], "TGT-WP-0001-T01": [TGT_T01],
"SRC-WP-0002": [wp["SRC-WP-0002"]]},
)
e = diff.edges
created = {(c["from"], c["target"], c["relationship_type"]) for c in e["create"]}
assert created == {("SRC-WP-0001", "TGT-WP-0001", "blocks"),
("SRC-WP-0001", "TGT-WP-0001-T01", "starts_after")}
assert [(x["from"], x["target"]) for x in e["external"]] == [("SRC-WP-0001-T01", "SRC-WP-0002")]
# T02 declares a missing target; the closed T03 declares nothing.
assert [(u["from"], u["reason"]) for u in e["unresolved"]] == [("SRC-WP-0001-T02", "missing")]
assert e["remove"] == [] and e["retain"] == []
assert not diff.clean
out = diff.to_dict()
assert out["edge_counts"] == {"create": 2, "remove": 0, "retain": 0, "unresolved": 1, "external": 1}
def test_preview_removes_only_undeclared_owned_edges(tmp_path):
derived = _derive(tmp_path, "", t02_target="SRC-WP-0002")
workplans, tasks = _hub(derived)
wp, tk = _ids(derived)
targets = {"SRC-WP-0002": [wp["SRC-WP-0002"]]}
owned_live = {"id": "o1", "from_workplan_id": wp["SRC-WP-0001"],
"from_task_id": tk["SRC-WP-0001-T01"], "to_workplan_id": wp["SRC-WP-0002"],
"to_task_id": None, "relationship_type": "blocks", "source_owner": OWNER}
owned_live_t02 = {**owned_live, "id": "o2", "from_task_id": tk["SRC-WP-0001-T02"]}
owned_gone = {"id": "o3", "from_workplan_id": wp["SRC-WP-0001"], "from_task_id": None,
"to_workplan_id": TGT_WP, "to_task_id": None, "relationship_type": "blocks",
"source_owner": OWNER}
foreign_gone = {**owned_gone, "id": "x1", "source_owner": "forge:other"}
null_gone = {**owned_gone, "id": "x2", "source_owner": None}
diff = fp.diff_against_hub(
derived, workplans, tasks,
hub_edges=[owned_live, owned_live_t02, owned_gone, foreign_gone, null_gone],
edge_targets=targets,
)
e = diff.edges
assert [r["id"] for r in e["remove"]] == ["o3"]
assert e["create"] == [] and e["external"] == [] and e["unresolved"] == []
assert not diff.clean
def test_preview_holds_owned_edges_of_unresolved_side_and_is_clean_when_settled(tmp_path):
derived = _derive(tmp_path, "depends_on: [TGT-WP-0001]\n")
workplans, tasks = _hub(derived)
wp, tk = _ids(derived)
owned = [
{"id": "o1", "from_workplan_id": wp["SRC-WP-0001"], "from_task_id": None,
"to_workplan_id": TGT_WP, "to_task_id": None, "relationship_type": "blocks",
"source_owner": OWNER},
{"id": "o2", "from_workplan_id": wp["SRC-WP-0001"], "from_task_id": tk["SRC-WP-0001-T01"],
"to_workplan_id": wp["SRC-WP-0002"], "to_task_id": None, "relationship_type": "blocks",
"source_owner": OWNER},
# Owned edge of T02, whose declared target is now ambiguous: retained.
{"id": "o3", "from_workplan_id": wp["SRC-WP-0001"], "from_task_id": tk["SRC-WP-0001-T02"],
"to_workplan_id": TGT_WP, "to_task_id": None, "relationship_type": "blocks",
"source_owner": OWNER},
]
diff = fp.diff_against_hub(
derived, workplans, tasks, hub_edges=owned,
edge_targets={"TGT-WP-0001": [TGT_WP], "SRC-WP-0002": [wp["SRC-WP-0002"]],
"MISSING-WP-0009": ["a", "b"]},
)
e = diff.edges
assert e["create"] == [] and e["remove"] == []
assert [r["id"] for r in e["retain"]] == ["o3"]
assert e["unresolved"][0]["reason"] == "ambiguous"
assert e["unresolved"][0]["candidates"] == ["a", "b"]
assert diff.clean # unresolved/retained/external alone do not change the hub
def test_preview_resolves_targets_created_in_the_same_receipt(tmp_path):
derived = _derive(tmp_path, "")
workplans, tasks = _hub(derived)
wp, tk = _ids(derived)
# The hub lacks SRC-WP-0002 entirely; the receipt would create it.
workplans = [w for w in workplans if w["slug"] != "src-wp-0002"]
diff = fp.diff_against_hub(derived, workplans, tasks, hub_edges=[], edge_targets={})
created = [(c["from"], c["to_workplan_id"]) for c in diff.edges["create"]]
assert created == [("SRC-WP-0001-T01", wp["SRC-WP-0002"])]
assert any(m["record_id"] == "SRC-WP-0002" for m in diff.missing)
def test_preview_flags_self_edge(tmp_path):
derived = _derive(tmp_path, "depends_on: [SRC-WP-0001]\n")
workplans, tasks = _hub(derived)
wp, _ = _ids(derived)
diff = fp.diff_against_hub(
derived, workplans, tasks, hub_edges=[],
edge_targets={"SRC-WP-0001": [wp["SRC-WP-0001"]], "SRC-WP-0002": [wp["SRC-WP-0002"]]},
)
assert ("SRC-WP-0001", "self") in {(u["from"], u["reason"]) for u in diff.edges["unresolved"]}
def test_classifier_never_collapses_duplicate_keys():
key = ("wp", None, "tgt", None, "blocks")
rows = [(key, "owned", OWNER), (key, "api", None)]
# Declared: the owned row satisfies it; the API duplicate is not reported.
plan = fp.classify_source_edges({key: {}}, set(), rows, OWNER)
assert plan == {"create": [], "external": [], "remove": [], "retain": []}
# Undeclared: only the owned row is removed, whatever the row order.
for ordered in (rows, rows[::-1]):
plan = fp.classify_source_edges({}, set(), ordered, OWNER)
assert plan["remove"] == [(key, "owned")]