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

Files changed (122) hide show
  1. ara_cli/__init__.py +18 -2
  2. ara_cli/__main__.py +245 -66
  3. ara_cli/ara_command_action.py +128 -63
  4. ara_cli/ara_config.py +201 -177
  5. ara_cli/ara_subcommands/__init__.py +0 -0
  6. ara_cli/ara_subcommands/autofix.py +26 -0
  7. ara_cli/ara_subcommands/chat.py +27 -0
  8. ara_cli/ara_subcommands/classifier_directory.py +16 -0
  9. ara_cli/ara_subcommands/common.py +100 -0
  10. ara_cli/ara_subcommands/create.py +75 -0
  11. ara_cli/ara_subcommands/delete.py +22 -0
  12. ara_cli/ara_subcommands/extract.py +22 -0
  13. ara_cli/ara_subcommands/fetch_templates.py +14 -0
  14. ara_cli/ara_subcommands/list.py +65 -0
  15. ara_cli/ara_subcommands/list_tags.py +25 -0
  16. ara_cli/ara_subcommands/load.py +48 -0
  17. ara_cli/ara_subcommands/prompt.py +136 -0
  18. ara_cli/ara_subcommands/read.py +47 -0
  19. ara_cli/ara_subcommands/read_status.py +20 -0
  20. ara_cli/ara_subcommands/read_user.py +20 -0
  21. ara_cli/ara_subcommands/reconnect.py +27 -0
  22. ara_cli/ara_subcommands/rename.py +22 -0
  23. ara_cli/ara_subcommands/scan.py +14 -0
  24. ara_cli/ara_subcommands/set_status.py +22 -0
  25. ara_cli/ara_subcommands/set_user.py +22 -0
  26. ara_cli/ara_subcommands/template.py +16 -0
  27. ara_cli/artefact_autofix.py +214 -28
  28. ara_cli/artefact_creator.py +5 -8
  29. ara_cli/artefact_deleter.py +2 -4
  30. ara_cli/artefact_fuzzy_search.py +13 -6
  31. ara_cli/artefact_lister.py +29 -55
  32. ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
  33. ara_cli/artefact_models/artefact_model.py +106 -25
  34. ara_cli/artefact_models/artefact_templates.py +23 -13
  35. ara_cli/artefact_models/epic_artefact_model.py +11 -2
  36. ara_cli/artefact_models/feature_artefact_model.py +56 -1
  37. ara_cli/artefact_models/userstory_artefact_model.py +15 -3
  38. ara_cli/artefact_reader.py +4 -5
  39. ara_cli/artefact_renamer.py +6 -2
  40. ara_cli/artefact_scan.py +2 -2
  41. ara_cli/chat.py +594 -219
  42. ara_cli/chat_agent/__init__.py +0 -0
  43. ara_cli/chat_agent/agent_communicator.py +62 -0
  44. ara_cli/chat_agent/agent_process_manager.py +211 -0
  45. ara_cli/chat_agent/agent_status_manager.py +73 -0
  46. ara_cli/chat_agent/agent_workspace_manager.py +76 -0
  47. ara_cli/commands/__init__.py +0 -0
  48. ara_cli/commands/command.py +7 -0
  49. ara_cli/commands/extract_command.py +15 -0
  50. ara_cli/commands/load_command.py +65 -0
  51. ara_cli/commands/load_image_command.py +34 -0
  52. ara_cli/commands/read_command.py +117 -0
  53. ara_cli/completers.py +144 -0
  54. ara_cli/directory_navigator.py +37 -4
  55. ara_cli/error_handler.py +134 -0
  56. ara_cli/file_classifier.py +3 -2
  57. ara_cli/file_loaders/__init__.py +0 -0
  58. ara_cli/file_loaders/binary_file_loader.py +33 -0
  59. ara_cli/file_loaders/document_file_loader.py +34 -0
  60. ara_cli/file_loaders/document_reader.py +245 -0
  61. ara_cli/file_loaders/document_readers.py +233 -0
  62. ara_cli/file_loaders/file_loader.py +50 -0
  63. ara_cli/file_loaders/file_loaders.py +123 -0
  64. ara_cli/file_loaders/image_processor.py +89 -0
  65. ara_cli/file_loaders/markdown_reader.py +75 -0
  66. ara_cli/file_loaders/text_file_loader.py +187 -0
  67. ara_cli/global_file_lister.py +51 -0
  68. ara_cli/prompt_extractor.py +214 -87
  69. ara_cli/prompt_handler.py +508 -146
  70. ara_cli/tag_extractor.py +54 -24
  71. ara_cli/template_loader.py +245 -0
  72. ara_cli/template_manager.py +14 -4
  73. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  74. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  75. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  76. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  77. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  78. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  79. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  80. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  81. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  82. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  83. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  84. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  85. ara_cli/update_config_prompt.py +7 -1
  86. ara_cli/version.py +1 -1
  87. ara_cli-0.1.10.8.dist-info/METADATA +241 -0
  88. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/RECORD +104 -59
  89. tests/test_ara_command_action.py +66 -52
  90. tests/test_ara_config.py +200 -279
  91. tests/test_artefact_autofix.py +361 -5
  92. tests/test_artefact_lister.py +52 -132
  93. tests/test_artefact_scan.py +1 -1
  94. tests/test_chat.py +2009 -603
  95. tests/test_file_classifier.py +23 -0
  96. tests/test_file_creator.py +3 -5
  97. tests/test_global_file_lister.py +131 -0
  98. tests/test_prompt_handler.py +746 -0
  99. tests/test_tag_extractor.py +19 -13
  100. tests/test_template_loader.py +192 -0
  101. tests/test_template_manager.py +5 -4
  102. ara_cli/ara_command_parser.py +0 -536
  103. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  104. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  105. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  106. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  107. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  108. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  109. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  110. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  111. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  112. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  113. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  114. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  115. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  116. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  117. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  118. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  119. ara_cli-0.1.9.77.dist-info/METADATA +0 -18
  120. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
  121. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
  122. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  import pytest
