ara-cli 0.1.9.68__py3-none-any.whl → 0.1.9.70__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.

@@ -9,10 +9,11 @@ from ara_cli.artefact_autofix import (
9
9
  run_agent,
10
10
  write_corrected_artefact,
11
11
  construct_prompt,
12
- fix_title_mismatch
12
+ fix_title_mismatch,
13
13
  )
14
14
  from ara_cli.artefact_models.artefact_model import ArtefactType
15
15
 
16
+
16
17
  @pytest.fixture
17
18
  def mock_artefact_type():
18
19
  """Provides a mock for the ArtefactType enum member."""
@@ -20,6 +21,7 @@ def mock_artefact_type():
20
21
  mock_type.value = "feature"
21
22
  return mock_type
22
23
 
24
+
23
25
  @pytest.fixture
24
26
  def mock_artefact_class():
25
27
  """Provides a mock for the Artefact class."""
@@ -30,13 +32,22 @@ def mock_artefact_class():
30
32
  return mock_class
31
33
 
32
34
 
35
+ @pytest.fixture
36
+ def mock_classified_artefact_info():
37
+ """Provides a mock for the classified artefact info dictionary."""
38
+ return MagicMock()
39
+
40
+
33
41
  def test_read_report_file_success():
34
42
  """Tests successful reading of the report file."""
35
43
  mock_content = "# Artefact Check Report\n- `file.feature`: reason"
36
44
  with patch("builtins.open", mock_open(read_data=mock_content)) as m:
37
45
  content = read_report_file()
38
46
  assert content == mock_content
39
- m.assert_called_once_with("incompatible_artefacts_report.md", "r", encoding="utf-8")
47
+ m.assert_called_once_with(
48
+ "incompatible_artefacts_report.md", "r", encoding="utf-8"
49
+ )
50
+
40
51
 
41
52
  def test_read_report_file_not_found(capsys):
42
53
  with patch("builtins.open", side_effect=OSError("File not found")):
@@ -44,28 +55,36 @@ def test_read_report_file_not_found(capsys):
44
55
  assert content is None
45
56
  assert "Artefact scan results file not found" in capsys.readouterr().out
46
57
 
58
+
47
59
  def test_parse_report_with_issues():
48
- content = "# Artefact Check Report\n\n## feature\n- `path/to/file.feature`: A reason\n"
60
+ content = (
61
+ "# Artefact Check Report\n\n## feature\n- `path/to/file.feature`: A reason\n"
62
+ )
49
63
  expected = {"feature": [("path/to/file.feature", "A reason")]}
50
64
  assert parse_report(content) == expected
51
65
 
66
+
52
67
  def test_parse_report_no_issues():
53
68
  content = "# Artefact Check Report\n\nNo problems found.\n"
54
69
  assert parse_report(content) == {}
55
70
 
71
+
56
72
  def test_parse_report_invalid_format():
57
73
  assert parse_report("This is not a valid report") == {}
58
74
 
75
+
59
76
  def test_parse_report_invalid_line_format():
60
77
  content = "# Artefact Check Report\n\n## feature\n- an invalid line\n"
61
78
  assert parse_report(content) == {"feature": []}
62
79
 
80
+
63
81
  def test_read_artefact_success():
64
82
  mock_content = "Feature: My Feature"
65
83
  with patch("builtins.open", mock_open(read_data=mock_content)) as m:
66
84
  content = read_artefact("file.feature")
67
85
  assert content == mock_content
68
- m.assert_called_once_with("file.feature", 'r', encoding="utf-8")
86
+ m.assert_called_once_with("file.feature", "r", encoding="utf-8")
87
+
69
88
 
70
89
  def test_read_artefact_file_not_found(capsys):
71
90
  with patch("builtins.open", side_effect=FileNotFoundError):
@@ -73,6 +92,7 @@ def test_read_artefact_file_not_found(capsys):
73
92
  assert result is None
74
93
  assert "File not found: nonexistent.feature" in capsys.readouterr().out
75
94
 
95
+
76
96
  @patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping")
77
97
  def test_determine_artefact_type_and_class_no_class_found(mock_mapping, capsys):
78
98
  mock_mapping.get.return_value = None
@@ -83,134 +103,248 @@ def test_determine_artefact_type_and_class_no_class_found(mock_mapping, capsys):
83
103
  # The print statement inside the function is called before returning, so this check is valid.
84
104
  assert "No artefact class found for" in capsys.readouterr().out
