ara-cli 0.1.9.73__py3-none-any.whl → 0.1.9.75__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/ara_command_action.py +15 -15
- ara_cli/ara_command_parser.py +2 -1
- ara_cli/ara_config.py +181 -73
- ara_cli/artefact_autofix.py +130 -68
- ara_cli/artefact_creator.py +1 -1
- ara_cli/artefact_models/artefact_model.py +26 -7
- ara_cli/artefact_models/artefact_templates.py +47 -31
- ara_cli/artefact_models/businessgoal_artefact_model.py +23 -25
- ara_cli/artefact_models/epic_artefact_model.py +23 -24
- ara_cli/artefact_models/feature_artefact_model.py +76 -46
- ara_cli/artefact_models/keyfeature_artefact_model.py +21 -24
- ara_cli/artefact_models/task_artefact_model.py +73 -13
- ara_cli/artefact_models/userstory_artefact_model.py +22 -24
- ara_cli/artefact_models/vision_artefact_model.py +23 -42
- ara_cli/artefact_scan.py +55 -17
- ara_cli/chat.py +23 -5
- ara_cli/prompt_handler.py +4 -4
- ara_cli/tag_extractor.py +43 -28
- ara_cli/template_manager.py +3 -8
- ara_cli/version.py +1 -1
- {ara_cli-0.1.9.73.dist-info → ara_cli-0.1.9.75.dist-info}/METADATA +1 -1
- {ara_cli-0.1.9.73.dist-info → ara_cli-0.1.9.75.dist-info}/RECORD +29 -39
- tests/test_ara_config.py +420 -36
- tests/test_artefact_autofix.py +289 -25
- tests/test_artefact_scan.py +296 -35
- tests/test_chat.py +35 -15
- ara_cli/templates/template.businessgoal +0 -10
- ara_cli/templates/template.capability +0 -10
- ara_cli/templates/template.epic +0 -15
- ara_cli/templates/template.example +0 -6
- ara_cli/templates/template.feature +0 -26
- ara_cli/templates/template.issue +0 -14
- ara_cli/templates/template.keyfeature +0 -15
- ara_cli/templates/template.task +0 -6
- ara_cli/templates/template.userstory +0 -17
- ara_cli/templates/template.vision +0 -14
- {ara_cli-0.1.9.73.dist-info → ara_cli-0.1.9.75.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.73.dist-info → ara_cli-0.1.9.75.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.73.dist-info → ara_cli-0.1.9.75.dist-info}/top_level.txt +0 -0
tests/test_artefact_autofix.py
CHANGED
|
@@ -10,8 +10,13 @@ from ara_cli.artefact_autofix import (
|
|
|
10
10
|
write_corrected_artefact,
|
|
11
11
|
construct_prompt,
|
|
12
12
|
fix_title_mismatch,
|
|
13
|
+
ask_for_correct_contribution,
|
|
14
|
+
ask_for_contribution_choice,
|
|
15
|
+
_has_valid_contribution,
|
|
16
|
+
set_closest_contribution,
|
|
17
|
+
fix_contribution,
|
|
13
18
|
)
|
|
14
|
-
from ara_cli.artefact_models.artefact_model import ArtefactType
|
|
19
|
+
from ara_cli.artefact_models.artefact_model import Artefact, ArtefactType, Contribution
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
@pytest.fixture
|
|
@@ -38,6 +43,23 @@ def mock_classified_artefact_info():
|
|
|
38
43
|
return MagicMock()
|
|
39
44
|
|
|
40
45
|
|
|
46
|
+
@pytest.fixture
|
|
47
|
+
def mock_artefact_with_contribution():
|
|
48
|
+
"""Provides a mock Artefact with a mock Contribution."""
|
|
49
|
+
mock_contribution = MagicMock(spec=Contribution)
|
|
50
|
+
mock_contribution.artefact_name = "some_artefact"
|
|
51
|
+
mock_contribution.classifier = "feature"
|
|
52
|
+
mock_contribution.rule = "some rule"
|
|
53
|
+
|
|
54
|
+
mock_artefact = MagicMock(spec=Artefact)
|
|
55
|
+
mock_artefact.contribution = mock_contribution
|
|
56
|
+
mock_artefact.title = "my_test_artefact"
|
|
57
|
+
mock_artefact._artefact_type.return_value.value = "requirement"
|
|
58
|
+
mock_artefact.serialize.return_value = "serialized artefact text"
|
|
59
|
+
|
|
60
|
+
return mock_artefact
|
|
61
|
+
|
|
62
|
+
|
|
41
63
|
def test_read_report_file_success():
|
|
42
64
|
"""Tests successful reading of the report file."""
|
|
43
65
|
mock_content = "# Artefact Check Report\n- `file.feature`: reason"
|
|
@@ -96,11 +118,9 @@ def test_read_artefact_file_not_found(capsys):
|
|
|
96
118
|
@patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping")
|
|
97
119
|
def test_determine_artefact_type_and_class_no_class_found(mock_mapping, capsys):
|
|
98
120
|
mock_mapping.get.return_value = None
|
|
99
|
-
# The function returns (None, None) if the class is not in the mapping.
|
|
100
121
|
artefact_type, artefact_class = determine_artefact_type_and_class("feature")
|
|
101
122
|
assert artefact_type is None
|
|
102
123
|
assert artefact_class is None
|
|
103
|
-
# The print statement inside the function is called before returning, so this check is valid.
|
|
104
124
|
assert "No artefact class found for" in capsys.readouterr().out
|
|
105
125
|
|
|
106
126
|
|
|
@@ -124,7 +144,8 @@ def test_write_corrected_artefact():
|
|
|
124
144
|
def test_construct_prompt_for_task():
|
|
125
145
|
prompt = construct_prompt(ArtefactType.task, "some reason", "file.task", "text")
|
|
126
146
|
assert (
|
|
127
|
-
"For task artefacts, if the action items looks like template or empty"
|
|
147
|
+
"For task artefacts, if the action items looks like template or empty"
|
|
148
|
+
in prompt
|
|
128
149
|
)
|
|
129
150
|
|
|
130
151
|
|
|
@@ -133,28 +154,30 @@ def test_construct_prompt_for_task():
|
|
|
133
154
|
"ara_cli.artefact_autofix.determine_artefact_type_and_class",
|
|
134
155
|
return_value=(None, None),
|
|
135
156
|
)
|
|
136
|
-
@patch("ara_cli.artefact_autofix.read_artefact"
|
|
157
|
+
@patch("ara_cli.artefact_autofix.read_artefact")
|
|
137
158
|
def test_apply_autofix_exits_when_classifier_is_invalid(
|
|
138
159
|
mock_read, mock_determine, mock_run_agent, mock_classified_artefact_info
|
|
139
160
|
):
|
|
140
161
|
"""Tests that apply_autofix exits early if the classifier is invalid."""
|
|
141
162
|
result = apply_autofix(
|
|
142
|
-
"file.feature",
|
|
143
|
-
"invalid",
|
|
144
|
-
"reason",
|
|
163
|
+
file_path="file.feature",
|
|
164
|
+
classifier="invalid",
|
|
165
|
+
reason="reason",
|
|
145
166
|
deterministic=True,
|
|
146
167
|
non_deterministic=True,
|
|
147
168
|
classified_artefact_info=mock_classified_artefact_info,
|
|
148
169
|
)
|
|
149
170
|
assert result is False
|
|
150
|
-
mock_read.assert_called_once_with("file.feature")
|
|
151
171
|
mock_determine.assert_called_once_with("invalid")
|
|
172
|
+
mock_read.assert_not_called()
|
|
152
173
|
mock_run_agent.assert_not_called()
|
|
153
174
|
|
|
154
175
|
|
|
176
|
+
@patch("ara_cli.artefact_autofix.FileClassifier")
|
|
177
|
+
@patch("ara_cli.artefact_autofix.check_file")
|
|
155
178
|
@patch("ara_cli.artefact_autofix.run_agent")
|
|
156
179
|
@patch("ara_cli.artefact_autofix.write_corrected_artefact")
|
|
157
|
-
@patch("ara_cli.artefact_autofix.fix_title_mismatch"
|
|
180
|
+
@patch("ara_cli.artefact_autofix.fix_title_mismatch")
|
|
158
181
|
@patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
|
|
159
182
|
@patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
|
|
160
183
|
def test_apply_autofix_for_title_mismatch_with_deterministic_flag(
|
|
@@ -163,24 +186,31 @@ def test_apply_autofix_for_title_mismatch_with_deterministic_flag(
|
|
|
163
186
|
mock_fix_title,
|
|
164
187
|
mock_write,
|
|
165
188
|
mock_run_agent,
|
|
189
|
+
mock_check_file,
|
|
190
|
+
mock_file_classifier,
|
|
166
191
|
mock_artefact_type,
|
|
167
192
|
mock_artefact_class,
|
|
168
193
|
mock_classified_artefact_info,
|
|
169
194
|
):
|
|
170
195
|
"""Tests that a deterministic fix is applied when the flag is True."""
|
|
171
196
|
mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
|
|
172
|
-
|
|
197
|
+
mock_check_file.side_effect = [
|
|
198
|
+
(False, "Filename-Title Mismatch: some details"),
|
|
199
|
+
(True, ""),
|
|
200
|
+
]
|
|
201
|
+
mock_fix_title.return_value = "fixed text"
|
|
173
202
|
|
|
174
203
|
result = apply_autofix(
|
|
175
|
-
"file.feature",
|
|
176
|
-
"feature",
|
|
177
|
-
reason,
|
|
204
|
+
file_path="file.feature",
|
|
205
|
+
classifier="feature",
|
|
206
|
+
reason="Filename-Title Mismatch: some details",
|
|
178
207
|
deterministic=True,
|
|
179
208
|
non_deterministic=False,
|
|
180
209
|
classified_artefact_info=mock_classified_artefact_info,
|
|
181
210
|
)
|
|
182
211
|
|
|
183
212
|
assert result is True
|
|
213
|
+
assert mock_check_file.call_count == 2
|
|
184
214
|
mock_fix_title.assert_called_once_with(
|
|
185
215
|
file_path="file.feature",
|
|
186
216
|
artefact_text="original text",
|
|
@@ -189,8 +219,10 @@ def test_apply_autofix_for_title_mismatch_with_deterministic_flag(
|
|
|
189
219
|
)
|
|
190
220
|
mock_write.assert_called_once_with("file.feature", "fixed text")
|
|
191
221
|
mock_run_agent.assert_not_called()
|
|
222
|
+
mock_file_classifier.assert_called_once()
|
|
192
223
|
|
|
193
224
|
|
|
225
|
+
@patch("ara_cli.artefact_autofix.check_file")
|
|
194
226
|
@patch("ara_cli.artefact_autofix.run_agent")
|
|
195
227
|
@patch("ara_cli.artefact_autofix.write_corrected_artefact")
|
|
196
228
|
@patch("ara_cli.artefact_autofix.fix_title_mismatch")
|
|
@@ -202,29 +234,34 @@ def test_apply_autofix_skips_title_mismatch_without_deterministic_flag(
|
|
|
202
234
|
mock_fix_title,
|
|
203
235
|
mock_write,
|
|
204
236
|
mock_run_agent,
|
|
237
|
+
mock_check_file,
|
|
205
238
|
mock_artefact_type,
|
|
206
239
|
mock_artefact_class,
|
|
207
240
|
mock_classified_artefact_info,
|
|
208
241
|
):
|
|
209
242
|
"""Tests that a deterministic fix is skipped when the flag is False."""
|
|
210
243
|
mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
|
|
211
|
-
|
|
244
|
+
mock_check_file.return_value = (False, "Filename-Title Mismatch: some details")
|
|
212
245
|
|
|
213
246
|
result = apply_autofix(
|
|
214
|
-
"file.feature",
|
|
215
|
-
"feature",
|
|
216
|
-
reason,
|
|
247
|
+
file_path="file.feature",
|
|
248
|
+
classifier="feature",
|
|
249
|
+
reason="Filename-Title Mismatch: some details",
|
|
217
250
|
deterministic=False,
|
|
218
251
|
non_deterministic=True,
|
|
219
252
|
classified_artefact_info=mock_classified_artefact_info,
|
|
220
253
|
)
|
|
221
254
|
|
|
222
255
|
assert result is False
|
|
256
|
+
mock_check_file.assert_called_once()
|
|
257
|
+
mock_read.assert_called_once_with("file.feature")
|
|
223
258
|
mock_fix_title.assert_not_called()
|
|
224
259
|
mock_write.assert_not_called()
|
|
225
260
|
mock_run_agent.assert_not_called()
|
|
226
261
|
|
|
227
262
|
|
|
263
|
+
@patch("ara_cli.artefact_autofix.FileClassifier")
|
|
264
|
+
@patch("ara_cli.artefact_autofix.check_file")
|
|
228
265
|
@patch("ara_cli.artefact_autofix.write_corrected_artefact")
|
|
229
266
|
@patch("ara_cli.artefact_autofix.run_agent")
|
|
230
267
|
@patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
|
|
@@ -234,27 +271,32 @@ def test_apply_autofix_for_llm_fix_with_non_deterministic_flag(
|
|
|
234
271
|
mock_determine,
|
|
235
272
|
mock_run_agent,
|
|
236
273
|
mock_write,
|
|
274
|
+
mock_check_file,
|
|
275
|
+
mock_file_classifier,
|
|
237
276
|
mock_artefact_type,
|
|
238
277
|
mock_artefact_class,
|
|
239
278
|
mock_classified_artefact_info,
|
|
240
279
|
):
|
|
241
280
|
"""Tests that an LLM fix is applied when the non-deterministic flag is True."""
|
|
242
281
|
mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
|
|
282
|
+
mock_check_file.side_effect = [(False, "Pydantic validation error"), (True, "")]
|
|
243
283
|
mock_run_agent.return_value = mock_artefact_class
|
|
244
|
-
reason = "Pydantic validation error"
|
|
245
284
|
|
|
246
285
|
result = apply_autofix(
|
|
247
|
-
"file.feature",
|
|
248
|
-
"feature",
|
|
249
|
-
reason,
|
|
286
|
+
file_path="file.feature",
|
|
287
|
+
classifier="feature",
|
|
288
|
+
reason="Pydantic validation error",
|
|
250
289
|
deterministic=False,
|
|
251
290
|
non_deterministic=True,
|
|
252
291
|
classified_artefact_info=mock_classified_artefact_info,
|
|
253
292
|
)
|
|
254
293
|
|
|
255
294
|
assert result is True
|
|
295
|
+
assert mock_check_file.call_count == 2
|
|
296
|
+
mock_read.assert_called_once_with("file.feature")
|
|
256
297
|
mock_run_agent.assert_called_once()
|
|
257
298
|
mock_write.assert_called_once_with("file.feature", "llm corrected content")
|
|
299
|
+
mock_file_classifier.assert_called_once()
|
|
258
300
|
|
|
259
301
|
|
|
260
302
|
@patch("ara_cli.artefact_autofix.write_corrected_artefact")
|
|
@@ -320,9 +362,6 @@ def test_apply_autofix_llm_exception(
|
|
|
320
362
|
)
|
|
321
363
|
|
|
322
364
|
|
|
323
|
-
# === Other Tests ===
|
|
324
|
-
|
|
325
|
-
|
|
326
365
|
def test_fix_title_mismatch_success(mock_artefact_class):
|
|
327
366
|
artefact_text = "Feature: wrong title\nSome other content"
|
|
328
367
|
file_path = "path/to/correct_title.feature"
|
|
@@ -351,3 +390,228 @@ def test_run_agent_exception_handling(mock_agent_class):
|
|
|
351
390
|
mock_agent_instance.run_sync.side_effect = Exception("Agent error")
|
|
352
391
|
with pytest.raises(Exception, match="Agent error"):
|
|
353
392
|
run_agent("prompt", MagicMock())
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@patch("builtins.input", side_effect=["1"])
|
|
396
|
+
def test_ask_for_contribution_choice_valid(mock_input):
|
|
397
|
+
"""Tests selecting a valid choice."""
|
|
398
|
+
choices = ["choice1", "choice2"]
|
|
399
|
+
# This simpler call now works without causing a TypeError
|
|
400
|
+
result = ask_for_contribution_choice(choices)
|
|
401
|
+
assert result == "choice1"
|
|
402
|
+
|
|
403
|
+
@patch("builtins.input", side_effect=["99"])
|
|
404
|
+
def test_ask_for_contribution_choice_out_of_range(mock_input, capsys):
|
|
405
|
+
"""Tests selecting a choice that is out of range."""
|
|
406
|
+
choices = ["choice1", "choice2"]
|
|
407
|
+
result = ask_for_contribution_choice(choices)
|
|
408
|
+
assert result is None
|
|
409
|
+
assert "Invalid choice" in capsys.readouterr().out
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@patch("builtins.input", side_effect=["not a number"])
|
|
413
|
+
def test_ask_for_contribution_choice_invalid_input(mock_input, capsys):
|
|
414
|
+
"""Tests providing non-numeric input."""
|
|
415
|
+
choices = ["choice1", "choice2"]
|
|
416
|
+
result = ask_for_contribution_choice(choices)
|
|
417
|
+
assert result is None
|
|
418
|
+
assert "Invalid input" in capsys.readouterr().out
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@patch("builtins.input", side_effect=["feature my_feature_name"])
|
|
422
|
+
def test_ask_for_correct_contribution_valid(mock_input):
|
|
423
|
+
"""Tests providing valid '<classifier> <name>' input."""
|
|
424
|
+
name, classifier = ask_for_correct_contribution(("old_name", "feature"))
|
|
425
|
+
assert name == "my_feature_name"
|
|
426
|
+
assert classifier == "feature"
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
@patch("builtins.input", side_effect=[""])
|
|
430
|
+
def test_ask_for_correct_contribution_empty_input(mock_input):
|
|
431
|
+
"""Tests providing empty input."""
|
|
432
|
+
name, classifier = ask_for_correct_contribution()
|
|
433
|
+
assert name is None
|
|
434
|
+
assert classifier is None
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@patch("builtins.input", side_effect=["invalid-one-word-input"])
|
|
438
|
+
def test_ask_for_correct_contribution_invalid_format(mock_input, capsys):
|
|
439
|
+
"""Tests providing input with the wrong format."""
|
|
440
|
+
# Fix: Use input that results in a single part after split()
|
|
441
|
+
name, classifier = ask_for_correct_contribution()
|
|
442
|
+
assert name is None
|
|
443
|
+
assert classifier is None
|
|
444
|
+
assert "Invalid input format" in capsys.readouterr().out
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def test_has_valid_contribution_true(mock_artefact_with_contribution):
|
|
448
|
+
"""Tests with a valid contribution object."""
|
|
449
|
+
# Fix: Check for truthiness, not strict boolean equality
|
|
450
|
+
assert _has_valid_contribution(mock_artefact_with_contribution)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def test_has_valid_contribution_false_no_contribution():
|
|
454
|
+
"""Tests when the artefact's contribution is None."""
|
|
455
|
+
mock_artefact = MagicMock(spec=Artefact)
|
|
456
|
+
mock_artefact.contribution = None
|
|
457
|
+
# Fix: Check for falsiness, not strict boolean equality
|
|
458
|
+
assert not _has_valid_contribution(mock_artefact)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@patch("ara_cli.artefact_autofix.FileClassifier")
|
|
462
|
+
@patch("ara_cli.artefact_autofix.extract_artefact_names_of_classifier")
|
|
463
|
+
@patch("ara_cli.artefact_autofix.find_closest_name_matches")
|
|
464
|
+
def test_set_closest_contribution_no_change_needed(
|
|
465
|
+
mock_find, mock_extract, mock_classifier, mock_artefact_with_contribution
|
|
466
|
+
):
|
|
467
|
+
"""Tests the case where the contribution name is already the best match."""
|
|
468
|
+
mock_find.return_value = ["some_artefact"] # Exact match is found
|
|
469
|
+
artefact, changed = set_closest_contribution(mock_artefact_with_contribution)
|
|
470
|
+
assert changed is False
|
|
471
|
+
assert artefact == mock_artefact_with_contribution
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
@patch("ara_cli.artefact_autofix.FileClassifier")
|
|
475
|
+
@patch("ara_cli.artefact_autofix.extract_artefact_names_of_classifier")
|
|
476
|
+
@patch("ara_cli.artefact_autofix.find_closest_name_matches", return_value=[])
|
|
477
|
+
@patch(
|
|
478
|
+
"ara_cli.artefact_autofix.ask_for_correct_contribution",
|
|
479
|
+
return_value=("new_name", "new_classifier"),
|
|
480
|
+
)
|
|
481
|
+
def test_set_closest_contribution_no_matches_user_provides(
|
|
482
|
+
mock_ask, mock_find, mock_extract, mock_classifier, mock_artefact_with_contribution
|
|
483
|
+
):
|
|
484
|
+
"""Tests when no matches are found and the user provides a new contribution."""
|
|
485
|
+
artefact, changed = set_closest_contribution(mock_artefact_with_contribution)
|
|
486
|
+
assert changed is True
|
|
487
|
+
assert artefact.contribution.artefact_name == "new_name"
|
|
488
|
+
assert artefact.contribution.classifier == "new_classifier"
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@patch("ara_cli.artefact_autofix.set_closest_contribution")
|
|
492
|
+
@patch("ara_cli.artefact_autofix.FileClassifier")
|
|
493
|
+
def test_fix_contribution(
|
|
494
|
+
mock_file_classifier, mock_set, mock_artefact_with_contribution
|
|
495
|
+
):
|
|
496
|
+
"""Tests the fix_contribution wrapper function."""
|
|
497
|
+
# Arrange
|
|
498
|
+
mock_artefact_class = MagicMock()
|
|
499
|
+
mock_artefact_class.deserialize.return_value = mock_artefact_with_contribution
|
|
500
|
+
mock_set.return_value = (mock_artefact_with_contribution, True)
|
|
501
|
+
|
|
502
|
+
# Act
|
|
503
|
+
result = fix_contribution(
|
|
504
|
+
file_path="dummy.path",
|
|
505
|
+
artefact_text="original text",
|
|
506
|
+
artefact_class=mock_artefact_class,
|
|
507
|
+
classified_artefact_info={},
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Assert
|
|
511
|
+
assert result == "serialized artefact text"
|
|
512
|
+
mock_artefact_class.deserialize.assert_called_once_with("original text")
|
|
513
|
+
mock_set.assert_called_once_with(mock_artefact_with_contribution)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
@patch("ara_cli.artefact_autofix.FileClassifier")
|
|
517
|
+
@patch("ara_cli.artefact_autofix.check_file")
|
|
518
|
+
@patch("ara_cli.artefact_autofix.write_corrected_artefact")
|
|
519
|
+
@patch("ara_cli.artefact_autofix.fix_contribution", return_value="fixed text")
|
|
520
|
+
@patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
|
|
521
|
+
@patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
|
|
522
|
+
def test_apply_autofix_for_contribution_mismatch(
|
|
523
|
+
mock_read,
|
|
524
|
+
mock_determine,
|
|
525
|
+
mock_fix_contribution,
|
|
526
|
+
mock_write,
|
|
527
|
+
mock_check_file,
|
|
528
|
+
mock_classifier,
|
|
529
|
+
mock_artefact_type,
|
|
530
|
+
mock_artefact_class,
|
|
531
|
+
mock_classified_artefact_info,
|
|
532
|
+
):
|
|
533
|
+
"""Tests the deterministic fix for 'Invalid Contribution Reference'."""
|
|
534
|
+
mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
|
|
535
|
+
mock_check_file.side_effect = [
|
|
536
|
+
(False, "Invalid Contribution Reference"),
|
|
537
|
+
(True, ""),
|
|
538
|
+
]
|
|
539
|
+
|
|
540
|
+
result = apply_autofix(
|
|
541
|
+
file_path="file.feature",
|
|
542
|
+
classifier="feature",
|
|
543
|
+
reason="Invalid Contribution Reference",
|
|
544
|
+
classified_artefact_info=mock_classified_artefact_info,
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
assert result is True
|
|
548
|
+
mock_fix_contribution.assert_called_once()
|
|
549
|
+
mock_write.assert_called_once_with("file.feature", "fixed text")
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@patch("ara_cli.artefact_autofix.check_file")
|
|
553
|
+
@patch("ara_cli.artefact_autofix.write_corrected_artefact")
|
|
554
|
+
@patch("ara_cli.artefact_autofix.fix_title_mismatch", return_value="original text")
|
|
555
|
+
@patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
|
|
556
|
+
@patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
|
|
557
|
+
def test_apply_autofix_stops_if_no_alteration(
|
|
558
|
+
mock_read,
|
|
559
|
+
mock_determine,
|
|
560
|
+
mock_fix_title,
|
|
561
|
+
mock_write,
|
|
562
|
+
mock_check_file,
|
|
563
|
+
capsys,
|
|
564
|
+
mock_artefact_type,
|
|
565
|
+
mock_artefact_class,
|
|
566
|
+
mock_classified_artefact_info,
|
|
567
|
+
):
|
|
568
|
+
"""Tests that the loop stops if a fix attempt does not change the file content."""
|
|
569
|
+
mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
|
|
570
|
+
mock_check_file.return_value = (False, "Filename-Title Mismatch")
|
|
571
|
+
|
|
572
|
+
result = apply_autofix(
|
|
573
|
+
file_path="file.feature",
|
|
574
|
+
classifier="feature",
|
|
575
|
+
reason="any",
|
|
576
|
+
classified_artefact_info=mock_classified_artefact_info,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
assert result is False
|
|
580
|
+
mock_fix_title.assert_called_once()
|
|
581
|
+
mock_write.assert_not_called()
|
|
582
|
+
assert (
|
|
583
|
+
"Fixing attempt did not alter the file. Stopping to prevent infinite loop."
|
|
584
|
+
in capsys.readouterr().out
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
@patch("ara_cli.artefact_autofix.check_file")
|
|
589
|
+
@patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
|
|
590
|
+
def test_apply_autofix_single_pass(
|
|
591
|
+
mock_determine,
|
|
592
|
+
mock_check_file,
|
|
593
|
+
capsys,
|
|
594
|
+
mock_artefact_type,
|
|
595
|
+
mock_artefact_class,
|
|
596
|
+
mock_classified_artefact_info,
|
|
597
|
+
):
|
|
598
|
+
"""Tests that single_pass=True runs the loop only once."""
|
|
599
|
+
mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
|
|
600
|
+
# Simulate a failure that won't be fixed to ensure the loop doesn't repeat
|
|
601
|
+
mock_check_file.return_value = (False, "Some unfixable error")
|
|
602
|
+
|
|
603
|
+
apply_autofix(
|
|
604
|
+
file_path="file.feature",
|
|
605
|
+
classifier="feature",
|
|
606
|
+
reason="any",
|
|
607
|
+
single_pass=True,
|
|
608
|
+
deterministic=False, # Disable fixes
|
|
609
|
+
non_deterministic=False,
|
|
610
|
+
classified_artefact_info=mock_classified_artefact_info,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
output = capsys.readouterr().out
|
|
614
|
+
assert "Single-pass mode enabled" in output
|
|
615
|
+
assert "Attempt 1/1" in output
|
|
616
|
+
assert "Attempt 2/1" not in output
|
|
617
|
+
mock_check_file.assert_called_once()
|