@@ -4296,3 +4296,159 @@ async def test_migrated_kind_with_property_level_changes(
42964296 assert is_protected_prop .action is DiffAction .UPDATED
42974297 assert is_protected_prop .new_value is True
42984298 assert is_protected_prop .previous_value is False
4299+
4300+
4301+ async def test_migrated_kind_on_main_then_relationship_update_on_branch (
4302+ db : InfrahubDatabase ,
4303+ default_branch : Branch ,
4304+ car_accord_main ,
4305+ car_camry_main ,
4306+ person_john_main ,
4307+ person_jane_main ,
4308+ person_alfred_main ,
4309+ person_albert_main ,
4310+ ):
4311+ """Test that when a schema kind is migrated on the default branch, relationships to instances
4312+ of the migrated node can be updated on a branch before the diff is calculated."""
4313+ # Migrate TestPerson kind on default branch
4314+ schema_branch = registry .schema .get_schema_branch (name = default_branch .name )
4315+ original_person_schema = schema_branch .get_node (name = "TestPerson" )
4316+ person_schema = schema_branch .get_node (name = "TestPerson" )
4317+ person_schema .inherit_from = ["GenericThing" ]
4318+ registry .schema .set (name = "TestPerson" , schema = person_schema , branch = default_branch .name )
4319+ migration = NodeKindUpdateMigration (
4320+ previous_node_schema = original_person_schema ,
4321+ new_node_schema = person_schema ,
4322+ schema_path = SchemaPath (path_type = SchemaPathType .ATTRIBUTE , schema_kind = "TestPerson" , field_name = "inherit_from" ),
4323+ )
4324+ execution_result = await migration .execute (db = db , branch = default_branch )
4325+ assert not execution_result .errors
4326+
4327+ # Create a branch after the migration
4328+ branch = await create_branch (db = db , branch_name = "branch-with-relationship-updates" )
4329+
4330+ # Update relationships on the branch that reference instances of the migrated node
4331+ branch_accord = await NodeManager .get_one (db = db , branch = branch , id = car_accord_main .id )
4332+ await branch_accord .owner .update (db = db , data = person_albert_main .id )
4333+ await branch_accord .save (db = db )
4334+
4335+ branch_camry = await NodeManager .get_one (db = db , branch = branch , id = car_camry_main .id )
4336+ await branch_camry .owner .update (db = db , data = {"id" : person_jane_main .id , "_relation__is_protected" : True })
4337+ await branch_camry .save (db = db )
4338+
4339+ # Calculate the diff
4340+ diff_calculator = DiffCalculator (db = db )
4341+
4342+ calculated_diffs = await diff_calculator .calculate_diff (
4343+ base_branch = default_branch ,
4344+ diff_branch = branch ,
4345+ from_time = Timestamp (branch .get_branched_from ()),
4346+ to_time = Timestamp (),
4347+ previous_node_specifiers = None ,
4348+ include_unchanged = True ,
4349+ )
4350+
4351+ base_diff = calculated_diffs .base_branch_diff
4352+ assert len (base_diff .nodes ) == 0
4353+
4354+ branch_diff = calculated_diffs .diff_branch_diff
4355+ nodes_by_id : dict [str , list [DiffNode ]] = defaultdict (list )
4356+ for node in branch_diff .nodes :
4357+ nodes_by_id [node .uuid ].append (node )
4358+
4359+ # Verify that the cars and related persons appear in the diff
4360+ expected_ids = {
4361+ car_accord_main .id ,
4362+ car_camry_main .id ,
4363+ person_john_main .id ,
4364+ person_jane_main .id ,
4365+ person_albert_main .id ,
4366+ }
4367+ assert set (nodes_by_id .keys ()) == expected_ids
4368+ assert len (nodes_by_id ) == len (expected_ids )
4369+
4370+ # Get the car schema to find the owner relationship identifier
4371+ car_schema = registry .schema .get (name = "TestCar" , branch = branch .name )
4372+ owner_rel_schema = car_schema .get_relationship (name = "owner" )
4373+ owner_identifier = owner_rel_schema .get_identifier ()
4374+
4375+ # Validate car_accord relationship update
4376+ accord_diffs = nodes_by_id [car_accord_main .id ]
4377+ assert len (accord_diffs ) == 1
4378+ accord_diff = accord_diffs [0 ]
4379+ assert accord_diff .action is DiffAction .UPDATED
4380+ assert accord_diff .is_node_kind_migration is False
4381+ rels_by_identifier = {r .identifier : r for r in accord_diff .relationships }
4382+ assert owner_identifier in rels_by_identifier
4383+ owner_rel_diff = rels_by_identifier [owner_identifier ]
4384+ assert owner_rel_diff .action is DiffAction .UPDATED
4385+ elements_by_id = {r .peer_id : r for r in owner_rel_diff .relationships }
4386+ assert set (elements_by_id .keys ()) == {person_john_main .id , person_albert_main .id }
4387+ # Original owner was person_john_main, so it should be REMOVED
4388+ john_element = elements_by_id [person_john_main .id ]
4389+ assert john_element .action is DiffAction .REMOVED
4390+ # Verify the relationship properties for the removed owner
4391+ props_by_type = {p .property_type : p for p in john_element .properties }
4392+ assert set (props_by_type .keys ()) == {
4393+ DatabaseEdgeType .IS_RELATED ,
4394+ DatabaseEdgeType .IS_VISIBLE ,
4395+ DatabaseEdgeType .IS_PROTECTED ,
4396+ }
4397+ for prop_type , previous_value in [
4398+ (DatabaseEdgeType .IS_RELATED , person_john_main .id ),
4399+ (DatabaseEdgeType .IS_VISIBLE , True ),
4400+ (DatabaseEdgeType .IS_PROTECTED , False ),
4401+ ]:
4402+ prop_diff = props_by_type [prop_type ]
4403+ assert prop_diff .action is DiffAction .REMOVED
4404+ assert prop_diff .previous_value == previous_value
4405+ assert prop_diff .new_value is None
4406+ # The owner should have been updated to person_albert_main
4407+ albert_element = elements_by_id [person_albert_main .id ]
4408+ assert albert_element .action is DiffAction .ADDED
4409+ # Verify the relationship properties for the new owner
4410+ props_by_type = {p .property_type : p for p in albert_element .properties }
4411+ assert DatabaseEdgeType .IS_RELATED in props_by_type
4412+ is_related_prop = props_by_type [DatabaseEdgeType .IS_RELATED ]
4413+ assert is_related_prop .action is DiffAction .ADDED
4414+ assert is_related_prop .new_value == person_albert_main .id
4415+
4416+ # Validate car_camry relationship update
4417+ camry_diffs = nodes_by_id [car_camry_main .id ]
4418+ assert len (camry_diffs ) == 1
4419+ camry_diff = camry_diffs [0 ]
4420+ assert camry_diff .action is DiffAction .UPDATED
4421+ assert camry_diff .is_node_kind_migration is False
4422+ rels_by_identifier = {r .identifier : r for r in camry_diff .relationships }
4423+ assert owner_identifier in rels_by_identifier
4424+ owner_rel_diff = rels_by_identifier [owner_identifier ]
4425+ assert owner_rel_diff .action is DiffAction .UPDATED
4426+ elements_by_id = {r .peer_id : r for r in owner_rel_diff .relationships }
4427+ # The owner relationship with person_jane_main should have is_protected updated
4428+ assert set (elements_by_id .keys ()) == {person_jane_main .id }
4429+ element_diff = elements_by_id [person_jane_main .id ]
4430+ assert element_diff .action is DiffAction .UPDATED
4431+ properties_by_type = {p .property_type : p for p in element_diff .properties if p .action != DiffAction .UNCHANGED }
4432+ assert set (properties_by_type .keys ()) == {DatabaseEdgeType .IS_PROTECTED }
4433+ is_protected_prop = properties_by_type [DatabaseEdgeType .IS_PROTECTED ]
4434+ assert is_protected_prop .action is DiffAction .UPDATED
4435+ assert is_protected_prop .new_value is True
4436+ assert is_protected_prop .previous_value is False
4437+
4438+ # Validate only correct TestPerson nodes are present in the diff
4439+ current_persons_map = await NodeManager .get_many (
4440+ db = db , ids = [person_john_main .id , person_jane_main .id , person_albert_main .id ]
4441+ )
4442+ for person_id in [person_john_main .id , person_jane_main .id , person_albert_main .id ]:
4443+ person_object = current_persons_map [person_id ]
4444+ person_diffs = nodes_by_id [person_id ]
4445+ assert len (person_diffs ) == 1
4446+ person_diff = person_diffs [0 ]
4447+ assert person_diff .identifier .db_id == person_object .db_id
4448+ assert person_diff .action is DiffAction .UPDATED
4449+ assert person_diff .is_node_kind_migration is False
4450+ assert person_diff .attributes == []
4451+ assert len (person_diff .relationships ) == 1
4452+ rel_diff = person_diff .relationships [0 ]
4453+ assert rel_diff .name == "cars"
4454+ assert rel_diff .action is DiffAction .UPDATED
0 commit comments