ara-cli 0.1.9.95__py3-none-any.whl → 0.1.9.96__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.
- ara_cli/__init__.py +4 -1
- ara_cli/__main__.py +57 -11
- ara_cli/ara_command_action.py +31 -19
- ara_cli/artefact_autofix.py +131 -2
- ara_cli/artefact_creator.py +2 -7
- ara_cli/artefact_deleter.py +2 -4
- ara_cli/artefact_fuzzy_search.py +13 -6
- ara_cli/artefact_models/artefact_templates.py +3 -3
- ara_cli/artefact_models/feature_artefact_model.py +25 -0
- ara_cli/artefact_reader.py +4 -5
- ara_cli/chat.py +12 -13
- ara_cli/commands/extract_command.py +4 -11
- ara_cli/error_handler.py +134 -0
- ara_cli/file_classifier.py +3 -2
- ara_cli/prompt_extractor.py +1 -1
- ara_cli/version.py +1 -1
- {ara_cli-0.1.9.95.dist-info → ara_cli-0.1.9.96.dist-info}/METADATA +1 -1
- {ara_cli-0.1.9.95.dist-info → ara_cli-0.1.9.96.dist-info}/RECORD +26 -25
- tests/test_ara_command_action.py +66 -52
- tests/test_artefact_autofix.py +361 -5
- tests/test_chat.py +88 -6
- tests/test_file_classifier.py +23 -0
- tests/test_file_creator.py +3 -5
- {ara_cli-0.1.9.95.dist-info → ara_cli-0.1.9.96.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.95.dist-info → ara_cli-0.1.9.96.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.95.dist-info → ara_cli-0.1.9.96.dist-info}/top_level.txt +0 -0
tests/test_artefact_autofix.py
CHANGED
|
@@ -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
|
-
|
|
141
|
-
|
|
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
|
tests/test_chat.py
CHANGED
|
@@ -1213,26 +1213,107 @@ def test_load_template_helper_load_default_pattern(monkeypatch, temp_chat_file,
|
|
|
1213
1213
|
"prompt.data", default_pattern, template_type)
|
|
1214
1214
|
|
|
1215
1215
|
|
|
1216
|
+
@pytest.mark.parametrize("force_flag, write_flag, expected_force, expected_write", [
|
|
1217
|
+
(False, False, False, False),
|
|
1218
|
+
(True, False, True, False),
|
|
1219
|
+
(False, True, False, True),
|
|
1220
|
+
(True, True, True, True),
|
|
1221
|
+
])
|
|
1216
1222
|
@patch('ara_cli.commands.extract_command.ExtractCommand')
|
|
1217
|
-
def
|
|
1223
|
+
def test_do_EXTRACT_with_flags(MockExtractCommand, temp_chat_file, force_flag, write_flag, expected_force, expected_write):
|
|
1224
|
+
"""Test do_EXTRACT with different flag combinations"""
|
|
1225
|
+
mock_config = get_default_config()
|
|
1226
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
1227
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
1228
|
+
|
|
1229
|
+
# Build command string with flags
|
|
1230
|
+
command_parts = ["EXTRACT"]
|
|
1231
|
+
if force_flag:
|
|
1232
|
+
command_parts.append("-f")
|
|
1233
|
+
if write_flag:
|
|
1234
|
+
command_parts.append("-w")
|
|
1235
|
+
|
|
1236
|
+
command_string = " ".join(command_parts)
|
|
1237
|
+
|
|
1238
|
+
chat.onecmd_plus_hooks(command_string, orig_rl_history_length=0)
|
|
1239
|
+
|
|
1240
|
+
MockExtractCommand.assert_called_once_with(
|
|
1241
|
+
file_name=chat.chat_name,
|
|
1242
|
+
force=expected_force,
|
|
1243
|
+
write=expected_write,
|
|
1244
|
+
output=chat.poutput
|
|
1245
|
+
)
|
|
1246
|
+
MockExtractCommand.return_value.execute.assert_called_once()
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
@pytest.mark.parametrize("command_string", [
|
|
1250
|
+
"EXTRACT --force",
|
|
1251
|
+
"EXTRACT --write",
|
|
1252
|
+
"EXTRACT -f -w",
|
|
1253
|
+
"EXTRACT --force --write",
|
|
1254
|
+
])
|
|
1255
|
+
@patch('ara_cli.commands.extract_command.ExtractCommand')
|
|
1256
|
+
def test_do_EXTRACT_long_form_flags(MockExtractCommand, temp_chat_file, command_string):
|
|
1257
|
+
"""Test do_EXTRACT with long-form flag variations"""
|
|
1258
|
+
mock_config = get_default_config()
|
|
1259
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
1260
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
1261
|
+
|
|
1262
|
+
chat.onecmd_plus_hooks(command_string, orig_rl_history_length=0)
|
|
1263
|
+
|
|
1264
|
+
MockExtractCommand.assert_called_once()
|
|
1265
|
+
MockExtractCommand.return_value.execute.assert_called_once()
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
@patch('ara_cli.commands.extract_command.ExtractCommand')
|
|
1269
|
+
def test_do_EXTRACT_no_flags(MockExtractCommand, temp_chat_file):
|
|
1270
|
+
"""Test do_EXTRACT with no flags (default behavior)"""
|
|
1218
1271
|
mock_config = get_default_config()
|
|
1219
1272
|
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
1220
1273
|
chat = Chat(temp_chat_file.name, reset=False)
|
|
1221
1274
|
|
|
1222
|
-
# The `onecmd_plus_hooks` method requires the `orig_rl_history_length` argument.
|
|
1223
|
-
# We can pass a dummy value like 0 for the test.
|
|
1224
1275
|
chat.onecmd_plus_hooks("EXTRACT", orig_rl_history_length=0)
|
|
1225
1276
|
|
|
1226
1277
|
MockExtractCommand.assert_called_once_with(
|
|
1227
1278
|
file_name=chat.chat_name,
|
|
1228
1279
|
force=False,
|
|
1229
1280
|
write=False,
|
|
1230
|
-
output=chat.poutput
|
|
1231
|
-
error_output=chat.perror
|
|
1281
|
+
output=chat.poutput
|
|
1232
1282
|
)
|
|
1233
1283
|
MockExtractCommand.return_value.execute.assert_called_once()
|
|
1234
1284
|
|
|
1235
1285
|
|
|
1286
|
+
@patch('ara_cli.commands.extract_command.ExtractCommand')
|
|
1287
|
+
def test_do_EXTRACT_command_instantiation(MockExtractCommand, temp_chat_file):
|
|
1288
|
+
"""Test that ExtractCommand is properly instantiated with correct parameters"""
|
|
1289
|
+
mock_config = get_default_config()
|
|
1290
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
1291
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
1292
|
+
|
|
1293
|
+
chat.onecmd_plus_hooks("EXTRACT -f", orig_rl_history_length=0)
|
|
1294
|
+
|
|
1295
|
+
# Verify the command was instantiated with the correct chat instance attributes
|
|
1296
|
+
call_args = MockExtractCommand.call_args
|
|
1297
|
+
assert call_args[1]['file_name'] == chat.chat_name
|
|
1298
|
+
assert call_args[1]['output'] == chat.poutput
|
|
1299
|
+
assert isinstance(call_args[1]['force'], bool)
|
|
1300
|
+
assert isinstance(call_args[1]['write'], bool)
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
@patch('ara_cli.commands.extract_command.ExtractCommand')
|
|
1304
|
+
def test_do_EXTRACT_command_execution(MockExtractCommand, temp_chat_file):
|
|
1305
|
+
"""Test that ExtractCommand.execute() is called"""
|
|
1306
|
+
mock_config = get_default_config()
|
|
1307
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
1308
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
1309
|
+
|
|
1310
|
+
mock_command_instance = MockExtractCommand.return_value
|
|
1311
|
+
|
|
1312
|
+
chat.onecmd_plus_hooks("EXTRACT", orig_rl_history_length=0)
|
|
1313
|
+
|
|
1314
|
+
mock_command_instance.execute.assert_called_once_with()
|
|
1315
|
+
|
|
1316
|
+
|
|
1236
1317
|
def test_do_SEND(temp_chat_file):
|
|
1237
1318
|
mock_config = get_default_config()
|
|
1238
1319
|
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
@@ -1281,7 +1362,8 @@ def test_do_LOAD_TEMPLATE_missing_artefact(temp_chat_file, template_name):
|
|
|
1281
1362
|
with patch('ara_cli.artefact_models.artefact_templates.template_artefact_of_type', return_value=None) as mock_template_loader, \
|
|
1282
1363
|
patch.object(chat, 'add_prompt_tag_if_needed') as mock_add_prompt_tag, \
|
|
1283
1364
|
patch("builtins.open", mock_open()) as mock_file:
|
|
1284
|
-
|
|
1365
|
+
with pytest.raises(ValueError, match=f"No template for '{template_name}' found."):
|
|
1366
|
+
chat.do_LOAD_TEMPLATE(template_name)
|
|
1285
1367
|
mock_template_loader.assert_called_once_with(template_name)
|
|
1286
1368
|
mock_add_prompt_tag.assert_not_called()
|
|
1287
1369
|
mock_file.assert_not_called()
|
tests/test_file_classifier.py
CHANGED
|
@@ -11,6 +11,12 @@ def mock_file_system():
|
|
|
11
11
|
return MagicMock()
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def mock_error_handler():
|
|
16
|
+
with patch('ara_cli.file_classifier.error_handler') as mock_handler:
|
|
17
|
+
yield mock_handler
|
|
18
|
+
|
|
19
|
+
|
|
14
20
|
@pytest.fixture
|
|
15
21
|
def mock_classifier():
|
|
16
22
|
with patch.object(Classifier, 'ordered_classifiers', return_value=['py', 'txt', 'bin']):
|
|
@@ -64,6 +70,23 @@ def test_is_binary_file(mock_file_system):
|
|
|
64
70
|
assert result is False
|
|
65
71
|
|
|
66
72
|
|
|
73
|
+
def test_is_binary_file_handles_error(mock_file_system, mock_error_handler):
|
|
74
|
+
classifier = FileClassifier(mock_file_system)
|
|
75
|
+
test_binary_file_path = "test_binary_file.bin"
|
|
76
|
+
|
|
77
|
+
# Simulate an exception being raised when attempting to open the file
|
|
78
|
+
with patch("builtins.open", side_effect=Exception("Unexpected error")):
|
|
79
|
+
result = classifier.is_binary_file(test_binary_file_path)
|
|
80
|
+
assert result is False
|
|
81
|
+
|
|
82
|
+
# Check that the error handler's report_error method was called
|
|
83
|
+
mock_error_handler.report_error.assert_called_once()
|
|
84
|
+
# You can also verify the specific arguments if needed
|
|
85
|
+
args, kwargs = mock_error_handler.report_error.call_args
|
|
86
|
+
assert "Unexpected error" in str(args[0])
|
|
87
|
+
assert "checking if file is binary" in args[1]
|
|
88
|
+
|
|
89
|
+
|
|
67
90
|
def test_read_file_with_fallback(mock_file_system):
|
|
68
91
|
classifier = FileClassifier(mock_file_system)
|
|
69
92
|
test_file_path = "test_file.txt"
|
tests/test_file_creator.py
CHANGED
|
@@ -15,12 +15,10 @@ def test_template_exists_with_valid_path():
|
|
|
15
15
|
assert result
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def test_run_with_invalid_classifier_raises_error(capfd):
|
|
19
19
|
fc = ArtefactCreator()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
captured = capfd.readouterr()
|
|
23
|
-
assert "Invalid classifier provided. Please provide a valid classifier." in captured.out
|
|
20
|
+
with pytest.raises(ValueError):
|
|
21
|
+
fc.run("filename", "invalid_classifier")
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
@patch("ara_cli.artefact_creator.input", return_value="n")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|