2
2
  from unittest.mock import patch, mock_open, MagicMock
3
+ from ara_cli.error_handler import AraError
3
4
  from ara_cli.artefact_autofix import (
4
5
  read_report_file,
5
6
  parse_report,
@@ -15,7 +16,22 @@ from ara_cli.artefact_autofix import (
15
16
  _has_valid_contribution,
16
17
  set_closest_contribution,
17
18
  fix_contribution,
18
- fix_rule
19
+ fix_rule,
20
+ fix_scenario_placeholder_mismatch,
21
+ _extract_scenario_block,
22
+ _is_scenario_boundary,
23
+ _process_scenario_block,
24
+ _get_line_indentation,
25
+ _extract_placeholders_from_scenario,
26
+ _update_docstring_state,
27
+ _convert_to_scenario_outline,
28
+ _create_examples_table,
29
+ populate_classified_artefact_info,
30
+ should_skip_issue,
31
+ determine_attempt_count,
32
+ apply_deterministic_fix,
33
+ apply_non_deterministic_fix,
34
+ attempt_autofix_loop,
19
35
  )
20
36
  from ara_cli.artefact_models.artefact_model import Artefact, ArtefactType, Contribution
21
37
 
@@ -137,10 +153,8 @@ def test_read_artefact_file_not_found(capsys):
137
153
  @patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping")
138
154
  def test_determine_artefact_type_and_class_no_class_found(mock_mapping, capsys):
139
155
  mock_mapping.get.return_value = None
140
- artefact_type, artefact_class = determine_artefact_type_and_class("feature")
141
- assert artefact_type is None
142
- assert artefact_class is None
143
- assert "No artefact class found for" in capsys.readouterr().out
156
+ with pytest.raises(AraError):
157
+ artefact_type, artefact_class = determine_artefact_type_and_class("feature")
144
158
 
145
159
 
146
160
  @patch("ara_cli.artefact_models.artefact_model.ArtefactType", side_effect=ValueError)
@@ -702,3 +716,345 @@ def test_fix_rule_contribution_none_raises(mock_populate):
702
716
  artefact_class=artefact_class,
703
717
  classified_artefact_info={},
704
718
  )
