def test_model_cannot_add_two_software_systems_with_same_name(empty_model: Model): """Ensure duplicate software systems are not allowed.""" empty_model.add_software_system(name="Bob") with pytest.raises( ValueError, match="A software system with the name 'Bob' already exists in the model.", ): empty_model.add_software_system(name="Bob")
def test_create_implied_relationships_unless_same_exists(): """Check logic of create_implied_relationships_unless_same_exists.""" model = Model(implied_relationship_strategy=create_unless_same_exists) system1 = model.add_software_system(name="system1") container1 = system1.add_container(name="container1", description="test") system2 = model.add_software_system(name="system2") system1.uses(system2, "Reads from") assert len(list(system1.get_relationships())) == 1 container1.uses(system2, "Reads from") assert len(list(system1.get_relationships())) == 1 container1.uses(system2, "Writes to") assert len(list(system1.get_relationships())) == 2
def test_ignore_implied_relationship_strategy(): """Check that by default no implied relationships are added.""" model = Model(implied_relationship_strategy=ignore) system1 = model.add_software_system(name="system1") container1 = system1.add_container(name="container1", description="test") system2 = model.add_software_system(name="system2") container2 = system2.add_container(name="container2", description="test") rel = container1.uses(container2, "Uses") assert len(list(container1.get_relationships())) == 1 assert len(list(system1.get_relationships())) == 0 assert set(container1.get_relationships()) == {rel} assert set(system1.get_relationships()) == set()
def test_add_nearest_neighbours(): """Test basic behaviour of add_nearest_neighbours.""" model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") person = model.add_person(name="Person 1") sys1.uses(sys2) person.uses(sys1) # Check neighbours from outbound relationships view = DerivedView(software_system=sys1, description="") view.add_nearest_neighbours(sys1, SoftwareSystem) assert any((elt_view.element is sys1 for elt_view in view.element_views)) assert any((elt_view.element is sys2 for elt_view in view.element_views)) assert not any( (elt_view.element is person for elt_view in view.element_views)) assert len(view.relationship_views) == 1 # Check neighbours from inbound relationships view = DerivedView(software_system=sys1, description="") view.add_nearest_neighbours(sys2, SoftwareSystem) assert any((elt_view.element is sys1 for elt_view in view.element_views)) assert any((elt_view.element is sys2 for elt_view in view.element_views)) assert not any( (elt_view.element is person for elt_view in view.element_views)) assert len(view.relationship_views) == 1
def test_model_add_element_with_existing_id_raises_error(empty_model: Model): """Test you can't add an element with the same ID as an existing one.""" system1 = empty_model.add_software_system(name="System") system2 = SoftwareSystem(name="System2", id=system1.id) with pytest.raises(ValueError, match="The element .* has an existing ID"): empty_model += system2
def test_suppressing_implied_relationships(): """Ensure you can explicitly suppress the current strategy.""" model = Model(implied_relationship_strategy=create_unless_any_exist) system1 = model.add_software_system(name="system1") container1 = system1.add_container(name="container1", description="test") system2 = model.add_software_system(name="system2") container2 = system2.add_container(name="container2", description="test") rel = container1.uses(container2, "Uses", create_implied_relationships=False) assert len(list(container1.get_relationships())) == 1 assert len(list(system1.get_relationships())) == 0 assert set(container1.get_relationships()) == {rel} assert set(system1.get_relationships()) == set()
def test_model_add_relationship_twice_ignored(empty_model: Model): """Ensure that adding an existing relationship to the Model makes no difference.""" sys1 = empty_model.add_software_system(name="sys1") sys2 = empty_model.add_software_system(name="sys2") relationship = empty_model.add_relationship(source=sys1, destination=sys2) assert set(empty_model.get_relationships()) == {relationship} empty_model.add_relationship(relationship) assert set(empty_model.get_relationships()) == {relationship}
def test_default_element_tags_order(empty_model: Model): """ Test that the default tags get added in the right order. Based on test_getTags_WhenThereAreNoTags() from the Java API. """ element = empty_model.add_software_system(name="Name", description="Description") assert list(element.tags) == ["Element", "Software System"]
def test_model_get_relationship_by_id(empty_model: Model): """Test retrieving relationships by their IDs.""" sys1 = empty_model.add_software_system(name="sys1") sys2 = empty_model.add_software_system(name="sys2") relationship = empty_model.add_relationship(source=sys1, destination=sys2, id="r1") assert empty_model.get_relationship("r1") is relationship
def test_model_cannot_add_relationship_with_same_id_as_element(empty_model: Model): """Ensure you can't add a relationship with the same ID as an element.""" sys1 = empty_model.add_software_system(name="sys1") sys2 = empty_model.add_software_system(name="sys2") with pytest.raises( ValueError, match="Relationship.* has the same ID as SoftwareSystem.*" ): empty_model.add_relationship(source=sys1, destination=sys2, id=sys1.id)
def test_model_cannot_add_relationship_with_same_id_as_existing(empty_model: Model): """Ensure you can't add two relationships with the same ID.""" sys1 = empty_model.add_software_system(name="sys1") sys2 = empty_model.add_software_system(name="sys2") empty_model.add_relationship(source=sys1, destination=sys2, id="r1") with pytest.raises( ValueError, match="Relationship.* has the same ID as Relationship.*" ): empty_model.add_relationship(source=sys1, destination=sys2, id="r1")
def test_self_references_are_not_implied(strategy): """Ensure references from an element to itself don't get implied to parents.""" model = Model(implied_relationship_strategy=strategy) system1 = model.add_software_system(name="system1") container1 = system1.add_container(name="container1", description="test") container1.uses(container1, "Uses") assert len(list(container1.get_relationships())) == 1 assert len(list(system1.get_relationships())) == 0
def test_tag_order_is_preserved_to_and_from_io(empty_model: Model): """Test that when serializing via IO classes or back, tag ordering is preserved.""" element = empty_model.add_software_system(name="Name", description="Description") element.tags.update(["tag3", "tag2", "tag1"]) # Deliberately not ascending element_io = SoftwareSystemIO.from_orm(element) assert element_io.tags == ["Element", "Software System", "tag3", "tag2", "tag1"] assert element_io.dict()["tags"] == "Element,Software System,tag3,tag2,tag1" element2 = SoftwareSystem.hydrate(element_io, Model()) assert list(element2.tags) == ["Element", "Software System", "tag3", "tag2", "tag1"]
def test_adding_all_relationships(): """Test adding all relationships for elements in the view.""" model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") sys3 = model.add_software_system(name="System 3") rel1 = sys1.uses(sys2) rel2 = sys3.uses(sys1) view = DerivedView(software_system=sys1, description="") view._add_element(sys1, False) view._add_element(sys2, False) view._add_element(sys3, False) assert view.relationship_views == set() view._add_relationships(sys1) assert len(view.relationship_views) == 2 assert rel1 in [vr.relationship for vr in view.relationship_views] assert rel2 in [vr.relationship for vr in view.relationship_views]
def test_create_implied_relationships_unless_any_exist(): """Check logic of create_implied_relationships_unless_any_exist.""" model = Model(implied_relationship_strategy=create_unless_any_exist) system1 = model.add_software_system(name="system1") container1 = system1.add_container(name="container1", description="test") component1 = container1.add_component(name="component1", description="test") system2 = model.add_software_system(name="system2") container2 = system2.add_container(name="container2", description="test") component2 = container2.add_component(name="component2", description="test") component1.uses(component2, "Uses") # We should now have every *1 element related to every *2 element assert len(list(component1.get_relationships())) == 3 assert len(list(container1.get_relationships())) == 3 assert len(list(system1.get_relationships())) == 3 assert any(rel.destination is component2 for rel in component1.get_relationships()) assert any(rel.destination is container2 for rel in component1.get_relationships()) assert any(rel.destination is system2 for rel in component1.get_relationships()) assert any(rel.destination is component2 for rel in container1.get_relationships()) assert any(rel.destination is container2 for rel in container1.get_relationships()) assert any(rel.destination is system2 for rel in container1.get_relationships()) assert any(rel.destination is component2 for rel in system1.get_relationships()) assert any(rel.destination is container2 for rel in system1.get_relationships()) assert any(rel.destination is system2 for rel in system1.get_relationships()) # Now add another relationship, which shouldn't copy upwards as some already exist container1.uses(container2, "Uses differently") assert len(list( container1.get_relationships())) == 4 # 3 from before plus new one assert len(list(system1.get_relationships())) == 3 # Same as before
def test_default_and_custom_tags(empty_model: Model): """ Test that tags are in the order that they were added. Based on test_getTags_ReturnsTheListOfTags_WhenThereAreSomeTags from the Java API. See https://github.com/Midnighter/structurizr-python/issues/22 """ element = empty_model.add_software_system(name="Name", description="Description") element.tags.update(["tag3", "tag2", "tag1"]) # Deliberately not ascending assert list(element.tags) == ["Element", "Software System", "tag3", "tag2", "tag1"]
def test_is_element_in_view(): """Test check for an element being in the view.""" model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") view = DerivedView(software_system=sys1, description="") view._add_element(sys1, False) assert view.is_element_in_view(sys1) assert not view.is_element_in_view(sys2)
def test_find_element_view(): """Test behaviour of find_element_view.""" model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") view = DerivedView(software_system=sys1, description="") view._add_element(sys1, False) assert view.find_element_view(element=sys1).element is sys1 assert view.find_element_view(element=sys2) is None
def test_adding_relationship_via_uses_adds_to_elements(): """Ensure uses() adds relationships to the model and elements.""" model = Model() sys1 = model.add_software_system(name="sys1") sys2 = model.add_software_system(name="sys2") relationship = sys1.uses(sys2, "uses") assert sys1.relationships == {relationship} assert set(sys1.get_relationships()) == {relationship} assert set(model.get_relationships()) == {relationship} assert sys2.relationships == set() # relationships only contains outbound assert set(sys2.get_relationships()) == {relationship}
def test_cloning_to_implied_relationship_copies_attributes_across(): """Make sure that attributes carry over to implied relationships.""" model = Model(implied_relationship_strategy=create_unless_any_exist) system1 = model.add_software_system(name="system1") container1 = system1.add_container(name="container1", description="test") system2 = model.add_software_system(name="system2") container2 = system2.add_container(name="container2", description="test") rel = container1.uses( container2, "Uses", technology="tech1", interaction_style=InteractionStyle.Asynchronous, tags="tag1,tag2", properties={"prop1": "val1"}, ) new_rel = next(system1.get_relationships()) assert new_rel.description == "Uses" assert new_rel.technology == "tech1" assert new_rel.tags == rel.tags assert new_rel.properties == rel.properties
def test_add_relationship_for_element_not_in_view(): """Ensures relationships for elements outside the view are ignored.""" model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") rel = sys1.uses(sys2) view = DerivedView(software_system=sys1, description="") view._add_element(sys1, False) # This relationship should be ignored as sys2 isn't in the view rel_view1 = view._add_relationship(rel) assert rel_view1 is None assert view.relationship_views == set()
def test_hydration(empty_model: Model): """Check dehydrating and hydrating.""" system = empty_model.add_software_system(name="system", id="sys1") view = DynamicView(key="dyn1", description="Description", element=system) view.set_model(empty_model) io = DynamicViewIO.from_orm(view) d = io.dict() assert d["elementId"] == "sys1" view2 = DynamicView.hydrate(io, element=system) assert view2.key == "dyn1" assert view2.description == "Description" assert view2.element is system
def test_add_relationship_doesnt_duplicate(): """Test that adding a relationships twice doesn't duplicate it.""" model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") rel = sys1.uses(sys2) view = DerivedView(software_system=sys1, description="") view._add_element(sys1, False) view._add_element(sys2, False) rel_view1 = view._add_relationship(rel) assert len(view.relationship_views) == 1 rel_view2 = view._add_relationship(rel) assert len(view.relationship_views) == 1 assert rel_view2 is rel_view1
def test_add_nearest_neighbours_doesnt_dupe_relationships(): """Test relationships aren't duplicated if neighbours added more than once. See https://github.com/Midnighter/structurizr-python/issues/63. """ model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") sys1.uses(sys2) view = DerivedView(software_system=sys1, description="") view.add_nearest_neighbours(sys1, SoftwareSystem) assert len(view.relationship_views) == 1 # The next line should not add any new relationships view.add_nearest_neighbours(sys1, Person) assert len(view.relationship_views) == 1
def test_adding_relationship_to_model_adds_to_element(): """Ensure relationships are added to elements. Make sure that when a relationship is added via Model.add_relationship it also gets added to the elements. """ model = Model() sys1 = model.add_software_system(name="sys1") sys2 = model.add_software_system(name="sys2") relationship = model.add_relationship(source=sys1, destination=sys2, description="uses") assert sys1.relationships == {relationship} assert set(sys1.get_relationships()) == {relationship} assert set(model.get_relationships()) == {relationship} assert sys2.relationships == set() # relationships only contains outbound assert set(sys2.get_relationships()) == {relationship}
def test_relationships_are_ordered(empty_model: Model): """Check that relationships are ordered for DynamicView.""" system1 = empty_model.add_software_system(name="System 1") container1 = system1.add_container(name="Container 1") container2 = system1.add_container(name="Container 2") container1.uses(container2) view = DynamicView(key="dyn1", description="test", element=system1) view.set_model(empty_model) # Test using 10 items, so we can check 1 < 2 < 10 (i.e. not string ordering) for i in range(10): view.add(container1, container2, description=f"rel {i}") relationship_views = list(view.relationship_views) assert len(relationship_views) == 10 for i in range(10): assert relationship_views[i].order == str(i + 1)
def test_relationships_with_subsequences_are_ordered(empty_model: Model): """Test ordering works with subsequences.""" system1 = empty_model.add_software_system(name="System 1") container1 = system1.add_container(name="Container 1") container2 = system1.add_container(name="Container 2") container1.uses(container2) view = DynamicView(key="dyn1", description="test", element=system1) view.set_model(empty_model) view.add(container1, container2, description="test 1") with view.subsequence(): view.add(container1, container2, description="test 1.1") view.add(container1, container2, description="test 1.2") for i in range(2, 11): view.add(container1, container2, description=f"test {i}") relationship_views = list(view.relationship_views) assert relationship_views[0].order == "1" assert relationship_views[1].order == "1.1" assert relationship_views[2].order == "1.2" assert relationship_views[3].order == "2" assert relationship_views[11].order == "10"
def test_find_relationship_view(): """Test behaviour of find_element_view.""" model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") rel1 = sys1.uses(sys2, "Uses") rel2 = sys2.uses(sys1, "Also uses") rel3 = sys2.uses(sys1, "Returns") view = DerivedView(software_system=sys1, description="") view._add_element(sys1, False) view._add_element(sys2, False) view._add_relationship(rel1).description = "Override" view._add_relationship(rel3).response = True assert view.find_relationship_view(relationship=rel1).relationship is rel1 assert view.find_relationship_view(relationship=rel2) is None assert view.find_relationship_view( description="Override").relationship is rel1 assert view.find_relationship_view(description="Uses") is None assert view.find_relationship_view( description="Returns").relationship is rel3 assert view.find_relationship_view(response=True).relationship is rel3 assert view.find_relationship_view(response=False).relationship is rel1
def test_copy_layout(): """Ensure that layout is copied over, including sub-views.""" model = Model() sys1 = model.add_software_system(name="System 1") sys2 = model.add_software_system(name="System 2") rel1 = sys1.uses(sys2) view1 = DerivedView(software_system=sys1, description="") view1._add_element(sys1, False).paper_size = PaperSize.A1_Portrait view1._add_element(sys2, False).paper_size = PaperSize.A2_Portrait view1._add_relationship(rel1).paper_size = PaperSize.A3_Portrait view1.paper_size = PaperSize.A4_Portrait view2 = DerivedView(software_system=sys1, description="") view2._add_element(sys1, False).paper_size = PaperSize.A1_Portrait view2._add_element(sys2, False).paper_size = PaperSize.A2_Portrait view2._add_relationship(rel1).paper_size = PaperSize.A3_Portrait view2.copy_layout_information_from(view1) assert view2.paper_size == PaperSize.A4_Portrait assert view2.find_element_view( element=sys1).paper_size == PaperSize.A1_Portrait rv = view2.find_relationship_view(description="Uses") assert rv.paper_size == PaperSize.A3_Portrait
def test_model_add_element_twice_is_ignored(empty_model: Model): """Test you can't add an element with the same ID as an existing one.""" system1 = empty_model.add_software_system(name="System") empty_model += system1 assert empty_model.software_systems == {system1}