ara-cli 0.1.9.50__py3-none-any.whl → 0.1.9.51__py3-none-any.whl
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.
Potentially problematic release.
This version of ara-cli might be problematic. Click here for more details.
- ara_cli/__main__.py +3 -1
- ara_cli/analyse_artefacts.py +133 -0
- ara_cli/ara_command_action.py +71 -50
- ara_cli/ara_command_parser.py +5 -0
- ara_cli/ara_config.py +65 -38
- ara_cli/artefact_lister.py +60 -49
- ara_cli/artefact_models/artefact_model.py +9 -7
- ara_cli/artefact_models/feature_artefact_model.py +4 -1
- ara_cli/artefact_reader.py +104 -57
- ara_cli/artefact_scan.py +46 -0
- ara_cli/file_classifier.py +21 -13
- ara_cli/prompt_extractor.py +10 -2
- ara_cli/tag_extractor.py +6 -16
- ara_cli/templates/specification_breakdown_files/template.concept.md +12 -14
- ara_cli/tests/test_ara_command_action.py +242 -108
- ara_cli/tests/test_artefact_lister.py +552 -183
- ara_cli/tests/test_artefact_reader.py +18 -46
- ara_cli/tests/test_artefact_scan.py +126 -0
- ara_cli/tests/test_file_classifier.py +68 -29
- ara_cli/tests/test_tag_extractor.py +42 -61
- ara_cli/version.py +1 -1
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.51.dist-info}/METADATA +1 -1
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.51.dist-info}/RECORD +26 -23
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.51.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.51.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.51.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from unittest.mock import MagicMock, patch
|
|
3
3
|
from ara_cli.artefact_lister import ArtefactLister
|
|
4
|
+
from ara_cli.list_filter import ListFilter
|
|
5
|
+
from ara_cli.artefact_models.artefact_model import ArtefactType
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
@pytest.fixture
|
|
@@ -8,197 +10,564 @@ def artefact_lister():
|
|
|
8
10
|
return ArtefactLister()
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
@pytest.mark.parametrize(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
@pytest.mark.parametrize(
|
|
14
|
+
"users, status, tags, expected_tags",
|
|
15
|
+
[
|
|
16
|
+
# Normal case with all fields populated
|
|
17
|
+
(
|
|
18
|
+
["john", "alice"],
|
|
19
|
+
"in-progress",
|
|
20
|
+
["important", "urgent"],
|
|
21
|
+
["user_john", "user_alice", "in-progress", "important", "urgent"]
|
|
22
|
+
),
|
|
23
|
+
# Case with empty users
|
|
24
|
+
(
|
|
25
|
+
[],
|
|
26
|
+
"to-do",
|
|
27
|
+
["feature", "backend"],
|
|
28
|
+
["to-do", "feature", "backend"]
|
|
29
|
+
),
|
|
30
|
+
# Case with empty tags
|
|
31
|
+
(
|
|
32
|
+
["bob"],
|
|
33
|
+
"done",
|
|
34
|
+
[],
|
|
35
|
+
["user_bob", "done"]
|
|
36
|
+
),
|
|
37
|
+
# Case with all empty fields
|
|
38
|
+
(
|
|
39
|
+
[],
|
|
40
|
+
"",
|
|
41
|
+
[],
|
|
42
|
+
[""]
|
|
43
|
+
),
|
|
44
|
+
# Case with None values for tags (should handle gracefully)
|
|
45
|
+
(
|
|
46
|
+
["admin"],
|
|
47
|
+
"closed",
|
|
48
|
+
None,
|
|
49
|
+
["user_admin", "closed"]
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
)
|
|
53
|
+
def test_artefact_tags_retrieval(users, status, tags, expected_tags):
|
|
54
|
+
# Create a mock artefact with the specified attributes
|
|
17
55
|
artefact_mock = MagicMock()
|
|
18
|
-
artefact_mock.
|
|
19
|
-
artefact_mock.
|
|
56
|
+
artefact_mock.users = users
|
|
57
|
+
artefact_mock.status = status
|
|
58
|
+
artefact_mock.tags = tags if tags is not None else []
|
|
59
|
+
|
|
60
|
+
# Call the method under test
|
|
61
|
+
result = ArtefactLister.artefact_tags_retrieval(artefact_mock)
|
|
62
|
+
|
|
63
|
+
# Verify the result
|
|
64
|
+
assert result == expected_tags
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_artefact_tags_retrieval_with_none():
|
|
68
|
+
# Test with None artefact
|
|
69
|
+
result = ArtefactLister.artefact_tags_retrieval(None)
|
|
70
|
+
assert result == []
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest.mark.parametrize(
|
|
74
|
+
"classified_files, list_filter, filter_result",
|
|
75
|
+
[
|
|
76
|
+
# Case 1: No filter applied
|
|
77
|
+
(
|
|
78
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
79
|
+
None,
|
|
80
|
+
{"type1": [MagicMock(), MagicMock()]}
|
|
81
|
+
),
|
|
82
|
+
# Case 2: Filter with include tags
|
|
83
|
+
(
|
|
84
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
85
|
+
ListFilter(include_tags=["tag1"]),
|
|
86
|
+
{"type1": [MagicMock()]}
|
|
87
|
+
),
|
|
88
|
+
# Case 3: Filter with exclude tags
|
|
89
|
+
(
|
|
90
|
+
{"type1": [MagicMock(), MagicMock()], "type2": [MagicMock()]},
|
|
91
|
+
ListFilter(exclude_tags=["tag2"]),
|
|
92
|
+
{"type1": [MagicMock()], "type2": []}
|
|
93
|
+
),
|
|
94
|
+
# Case 4: Empty result after filtering
|
|
95
|
+
(
|
|
96
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
97
|
+
ListFilter(include_tags=["nonexistent"]),
|
|
98
|
+
{"type1": []}
|
|
99
|
+
),
|
|
100
|
+
# Case 5: Multiple artefact types
|
|
101
|
+
(
|
|
102
|
+
{"type1": [MagicMock()], "type2": [MagicMock(), MagicMock()]},
|
|
103
|
+
ListFilter(exclude_extension=[".txt"]),
|
|
104
|
+
{"type1": [], "type2": [MagicMock()]}
|
|
105
|
+
),
|
|
106
|
+
],
|
|
107
|
+
)
|
|
108
|
+
def test_filter_artefacts(
|
|
109
|
+
artefact_lister,
|
|
110
|
+
classified_files,
|
|
111
|
+
list_filter,
|
|
112
|
+
filter_result
|
|
113
|
+
):
|
|
114
|
+
# Mock the filter_list function
|
|
115
|
+
with patch("ara_cli.artefact_lister.filter_list") as mock_filter_list:
|
|
116
|
+
mock_filter_list.return_value = filter_result
|
|
117
|
+
|
|
118
|
+
# Call the method under test
|
|
119
|
+
result = artefact_lister.filter_artefacts(classified_files, list_filter)
|
|
120
|
+
|
|
121
|
+
# Verify filter_list was called with correct parameters
|
|
122
|
+
mock_filter_list.assert_called_once_with(
|
|
123
|
+
list_to_filter=classified_files,
|
|
124
|
+
list_filter=list_filter,
|
|
125
|
+
content_retrieval_strategy=ArtefactLister.artefact_content_retrieval,
|
|
126
|
+
file_path_retrieval=ArtefactLister.artefact_path_retrieval,
|
|
127
|
+
tag_retrieval=ArtefactLister.artefact_tags_retrieval
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Verify the structure matches (don't compare the actual MagicMock objects)
|
|
131
|
+
assert set(result.keys()) == set(filter_result.keys())
|
|
132
|
+
for key in filter_result:
|
|
133
|
+
assert len(result[key]) == len(filter_result[key])
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.mark.parametrize("artefact_content", ["content1", "content2", "content3"])
|
|
137
|
+
def test_artefact_content_retrieval(artefact_content):
|
|
138
|
+
artefact_mock = MagicMock()
|
|
139
|
+
artefact_mock.serialize.return_value = artefact_content # Mock serialize()
|
|
20
140
|
|
|
21
141
|
content = ArtefactLister.artefact_content_retrieval(artefact_mock)
|
|
22
142
|
assert content == artefact_content
|
|
23
143
|
|
|
24
144
|
|
|
25
|
-
@pytest.mark.parametrize(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
145
|
+
@pytest.mark.parametrize(
|
|
146
|
+
"file_path",
|
|
147
|
+
[
|
|
148
|
+
("./ara/userstories/test.userstory"),
|
|
149
|
+
("./ara/epics/test.epic"),
|
|
150
|
+
],
|
|
151
|
+
)
|
|
152
|
+
def test_artefact_path_retrieval(file_path):
|
|
31
153
|
artefact_mock = MagicMock()
|
|
32
|
-
artefact_mock.
|
|
33
|
-
artefact_mock.file_path = artefact_path
|
|
154
|
+
artefact_mock.file_path = file_path
|
|
34
155
|
|
|
35
156
|
path = ArtefactLister.artefact_path_retrieval(artefact_mock)
|
|
36
|
-
assert path ==
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@pytest.mark.parametrize(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
("
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
157
|
+
assert path == file_path
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@pytest.mark.parametrize(
|
|
161
|
+
"tags, navigate_to_target, list_filter, mock_artefacts, filtered_artefacts, expected_filtered",
|
|
162
|
+
[
|
|
163
|
+
# Test with no filter
|
|
164
|
+
(
|
|
165
|
+
None,
|
|
166
|
+
False,
|
|
167
|
+
None,
|
|
168
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
169
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
170
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
171
|
+
),
|
|
172
|
+
# Test with filter applied
|
|
173
|
+
(
|
|
174
|
+
["tag1"],
|
|
175
|
+
False,
|
|
176
|
+
ListFilter(include_tags=["tag1"]),
|
|
177
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
178
|
+
{"type1": [MagicMock()]},
|
|
179
|
+
{"type1": [MagicMock()]},
|
|
180
|
+
),
|
|
181
|
+
# Test with None values in artefact list
|
|
182
|
+
(
|
|
183
|
+
None,
|
|
184
|
+
False,
|
|
185
|
+
None,
|
|
186
|
+
{"type1": [MagicMock(), None, MagicMock()]},
|
|
187
|
+
{"type1": [MagicMock(), None, MagicMock()]},
|
|
188
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
189
|
+
),
|
|
190
|
+
# Test with empty filtered results
|
|
191
|
+
(
|
|
192
|
+
["tag1"],
|
|
193
|
+
False,
|
|
194
|
+
ListFilter(include_tags=["nonexistent"]),
|
|
195
|
+
{"type1": [MagicMock(), MagicMock()]},
|
|
196
|
+
{"type1": []},
|
|
197
|
+
{"type1": []},
|
|
198
|
+
),
|
|
199
|
+
# Test with multiple artefact types
|
|
200
|
+
(
|
|
201
|
+
None,
|
|
202
|
+
True,
|
|
203
|
+
ListFilter(exclude_extension=[".txt"]),
|
|
204
|
+
{"type1": [MagicMock()], "type2": [MagicMock(), MagicMock()]},
|
|
205
|
+
{"type1": [], "type2": [MagicMock()]},
|
|
206
|
+
{"type1": [], "type2": [MagicMock()]},
|
|
207
|
+
),
|
|
208
|
+
],
|
|
209
|
+
)
|
|
210
|
+
def test_list_files(
|
|
211
|
+
artefact_lister,
|
|
212
|
+
tags,
|
|
213
|
+
navigate_to_target,
|
|
214
|
+
list_filter,
|
|
215
|
+
mock_artefacts,
|
|
216
|
+
filtered_artefacts,
|
|
217
|
+
expected_filtered,
|
|
218
|
+
):
|
|
219
|
+
# Mock ArtefactReader.read_artefacts
|
|
220
|
+
with patch("ara_cli.artefact_lister.ArtefactReader") as mock_reader:
|
|
221
|
+
mock_reader.read_artefacts.return_value = mock_artefacts
|
|
222
|
+
|
|
223
|
+
# Mock filter_artefacts method
|
|
224
|
+
artefact_lister.filter_artefacts = MagicMock(return_value=filtered_artefacts)
|
|
225
|
+
|
|
226
|
+
# Mock FileClassifier
|
|
227
|
+
with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier:
|
|
228
|
+
mock_classifier_instance = MagicMock()
|
|
229
|
+
mock_file_classifier.return_value = mock_classifier_instance
|
|
230
|
+
|
|
231
|
+
# Call the method under test
|
|
232
|
+
artefact_lister.list_files(
|
|
233
|
+
tags=tags,
|
|
234
|
+
navigate_to_target=navigate_to_target,
|
|
235
|
+
list_filter=list_filter,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Verify the correct calls were made
|
|
239
|
+
mock_reader.read_artefacts.assert_called_once_with(tags=tags)
|
|
240
|
+
artefact_lister.filter_artefacts.assert_called_once_with(
|
|
241
|
+
mock_artefacts, list_filter
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Check that the correct filtered list is passed to print_classified_files
|
|
245
|
+
# We need to check structure rather than exact equality because MagicMock instances are different
|
|
246
|
+
call_arg = mock_classifier_instance.print_classified_files.call_args[0][0]
|
|
247
|
+
|
|
248
|
+
# Verify structure matches expected filtered artefacts
|
|
249
|
+
assert set(call_arg.keys()) == set(expected_filtered.keys())
|
|
250
|
+
for key in expected_filtered:
|
|
251
|
+
assert len(call_arg[key]) == len(expected_filtered[key])
|
|
252
|
+
|
|
253
|
+
# Verify FileClassifier was initialized with the correct file system
|
|
254
|
+
mock_file_classifier.assert_called_once_with(artefact_lister.file_system)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@pytest.mark.parametrize(
|
|
258
|
+
"classifier, artefact_name, list_filter, classified_artefacts, artefact_info, matching_artefacts, child_artefacts, filtered_artefacts",
|
|
259
|
+
[
|
|
260
|
+
# Case 1: Artefact found, with children, no filter
|
|
261
|
+
(
|
|
262
|
+
"epic",
|
|
263
|
+
"Epic1",
|
|
264
|
+
None,
|
|
265
|
+
{"epic": [{"title": "Epic1"}, {"title": "Epic2"}]},
|
|
266
|
+
[{"title": "Epic1"}, {"title": "Epic2"}],
|
|
267
|
+
[{"title": "Epic1"}],
|
|
268
|
+
{"userstory": ["Story1", "Story2"]},
|
|
269
|
+
{"userstory": ["Story1", "Story2"]},
|
|
270
|
+
),
|
|
271
|
+
# Case 2: Artefact found, with children, with filter
|
|
272
|
+
(
|
|
273
|
+
"epic",
|
|
274
|
+
"Epic1",
|
|
275
|
+
ListFilter(include_tags=["tag1"]),
|
|
276
|
+
{"epic": [{"title": "Epic1"}, {"title": "Epic2"}]},
|
|
277
|
+
[{"title": "Epic1"}, {"title": "Epic2"}],
|
|
278
|
+
[{"title": "Epic1"}],
|
|
279
|
+
{"userstory": ["Story1", "Story2"]},
|
|
280
|
+
{"userstory": ["Story1"]}, # Filtered result
|
|
281
|
+
),
|
|
282
|
+
# Case 3: Artefact not found
|
|
283
|
+
(
|
|
284
|
+
"epic",
|
|
285
|
+
"NonExistentEpic",
|
|
286
|
+
None,
|
|
287
|
+
{"epic": [{"title": "Epic1"}, {"title": "Epic2"}]},
|
|
288
|
+
[{"title": "Epic1"}, {"title": "Epic2"}],
|
|
289
|
+
[],
|
|
290
|
+
{},
|
|
291
|
+
{},
|
|
292
|
+
),
|
|
293
|
+
],
|
|
294
|
+
)
|
|
295
|
+
def test_list_children(
|
|
296
|
+
artefact_lister,
|
|
297
|
+
classifier,
|
|
298
|
+
artefact_name,
|
|
299
|
+
list_filter,
|
|
300
|
+
classified_artefacts,
|
|
301
|
+
artefact_info,
|
|
302
|
+
matching_artefacts,
|
|
303
|
+
child_artefacts,
|
|
304
|
+
filtered_artefacts,
|
|
305
|
+
):
|
|
306
|
+
# Setup mocks
|
|
307
|
+
with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, patch(
|
|
308
|
+
"ara_cli.artefact_lister.suggest_close_name_matches"
|
|
309
|
+
) as mock_suggest, patch(
|
|
310
|
+
"ara_cli.artefact_lister.ArtefactReader"
|
|
311
|
+
) as mock_artefact_reader:
|
|
312
|
+
|
|
313
|
+
# Configure mock FileClassifier
|
|
314
|
+
mock_classifier_instance = MagicMock()
|
|
315
|
+
mock_file_classifier.return_value = mock_classifier_instance
|
|
316
|
+
mock_classifier_instance.classify_files_new.return_value = classified_artefacts
|
|
317
|
+
|
|
318
|
+
# Configure ArtefactReader mock
|
|
319
|
+
mock_artefact_reader.find_children.return_value = child_artefacts
|
|
320
|
+
|
|
321
|
+
# Mock filter_artefacts method
|
|
322
|
+
artefact_lister.filter_artefacts = MagicMock(return_value=filtered_artefacts)
|
|
323
|
+
|
|
324
|
+
# Call the method under test
|
|
325
|
+
artefact_lister.list_children(classifier, artefact_name, list_filter)
|
|
326
|
+
|
|
327
|
+
# Verify interactions
|
|
328
|
+
mock_classifier_instance.classify_files_new.assert_called_once()
|
|
329
|
+
|
|
330
|
+
# Check if suggestions were made for non-existent artefacts
|
|
331
|
+
if not matching_artefacts:
|
|
332
|
+
mock_suggest.assert_called_once_with(
|
|
333
|
+
artefact_name, [info["title"] for info in artefact_info]
|
|
334
|
+
)
|
|
335
|
+
else:
|
|
336
|
+
mock_suggest.assert_not_called()
|
|
337
|
+
|
|
338
|
+
# Verify ArtefactReader.find_children was called
|
|
120
339
|
mock_artefact_reader.find_children.assert_called_once_with(
|
|
121
|
-
artefact_name=artefact_name,
|
|
122
|
-
|
|
340
|
+
artefact_name=artefact_name, classifier=classifier
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Verify filter_artefacts was called with correct parameters
|
|
344
|
+
artefact_lister.filter_artefacts.assert_called_once_with(
|
|
345
|
+
child_artefacts, list_filter
|
|
123
346
|
)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
]
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
347
|
+
|
|
348
|
+
# Verify print_classified_files was called with filtered results
|
|
349
|
+
mock_classifier_instance.print_classified_files.assert_called_once_with(
|
|
350
|
+
filtered_artefacts
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@pytest.mark.parametrize(
|
|
355
|
+
"classifier, artefact_name, list_filter, classified_artefacts, artefact_info, matching_artefacts, value_chain_artefacts, filtered_artefacts",
|
|
356
|
+
[
|
|
357
|
+
# Case 1: Artefact found, with value chain, no filter
|
|
358
|
+
(
|
|
359
|
+
"epic",
|
|
360
|
+
"Epic1",
|
|
361
|
+
None,
|
|
362
|
+
{"epic": [{"title": "Epic1"}, {"title": "Epic2"}]},
|
|
363
|
+
[{"title": "Epic1"}, {"title": "Epic2"}],
|
|
364
|
+
[{"title": "Epic1"}],
|
|
365
|
+
{"epic": ["Epic1"], "userstory": ["Story1", "Story2"]},
|
|
366
|
+
{"epic": ["Epic1"], "userstory": ["Story1", "Story2"]},
|
|
367
|
+
),
|
|
368
|
+
# Case 2: Artefact found, with value chain, with filter
|
|
369
|
+
(
|
|
370
|
+
"epic",
|
|
371
|
+
"Epic1",
|
|
372
|
+
ListFilter(include_tags=["tag1"]),
|
|
373
|
+
{"epic": [{"title": "Epic1"}, {"title": "Epic2"}]},
|
|
374
|
+
[{"title": "Epic1"}, {"title": "Epic2"}],
|
|
375
|
+
[{"title": "Epic1"}],
|
|
376
|
+
{"epic": ["Epic1"], "userstory": ["Story1", "Story2"]},
|
|
377
|
+
{"epic": ["Epic1"], "userstory": ["Story1"]}, # Filtered result
|
|
378
|
+
),
|
|
379
|
+
# Case 3: Artefact not found
|
|
380
|
+
(
|
|
381
|
+
"epic",
|
|
382
|
+
"NonExistentEpic",
|
|
383
|
+
None,
|
|
384
|
+
{"epic": [{"title": "Epic1"}, {"title": "Epic2"}]},
|
|
385
|
+
[{"title": "Epic1"}, {"title": "Epic2"}],
|
|
386
|
+
[],
|
|
387
|
+
{"epic": []},
|
|
388
|
+
{"epic": []},
|
|
389
|
+
),
|
|
390
|
+
# Case 4: Empty artefact list
|
|
391
|
+
(
|
|
392
|
+
"epic",
|
|
393
|
+
"Epic1",
|
|
394
|
+
None,
|
|
395
|
+
{"epic": []},
|
|
396
|
+
[],
|
|
397
|
+
[],
|
|
398
|
+
{"epic": []},
|
|
399
|
+
{"epic": []},
|
|
400
|
+
),
|
|
401
|
+
],
|
|
402
|
+
)
|
|
403
|
+
def test_list_branch(
|
|
404
|
+
artefact_lister,
|
|
405
|
+
classifier,
|
|
406
|
+
artefact_name,
|
|
407
|
+
list_filter,
|
|
408
|
+
classified_artefacts,
|
|
409
|
+
artefact_info,
|
|
410
|
+
matching_artefacts,
|
|
411
|
+
value_chain_artefacts,
|
|
412
|
+
filtered_artefacts,
|
|
413
|
+
):
|
|
414
|
+
# Setup mocks
|
|
415
|
+
with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, patch(
|
|
416
|
+
"ara_cli.artefact_lister.suggest_close_name_matches"
|
|
417
|
+
) as mock_suggest, patch(
|
|
418
|
+
"ara_cli.artefact_lister.ArtefactReader"
|
|
419
|
+
) as mock_artefact_reader, patch(
|
|
420
|
+
"ara_cli.artefact_lister.os"
|
|
421
|
+
) as mock_os:
|
|
422
|
+
|
|
423
|
+
# Configure mock FileClassifier
|
|
424
|
+
mock_classifier_instance = MagicMock()
|
|
425
|
+
mock_file_classifier.return_value = mock_classifier_instance
|
|
426
|
+
mock_classifier_instance.classify_files_new.return_value = classified_artefacts
|
|
427
|
+
|
|
428
|
+
# Mock step_through_value_chain to modify the provided dictionary
|
|
429
|
+
def mock_step_through(artefact_name, classifier, artefacts_by_classifier):
|
|
430
|
+
# Replace the artefacts_by_classifier with our test data
|
|
431
|
+
for k, v in value_chain_artefacts.items():
|
|
432
|
+
artefacts_by_classifier[k] = v
|
|
433
|
+
|
|
434
|
+
mock_artefact_reader.step_through_value_chain.side_effect = mock_step_through
|
|
435
|
+
|
|
436
|
+
# Mock filter_artefacts method
|
|
437
|
+
artefact_lister.filter_artefacts = MagicMock(return_value=filtered_artefacts)
|
|
438
|
+
|
|
439
|
+
# Call the method under test
|
|
440
|
+
artefact_lister.list_branch(classifier, artefact_name, list_filter)
|
|
441
|
+
|
|
442
|
+
# Verify interactions
|
|
443
|
+
mock_file_classifier.assert_called_once_with(mock_os)
|
|
444
|
+
mock_classifier_instance.classify_files_new.assert_called_once()
|
|
445
|
+
|
|
446
|
+
# Check if suggestions were made for non-existent artefacts
|
|
447
|
+
if not matching_artefacts:
|
|
448
|
+
mock_suggest.assert_called_once_with(
|
|
449
|
+
artefact_name, [info["title"] for info in artefact_info]
|
|
450
|
+
)
|
|
451
|
+
else:
|
|
452
|
+
mock_suggest.assert_not_called()
|
|
453
|
+
|
|
454
|
+
# Verify ArtefactReader.step_through_value_chain was called with correct parameters
|
|
455
|
+
mock_artefact_reader.step_through_value_chain.assert_called_once()
|
|
456
|
+
call_args = mock_artefact_reader.step_through_value_chain.call_args[1]
|
|
457
|
+
assert call_args["artefact_name"] == artefact_name
|
|
458
|
+
assert call_args["classifier"] == classifier
|
|
459
|
+
assert classifier in call_args["artefacts_by_classifier"]
|
|
460
|
+
|
|
461
|
+
# Verify filter_artefacts was called with correct parameters
|
|
462
|
+
# The exact contents will have been modified by the mock_step_through function
|
|
463
|
+
artefact_lister.filter_artefacts.assert_called_once()
|
|
464
|
+
filter_args = artefact_lister.filter_artefacts.call_args[0]
|
|
465
|
+
assert filter_args[1] == list_filter
|
|
466
|
+
|
|
467
|
+
# Verify print_classified_files was called with filtered results
|
|
468
|
+
mock_classifier_instance.print_classified_files.assert_called_once_with(
|
|
469
|
+
filtered_artefacts
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
@pytest.mark.parametrize(
|
|
474
|
+
"classifier, artefact_name, list_filter, classified_artefacts, artefact_paths, matching_paths, data_dir_exists",
|
|
475
|
+
[
|
|
476
|
+
# Case 1: Artefact found, data directory exists
|
|
477
|
+
(
|
|
478
|
+
"epic",
|
|
479
|
+
"Epic1",
|
|
480
|
+
None,
|
|
481
|
+
{"epic": ["path/to/Epic1.epic", "path/to/Epic2.epic"]},
|
|
482
|
+
["path/to/Epic1.epic", "path/to/Epic2.epic"],
|
|
483
|
+
["path/to/Epic1.epic"],
|
|
484
|
+
True
|
|
485
|
+
),
|
|
486
|
+
# Case 2: Artefact found, data directory exists, with filter
|
|
487
|
+
(
|
|
488
|
+
"userstory",
|
|
489
|
+
"Story1",
|
|
490
|
+
ListFilter(include_tags=["tag1"]),
|
|
491
|
+
{"userstory": ["path/to/Story1.userstory", "path/to/Story2.userstory"]},
|
|
492
|
+
["path/to/Story1.userstory", "path/to/Story2.userstory"],
|
|
493
|
+
["path/to/Story1.userstory"],
|
|
494
|
+
True
|
|
495
|
+
),
|
|
496
|
+
# Case 3: Artefact found, data directory doesn't exist
|
|
497
|
+
(
|
|
498
|
+
"epic",
|
|
499
|
+
"Epic1",
|
|
500
|
+
None,
|
|
501
|
+
{"epic": ["path/to/Epic1.epic", "path/to/Epic2.epic"]},
|
|
502
|
+
["path/to/Epic1.epic", "path/to/Epic2.epic"],
|
|
503
|
+
["path/to/Epic1.epic"],
|
|
504
|
+
False
|
|
505
|
+
),
|
|
506
|
+
# Case 4: Artefact not found
|
|
507
|
+
(
|
|
508
|
+
"epic",
|
|
509
|
+
"NonExistentEpic",
|
|
510
|
+
None,
|
|
511
|
+
{"epic": ["path/to/Epic1.epic", "path/to/Epic2.epic"]},
|
|
512
|
+
["path/to/Epic1.epic", "path/to/Epic2.epic"],
|
|
513
|
+
[],
|
|
514
|
+
False
|
|
515
|
+
),
|
|
516
|
+
# Case 5: Empty artefact list
|
|
517
|
+
(
|
|
518
|
+
"epic",
|
|
519
|
+
"Epic1",
|
|
520
|
+
None,
|
|
521
|
+
{"epic": []},
|
|
522
|
+
[],
|
|
523
|
+
[],
|
|
524
|
+
False
|
|
525
|
+
),
|
|
526
|
+
],
|
|
527
|
+
)
|
|
528
|
+
def test_list_data(
|
|
529
|
+
artefact_lister,
|
|
530
|
+
classifier,
|
|
531
|
+
artefact_name,
|
|
532
|
+
list_filter,
|
|
533
|
+
classified_artefacts,
|
|
534
|
+
artefact_paths,
|
|
535
|
+
matching_paths,
|
|
536
|
+
data_dir_exists,
|
|
537
|
+
):
|
|
538
|
+
# Setup mocks
|
|
539
|
+
with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, \
|
|
540
|
+
patch("ara_cli.artefact_lister.suggest_close_name_matches") as mock_suggest, \
|
|
541
|
+
patch("ara_cli.artefact_lister.os") as mock_os, \
|
|
542
|
+
patch("ara_cli.artefact_lister.list_files_in_directory") as mock_list_files:
|
|
543
|
+
|
|
544
|
+
# Configure mock FileClassifier
|
|
545
|
+
mock_classifier_instance = MagicMock()
|
|
546
|
+
mock_file_classifier.return_value = mock_classifier_instance
|
|
547
|
+
mock_classifier_instance.classify_files.return_value = classified_artefacts
|
|
548
|
+
|
|
549
|
+
# Configure os path mocks
|
|
550
|
+
mock_os.path.basename.side_effect = lambda p: p.split('/')[-1]
|
|
551
|
+
mock_os.path.splitext.side_effect = lambda p: (p.rsplit('.', 1)[0], '.' + p.rsplit('.', 1)[1])
|
|
552
|
+
mock_os.path.exists.return_value = data_dir_exists
|
|
553
|
+
|
|
554
|
+
# Call the method under test
|
|
555
|
+
artefact_lister.list_data(classifier, artefact_name, list_filter)
|
|
556
|
+
|
|
557
|
+
# Verify interactions
|
|
558
|
+
mock_file_classifier.assert_called_once_with(mock_os)
|
|
559
|
+
mock_classifier_instance.classify_files.assert_called_once()
|
|
560
|
+
|
|
561
|
+
# Check if suggestions were made for non-existent artefacts
|
|
562
|
+
if not matching_paths:
|
|
563
|
+
mock_suggest.assert_called_once()
|
|
564
|
+
else:
|
|
565
|
+
# Verify path handling for found artefact
|
|
566
|
+
expected_data_dir = matching_paths[0].rsplit('.', 1)[0] + '.data'
|
|
567
|
+
mock_os.path.exists.assert_called_with(expected_data_dir)
|
|
568
|
+
|
|
569
|
+
# Verify list_files_in_directory was called if data dir exists
|
|
570
|
+
if data_dir_exists:
|
|
571
|
+
mock_list_files.assert_called_once_with(expected_data_dir, list_filter)
|
|
572
|
+
else:
|
|
573
|
+
mock_list_files.assert_not_called()
|