85
105
 
106
+
86
107
  @patch("ara_cli.artefact_models.artefact_model.ArtefactType", side_effect=ValueError)
87
108
  def test_determine_artefact_type_and_class_invalid(mock_artefact_type_enum, capsys):
88
- artefact_type, artefact_class = determine_artefact_type_and_class("invalid_classifier")
109
+ artefact_type, artefact_class = determine_artefact_type_and_class(
110
+ "invalid_classifier"
111
+ )
89
112
  assert artefact_type is None
90
113
  assert artefact_class is None
91
114
  assert "Invalid classifier: invalid_classifier" in capsys.readouterr().out
92
115
 
116
+
93
117
  def test_write_corrected_artefact():
94
118
  with patch("builtins.open", mock_open()) as m:
95
119
  write_corrected_artefact("file.feature", "corrected content")
96
- m.assert_called_once_with("file.feature", 'w', encoding="utf-8")
120
+ m.assert_called_once_with("file.feature", "w", encoding="utf-8")
97
121
  m().write.assert_called_once_with("corrected content")
98
122
 
123
+
99
124
  def test_construct_prompt_for_task():
100
125
  prompt = construct_prompt(ArtefactType.task, "some reason", "file.task", "text")
101
- assert "For task artefacts, if the action items looks like template or empty" in prompt
126
+ assert (
127
+ "For task artefacts, if the action items looks like template or empty" in prompt
128
+ )
129
+
102
130
 
103
131
  @patch("ara_cli.artefact_autofix.run_agent")
104
- @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class", return_value=(None, None))
132
+ @patch(
133
+ "ara_cli.artefact_autofix.determine_artefact_type_and_class",
134
+ return_value=(None, None),
135
+ )
105
136
  @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
106
- def test_apply_autofix_exits_when_classifier_is_invalid(mock_read, mock_determine, mock_run_agent):
137
+ def test_apply_autofix_exits_when_classifier_is_invalid(
138
+ mock_read, mock_determine, mock_run_agent, mock_classified_artefact_info
139
+ ):
107
140
  """Tests that apply_autofix exits early if the classifier is invalid."""
108
- result = apply_autofix("file.feature", "invalid", "reason", deterministic=True, non_deterministic=True)
141
+ result = apply_autofix(
142
+ "file.feature",
143
+ "invalid",
144
+ "reason",
145
+ deterministic=True,
146
+ non_deterministic=True,
147
+ classified_artefact_info=mock_classified_artefact_info,
148
+ )
109
149
  assert result is False
110
150
  mock_read.assert_called_once_with("file.feature")
111
151
  mock_determine.assert_called_once_with("invalid")
112
152
  mock_run_agent.assert_not_called()
113
153
 
154
+
114
155
  @patch("ara_cli.artefact_autofix.run_agent")
115
156
  @patch("ara_cli.artefact_autofix.write_corrected_artefact")
116
157
  @patch("ara_cli.artefact_autofix.fix_title_mismatch", return_value="fixed text")
117
158
  @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
118
159
  @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
119
- def test_apply_autofix_for_title_mismatch_with_deterministic_flag(mock_read, mock_determine, mock_fix_title, mock_write, mock_run_agent, mock_artefact_type, mock_artefact_class):
160
+ def test_apply_autofix_for_title_mismatch_with_deterministic_flag(
161
+ mock_read,
162
+ mock_determine,
163
+ mock_fix_title,
164
+ mock_write,
165
+ mock_run_agent,
166
+ mock_artefact_type,
167
+ mock_artefact_class,
168
+ mock_classified_artefact_info,
169
+ ):
120
170
  """Tests that a deterministic fix is applied when the flag is True."""
121
171
  mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
122
172
  reason = "Filename-Title Mismatch: some details"
123
-
124
- result = apply_autofix("file.feature", "feature", reason, deterministic=True, non_deterministic=False)
173
+
174
+ result = apply_autofix(
175
+ "file.feature",
176
+ "feature",
177
+ reason,
178
+ deterministic=True,
179
+ non_deterministic=False,
180
+ classified_artefact_info=mock_classified_artefact_info,
181
+ )
125
182
 
126
183
  assert result is True
127
- mock_fix_title.assert_called_once_with("file.feature", "original text", mock_artefact_class)
184
+ mock_fix_title.assert_called_once_with(
185
+ file_path="file.feature",
186
+ artefact_text="original text",
187
+ artefact_class=mock_artefact_class,
188
+ classified_artefact_info=mock_classified_artefact_info,
189
+ )
128
190
  mock_write.assert_called_once_with("file.feature", "fixed text")