719
+
720
+ def test_populate_classified_artefact_info_force_true():
721
+ """Test populate_classified_artefact_info with force=True"""
722
+ with patch('ara_cli.artefact_autofix.FileClassifier') as mock_classifier:
723
+ mock_instance = mock_classifier.return_value
724
+ mock_instance.classify_files.return_value = {"new": "data"}
725
+
726
+ result = populate_classified_artefact_info({"old": "data"}, force=True)
727
+
728
+ assert result == {"new": "data"}
729
+ mock_classifier.assert_called_once()
730
+
731
+ def test_populate_classified_artefact_info_none_input():
732
+ """Test populate_classified_artefact_info with None input"""
733
+ with patch('ara_cli.artefact_autofix.FileClassifier') as mock_classifier:
734
+ mock_instance = mock_classifier.return_value
735
+ mock_instance.classify_files.return_value = {"classified": "data"}
736
+
737
+ result = populate_classified_artefact_info(None)
738
+
739
+ assert result == {"classified": "data"}
740
+ mock_classifier.assert_called_once()
741
+
742
+ def test_parse_report_empty_content():
743
+ """Test parse_report with empty content"""
744
+ assert parse_report("") == {}
745
+
746
+ def test_parse_report_missing_reason():
747
+ """Test parse_report with missing reason in issue line"""
748
+ content = "# Artefact Check Report\n\n## feature\n- `file.feature`\n"
749
+ expected = {"feature": [("file.feature", "")]}
750
+ assert parse_report(content) == expected
751
+
752
+ def test_parse_report_multiple_classifiers():
753
+ """Test parse_report with multiple classifiers"""
754
+ content = (
755
+ "# Artefact Check Report\n\n"
756
+ "## feature\n- `file1.feature`: reason1\n\n"
757
+ "## task\n- `file2.task`: reason2\n"
758
+ )
759
+ expected = {
760
+ "feature": [("file1.feature", "reason1")],
761
+ "task": [("file2.task", "reason2")]
762
+ }
763
+ assert parse_report(content) == expected
764
+
765
+ def test_construct_prompt_non_task_artefact():
766
+ """Test construct_prompt for non-task artefact types"""
767
+ prompt = construct_prompt(ArtefactType.feature, "some reason", "file.feature", "text")
768
+ assert "For task artefacts" not in prompt
769
+ assert "some reason" in prompt
770
+ assert "file.feature" in prompt
771
+
772
+ @patch("pydantic_ai.Agent")
773
+ def test_run_agent_success(mock_agent_class):
774
+ """Test successful run_agent execution"""
775
+ mock_agent_instance = mock_agent_class.return_value
776
+ mock_result = MagicMock()
777
+ mock_result.output = "agent output"
778
+ mock_agent_instance.run_sync.return_value = mock_result
779
+
780
+ result = run_agent("test prompt", MagicMock())
781
+
782
+ assert result == "agent output"
783
+ mock_agent_class.assert_called_once()
784
+
785
+ def test_write_corrected_artefact_with_print(capsys):
786
+ """Test write_corrected_artefact prints success message"""
787
+ with patch("builtins.open", mock_open()) as m:
788
+ write_corrected_artefact("file.feature", "corrected content")
789
+
790
+ captured = capsys.readouterr()
791
+ assert "Fixed artefact at file.feature" in captured.out
792
+
793
+ # Tests for the new scenario placeholder functions
794
+ def test_extract_scenario_block():
795
+ """Test _extract_scenario_block function"""
796
+ lines = [
797
+ "Feature: Test",
798
+ "Scenario: Test scenario",
799
+ " Given something",
800
+ " When something",
801
+ "Scenario: Another scenario"
802
+ ]
803
+
804
+ scenario_lines, next_index = _extract_scenario_block(lines, 1)
805
+
806
+ assert len(scenario_lines) == 3
807
+ assert scenario_lines[0] == "Scenario: Test scenario"
808
+ assert next_index == 4
809
+
810
+ def test_is_scenario_boundary():
811
+ """Test _is_scenario_boundary function"""
812
+ assert _is_scenario_boundary("Scenario: test")
813
+ assert _is_scenario_boundary("Scenario Outline: test")
814
+ assert _is_scenario_boundary("Background:")
815
+ assert _is_scenario_boundary("Feature: test")
816
+ assert not _is_scenario_boundary("Given something")
817
+
818
+ def test_process_scenario_block_no_placeholders():
819
+ """Test _process_scenario_block with no placeholders"""
820
+ scenario_lines = [
821
+ " Scenario: Test",
822
+ " Given something",
823
+ " When something"
824
+ ]
825
+
826
+ result = _process_scenario_block(scenario_lines)
827
+
828
+ assert result == scenario_lines
829
+
830
+ def test_process_scenario_block_with_placeholders():
831
+ """Test _process_scenario_block with placeholders"""
832
+ scenario_lines = [
833
+ " Scenario: Test",
834
+ " Given something with <placeholder>",
835
+ " When something with <another>"
836
+ ]
837
+
838
+ result = _process_scenario_block(scenario_lines)
839
+
840
+ assert "Scenario Outline:" in result[0]
841
+ assert "Examples:" in result[-3]
842
+
843
+ def test_get_line_indentation():
844
+ """Test _get_line_indentation function"""
845
+ assert _get_line_indentation(" indented line") == " "
846
+ assert _get_line_indentation("no indent") == ""
847
+ assert _get_line_indentation(" two spaces") == " "
848
+
849
+ def test_extract_placeholders_from_scenario():
850
+ """Test _extract_placeholders_from_scenario function"""
851
+ step_lines = [
852
+ " Given something with <placeholder1>",
853
+ " When something with <placeholder2>",
854
+ " Then something normal"
855
+ ]
856
+
857
+ placeholders = _extract_placeholders_from_scenario(step_lines)
858
+
859
+ assert placeholders == {"placeholder1", "placeholder2"}
860
+
861
+ def test_extract_placeholders_with_docstring():
862
+ """Test _extract_placeholders_from_scenario ignoring docstrings"""
863
+ step_lines = [
864
+ " Given something with <placeholder1>",
865
+ ' """',
866
+ " Some docstring with <not_a_placeholder>",
867
+ ' """',
868
+ " When something with <placeholder2>"
869
+ ]
870
+
871
+ placeholders = _extract_placeholders_from_scenario(step_lines)
872
+
873
+ assert placeholders == {"placeholder1", "placeholder2"}
874
+
875
+ def test_update_docstring_state():
876
+ """Test _update_docstring_state function"""
877
+ assert _update_docstring_state('"""', False) == True
878
+ assert _update_docstring_state('"""', True) == False
879
+ assert _update_docstring_state('normal line', False) == False
880
+ assert _update_docstring_state('normal line', True) == True
881
+
882
+ def test_convert_to_scenario_outline():
883
+ """Test _convert_to_scenario_outline function"""
884
+ scenario_lines = [
885
+ " Scenario: Test scenario",
886
+ " Given something",
887
+ " When something"
888
+ ]
889
+ placeholders = {"placeholder1", "placeholder2"}
890
+
891
+ result = _convert_to_scenario_outline(scenario_lines, placeholders, " ")
892
+
893
+ assert "Scenario Outline: Test scenario" in result[0]
894
+ assert "Examples:" in result[-3]
895
+
896
+ def test_create_examples_table():
897
+ """Test _create_examples_table function"""
898
+ placeholders = {"param1", "param2"}
899
+
900
+ result = _create_examples_table(placeholders, " ")
901
+
902
+ assert len(result) == 3
903
+ assert "Examples:" in result[0]
904
+ assert "| param1 | param2 |" in result[1]
905
+ assert "<param1_value>" in result[2]
906
+
907
+ def test_fix_scenario_placeholder_mismatch_no_scenarios():
908
+ """Test fix_scenario_placeholder_mismatch with no scenarios"""
909
+ artefact_text = "Feature: Test\nBackground:\n Given something"
910
+
911
+ result = fix_scenario_placeholder_mismatch("file.feature", artefact_text, MagicMock())
912
+
913
+ assert result == artefact_text
914
+
915
+ def test_fix_scenario_placeholder_mismatch_with_placeholders():
916
+ """Test fix_scenario_placeholder_mismatch converting to outline"""
917
+ artefact_text = """Feature: Test
918
+ Scenario: Test scenario
919
+ Given something with <placeholder>
920
+ When something happens"""
921
+
922
+ result = fix_scenario_placeholder_mismatch("file.feature", artefact_text, MagicMock())
923
+
924
+ assert "Scenario Outline:" in result
925
+ assert "Examples:" in result
926
+ assert "<placeholder>" in result
927
+
928
+ def test_should_skip_issue_non_deterministic_false():
929
+ """Test should_skip_issue when non_deterministic is False"""
930
+ result = should_skip_issue(None, True, False, "file.txt")
931
+ assert result == True
932
+
933
+ def test_should_skip_issue_deterministic_false():
934
+ """Test should_skip_issue when deterministic is False"""
935
+ result = should_skip_issue("some_issue", False, True, "file.txt")
936
+ assert result == True
937
+
938
+ def test_should_skip_issue_no_skip():
939
+ """Test should_skip_issue when no skip is needed"""
940
+ result = should_skip_issue("some_issue", True, True, "file.txt")
941
+ assert result == False
942
+
943
+ def test_determine_attempt_count_single_pass():
944
+ """Test determine_attempt_count with single_pass=True"""
945
+ result = determine_attempt_count(True, "file.txt")
946
+ assert result == 1
947
+
948
+ def test_determine_attempt_count_multiple_pass():
949
+ """Test determine_attempt_count with single_pass=False"""
950
+ result = determine_attempt_count(False, "file.txt")
951
+ assert result == 3
952
+
953
+ def test_apply_deterministic_fix_with_issue():
954
+ """Test apply_deterministic_fix when deterministic issue exists"""
955
+ mock_fix_function = MagicMock(return_value="fixed_text")
956
+ deterministic_markers = {"test_issue": mock_fix_function}
957
+
958
+ result = apply_deterministic_fix(
959
+ True, "test_issue", "file.txt", "original", MagicMock(),
960
+ {}, deterministic_markers, "corrected"
961
+ )
962
+
963
+ assert result == "fixed_text"
964
+ mock_fix_function.assert_called_once()
965
+
966
+ def test_apply_deterministic_fix_no_issue():
967
+ """Test apply_deterministic_fix when no deterministic issue"""
968
+ result = apply_deterministic_fix(
969
+ True, None, "file.txt", "original", MagicMock(),
970
+ {}, {}, "corrected"
971
+ )
972
+
973
+ assert result == "corrected"
974
+
975
+ @patch('ara_cli.artefact_autofix.construct_prompt')
976
+ @patch('ara_cli.artefact_autofix.run_agent')
977
+ def test_apply_non_deterministic_fix_success(mock_run_agent, mock_construct_prompt):
978
+ """Test apply_non_deterministic_fix successful execution"""
979
+ mock_construct_prompt.return_value = "test prompt"
980
+ mock_artefact = MagicMock()
981
+ mock_artefact.serialize.return_value = "fixed_text"
982
+ mock_run_agent.return_value = mock_artefact
983
+
984
+ result = apply_non_deterministic_fix(
985
+ True, None, "corrected", MagicMock(), "reason",
986
+ "file.txt", "original", MagicMock()
987
+ )
988
+
989
+ assert result == "fixed_text"
990
+
991
+ def test_apply_non_deterministic_fix_with_deterministic_issue():
992
+ """Test apply_non_deterministic_fix when deterministic issue exists"""
993
+ result = apply_non_deterministic_fix(
994
+ True, "some_issue", "corrected", MagicMock(), "reason",
995
+ "file.txt", "original", MagicMock()
996
+ )
997
+
998
+ assert result == "corrected"
999
+
1000
+ @patch('ara_cli.artefact_autofix.construct_prompt')
1001
+ @patch('ara_cli.artefact_autofix.run_agent', side_effect=Exception("LLM Error"))
1002
+ def test_apply_non_deterministic_fix_exception(mock_run_agent, mock_construct_prompt, capsys):
1003
+ """Test apply_non_deterministic_fix with exception"""
1004
+ mock_construct_prompt.return_value = "test prompt"
1005
+
1006
+ result = apply_non_deterministic_fix(
1007
+ True, None, "corrected", MagicMock(), "reason",
1008
+ "file.txt", "original", MagicMock()
1009
+ )
1010
+
1011
+ assert result is None
1012
+ assert "LLM agent failed" in capsys.readouterr().out
1013
+
1014
+ @patch('ara_cli.artefact_autofix.check_file')
1015
+ @patch('ara_cli.artefact_autofix.read_artefact')
1016
+ @patch('ara_cli.artefact_autofix.write_corrected_artefact')
1017
+ @patch('ara_cli.artefact_autofix.populate_classified_artefact_info')
1018
+ @patch('ara_cli.artefact_autofix.should_skip_issue', return_value=False)
1019
+ @patch('ara_cli.artefact_autofix.apply_deterministic_fix')
1020
+ @patch('ara_cli.artefact_autofix.apply_non_deterministic_fix')
1021
+ def test_attempt_autofix_loop_max_attempts_reached(
1022
+ mock_apply_non_det, mock_apply_det, mock_should_skip, mock_populate,
1023
+ mock_write, mock_read, mock_check_file, capsys
1024
+ ):
1025
+ """Test attempt_autofix_loop when max attempts are reached"""
1026
+ mock_check_file.return_value = (False, "persistent error")
1027
+ mock_read.return_value = "original text"
1028
+ mock_apply_det.return_value = "modified text" # Ensure text is modified
1029
+ mock_apply_non_det.return_value = "modified text" # Ensure text is modified
1030
+
1031
+ result = attempt_autofix_loop(
1032
+ "file.txt", MagicMock(), MagicMock(), {}, 2, True, True, {}
1033
+ )
1034
+
1035
+ assert result == False
1036
+ assert "Failed to fix file.txt after 2 attempts" in capsys.readouterr().out
1037
+
1038
+ @patch('ara_cli.artefact_autofix.check_file')
1039
+ def test_attempt_autofix_loop_already_valid(mock_check_file, capsys):
1040
+ """Test attempt_autofix_loop when file is already valid"""
1041
+ mock_check_file.return_value = (True, "")
1042
+
1043
+ result = attempt_autofix_loop(
1044
+ "file.txt", MagicMock(), MagicMock(), {}, 3, True, True, {}
1045
+ )
1046
+
1047
+ assert result == True
1048
+ assert "is now valid" in capsys.readouterr().out
1049
+
1050
+ @patch('ara_cli.artefact_autofix.check_file')
1051
+ @patch('ara_cli.artefact_autofix.read_artefact', return_value=None)
1052
+ def test_attempt_autofix_loop_read_fails(mock_read, mock_check_file):
1053
+ """Test attempt_autofix_loop when reading artefact fails"""
1054
+ mock_check_file.return_value = (False, "some error")
1055
+
1056
+ result = attempt_autofix_loop(
1057
+ "file.txt", MagicMock(), MagicMock(), {}, 3, True, True, {}
1058
+ )
1059
+
1060
+ assert result == False