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.

@@ -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("artefact_content, artefact_path", [
12
- ("content1", "path1"),
13
- ("content2", "path2"),
14
- ("content3", "path3"),
15
- ])
16
- def test_artefact_content_retrieval(artefact_content, artefact_path):
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.content = artefact_content
19
- artefact_mock.file_path = artefact_path
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("artefact_content, artefact_path", [
26
- ("content1", "path1"),
27
- ("content2", "path2"),
28
- ("content3", "path3"),
29
- ])
30
- def test_artefact_path_retrieval(artefact_content, artefact_path):
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.content = artefact_content
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 == artefact_path
37
-
38
-
39
- @pytest.mark.parametrize("tags, navigate_to_target", [
40
- (None, False),
41
- (["tag1", "tag2"], False),
42
- (["tag1"], True),
43
- ([], True)
44
- ])
45
- @patch('ara_cli.artefact_lister.FileClassifier')
46
- @patch('ara_cli.artefact_lister.DirectoryNavigator')
47
- def test_list_files(mock_directory_navigator, mock_file_classifier, artefact_lister, tags, navigate_to_target):
48
- mock_navigator_instance = mock_directory_navigator.return_value
49
- mock_navigator_instance.navigate_to_target = MagicMock()
50
-
51
- mock_classifier_instance = mock_file_classifier.return_value
52
- mock_classifier_instance.classify_files = MagicMock(return_value={'mocked_files': []})
53
- mock_classifier_instance.print_classified_files = MagicMock()
54
-
55
- artefact_lister.list_files(tags=tags, navigate_to_target=navigate_to_target)
56
-
57
- if navigate_to_target:
58
- mock_navigator_instance.navigate_to_target.assert_called_once()
59
- else:
60
- mock_navigator_instance.navigate_to_target.assert_not_called()
61
-
62
- mock_classifier_instance.classify_files.assert_called_once_with(tags=tags)
63
-
64
- mock_classifier_instance.print_classified_files.assert_called_once_with({'mocked_files': []})
65
-
66
-
67
- @pytest.mark.parametrize("classifier, artefact_name, classified_artefacts, expected_suggestions", [
68
- ("classifier1", "missing_artefact", {"classifier1": []}, []),
69
- ("classifier2", "artefact1", {"classifier2": [MagicMock(file_name="artefact1")]}, None)
70
- ])
71
- @patch('ara_cli.artefact_lister.FileClassifier')
72
- @patch('ara_cli.artefact_lister.suggest_close_name_matches')
73
- @patch('ara_cli.artefact_lister.ArtefactReader')
74
- def test_list_branch_no_match(mock_artefact_reader, mock_suggest, mock_file_classifier, classifier, artefact_name, classified_artefacts, expected_suggestions, artefact_lister):
75
- mock_classifier_instance = mock_file_classifier.return_value
76
- mock_classifier_instance.classify_files = MagicMock(return_value=classified_artefacts)
77
- mock_classifier_instance.print_classified_files = MagicMock()
78
-
79
- mock_artefact_reader.step_through_value_chain = MagicMock()
80
-
81
- artefact_lister.list_branch(classifier, artefact_name)
82
-
83
- if expected_suggestions is not None:
84
- mock_suggest.assert_called_once_with(artefact_name, [])
85
- mock_artefact_reader.step_through_value_chain.assert_not_called()
86
- mock_classifier_instance.print_classified_files.assert_not_called()
87
- else:
88
- mock_suggest.assert_not_called()
89
- mock_artefact_reader.step_through_value_chain.assert_called_once_with(
90
- artefact_name=artefact_name,
91
- classifier=classifier,
92
- artefacts_by_classifier={classifier: []}
93
- )
94
- mock_classifier_instance.print_classified_files.assert_called_once()
95
-
96
-
97
- @pytest.mark.parametrize("classifier, artefact_name, classified_artefacts, expected_suggestions", [
98
- ("classifier1", "missing_artefact", {"classifier1": []}, []),
99
- ("classifier2", "artefact1", {"classifier2": [MagicMock(file_name="artefact1")]}, None)
100
- ])
101
- @patch('ara_cli.artefact_lister.FileClassifier')
102
- @patch('ara_cli.artefact_lister.suggest_close_name_matches')
103
- @patch('ara_cli.artefact_lister.ArtefactReader')
104
- def test_list_children_no_match(mock_artefact_reader, mock_suggest, mock_file_classifier, classifier, artefact_name, classified_artefacts, expected_suggestions, artefact_lister):
105
- # Mock the FileClassifier and its methods
106
- mock_classifier_instance = mock_file_classifier.return_value
107
- mock_classifier_instance.classify_files = MagicMock(return_value=classified_artefacts)
108
- mock_classifier_instance.print_classified_files = MagicMock()
109
-
110
- mock_artefact_reader.find_children = MagicMock()
111
-
112
- artefact_lister.list_children(classifier, artefact_name)
113
-
114
- if expected_suggestions is not None:
115
- mock_suggest.assert_called_once_with(artefact_name, [])
116
- mock_artefact_reader.find_children.assert_not_called()
117
- mock_classifier_instance.print_classified_files.assert_not_called()
118
- else:
119
- mock_suggest.assert_not_called()
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
- classifier=classifier
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
- mock_classifier_instance.print_classified_files.assert_called_once()
125
-
126
- @pytest.mark.parametrize("classifier, artefact_name, child_artefacts, filtered_child_artefacts", [
127
- ("classifier2", "artefact1", [MagicMock(), MagicMock()], [MagicMock()]),
128
- ])
129
- @patch('ara_cli.artefact_lister.FileClassifier')
130
- @patch('ara_cli.artefact_lister.filter_list')
131
- @patch('ara_cli.artefact_lister.ArtefactReader')
132
- def test_list_children_with_children(mock_artefact_reader, mock_filter_list, mock_file_classifier, classifier, artefact_name, child_artefacts, filtered_child_artefacts, artefact_lister):
133
- mock_classifier_instance = mock_file_classifier.return_value
134
- mock_classifier_instance.classify_files = MagicMock(return_value={classifier: [MagicMock(file_name=artefact_name)]})
135
- mock_classifier_instance.print_classified_files = MagicMock()
136
-
137
- mock_artefact_reader.find_children = MagicMock(return_value=child_artefacts)
138
-
139
- mock_filter_list.return_value = filtered_child_artefacts
140
-
141
- artefact_lister.list_children(classifier, artefact_name)
142
-
143
- mock_artefact_reader.find_children.assert_called_once_with(
144
- artefact_name=artefact_name,
145
- classifier=classifier
146
- )
147
-
148
- mock_filter_list.assert_called_once_with(
149
- list_to_filter=child_artefacts,
150
- list_filter=None,
151
- content_retrieval_strategy=ArtefactLister.artefact_content_retrieval,
152
- file_path_retrieval=ArtefactLister.artefact_path_retrieval,
153
- tag_retrieval=ArtefactLister.artefact_tags_retrieval
154
- )
155
-
156
- mock_classifier_instance.print_classified_files.assert_called_once_with(
157
- files_by_classifier=filtered_child_artefacts
158
- )
159
-
160
-
161
- @pytest.mark.parametrize("classifier, artefact_name, classified_artefacts, expected_suggestions", [
162
- ("classifier1", "missing_artefact", {"classifier1": []}, True),
163
- ])
164
- @patch('ara_cli.artefact_lister.FileClassifier')
165
- @patch('ara_cli.artefact_lister.suggest_close_name_matches')
166
- @patch('ara_cli.artefact_lister.ArtefactReader')
167
- def test_list_data_no_match(mock_artefact_reader, mock_suggest, mock_file_classifier, classifier, artefact_name, classified_artefacts, expected_suggestions, artefact_lister):
168
- mock_classifier_instance = mock_file_classifier.return_value
169
- mock_classifier_instance.classify_files = MagicMock(return_value=classified_artefacts)
170
-
171
- artefact_lister.list_data(classifier, artefact_name)
172
-
173
- if expected_suggestions:
174
- mock_suggest.assert_called_once_with(artefact_name, [])
175
- else:
176
- mock_suggest.assert_not_called()
177
-
178
- @pytest.mark.parametrize("classifier, artefact_name, content, file_path, file_exists", [
179
- ("classifier2", "artefact1", "some content", "path/to/artefact", True),
180
- ("classifier2", "artefact1", "some content", "path/to/artefact", False)
181
- ])
182
- @patch('ara_cli.artefact_lister.FileClassifier')
183
- @patch('ara_cli.artefact_lister.ArtefactReader')
184
- @patch('ara_cli.artefact_lister.Artefact')
185
- @patch('ara_cli.artefact_lister.os.path')
186
- @patch('ara_cli.artefact_lister.list_files_in_directory')
187
- def test_list_data_with_match(mock_list_files, mock_os_path, mock_artefact, mock_artefact_reader, mock_file_classifier, classifier, artefact_name, content, file_path, file_exists, artefact_lister):
188
- mock_classifier_instance = mock_file_classifier.return_value
189
- mock_classifier_instance.classify_files = MagicMock(return_value={classifier: [MagicMock(file_name=artefact_name)]})
190
-
191
- mock_artefact_reader.read_artefact = MagicMock(return_value=(content, file_path))
192
-
193
- artefact_instance = MagicMock(name=artefact_name)
194
- mock_artefact.from_content = MagicMock(return_value=artefact_instance)
195
-
196
- mock_os_path.splitext = MagicMock(return_value=(file_path, '.ext'))
197
- mock_os_path.exists = MagicMock(return_value=file_exists)
198
-
199
- artefact_lister.list_data(classifier, artefact_name)
200
-
201
- if file_exists:
202
- mock_list_files.assert_called_once_with(file_path + '.data', None)
203
- else:
204
- mock_list_files.assert_not_called()
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()