Skip to content

Commit b8262d2

Browse files
yt-msMidnighter
authored andcommitted
feat: implement InfrastructureNode
1 parent abffa83 commit b8262d2

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# https://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
14+
"""Provide an infrastructure node model."""
15+
16+
from typing import TYPE_CHECKING, Optional
17+
18+
from .deployment_element import DeploymentElement, DeploymentElementIO
19+
from .tags import Tags
20+
21+
22+
if TYPE_CHECKING:
23+
from .model import Model
24+
25+
26+
__all__ = ("InfrastructureNode", "InfrastructureNodeIO")
27+
28+
29+
class InfrastructureNodeIO(DeploymentElementIO):
30+
"""
31+
Represent an infrastructure node.
32+
33+
An infrastructure node is something like:
34+
* Load balancer
35+
* Firewall
36+
* DNS service
37+
* etc
38+
"""
39+
40+
technology: Optional[str] = ""
41+
42+
43+
class InfrastructureNode(DeploymentElement):
44+
"""
45+
Represent an infrastructure node.
46+
47+
An infrastructure node is something like:
48+
* Load balancer
49+
* Firewall
50+
* DNS service
51+
* etc
52+
"""
53+
54+
def __init__(
55+
self,
56+
*,
57+
technology: str = "",
58+
**kwargs,
59+
):
60+
"""Initialize an infrastructure node model."""
61+
super().__init__(**kwargs)
62+
self.technology = technology
63+
self.tags.add(Tags.INFRASTRUCTURE_NODE)
64+
65+
@classmethod
66+
def hydrate(
67+
cls,
68+
node_io: InfrastructureNodeIO,
69+
model: "Model",
70+
) -> "InfrastructureNode":
71+
"""Hydrate a new InfrastructureNode instance from its IO.
72+
73+
This will also automatically register with the model.
74+
"""
75+
node = cls(
76+
**cls.hydrate_arguments(node_io),
77+
technology=node_io.technology,
78+
)
79+
model += node
80+
return node

src/structurizr/model/tags.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class Tags(AbstractBase):
3535

3636
DEPLOYMENT_NODE = "Deployment Node"
3737
CONTAINER_INSTANCE = "Container Instance"
38+
SOFTWARE_SYSTEM_INSTANCE = "Software System Instance"
39+
INFRASTRUCTURE_NODE = "Infrastructure Node"
3840

3941
SYNCHRONOUS = "Synchronous"
4042
ASYNCHRONOUS = "Asynchronous"
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# https://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
14+
"""Ensure the expected behaviour of the container element."""
15+
16+
17+
import pytest
18+
19+
from structurizr.model.infrastructure_node import (
20+
InfrastructureNode,
21+
InfrastructureNodeIO,
22+
)
23+
from structurizr.model.tags import Tags
24+
25+
26+
class MockModel:
27+
"""Implement a mock model for testing."""
28+
29+
def __init__(self):
30+
"""Initialize the mock."""
31+
pass
32+
33+
def __iadd__(self, node):
34+
"""Simulate the model assigning IDs to new elements."""
35+
if not node.id:
36+
node.id = "id"
37+
node.set_model(self)
38+
return self
39+
40+
41+
@pytest.fixture(scope="function")
42+
def empty_model() -> MockModel:
43+
"""Provide an new empty model on demand for test cases to use."""
44+
return MockModel()
45+
46+
47+
@pytest.mark.parametrize(
48+
"attributes",
49+
[
50+
pytest.param({}, marks=pytest.mark.raises(exception=TypeError)),
51+
{"name": "Node1", "technology": "tech1"},
52+
],
53+
)
54+
def test_infrastructure_node_init(attributes):
55+
"""Expect proper initialization from arguments."""
56+
node = InfrastructureNode(**attributes)
57+
for attr, expected in attributes.items():
58+
assert getattr(node, attr) == expected
59+
60+
61+
def test_infrastructure_node_tags():
62+
"""Check default tags."""
63+
node = InfrastructureNode(name="Node")
64+
assert Tags.ELEMENT in node.tags
65+
assert Tags.INFRASTRUCTURE_NODE in node.tags
66+
67+
68+
def test_infrastructure_node_hydration(empty_model):
69+
"""Check hydrating an infrastructure node from its IO."""
70+
io = InfrastructureNodeIO(name="node1", technology="tech")
71+
72+
node = InfrastructureNode.hydrate(io, empty_model)
73+
74+
assert node.name == "node1"
75+
assert node.technology == "tech"

0 commit comments

Comments
 (0)