From 4f22a434a63862092d4868b337207bef5c5bef8d Mon Sep 17 00:00:00 2001 From: Dimitri Yatsenko Date: Mon, 20 Jul 2026 12:47:59 -0500 Subject: [PATCH] test: cover parallel foreign-key edges to the same parent (#1492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #1508. The MultiDiGraph migration enables two foreign keys from one child to the same parent as distinct parallel edges; the prior name-keyed dependency dict collapsed them into one. LocalSynapse (two renamed FKs to Cell) had no direct test for this. Asserts Cell appears as two parent edges of LocalSynapse — across Table.parents()/children(), the dependency MultiDiGraph, and the rendered Diagram — with distinct rename maps and edge keys. --- tests/integration/test_foreign_keys.py | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/integration/test_foreign_keys.py b/tests/integration/test_foreign_keys.py index 588c12cbf..64de9cb94 100644 --- a/tests/integration/test_foreign_keys.py +++ b/tests/integration/test_foreign_keys.py @@ -1,3 +1,4 @@ +import datajoint as dj from datajoint.declare import declare from tests.schema_advanced import ( @@ -51,3 +52,40 @@ def test_delete(schema_adv): to_delete = len(parent & "11 in (person_id, parent)") (person & "person_id=11").delete() assert to_delete and len(parent) == original_len - to_delete + + +def test_parallel_edges_to_same_parent(schema_adv): + """Two foreign keys from one child to the same parent are preserved as two + distinct parallel edges (``nx.MultiDiGraph`` migration, #1492 / PR #1508). + + ``LocalSynapse`` references ``Cell`` twice — + ``-> Cell.proj(presynaptic='cell')`` and ``-> Cell.proj(postsynaptic='cell')`` + — so ``Cell`` must appear as TWO parent edges of ``LocalSynapse``. The + pre-migration name-keyed dependency dict collapsed these into a single entry; + this test locks the parallel-edges capability across all three layers that + consume it: the ``Dependencies`` accessors, the dependency graph itself, and + the rendered ``Diagram``. + """ + cell_name = Cell.full_table_name + local_name = LocalSynapse.full_table_name + + # (a) Table.parents() surfaces BOTH edges to Cell, with distinct rename maps. + cell_edges = [props for name, props in LocalSynapse().parents(foreign_key_info=True) if name == cell_name] + assert len(cell_edges) == 2, "LocalSynapse must expose two parallel FK edges to Cell" + renamed = {child for props in cell_edges for child, parent in props["attr_map"].items() if parent == "cell"} + assert renamed == {"presynaptic", "postsynaptic"} + # the two edges must carry distinct keys (``tuple(attr_map)``) — the uniqueness + # the keyed-edge migration relies on to keep them apart. + assert len({tuple(sorted(props["attr_map"].items())) for props in cell_edges}) == 2 + + # (b) The dependency graph keeps both edges — cascade/trace traverse per edge. + deps = LocalSynapse.connection.dependencies + deps.load(force=False) + assert deps.number_of_edges(cell_name, local_name) == 2 + # symmetric view from the parent side (children direction) + local_as_child = [name for name, _ in Cell().children(foreign_key_info=True) if name == local_name] + assert len(local_as_child) == 2 + + # (c) The rendered Diagram (a MultiDiGraph over full table names) draws both. + diagram = dj.Diagram(schema_adv) + assert diagram.number_of_edges(cell_name, local_name) == 2