129
191
  mock_run_agent.assert_not_called()
130
192
 
193
+
131
194
  @patch("ara_cli.artefact_autofix.run_agent")
132
195
  @patch("ara_cli.artefact_autofix.write_corrected_artefact")
133
196
  @patch("ara_cli.artefact_autofix.fix_title_mismatch")
134
197
  @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
135
198
  @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
136
- def test_apply_autofix_skips_title_mismatch_without_deterministic_flag(mock_read, mock_determine, mock_fix_title, mock_write, mock_run_agent, mock_artefact_type, mock_artefact_class):
199
+ def test_apply_autofix_skips_title_mismatch_without_deterministic_flag(
200
+ mock_read,
201
+ mock_determine,
202
+ mock_fix_title,
203
+ mock_write,
204
+ mock_run_agent,
205
+ mock_artefact_type,
206
+ mock_artefact_class,
207
+ mock_classified_artefact_info,
208
+ ):
137
209
  """Tests that a deterministic fix is skipped when the flag is False."""
138
210
  mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
139
211
  reason = "Filename-Title Mismatch: some details"
140
-
141
- result = apply_autofix("file.feature", "feature", reason, deterministic=False, non_deterministic=True)
212
+
213
+ result = apply_autofix(
214
+ "file.feature",
215
+ "feature",
216
+ reason,
217
+ deterministic=False,
218
+ non_deterministic=True,
219
+ classified_artefact_info=mock_classified_artefact_info,
220
+ )
142
221
 
143
222
  assert result is False
144
223
  mock_fix_title.assert_not_called()
145
224
  mock_write.assert_not_called()
146
225
  mock_run_agent.assert_not_called()
147
226
 
227
+
148
228
  @patch("ara_cli.artefact_autofix.write_corrected_artefact")
149
229
  @patch("ara_cli.artefact_autofix.run_agent")
150
230
  @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
151
231
  @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
152
- def test_apply_autofix_for_llm_fix_with_non_deterministic_flag(mock_read, mock_determine, mock_run_agent, mock_write, mock_artefact_type, mock_artefact_class):
232
+ def test_apply_autofix_for_llm_fix_with_non_deterministic_flag(
233
+ mock_read,
234
+ mock_determine,
235
+ mock_run_agent,
236
+ mock_write,
237
+ mock_artefact_type,
238
+ mock_artefact_class,
239
+ mock_classified_artefact_info,
240
+ ):
153
241
  """Tests that an LLM fix is applied when the non-deterministic flag is True."""
154
242
  mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
155
243
  mock_run_agent.return_value = mock_artefact_class
156
244
  reason = "Pydantic validation error"
157
245
 
158
- result = apply_autofix("file.feature", "feature", reason, deterministic=False, non_deterministic=True)
246
+ result = apply_autofix(
247
+ "file.feature",
248
+ "feature",
249
+ reason,
250
+ deterministic=False,
251
+ non_deterministic=True,
252
+ classified_artefact_info=mock_classified_artefact_info,
253
+ )
159
254
 
160
255
  assert result is True
161
256
  mock_run_agent.assert_called_once()
162
257
  mock_write.assert_called_once_with("file.feature", "llm corrected content")
163
258
 
259
+
164
260
  @patch("ara_cli.artefact_autofix.write_corrected_artefact")
165
261
  @patch("ara_cli.artefact_autofix.run_agent")
166
262
  @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
167
263
  @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
168
- def test_apply_autofix_skips_llm_fix_without_non_deterministic_flag(mock_read, mock_determine, mock_run_agent, mock_write, mock_artefact_type, mock_artefact_class):
264
+ def test_apply_autofix_skips_llm_fix_without_non_deterministic_flag(
265
+ mock_read,
266
+ mock_determine,
267
+ mock_run_agent,
268
+ mock_write,
269
+ mock_artefact_type,
270
+ mock_artefact_class,
271
+ mock_classified_artefact_info,
272
+ ):
169
273
  """Tests that an LLM fix is skipped when the non-deterministic flag is False."""
170
274
  mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
171
275
  reason = "Pydantic validation error"
172
276
 
