mal-toolbox 1.1.3__tar.gz → 1.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mal_toolbox-1.1.3/mal_toolbox.egg-info → mal_toolbox-1.2.1}/PKG-INFO +10 -1
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/README.md +9 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1/mal_toolbox.egg-info}/PKG-INFO +10 -1
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/mal_toolbox.egg-info/SOURCES.txt +3 -1
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/__init__.py +2 -2
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/attackgraph/attackgraph.py +23 -2
- mal_toolbox-1.2.1/maltoolbox/str_utils.py +22 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/visualization/graphviz_utils.py +55 -30
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/pyproject.toml +1 -1
- mal_toolbox-1.2.1/tests/test_visualization.py +104 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/AUTHORS +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/LICENSE +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/mal_toolbox.egg-info/dependency_links.txt +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/mal_toolbox.egg-info/entry_points.txt +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/mal_toolbox.egg-info/requires.txt +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/mal_toolbox.egg-info/top_level.txt +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/__main__.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/attackgraph/__init__.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/attackgraph/node.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/exceptions.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/file_utils.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/language/__init__.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/language/compiler/__init__.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/language/compiler/mal_lexer.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/language/compiler/mal_parser.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/language/languagegraph.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/model.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/patternfinder/__init__.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/patternfinder/attackgraph_patterns.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/py.typed +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/translators/__init__.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/translators/networkx.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/translators/updater.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/visualization/__init__.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/visualization/draw_io_utils.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/visualization/neo4j_utils.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/maltoolbox/visualization/utils.py +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/setup.cfg +0 -0
- {mal_toolbox-1.1.3 → mal_toolbox-1.2.1}/tests/test_model.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mal-toolbox
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: A collection of tools used to create MAL models and attack graphs.
|
|
5
5
|
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Sandor Berglund <sandor@kth.se>
|
|
6
6
|
License: Apache Software License
|
|
@@ -75,6 +75,15 @@ available.
|
|
|
75
75
|
pip install mal-toolbox
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
### Requirements
|
|
79
|
+
|
|
80
|
+
If you wish to run visualisations with graphviz, you must first download and install it on your computer. Depending on your operating system, you can find out how to do this here: [link to graphviz installation](https://graphviz.org/download/).
|
|
81
|
+
|
|
82
|
+
Once the software has been successfully installed, you must also include the python package by running:
|
|
83
|
+
```
|
|
84
|
+
pip install graphviz
|
|
85
|
+
```
|
|
86
|
+
|
|
78
87
|
## Configuration
|
|
79
88
|
You can use a `maltoolbox.yml` file in the current working directory to
|
|
80
89
|
configure the toolbox.
|
|
@@ -44,6 +44,15 @@ available.
|
|
|
44
44
|
pip install mal-toolbox
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### Requirements
|
|
48
|
+
|
|
49
|
+
If you wish to run visualisations with graphviz, you must first download and install it on your computer. Depending on your operating system, you can find out how to do this here: [link to graphviz installation](https://graphviz.org/download/).
|
|
50
|
+
|
|
51
|
+
Once the software has been successfully installed, you must also include the python package by running:
|
|
52
|
+
```
|
|
53
|
+
pip install graphviz
|
|
54
|
+
```
|
|
55
|
+
|
|
47
56
|
## Configuration
|
|
48
57
|
You can use a `maltoolbox.yml` file in the current working directory to
|
|
49
58
|
configure the toolbox.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mal-toolbox
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: A collection of tools used to create MAL models and attack graphs.
|
|
5
5
|
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Sandor Berglund <sandor@kth.se>
|
|
6
6
|
License: Apache Software License
|
|
@@ -75,6 +75,15 @@ available.
|
|
|
75
75
|
pip install mal-toolbox
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
### Requirements
|
|
79
|
+
|
|
80
|
+
If you wish to run visualisations with graphviz, you must first download and install it on your computer. Depending on your operating system, you can find out how to do this here: [link to graphviz installation](https://graphviz.org/download/).
|
|
81
|
+
|
|
82
|
+
Once the software has been successfully installed, you must also include the python package by running:
|
|
83
|
+
```
|
|
84
|
+
pip install graphviz
|
|
85
|
+
```
|
|
86
|
+
|
|
78
87
|
## Configuration
|
|
79
88
|
You can use a `maltoolbox.yml` file in the current working directory to
|
|
80
89
|
configure the toolbox.
|
|
@@ -14,6 +14,7 @@ maltoolbox/exceptions.py
|
|
|
14
14
|
maltoolbox/file_utils.py
|
|
15
15
|
maltoolbox/model.py
|
|
16
16
|
maltoolbox/py.typed
|
|
17
|
+
maltoolbox/str_utils.py
|
|
17
18
|
maltoolbox/attackgraph/__init__.py
|
|
18
19
|
maltoolbox/attackgraph/attackgraph.py
|
|
19
20
|
maltoolbox/attackgraph/node.py
|
|
@@ -33,4 +34,5 @@ maltoolbox/visualization/draw_io_utils.py
|
|
|
33
34
|
maltoolbox/visualization/graphviz_utils.py
|
|
34
35
|
maltoolbox/visualization/neo4j_utils.py
|
|
35
36
|
maltoolbox/visualization/utils.py
|
|
36
|
-
tests/test_model.py
|
|
37
|
+
tests/test_model.py
|
|
38
|
+
tests/test_visualization.py
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MAL Toolbox v1.1
|
|
1
|
+
# MAL Toolbox v1.2.1
|
|
2
2
|
# Copyright 2025, Andrei Buhaiu.
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
__title__ = "maltoolbox"
|
|
22
|
-
__version__ = "1.1
|
|
22
|
+
__version__ = "1.2.1"
|
|
23
23
|
__authors__ = [
|
|
24
24
|
"Andrei Buhaiu",
|
|
25
25
|
"Giuseppe Nebbione",
|
|
@@ -26,6 +26,8 @@ from ..language import (
|
|
|
26
26
|
LanguageGraphAttackStep,
|
|
27
27
|
disaggregate_attack_step_full_name,
|
|
28
28
|
)
|
|
29
|
+
|
|
30
|
+
from ..str_utils import levenshtein_distance
|
|
29
31
|
from ..model import Model
|
|
30
32
|
from .node import AttackGraphNode
|
|
31
33
|
|
|
@@ -277,7 +279,7 @@ class AttackGraph:
|
|
|
277
279
|
return cls._from_dict(serialized_attack_graph,
|
|
278
280
|
lang_graph, model=model)
|
|
279
281
|
|
|
280
|
-
def get_node_by_full_name(self, full_name: str) -> AttackGraphNode
|
|
282
|
+
def get_node_by_full_name(self, full_name: str) -> AttackGraphNode:
|
|
281
283
|
"""Return the attack node that matches the full name provided.
|
|
282
284
|
|
|
283
285
|
Arguments:
|
|
@@ -291,7 +293,13 @@ class AttackGraph:
|
|
|
291
293
|
|
|
292
294
|
"""
|
|
293
295
|
logger.debug('Looking up node with full name "%s"', full_name)
|
|
294
|
-
|
|
296
|
+
if full_name not in self._full_name_to_node:
|
|
297
|
+
similar_names = self._get_similar_full_names(full_name)
|
|
298
|
+
raise LookupError(
|
|
299
|
+
f'Could not find node with name "{full_name}". '
|
|
300
|
+
f'Did you mean: {", ".join(similar_names)}?'
|
|
301
|
+
)
|
|
302
|
+
return self._full_name_to_node[full_name]
|
|
295
303
|
|
|
296
304
|
def _follow_field_expr_chain(
|
|
297
305
|
self, target_assets: set[ModelAsset], expr_chain: ExpressionsChain
|
|
@@ -623,6 +631,19 @@ class AttackGraph:
|
|
|
623
631
|
ag_node.children.add(target_node)
|
|
624
632
|
target_node.parents.add(ag_node)
|
|
625
633
|
|
|
634
|
+
def _get_similar_full_names(self, q: str) -> list[str]:
|
|
635
|
+
"""Return a list of node full names that are similar to `q`"""
|
|
636
|
+
shortest_dist = 100
|
|
637
|
+
similar_names = []
|
|
638
|
+
for full_name in self._full_name_to_node:
|
|
639
|
+
dist = levenshtein_distance(q, full_name)
|
|
640
|
+
if dist == shortest_dist:
|
|
641
|
+
similar_names.append(full_name)
|
|
642
|
+
elif dist < shortest_dist:
|
|
643
|
+
similar_names = [full_name]
|
|
644
|
+
shortest_dist = dist
|
|
645
|
+
return similar_names
|
|
646
|
+
|
|
626
647
|
def regenerate_graph(self) -> None:
|
|
627
648
|
"""Regenerate the attack graph based on the original model instance and
|
|
628
649
|
the MAL language specification provided at initialization.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""String related methods"""
|
|
2
|
+
|
|
3
|
+
def levenshtein_distance(a: str, b: str) -> int:
|
|
4
|
+
"""Get distance between two strings"""
|
|
5
|
+
if a == b:
|
|
6
|
+
return 0
|
|
7
|
+
if not a:
|
|
8
|
+
return len(b)
|
|
9
|
+
if not b:
|
|
10
|
+
return len(a)
|
|
11
|
+
|
|
12
|
+
prev_row = list(range(len(b) + 1))
|
|
13
|
+
for i, ca in enumerate(a, start=1):
|
|
14
|
+
curr_row = [i]
|
|
15
|
+
for j, cb in enumerate(b, start=1):
|
|
16
|
+
insertions = prev_row[j] + 1
|
|
17
|
+
deletions = curr_row[j - 1] + 1
|
|
18
|
+
substitutions = prev_row[j - 1] + (ca != cb)
|
|
19
|
+
curr_row.append(min(insertions, deletions, substitutions))
|
|
20
|
+
prev_row = curr_row
|
|
21
|
+
return prev_row[-1]
|
|
22
|
+
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from os import PathLike
|
|
3
|
+
from typing import Optional
|
|
1
4
|
import random
|
|
2
5
|
|
|
3
6
|
import graphviz
|
|
@@ -36,8 +39,28 @@ graphviz_bright_colors = [
|
|
|
36
39
|
]
|
|
37
40
|
|
|
38
41
|
|
|
39
|
-
def
|
|
40
|
-
"""
|
|
42
|
+
def _resolve_graphviz_path(path: Optional[PathLike], default_name: str):
|
|
43
|
+
"""
|
|
44
|
+
Resolve a user-provided path into (directory, filename_without_ext).
|
|
45
|
+
|
|
46
|
+
- If path is None → ('.', default_name)
|
|
47
|
+
- If path is a directory → (path, default_name)
|
|
48
|
+
- If path is a file → (parent_directory, file_stem)
|
|
49
|
+
"""
|
|
50
|
+
if path is None:
|
|
51
|
+
return ".", default_name
|
|
52
|
+
|
|
53
|
+
p = Path(path)
|
|
54
|
+
|
|
55
|
+
if p.is_dir():
|
|
56
|
+
return str(p), default_name
|
|
57
|
+
|
|
58
|
+
# It's a file path
|
|
59
|
+
return str(p.parent), p.stem
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def render_model(model: Model, path: Optional[PathLike] = None, view=True):
|
|
63
|
+
"""Render a model in graphviz, create PDF, and open it."""
|
|
41
64
|
dot = graphviz.Digraph(model.name)
|
|
42
65
|
|
|
43
66
|
# Create nodes
|
|
@@ -47,58 +70,60 @@ def render_model(model: Model):
|
|
|
47
70
|
if not bg_color:
|
|
48
71
|
bg_color = random.choice(graphviz_bright_colors)
|
|
49
72
|
asset_type_colors[asset.lg_asset.name] = bg_color
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
73
|
+
|
|
74
|
+
dot.node(str(asset.id), asset.name, style="filled", fillcolor=bg_color)
|
|
53
75
|
|
|
54
76
|
# Create edges
|
|
55
77
|
for from_asset in model.assets.values():
|
|
56
|
-
|
|
57
78
|
for fieldname, to_assets in from_asset.associated_assets.items():
|
|
58
79
|
for to_asset in to_assets:
|
|
59
|
-
dot.edge(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
dot.render(directory=
|
|
80
|
+
dot.edge(str(from_asset.id), str(to_asset.id), label=fieldname)
|
|
81
|
+
|
|
82
|
+
directory, filename = _resolve_graphviz_path(path, model.name)
|
|
83
|
+
dot.render(directory=directory, filename=f"{filename}.gv", view=view, format="pdf")
|
|
63
84
|
|
|
64
85
|
|
|
65
|
-
def render_attack_graph(attack_graph: AttackGraph):
|
|
66
|
-
"""Render attack graph graphviz, create
|
|
86
|
+
def render_attack_graph(attack_graph: AttackGraph, path: Optional[PathLike] = None, view = True):
|
|
87
|
+
"""Render attack graph graphviz, create PDF, and open it."""
|
|
67
88
|
assert attack_graph.model, "Attack graph needs a model"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
dot
|
|
89
|
+
|
|
90
|
+
name = attack_graph.model.name + "-attack_graph"
|
|
91
|
+
dot = graphviz.Digraph(name)
|
|
92
|
+
dot.graph_attr["nodesep"] = "3.0"
|
|
93
|
+
dot.graph_attr["ratio"] = "compress"
|
|
71
94
|
|
|
72
95
|
# Create nodes
|
|
73
96
|
asset_colors: dict[str, str] = {}
|
|
74
97
|
for node in attack_graph.nodes.values():
|
|
75
98
|
assert node.model_asset, "Node needs model"
|
|
99
|
+
|
|
76
100
|
bg_color = asset_colors.get(node.model_asset.name)
|
|
77
101
|
if not bg_color:
|
|
78
102
|
bg_color = random.choice(graphviz_bright_colors)
|
|
79
103
|
asset_colors[node.model_asset.name] = bg_color
|
|
80
|
-
|
|
104
|
+
|
|
81
105
|
match node.type:
|
|
82
|
-
case
|
|
83
|
-
path_color =
|
|
84
|
-
case
|
|
85
|
-
path_color =
|
|
86
|
-
case
|
|
87
|
-
path_color =
|
|
88
|
-
case 'exist':
|
|
89
|
-
path_color = 'grey'
|
|
90
|
-
case 'notExist':
|
|
91
|
-
path_color = 'grey'
|
|
106
|
+
case "defense":
|
|
107
|
+
path_color = "blue"
|
|
108
|
+
case "or" | "and":
|
|
109
|
+
path_color = "red"
|
|
110
|
+
case "exist" | "notExist":
|
|
111
|
+
path_color = "grey"
|
|
92
112
|
case t:
|
|
93
|
-
raise ValueError(f
|
|
113
|
+
raise ValueError(f"Type {t} not supported")
|
|
94
114
|
|
|
95
115
|
dot.node(
|
|
96
|
-
str(node.id),
|
|
116
|
+
str(node.id),
|
|
117
|
+
node.full_name,
|
|
118
|
+
style="filled",
|
|
119
|
+
color=path_color,
|
|
120
|
+
fillcolor=bg_color
|
|
97
121
|
)
|
|
98
122
|
|
|
99
123
|
# Create edges
|
|
100
124
|
for parent in attack_graph.nodes.values():
|
|
101
125
|
for child in parent.children:
|
|
102
|
-
dot.edge(str(parent.id), str(child.id))
|
|
126
|
+
dot.edge(str(parent.id), str(child.id), arrowhead="normal")
|
|
103
127
|
|
|
104
|
-
|
|
128
|
+
directory, filename = _resolve_graphviz_path(path, name)
|
|
129
|
+
dot.render(directory=directory, filename=f"{filename}.gv", view=view, format="pdf")
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from maltoolbox.model import Model
|
|
3
|
+
from maltoolbox.attackgraph import AttackGraph
|
|
4
|
+
from maltoolbox.visualization import render_attack_graph, render_model
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# -------------------------------------------------------------------
|
|
8
|
+
# 1. Tests when PATH is a directory
|
|
9
|
+
# -------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
def test_render_model_to_dir(example_model: Model, tmp_path: Path):
|
|
12
|
+
"""Ensure the model renders to directory with .gv and .gv.pdf."""
|
|
13
|
+
outdir = tmp_path
|
|
14
|
+
|
|
15
|
+
render_model(example_model, path=outdir, view=False)
|
|
16
|
+
|
|
17
|
+
expected_pdf = outdir / f"{example_model.name}.gv.pdf"
|
|
18
|
+
expected_gv = outdir / f"{example_model.name}.gv"
|
|
19
|
+
|
|
20
|
+
assert expected_pdf.exists()
|
|
21
|
+
assert expected_gv.exists()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_render_attack_graph_to_dir(example_attackgraph: AttackGraph, tmp_path: Path):
|
|
25
|
+
"""Ensure the attack graph renders to directory with .gv and .gv.pdf."""
|
|
26
|
+
outdir = tmp_path
|
|
27
|
+
assert example_attackgraph.model, "Need model"
|
|
28
|
+
name = example_attackgraph.model.name + "-attack_graph"
|
|
29
|
+
|
|
30
|
+
render_attack_graph(example_attackgraph, path=outdir, view=False)
|
|
31
|
+
|
|
32
|
+
expected_pdf = outdir / f"{name}.gv.pdf"
|
|
33
|
+
expected_gv = outdir / f"{name}.gv"
|
|
34
|
+
|
|
35
|
+
assert expected_pdf.exists()
|
|
36
|
+
assert expected_gv.exists()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# -------------------------------------------------------------------
|
|
40
|
+
# 2. Tests when PATH is a specific file path
|
|
41
|
+
# -------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def test_render_model_to_exact_file(example_model: Model, tmp_path: Path):
|
|
44
|
+
"""Ensure rendering works when given an explicit file path."""
|
|
45
|
+
out_file = tmp_path / "custom_output.gv" # user-specified file name
|
|
46
|
+
|
|
47
|
+
render_model(example_model, path=out_file, view=False)
|
|
48
|
+
|
|
49
|
+
expected_pdf = tmp_path / "custom_output.gv.pdf"
|
|
50
|
+
expected_gv = tmp_path / "custom_output.gv"
|
|
51
|
+
|
|
52
|
+
assert expected_pdf.exists()
|
|
53
|
+
assert expected_gv.exists()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_render_attack_graph_to_exact_file(example_attackgraph: AttackGraph, tmp_path: Path):
|
|
57
|
+
"""Ensure attack graph renders to an exact file path."""
|
|
58
|
+
out_file = tmp_path / "attack_output.gv"
|
|
59
|
+
|
|
60
|
+
render_attack_graph(example_attackgraph, path=out_file, view=False)
|
|
61
|
+
|
|
62
|
+
expected_pdf = tmp_path / "attack_output.gv.pdf"
|
|
63
|
+
expected_gv = tmp_path / "attack_output.gv"
|
|
64
|
+
|
|
65
|
+
assert expected_pdf.exists()
|
|
66
|
+
assert expected_gv.exists()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# -------------------------------------------------------------------
|
|
70
|
+
# 3. Tests when PATH is None (default behavior)
|
|
71
|
+
# -------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def test_render_model_no_path(example_model: Model, tmp_path: Path, monkeypatch):
|
|
74
|
+
"""
|
|
75
|
+
Ensure rendering works with path=None.
|
|
76
|
+
We monkeypatch cwd so files go into tmp_path.
|
|
77
|
+
"""
|
|
78
|
+
monkeypatch.chdir(tmp_path)
|
|
79
|
+
|
|
80
|
+
render_model(example_model, path=None, view=False)
|
|
81
|
+
|
|
82
|
+
expected_pdf = tmp_path / f"{example_model.name}.gv.pdf"
|
|
83
|
+
expected_gv = tmp_path / f"{example_model.name}.gv"
|
|
84
|
+
|
|
85
|
+
assert expected_pdf.exists()
|
|
86
|
+
assert expected_gv.exists()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_render_attack_graph_no_path(example_attackgraph: AttackGraph, tmp_path: Path, monkeypatch):
|
|
90
|
+
"""
|
|
91
|
+
Ensure rendering attack graph works with no path.
|
|
92
|
+
Files should appear in the working directory.
|
|
93
|
+
"""
|
|
94
|
+
monkeypatch.chdir(tmp_path)
|
|
95
|
+
assert example_attackgraph.model, "Need model"
|
|
96
|
+
name = example_attackgraph.model.name + "-attack_graph"
|
|
97
|
+
|
|
98
|
+
render_attack_graph(example_attackgraph, path=None, view=False)
|
|
99
|
+
|
|
100
|
+
expected_pdf = tmp_path / f"{name}.gv.pdf"
|
|
101
|
+
expected_gv = tmp_path / f"{name}.gv"
|
|
102
|
+
|
|
103
|
+
assert expected_pdf.exists()
|
|
104
|
+
assert expected_gv.exists()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|