ara-cli 0.1.10.0__py3-none-any.whl → 0.1.13.3__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 +51 -6
- ara_cli/__main__.py +270 -103
- ara_cli/ara_command_action.py +106 -63
- ara_cli/ara_config.py +187 -128
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/config.py +221 -0
- ara_cli/ara_subcommands/convert.py +43 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch.py +41 -0
- ara_cli/ara_subcommands/fetch_agents.py +22 -0
- ara_cli/ara_subcommands/fetch_scripts.py +19 -0
- ara_cli/ara_subcommands/fetch_templates.py +19 -0
- ara_cli/ara_subcommands/list.py +139 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +154 -63
- ara_cli/artefact_converter.py +256 -0
- ara_cli/artefact_models/artefact_model.py +106 -25
- ara_cli/artefact_models/artefact_templates.py +20 -10
- ara_cli/artefact_models/epic_artefact_model.py +11 -2
- ara_cli/artefact_models/feature_artefact_model.py +31 -1
- ara_cli/artefact_models/userstory_artefact_model.py +15 -3
- ara_cli/artefact_scan.py +2 -2
- ara_cli/chat.py +283 -80
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_process_manager.py +155 -0
- ara_cli/chat_script_runner/__init__.py +0 -0
- ara_cli/chat_script_runner/script_completer.py +23 -0
- ara_cli/chat_script_runner/script_finder.py +41 -0
- ara_cli/chat_script_runner/script_lister.py +36 -0
- ara_cli/chat_script_runner/script_runner.py +36 -0
- ara_cli/chat_web_search/__init__.py +0 -0
- ara_cli/chat_web_search/web_search.py +263 -0
- ara_cli/commands/agent_run_command.py +98 -0
- ara_cli/commands/fetch_agents_command.py +106 -0
- ara_cli/commands/fetch_scripts_command.py +43 -0
- ara_cli/commands/fetch_templates_command.py +39 -0
- ara_cli/commands/fetch_templates_commands.py +39 -0
- ara_cli/commands/list_agents_command.py +39 -0
- ara_cli/commands/read_command.py +17 -4
- ara_cli/completers.py +180 -0
- ara_cli/constants.py +2 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/file_loaders/text_file_loader.py +2 -2
- ara_cli/global_file_lister.py +5 -15
- ara_cli/llm_utils.py +58 -0
- ara_cli/prompt_chat.py +20 -4
- ara_cli/prompt_extractor.py +199 -76
- ara_cli/prompt_handler.py +160 -59
- ara_cli/tag_extractor.py +38 -18
- ara_cli/template_loader.py +3 -2
- ara_cli/template_manager.py +52 -21
- ara_cli/templates/global-scripts/hello_global.py +1 -0
- ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
- ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
- ara_cli/version.py +1 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +34 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +123 -54
- tests/test_ara_command_action.py +31 -19
- tests/test_ara_config.py +177 -90
- tests/test_artefact_autofix.py +170 -97
- tests/test_artefact_autofix_integration.py +495 -0
- tests/test_artefact_converter.py +357 -0
- tests/test_artefact_extraction.py +564 -0
- tests/test_artefact_scan.py +1 -1
- tests/test_chat.py +162 -126
- tests/test_chat_givens_images.py +603 -0
- tests/test_chat_script_runner.py +454 -0
- tests/test_global_file_lister.py +1 -1
- tests/test_llm_utils.py +164 -0
- tests/test_prompt_chat.py +343 -0
- tests/test_prompt_extractor.py +683 -0
- tests/test_prompt_handler.py +12 -4
- tests/test_tag_extractor.py +19 -13
- tests/test_web_search.py +467 -0
- ara_cli/ara_command_parser.py +0 -605
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
tests/test_ara_config.py
CHANGED
|
@@ -41,10 +41,11 @@ def valid_config_dict():
|
|
|
41
41
|
"provider": "openai",
|
|
42
42
|
"model": "openai/gpt-4o",
|
|
43
43
|
"temperature": 0.5,
|
|
44
|
-
"max_tokens": 4096
|
|
44
|
+
"max_tokens": 4096,
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
|
-
"default_llm": "gpt-4o-custom"
|
|
47
|
+
"default_llm": "gpt-4o-custom",
|
|
48
|
+
"conversion_llm": "gpt-4o-custom",
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
|
|
@@ -58,10 +59,10 @@ def corrupted_config_dict():
|
|
|
58
59
|
"bad-model": {
|
|
59
60
|
"provider": "test",
|
|
60
61
|
"model": "test/model",
|
|
61
|
-
"temperature": "not_a_float"
|
|
62
|
+
"temperature": "not_a_float",
|
|
62
63
|
}
|
|
63
64
|
},
|
|
64
|
-
"default_llm": 999
|
|
65
|
+
"default_llm": 999,
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
|
|
@@ -72,8 +73,10 @@ def reset_config_manager():
|
|
|
72
73
|
yield
|
|
73
74
|
ConfigManager.reset()
|
|
74
75
|
|
|
76
|
+
|
|
75
77
|
# --- Test Pydantic Models ---
|
|
76
78
|
|
|
79
|
+
|
|
77
80
|
class TestLLMConfigItem:
|
|
78
81
|
def test_valid_temperature(self):
|
|
79
82
|
"""Tests that a valid temperature is accepted."""
|
|
@@ -81,13 +84,22 @@ class TestLLMConfigItem:
|
|
|
81
84
|
assert config.temperature == 0.7
|
|
82
85
|
|
|
83
86
|
def test_invalid_temperature_too_high_raises_error(self):
|
|
84
|
-
"""Tests that temperature >
|
|
85
|
-
with pytest.raises(
|
|
86
|
-
|
|
87
|
+
"""Tests that temperature > 2.0 raises a ValidationError."""
|
|
88
|
+
with pytest.raises(
|
|
89
|
+
ValidationError, match="Input should be less than or equal to 2"
|
|
90
|
+
):
|
|
91
|
+
LLMConfigItem(provider="test", model="test/model", temperature=2.1)
|
|
92
|
+
|
|
93
|
+
def test_valid_high_temperature(self):
|
|
94
|
+
"""Tests that a temperature between 1.0 and 2.0 is accepted."""
|
|
95
|
+
config = LLMConfigItem(provider="test", model="test/model", temperature=1.5)
|
|
96
|
+
assert config.temperature == 1.5
|
|
87
97
|
|
|
88
98
|
def test_invalid_temperature_too_low_raises_error(self):
|
|
89
99
|
"""Tests that temperature < 0.0 raises a ValidationError."""
|
|
90
|
-
with pytest.raises(
|
|
100
|
+
with pytest.raises(
|
|
101
|
+
ValidationError, match="Input should be greater than or equal to 0"
|
|
102
|
+
):
|
|
91
103
|
LLMConfigItem(provider="test", model="test/model", temperature=-0.5)
|
|
92
104
|
|
|
93
105
|
|
|
@@ -95,27 +107,40 @@ class TestARAconfig:
|
|
|
95
107
|
def test_default_values_are_correct(self):
|
|
96
108
|
"""Tests that the model initializes with correct default values."""
|
|
97
109
|
config = ARAconfig()
|
|
98
|
-
assert config.ext_code_dirs == [
|
|
110
|
+
assert config.ext_code_dirs == [
|
|
111
|
+
{"source_dir": "./src"},
|
|
112
|
+
{"source_dir": "./tests"},
|
|
113
|
+
]
|
|
99
114
|
assert config.glossary_dir == "./glossary"
|
|
100
|
-
assert config.default_llm == "gpt-5"
|
|
101
|
-
assert "gpt-5" in config.llm_config
|
|
115
|
+
assert config.default_llm == "gpt-5.2"
|
|
116
|
+
assert "gpt-5.2" in config.llm_config
|
|
102
117
|
|
|
103
|
-
@patch(
|
|
104
|
-
def test_check_critical_fields_with_empty_list_reverts_to_default(
|
|
118
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
119
|
+
def test_check_critical_fields_with_empty_list_reverts_to_default(
|
|
120
|
+
self, mock_stdout
|
|
121
|
+
):
|
|
105
122
|
"""Tests that an empty list for a critical field is reverted to its default."""
|
|
106
123
|
config = ARAconfig(ext_code_dirs=[])
|
|
107
124
|
assert len(config.ext_code_dirs) == 2
|
|
108
125
|
assert config.ext_code_dirs[0] == {"source_dir": "./src"}
|
|
109
|
-
assert
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
126
|
+
assert (
|
|
127
|
+
"Warning: Value for 'ext_code_dirs' is missing or empty. Using default."
|
|
128
|
+
in mock_stdout.getvalue()
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
132
|
+
def test_check_critical_fields_with_empty_string_reverts_to_default(
|
|
133
|
+
self, mock_stdout
|
|
134
|
+
):
|
|
113
135
|
"""Tests that an empty string for a critical field is reverted to its default."""
|
|
114
136
|
config = ARAconfig(glossary_dir="")
|
|
115
137
|
assert config.glossary_dir == "./glossary"
|
|
116
|
-
assert
|
|
138
|
+
assert (
|
|
139
|
+
"Warning: Value for 'glossary_dir' is missing or empty. Using default."
|
|
140
|
+
in mock_stdout.getvalue()
|
|
141
|
+
)
|
|
117
142
|
|
|
118
|
-
@patch(
|
|
143
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
119
144
|
def test_validator_with_empty_llm_config(self, mock_stdout):
|
|
120
145
|
"""Tests validator when llm_config is empty, setting default and extraction to None."""
|
|
121
146
|
config = ARAconfig(llm_config={})
|
|
@@ -124,37 +149,59 @@ class TestARAconfig:
|
|
|
124
149
|
assert config.extraction_llm is None
|
|
125
150
|
assert "Warning: 'llm_config' is empty" in mock_stdout.getvalue()
|
|
126
151
|
|
|
127
|
-
@patch(
|
|
152
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
128
153
|
def test_validator_with_invalid_default_llm(self, mock_stdout):
|
|
129
154
|
"""Tests that an invalid default_llm is reverted to the first available model."""
|
|
130
155
|
config = ARAconfig(default_llm="non_existent_model")
|
|
131
156
|
first_llm = next(iter(config.llm_config))
|
|
132
157
|
assert config.default_llm == first_llm
|
|
133
158
|
output = mock_stdout.getvalue()
|
|
134
|
-
assert
|
|
159
|
+
assert (
|
|
160
|
+
"Warning: The configured 'default_llm' ('non_existent_model') does not exist"
|
|
161
|
+
in output
|
|
162
|
+
)
|
|
135
163
|
assert f"-> Reverting to the first available model: '{first_llm}'" in output
|
|
136
164
|
|
|
137
|
-
@patch(
|
|
165
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
138
166
|
def test_validator_with_invalid_extraction_llm(self, mock_stdout):
|
|
139
167
|
"""Tests that an invalid extraction_llm is reverted to the default_llm."""
|
|
140
168
|
config = ARAconfig(default_llm="gpt-4o", extraction_llm="non_existent_model")
|
|
141
169
|
assert config.extraction_llm == "gpt-4o"
|
|
142
170
|
output = mock_stdout.getvalue()
|
|
143
|
-
assert
|
|
171
|
+
assert (
|
|
172
|
+
"Warning: The configured 'extraction_llm' ('non_existent_model') does not exist"
|
|
173
|
+
in output
|
|
174
|
+
)
|
|
144
175
|
assert "-> Reverting to the 'default_llm' value: 'gpt-4o'" in output
|
|
145
176
|
|
|
177
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
178
|
+
def test_validator_with_invalid_conversion_llm(self, mock_stdout):
|
|
179
|
+
"""Tests that an invalid conversion_llm is reverted to the default_llm."""
|
|
180
|
+
config = ARAconfig(default_llm="gpt-4o", conversion_llm="non_existent_model")
|
|
181
|
+
assert config.conversion_llm == "gpt-4o"
|
|
182
|
+
output = mock_stdout.getvalue()
|
|
183
|
+
assert (
|
|
184
|
+
"Warning: The configured 'conversion_llm' ('non_existent_model') does not exist"
|
|
185
|
+
in output
|
|
186
|
+
)
|
|
187
|
+
assert "-> Reverting to the 'default_llm' value: 'gpt-4o'" in output
|
|
188
|
+
|
|
189
|
+
|
|
146
190
|
# --- Test Helper Functions ---
|
|
147
191
|
|
|
192
|
+
|
|
148
193
|
class TestEnsureDirectoryExists:
|
|
149
|
-
@patch(
|
|
194
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
150
195
|
@patch("os.makedirs")
|
|
151
196
|
@patch("ara_cli.ara_config.exists", return_value=False)
|
|
152
|
-
def test_directory_creation_when_not_exists(
|
|
197
|
+
def test_directory_creation_when_not_exists(
|
|
198
|
+
self, mock_exists, mock_makedirs, mock_stdout
|
|
199
|
+
):
|
|
153
200
|
"""Tests that a directory is created if it doesn't exist."""
|
|
154
201
|
ensure_directory_exists.cache_clear()
|
|
155
202
|
directory = "/tmp/new/dir"
|
|
156
203
|
result = ensure_directory_exists(directory)
|
|
157
|
-
|
|
204
|
+
|
|
158
205
|
mock_exists.assert_called_once_with(directory)
|
|
159
206
|
mock_makedirs.assert_called_once_with(directory)
|
|
160
207
|
assert result == directory
|
|
@@ -167,34 +214,39 @@ class TestEnsureDirectoryExists:
|
|
|
167
214
|
ensure_directory_exists.cache_clear()
|
|
168
215
|
directory = "/tmp/existing/dir"
|
|
169
216
|
result = ensure_directory_exists(directory)
|
|
170
|
-
|
|
217
|
+
|
|
171
218
|
mock_exists.assert_called_once_with(directory)
|
|
172
219
|
mock_makedirs.assert_not_called()
|
|
173
220
|
assert result == directory
|
|
174
221
|
|
|
175
222
|
|
|
176
223
|
class TestHandleUnrecognizedKeys:
|
|
177
|
-
@patch(
|
|
224
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
178
225
|
def test_removes_unrecognized_keys_and_warns(self, mock_stdout):
|
|
179
226
|
"""Tests that unknown keys are removed and a warning is printed."""
|
|
180
227
|
data = {"glossary_dir": "./glossary", "unknown_key": "some_value"}
|
|
181
228
|
cleaned_data = handle_unrecognized_keys(data)
|
|
182
|
-
|
|
229
|
+
|
|
183
230
|
assert "unknown_key" not in cleaned_data
|
|
184
231
|
assert "glossary_dir" in cleaned_data
|
|
185
|
-
assert
|
|
232
|
+
assert (
|
|
233
|
+
"Warning: Unrecognized configuration key 'unknown_key' will be ignored."
|
|
234
|
+
in mock_stdout.getvalue()
|
|
235
|
+
)
|
|
186
236
|
|
|
187
|
-
@patch(
|
|
237
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
188
238
|
def test_no_action_for_valid_data(self, mock_stdout):
|
|
189
239
|
"""Tests that no changes are made when there are no unrecognized keys."""
|
|
190
240
|
data = {"glossary_dir": "./glossary", "doc_dir": "./docs"}
|
|
191
241
|
cleaned_data = handle_unrecognized_keys(data)
|
|
192
|
-
|
|
242
|
+
|
|
193
243
|
assert cleaned_data == data
|
|
194
244
|
assert mock_stdout.getvalue() == ""
|
|
195
245
|
|
|
246
|
+
|
|
196
247
|
# --- Test Core I/O and Logic ---
|
|
197
248
|
|
|
249
|
+
|
|
198
250
|
class TestSaveData:
|
|
199
251
|
@patch("builtins.open", new_callable=mock_open)
|
|
200
252
|
def test_save_data_writes_correct_json(self, mock_file, default_config_data):
|
|
@@ -204,17 +256,19 @@ class TestSaveData:
|
|
|
204
256
|
|
|
205
257
|
mock_file.assert_called_once_with("config.json", "w", encoding="utf-8")
|
|
206
258
|
handle = mock_file()
|
|
207
|
-
written_data =
|
|
208
|
-
|
|
259
|
+
written_data = "".join(call.args[0] for call in handle.write.call_args_list)
|
|
260
|
+
|
|
209
261
|
assert json.loads(written_data) == default_config_data
|
|
210
262
|
|
|
211
263
|
|
|
212
264
|
class TestReadData:
|
|
213
|
-
@patch(
|
|
214
|
-
@patch(
|
|
215
|
-
@patch(
|
|
216
|
-
@patch(
|
|
217
|
-
def test_file_not_found_creates_default_and_exits(
|
|
265
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
266
|
+
@patch("ara_cli.ara_config.save_data")
|
|
267
|
+
@patch("ara_cli.ara_config.ensure_directory_exists")
|
|
268
|
+
@patch("ara_cli.ara_config.exists", return_value=False)
|
|
269
|
+
def test_file_not_found_creates_default_and_exits(
|
|
270
|
+
self, mock_exists, mock_ensure_dir, mock_save, mock_stdout
|
|
271
|
+
):
|
|
218
272
|
"""Tests that a default config is created and the program exits if no config file is found."""
|
|
219
273
|
with pytest.raises(SystemExit) as exc_info:
|
|
220
274
|
read_data.cache_clear()
|
|
@@ -223,16 +277,23 @@ class TestReadData:
|
|
|
223
277
|
assert exc_info.value.code == 0
|
|
224
278
|
mock_ensure_dir.assert_called_once_with(os.path.dirname("config.json"))
|
|
225
279
|
mock_save.assert_called_once()
|
|
226
|
-
|
|
280
|
+
|
|
227
281
|
output = mock_stdout.getvalue()
|
|
228
|
-
assert
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
282
|
+
assert (
|
|
283
|
+
"Configuration file not found. Creating a default one at 'config.json'."
|
|
284
|
+
in output
|
|
285
|
+
)
|
|
286
|
+
assert (
|
|
287
|
+
"Please review the default configuration and re-run your command." in output
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
@patch("ara_cli.ara_config.save_data")
|
|
291
|
+
@patch("builtins.open")
|
|
292
|
+
@patch("ara_cli.ara_config.ensure_directory_exists")
|
|
293
|
+
@patch("ara_cli.ara_config.exists", return_value=True)
|
|
294
|
+
def test_valid_config_is_loaded_and_resaved(
|
|
295
|
+
self, mock_exists, mock_ensure_dir, mock_open_func, mock_save, valid_config_dict
|
|
296
|
+
):
|
|
236
297
|
"""Tests that a valid config is loaded correctly and re-saved (to clean it)."""
|
|
237
298
|
m = mock_open(read_data=json.dumps(valid_config_dict))
|
|
238
299
|
mock_open_func.return_value = m()
|
|
@@ -242,38 +303,48 @@ class TestReadData:
|
|
|
242
303
|
|
|
243
304
|
assert isinstance(result, ARAconfig)
|
|
244
305
|
assert result.default_llm == "gpt-4o-custom"
|
|
245
|
-
mock_save.assert_called_once()
|
|
306
|
+
# mock_save.assert_called_once() # Logic does not save if no changes needed
|
|
246
307
|
|
|
247
|
-
@patch(
|
|
248
|
-
@patch(
|
|
249
|
-
@patch(
|
|
250
|
-
@patch(
|
|
251
|
-
@patch(
|
|
252
|
-
def test_invalid_json_creates_default_config(
|
|
308
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
309
|
+
@patch("ara_cli.ara_config.save_data")
|
|
310
|
+
@patch("builtins.open", new_callable=mock_open, read_data="this is not json")
|
|
311
|
+
@patch("ara_cli.ara_config.ensure_directory_exists")
|
|
312
|
+
@patch("ara_cli.ara_config.exists", return_value=True)
|
|
313
|
+
def test_invalid_json_creates_default_config(
|
|
314
|
+
self, mock_exists, mock_ensure_dir, mock_open_func, mock_save, mock_stdout
|
|
315
|
+
):
|
|
253
316
|
"""Tests that a JSON decoding error results in a new default configuration."""
|
|
254
317
|
read_data.cache_clear()
|
|
255
|
-
|
|
318
|
+
|
|
256
319
|
result = read_data("config.json")
|
|
257
|
-
|
|
320
|
+
|
|
258
321
|
assert isinstance(result, ARAconfig)
|
|
259
|
-
assert result.default_llm == "gpt-5" # Should be the default config
|
|
260
|
-
|
|
322
|
+
assert result.default_llm == "gpt-5.2" # Should be the default config
|
|
323
|
+
|
|
261
324
|
output = mock_stdout.getvalue()
|
|
262
325
|
assert "Error: Invalid JSON in configuration file" in output
|
|
263
326
|
assert "Creating a new configuration with defaults..." in output
|
|
264
327
|
mock_save.assert_called_once()
|
|
265
328
|
|
|
266
|
-
@patch(
|
|
267
|
-
@patch(
|
|
268
|
-
@patch(
|
|
269
|
-
@patch(
|
|
270
|
-
@patch(
|
|
271
|
-
def test_config_with_validation_errors_is_fixed(
|
|
329
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
330
|
+
@patch("ara_cli.ara_config.save_data")
|
|
331
|
+
@patch("builtins.open")
|
|
332
|
+
@patch("ara_cli.ara_config.ensure_directory_exists")
|
|
333
|
+
@patch("ara_cli.ara_config.exists", return_value=True)
|
|
334
|
+
def test_config_with_validation_errors_is_fixed(
|
|
335
|
+
self,
|
|
336
|
+
mock_exists,
|
|
337
|
+
mock_ensure_dir,
|
|
338
|
+
mock_open_func,
|
|
339
|
+
mock_save,
|
|
340
|
+
mock_stdout,
|
|
341
|
+
corrupted_config_dict,
|
|
342
|
+
):
|
|
272
343
|
"""Tests that a config with invalid fields is automatically corrected to defaults."""
|
|
273
344
|
m = mock_open(read_data=json.dumps(corrupted_config_dict))
|
|
274
345
|
mock_open_func.return_value = m()
|
|
275
346
|
read_data.cache_clear()
|
|
276
|
-
|
|
347
|
+
|
|
277
348
|
defaults = ARAconfig()
|
|
278
349
|
result = read_data("config.json")
|
|
279
350
|
|
|
@@ -285,29 +356,40 @@ class TestReadData:
|
|
|
285
356
|
|
|
286
357
|
output = mock_stdout.getvalue()
|
|
287
358
|
assert "--- Configuration Error Detected ---" in output
|
|
288
|
-
assert
|
|
289
|
-
|
|
290
|
-
|
|
359
|
+
assert (
|
|
360
|
+
"-> Field 'ext_code_dirs' is invalid and will be reverted to its default value."
|
|
361
|
+
in output
|
|
362
|
+
)
|
|
363
|
+
assert (
|
|
364
|
+
"-> Field 'glossary_dir' is invalid and will be reverted to its default value."
|
|
365
|
+
in output
|
|
366
|
+
)
|
|
367
|
+
assert (
|
|
368
|
+
"-> Field 'llm_config' is invalid and will be reverted to its default value."
|
|
369
|
+
in output
|
|
370
|
+
)
|
|
291
371
|
assert "Configuration has been corrected and saved" in output
|
|
292
|
-
|
|
372
|
+
|
|
293
373
|
mock_save.assert_called_once_with("config.json", result)
|
|
294
374
|
|
|
295
|
-
@patch(
|
|
296
|
-
@patch(
|
|
297
|
-
@patch(
|
|
298
|
-
@patch(
|
|
299
|
-
@patch(
|
|
300
|
-
def test_preserves_valid_fields_when_fixing_errors(
|
|
375
|
+
@patch("sys.stdout", new_callable=StringIO)
|
|
376
|
+
@patch("ara_cli.ara_config.save_data")
|
|
377
|
+
@patch("builtins.open")
|
|
378
|
+
@patch("ara_cli.ara_config.ensure_directory_exists")
|
|
379
|
+
@patch("ara_cli.ara_config.exists", return_value=True)
|
|
380
|
+
def test_preserves_valid_fields_when_fixing_errors(
|
|
381
|
+
self, mock_exists, mock_ensure_dir, mock_open_func, mock_save, mock_stdout
|
|
382
|
+
):
|
|
301
383
|
"""Tests that valid, non-default values are preserved during a fix."""
|
|
302
384
|
mixed_config = {
|
|
303
385
|
"glossary_dir": "./my-custom-glossary", # Valid, non-default
|
|
304
|
-
"default_llm": 12345,
|
|
305
|
-
"unrecognized_key": "will_be_ignored"
|
|
386
|
+
"default_llm": 12345, # Invalid type
|
|
387
|
+
"unrecognized_key": "will_be_ignored", # Unrecognized
|
|
306
388
|
}
|
|
307
389
|
m = mock_open(read_data=json.dumps(mixed_config))
|
|
308
390
|
mock_open_func.return_value = m()
|
|
309
391
|
read_data.cache_clear()
|
|
310
|
-
|
|
392
|
+
|
|
311
393
|
defaults = ARAconfig()
|
|
312
394
|
result = read_data("config.json")
|
|
313
395
|
|
|
@@ -315,7 +397,10 @@ class TestReadData:
|
|
|
315
397
|
assert result.default_llm == defaults.default_llm
|
|
316
398
|
|
|
317
399
|
output = mock_stdout.getvalue()
|
|
318
|
-
assert
|
|
400
|
+
assert (
|
|
401
|
+
"Warning: Unrecognized configuration key 'unrecognized_key' will be ignored."
|
|
402
|
+
in output
|
|
403
|
+
)
|
|
319
404
|
assert "-> Field 'default_llm' is invalid" in output
|
|
320
405
|
assert "-> Field 'glossary_dir' is invalid" not in output
|
|
321
406
|
|
|
@@ -324,41 +409,43 @@ class TestReadData:
|
|
|
324
409
|
assert saved_config.glossary_dir == "./my-custom-glossary"
|
|
325
410
|
assert saved_config.default_llm == defaults.default_llm
|
|
326
411
|
|
|
412
|
+
|
|
327
413
|
# --- Test Singleton Manager ---
|
|
328
414
|
|
|
415
|
+
|
|
329
416
|
class TestConfigManager:
|
|
330
|
-
@patch(
|
|
417
|
+
@patch("ara_cli.ara_config.read_data")
|
|
331
418
|
def test_get_config_is_singleton(self, mock_read):
|
|
332
419
|
"""Tests that get_config returns the same instance on subsequent calls."""
|
|
333
420
|
mock_read.return_value = MagicMock(spec=ARAconfig)
|
|
334
|
-
|
|
421
|
+
|
|
335
422
|
config1 = ConfigManager.get_config()
|
|
336
423
|
config2 = ConfigManager.get_config()
|
|
337
|
-
|
|
424
|
+
|
|
338
425
|
assert config1 is config2
|
|
339
426
|
mock_read.assert_called_once()
|
|
340
427
|
|
|
341
|
-
@patch(
|
|
428
|
+
@patch("ara_cli.ara_config.read_data")
|
|
342
429
|
def test_reset_clears_instance_and_caches(self, mock_read):
|
|
343
430
|
"""Tests that the reset method clears the instance and underlying caches."""
|
|
344
431
|
mock_read.return_value = MagicMock(spec=ARAconfig)
|
|
345
432
|
|
|
346
433
|
ConfigManager.get_config()
|
|
347
434
|
mock_read.assert_called_once()
|
|
348
|
-
|
|
435
|
+
|
|
349
436
|
ConfigManager.reset()
|
|
350
437
|
assert ConfigManager._config_instance is None
|
|
351
438
|
mock_read.cache_clear.assert_called_once()
|
|
352
439
|
|
|
353
440
|
ConfigManager.get_config()
|
|
354
|
-
assert mock_read.call_count == 2
|
|
441
|
+
assert mock_read.call_count == 2 # Called again after reset
|
|
355
442
|
|
|
356
|
-
@patch(
|
|
443
|
+
@patch("ara_cli.ara_config.read_data")
|
|
357
444
|
def test_get_config_with_custom_filepath(self, mock_read):
|
|
358
445
|
"""Tests that get_config can be called with a custom file path."""
|
|
359
446
|
mock_read.return_value = MagicMock(spec=ARAconfig)
|
|
360
447
|
custom_path = "/custom/path/config.json"
|
|
361
|
-
|
|
448
|
+
|
|
362
449
|
ConfigManager.get_config(custom_path)
|
|
363
|
-
|
|
364
|
-
mock_read.assert_called_once_with(custom_path)
|
|
450
|
+
|
|
451
|
+
mock_read.assert_called_once_with(custom_path)
|