173
- result = apply_autofix("file.feature", "feature", reason, deterministic=True, non_deterministic=False)
277
+ result = apply_autofix(
278
+ "file.feature",
279
+ "feature",
280
+ reason,
281
+ deterministic=True,
282
+ non_deterministic=False,
283
+ classified_artefact_info=mock_classified_artefact_info,
284
+ )
174
285
 
175
286
  assert result is False
176
287
  mock_run_agent.assert_not_called()
177
288
  mock_write.assert_not_called()
178
289
 
290
+
179
291
  @patch("ara_cli.artefact_autofix.run_agent", side_effect=Exception("LLM failed"))
180
292
  @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
181
293
  @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
182
- def test_apply_autofix_llm_exception(mock_read, mock_determine, mock_run_agent, capsys, mock_artefact_type, mock_artefact_class):
294
+ def test_apply_autofix_llm_exception(
295
+ mock_read,
296
+ mock_determine,
297
+ mock_run_agent,
298
+ capsys,
299
+ mock_artefact_type,
300
+ mock_artefact_class,
301
+ mock_classified_artefact_info,
302
+ ):
183
303
  """Tests that an exception during an LLM fix is handled gracefully."""
184
304
  mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
185
305
  reason = "Pydantic validation error"
186
306
 
187
- result = apply_autofix("file.feature", "feature", reason, deterministic=False, non_deterministic=True)
307
+ result = apply_autofix(
308
+ "file.feature",
309
+ "feature",
310
+ reason,
311
+ deterministic=False,
312
+ non_deterministic=True,
313
+ classified_artefact_info=mock_classified_artefact_info,
314
+ )
188
315
 
189
316
  assert result is False
190
- assert "LLM agent failed to fix artefact at file.feature: LLM failed" in capsys.readouterr().out
317
+ assert (
318
+ "LLM agent failed to fix artefact at file.feature: LLM failed"
319
+ in capsys.readouterr().out
320
+ )
321
+
191
322
 
192
323
  # === Other Tests ===
193
324
 
325
+
194
326
  def test_fix_title_mismatch_success(mock_artefact_class):
195
327
  artefact_text = "Feature: wrong title\nSome other content"
196
328
  file_path = "path/to/correct_title.feature"
197
-
329
+
198
330
  expected_text = "Feature: correct title\nSome other content"
199
-
331
+
200
332
  result = fix_title_mismatch(file_path, artefact_text, mock_artefact_class)
201
-
333
+
202
334
  assert result == expected_text
203
335
  mock_artefact_class._title_prefix.assert_called_once()
204
336
 
337
+
205
338
  def test_fix_title_mismatch_prefix_not_found(capsys, mock_artefact_class):
206
339
  artefact_text = "No title prefix here"
207
340
  file_path = "path/to/correct_title.feature"
208
341
 
209
342
  result = fix_title_mismatch(file_path, artefact_text, mock_artefact_class)
210
-
211
- assert result == artefact_text # Should return original text
343
+
344
+ assert result == artefact_text # Should return original text
212
345
  assert "Warning: Title prefix 'Feature:' not found" in capsys.readouterr().out
213
346
 
347
+
214
348
  @patch("pydantic_ai.Agent")
215
349
  def test_run_agent_exception_handling(mock_agent_class):
216
350
  mock_agent_instance = mock_agent_class.return_value
@@ -8,6 +8,8 @@ def test_check_file_valid():
8
8
  """Tests the happy path where the file is valid and the title matches."""
9
9
  mock_artefact_instance = MagicMock()
10
10
  mock_artefact_instance.title = "dummy_path"
11
+ # Mock contribution to be None to avoid contribution reference check
12
+ mock_artefact_instance.contribution = None
11
13
 
12
14
  mock_artefact_class = MagicMock()
13
15
  mock_artefact_class.deserialize.return_value = mock_artefact_instance
@@ -15,8 +17,8 @@ def test_check_file_valid():
15
17
  with patch("builtins.open", mock_open(read_data="valid content")):
16
18
  is_valid, reason = check_file("dummy_path.feature", mock_artefact_class)
17
19
 
18
- assert is_valid is True
19
- assert reason is None
20
+ assert reason is None, f"Reason for invalid found, expected none to be found. The reason found: {reason}"
21
+ assert is_valid is True, "File detected as invalid, expected to be valid"
20
22
 
21
23
 
22
24
  def test_check_file_title_mismatch():
@@ -80,31 +82,35 @@ def test_check_file_unexpected_error():
80
82
 
81
83
 
