ara-cli 0.1.10.5__py3-none-any.whl → 0.1.14.0__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.
Files changed (151) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +87 -75
  3. ara_cli/ara_command_action.py +189 -101
  4. ara_cli/ara_config.py +187 -128
  5. ara_cli/ara_subcommands/common.py +2 -2
  6. ara_cli/ara_subcommands/config.py +221 -0
  7. ara_cli/ara_subcommands/convert.py +107 -0
  8. ara_cli/ara_subcommands/fetch.py +41 -0
  9. ara_cli/ara_subcommands/fetch_agents.py +22 -0
  10. ara_cli/ara_subcommands/fetch_scripts.py +19 -0
  11. ara_cli/ara_subcommands/fetch_templates.py +15 -10
  12. ara_cli/ara_subcommands/list.py +97 -23
  13. ara_cli/ara_subcommands/prompt.py +266 -106
  14. ara_cli/artefact_autofix.py +117 -64
  15. ara_cli/artefact_converter.py +355 -0
  16. ara_cli/artefact_creator.py +41 -17
  17. ara_cli/artefact_lister.py +3 -3
  18. ara_cli/artefact_models/artefact_model.py +1 -1
  19. ara_cli/artefact_models/artefact_templates.py +0 -9
  20. ara_cli/artefact_models/feature_artefact_model.py +8 -8
  21. ara_cli/artefact_reader.py +62 -43
  22. ara_cli/artefact_scan.py +39 -17
  23. ara_cli/chat.py +300 -71
  24. ara_cli/chat_agent/__init__.py +0 -0
  25. ara_cli/chat_agent/agent_process_manager.py +155 -0
  26. ara_cli/chat_script_runner/__init__.py +0 -0
  27. ara_cli/chat_script_runner/script_completer.py +23 -0
  28. ara_cli/chat_script_runner/script_finder.py +41 -0
  29. ara_cli/chat_script_runner/script_lister.py +36 -0
  30. ara_cli/chat_script_runner/script_runner.py +36 -0
  31. ara_cli/chat_web_search/__init__.py +0 -0
  32. ara_cli/chat_web_search/web_search.py +263 -0
  33. ara_cli/children_contribution_updater.py +737 -0
  34. ara_cli/classifier.py +34 -0
  35. ara_cli/commands/agent_run_command.py +98 -0
  36. ara_cli/commands/fetch_agents_command.py +106 -0
  37. ara_cli/commands/fetch_scripts_command.py +43 -0
  38. ara_cli/commands/fetch_templates_command.py +39 -0
  39. ara_cli/commands/fetch_templates_commands.py +39 -0
  40. ara_cli/commands/list_agents_command.py +39 -0
  41. ara_cli/commands/load_command.py +4 -3
  42. ara_cli/commands/load_image_command.py +1 -1
  43. ara_cli/commands/read_command.py +23 -27
  44. ara_cli/completers.py +95 -35
  45. ara_cli/constants.py +2 -0
  46. ara_cli/directory_navigator.py +37 -4
  47. ara_cli/error_handler.py +26 -11
  48. ara_cli/file_loaders/document_reader.py +0 -178
  49. ara_cli/file_loaders/factories/__init__.py +0 -0
  50. ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
  51. ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
  52. ara_cli/file_loaders/file_loader.py +1 -30
  53. ara_cli/file_loaders/loaders/__init__.py +0 -0
  54. ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
  55. ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
  56. ara_cli/file_loaders/readers/__init__.py +0 -0
  57. ara_cli/file_loaders/readers/docx_reader.py +49 -0
  58. ara_cli/file_loaders/readers/excel_reader.py +27 -0
  59. ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
  60. ara_cli/file_loaders/readers/odt_reader.py +59 -0
  61. ara_cli/file_loaders/readers/pdf_reader.py +54 -0
  62. ara_cli/file_loaders/readers/pptx_reader.py +104 -0
  63. ara_cli/file_loaders/tools/__init__.py +0 -0
  64. ara_cli/llm_utils.py +58 -0
  65. ara_cli/output_suppressor.py +53 -0
  66. ara_cli/prompt_chat.py +20 -4
  67. ara_cli/prompt_extractor.py +47 -32
  68. ara_cli/prompt_handler.py +123 -17
  69. ara_cli/tag_extractor.py +8 -7
  70. ara_cli/template_loader.py +2 -1
  71. ara_cli/template_manager.py +52 -21
  72. ara_cli/templates/global-scripts/hello_global.py +1 -0
  73. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  74. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  75. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  76. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  77. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  78. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  79. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  80. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  81. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  82. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  83. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  84. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  85. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  86. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  87. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  88. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  89. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  90. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  91. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  92. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  93. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  94. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  95. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  96. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  97. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  98. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  99. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  100. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  101. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  102. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  103. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  104. ara_cli/version.py +1 -1
  105. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +49 -11
  106. ara_cli-0.1.14.0.dist-info/RECORD +253 -0
  107. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
  108. tests/test_ara_command_action.py +31 -19
  109. tests/test_ara_config.py +177 -90
  110. tests/test_artefact_autofix.py +170 -97
  111. tests/test_artefact_autofix_integration.py +495 -0
  112. tests/test_artefact_converter.py +312 -0
  113. tests/test_artefact_extraction.py +564 -0
  114. tests/test_artefact_lister.py +11 -8
  115. tests/test_chat.py +166 -130
  116. tests/test_chat_givens_images.py +603 -0
  117. tests/test_chat_script_runner.py +454 -0
  118. tests/test_children_contribution_updater.py +98 -0
  119. tests/test_document_loader_office.py +267 -0
  120. tests/test_llm_utils.py +164 -0
  121. tests/test_prompt_chat.py +343 -0
  122. tests/test_prompt_extractor.py +683 -0
  123. tests/test_prompt_handler.py +416 -214
  124. tests/test_setup_default_chat_prompt_mode.py +198 -0
  125. tests/test_tag_extractor.py +95 -49
  126. tests/test_web_search.py +467 -0
  127. ara_cli/file_loaders/document_readers.py +0 -233
  128. ara_cli/file_loaders/file_loaders.py +0 -123
  129. ara_cli/file_loaders/text_file_loader.py +0 -187
  130. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  131. ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
  132. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  133. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  134. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  135. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  136. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  137. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  138. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  139. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  140. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  141. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  142. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  143. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  144. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  145. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  146. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  147. ara_cli-0.1.10.5.dist-info/RECORD +0 -194
  148. /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
  149. /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
  150. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
  151. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.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 > 1.0 raises a ValidationError."""
85
- with pytest.raises(ValidationError, match="Input should be less than or equal to 1"):
86
- LLMConfigItem(provider="test", model="test/model", temperature=1.5)
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(ValidationError, match="Input should be greater than or equal to 0"):
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 == [{"source_dir": "./src"}, {"source_dir": "./tests"}]
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('sys.stdout', new_callable=StringIO)
104
- def test_check_critical_fields_with_empty_list_reverts_to_default(self, mock_stdout):
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 "Warning: Value for 'ext_code_dirs' is missing or empty. Using default." in mock_stdout.getvalue()
110
-
111
- @patch('sys.stdout', new_callable=StringIO)
112
- def test_check_critical_fields_with_empty_string_reverts_to_default(self, mock_stdout):
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 "Warning: Value for 'glossary_dir' is missing or empty. Using default." in mock_stdout.getvalue()
138
+ assert (
139
+ "Warning: Value for 'glossary_dir' is missing or empty. Using default."
140
+ in mock_stdout.getvalue()
141
+ )
117
142
 
118
- @patch('sys.stdout', new_callable=StringIO)
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('sys.stdout', new_callable=StringIO)
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 "Warning: The configured 'default_llm' ('non_existent_model') does not exist" in output
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('sys.stdout', new_callable=StringIO)
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 "Warning: The configured 'extraction_llm' ('non_existent_model') does not exist" in output
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('sys.stdout', new_callable=StringIO)
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(self, mock_exists, mock_makedirs, mock_stdout):
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('sys.stdout', new_callable=StringIO)
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 "Warning: Unrecognized configuration key 'unknown_key' will be ignored." in mock_stdout.getvalue()
232
+ assert (
233
+ "Warning: Unrecognized configuration key 'unknown_key' will be ignored."
234
+ in mock_stdout.getvalue()
235
+ )
186
236
 
187
- @patch('sys.stdout', new_callable=StringIO)
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 = ''.join(call.args[0] for call in handle.write.call_args_list)
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('sys.stdout', new_callable=StringIO)
214
- @patch('ara_cli.ara_config.save_data')
215
- @patch('ara_cli.ara_config.ensure_directory_exists')
216
- @patch('ara_cli.ara_config.exists', return_value=False)
217
- def test_file_not_found_creates_default_and_exits(self, mock_exists, mock_ensure_dir, mock_save, mock_stdout):
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 "Configuration file not found. Creating a default one at 'config.json'." in output
229
- assert "Please review the default configuration and re-run your command." in output
230
-
231
- @patch('ara_cli.ara_config.save_data')
232
- @patch('builtins.open')
233
- @patch('ara_cli.ara_config.ensure_directory_exists')
234
- @patch('ara_cli.ara_config.exists', return_value=True)
235
- def test_valid_config_is_loaded_and_resaved(self, mock_exists, mock_ensure_dir, mock_open_func, mock_save, valid_config_dict):
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('sys.stdout', new_callable=StringIO)
248
- @patch('ara_cli.ara_config.save_data')
249
- @patch('builtins.open', new_callable=mock_open, read_data="this is not json")
250
- @patch('ara_cli.ara_config.ensure_directory_exists')
251
- @patch('ara_cli.ara_config.exists', return_value=True)
252
- def test_invalid_json_creates_default_config(self, mock_exists, mock_ensure_dir, mock_open_func, mock_save, mock_stdout):
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('sys.stdout', new_callable=StringIO)
267
- @patch('ara_cli.ara_config.save_data')
268
- @patch('builtins.open')
269
- @patch('ara_cli.ara_config.ensure_directory_exists')
270
- @patch('ara_cli.ara_config.exists', return_value=True)
271
- def test_config_with_validation_errors_is_fixed(self, mock_exists, mock_ensure_dir, mock_open_func, mock_save, mock_stdout, corrupted_config_dict):
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 "-> Field 'ext_code_dirs' is invalid and will be reverted to its default value." in output
289
- assert "-> Field 'glossary_dir' is invalid and will be reverted to its default value." in output
290
- assert "-> Field 'llm_config' is invalid and will be reverted to its default value." in output
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('sys.stdout', new_callable=StringIO)
296
- @patch('ara_cli.ara_config.save_data')
297
- @patch('builtins.open')
298
- @patch('ara_cli.ara_config.ensure_directory_exists')
299
- @patch('ara_cli.ara_config.exists', return_value=True)
300
- def test_preserves_valid_fields_when_fixing_errors(self, mock_exists, mock_ensure_dir, mock_open_func, mock_save, mock_stdout):
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, # Invalid type
305
- "unrecognized_key": "will_be_ignored" # Unrecognized
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 "Warning: Unrecognized configuration key 'unrecognized_key' will be ignored." in output
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('ara_cli.ara_config.read_data')
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('ara_cli.ara_config.read_data')
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 # Called again after reset
441
+ assert mock_read.call_count == 2 # Called again after reset
355
442
 
356
- @patch('ara_cli.ara_config.read_data')
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)