82
84
  def test_find_invalid_files():
85
+ """Tests finding invalid files with proper mocking of check_file."""
83
86
  mock_artefact_class = MagicMock()
84
- with patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping", {"test_classifier": mock_artefact_class}):
85
- artefact_files = {
86
- "test_classifier": [
87
- {"file_path": "file1.txt"}, # Should be checked
88
- {"file_path": "file2.txt"}, # Should be checked
89
- {"file_path": "templates/file3.txt"}, # Should be skipped
90
- {"file_path": "some/path/file.data"} # Should be skipped
91
- ]
92
- }
93
-
87
+ classified_info = {
88
+ "test_classifier": [
89
+ {"file_path": "file1.txt"}, # Should be checked
90
+ {"file_path": "file2.txt"}, # Should be checked
91
+ {"file_path": "templates/file3.txt"}, # Should be skipped
92
+ {"file_path": "some/path/file.data"} # Should be skipped
93
+ ]
94
+ }
95
+
96
+ with patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping",
97
+ {"test_classifier": mock_artefact_class}):
94
98
  with patch("ara_cli.artefact_scan.check_file") as mock_check_file:
95
99
  mock_check_file.side_effect = [
96
100
  (True, None), # for file1.txt
97
101
  (False, "Invalid content") # for file2.txt
98
102
  ]
99
103
 
100
- invalid_files = find_invalid_files(
101
- artefact_files, "test_classifier")
104
+ invalid_files = find_invalid_files(classified_info, "test_classifier")
105
+
102
106
  assert len(invalid_files) == 1
103
107
  assert invalid_files[0] == ("file2.txt", "Invalid content")
104
108
  assert mock_check_file.call_count == 2
109
+
110
+ # Check that check_file was called with the correct parameters
105
111
  mock_check_file.assert_has_calls([
106
- call("file1.txt", mock_artefact_class),
107
- call("file2.txt", mock_artefact_class)
112
+ call("file1.txt", mock_artefact_class, classified_info),
113
+ call("file2.txt", mock_artefact_class, classified_info)
108
114
  ], any_order=False)
109
115
 
110
116
 
@@ -153,4 +159,31 @@ def test_show_results_with_issues(capsys):
153
159
  call("- `file3.txt`: reason3\n"),
154
160
  call("\n")
155
161
  ]
156
- handle.write.assert_has_calls(expected_writes, any_order=False)
162
+ handle.write.assert_has_calls(expected_writes, any_order=False)
163
+
164
+
165
+ def test_check_file_with_invalid_contribution():
166
+ """Tests file with invalid contribution reference."""
167
+ mock_artefact_instance = MagicMock()
168
+ mock_artefact_instance.title = "dummy_path"
169
+
170
+ # Set up invalid contribution
171
+ mock_contribution = MagicMock()
172
+ mock_contribution.classifier = "test_classifier"
173
+ mock_contribution.artefact_name = "non_existing_artefact"
174
+ mock_artefact_instance.contribution = mock_contribution
175
+
176
+ mock_artefact_class = MagicMock()
177
+ mock_artefact_class.deserialize.return_value = mock_artefact_instance
178
+
179
+ # Mock classified_artefact_info
180
+ classified_info = {"test_classifier": [{"name": "existing_artefact"}]}
181
+
182
+ # Mock extract_artefact_names_of_classifier to return a list without the referenced artefact
183
+ with patch("builtins.open", mock_open(read_data="valid content")):
184
+ with patch("ara_cli.artefact_fuzzy_search.extract_artefact_names_of_classifier",
185
+ return_value=["existing_artefact"]):
186
+ is_valid, reason = check_file("dummy_path.feature", mock_artefact_class, classified_info)
187
+
188
+ assert is_valid is False
189
+ assert "Invalid Contribution Reference" in reason
@@ -241,7 +241,7 @@ def test_find_closest_artefact_name_match(mock_file_system):
241
241
  'file1', 'py') == 'file1'
242
242
 
243
243
  # Fuzzy match
244
- with patch('ara_cli.file_classifier.find_closest_name_match', return_value='file2') as mock_fuzzy:
244
+ with patch('ara_cli.file_classifier.find_closest_name_matches', return_value='file2') as mock_fuzzy:
245
245
  assert classifier.find_closest_artefact_name_match(
246
246
  'file3', 'py') == 'file2'
247
247
  mock_fuzzy.assert_called_once_with('file3', ['file1', 'file2'])