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.
- ara_cli/__init__.py +51 -6
- ara_cli/__main__.py +87 -75
- ara_cli/ara_command_action.py +189 -101
- ara_cli/ara_config.py +187 -128
- ara_cli/ara_subcommands/common.py +2 -2
- ara_cli/ara_subcommands/config.py +221 -0
- ara_cli/ara_subcommands/convert.py +107 -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 +15 -10
- ara_cli/ara_subcommands/list.py +97 -23
- ara_cli/ara_subcommands/prompt.py +266 -106
- ara_cli/artefact_autofix.py +117 -64
- ara_cli/artefact_converter.py +355 -0
- ara_cli/artefact_creator.py +41 -17
- ara_cli/artefact_lister.py +3 -3
- ara_cli/artefact_models/artefact_model.py +1 -1
- ara_cli/artefact_models/artefact_templates.py +0 -9
- ara_cli/artefact_models/feature_artefact_model.py +8 -8
- ara_cli/artefact_reader.py +62 -43
- ara_cli/artefact_scan.py +39 -17
- ara_cli/chat.py +300 -71
- 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/children_contribution_updater.py +737 -0
- ara_cli/classifier.py +34 -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/load_command.py +4 -3
- ara_cli/commands/load_image_command.py +1 -1
- ara_cli/commands/read_command.py +23 -27
- ara_cli/completers.py +95 -35
- ara_cli/constants.py +2 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/error_handler.py +26 -11
- ara_cli/file_loaders/document_reader.py +0 -178
- ara_cli/file_loaders/factories/__init__.py +0 -0
- ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
- ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
- ara_cli/file_loaders/file_loader.py +1 -30
- ara_cli/file_loaders/loaders/__init__.py +0 -0
- ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
- ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
- ara_cli/file_loaders/readers/__init__.py +0 -0
- ara_cli/file_loaders/readers/docx_reader.py +49 -0
- ara_cli/file_loaders/readers/excel_reader.py +27 -0
- ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
- ara_cli/file_loaders/readers/odt_reader.py +59 -0
- ara_cli/file_loaders/readers/pdf_reader.py +54 -0
- ara_cli/file_loaders/readers/pptx_reader.py +104 -0
- ara_cli/file_loaders/tools/__init__.py +0 -0
- ara_cli/llm_utils.py +58 -0
- ara_cli/output_suppressor.py +53 -0
- ara_cli/prompt_chat.py +20 -4
- ara_cli/prompt_extractor.py +47 -32
- ara_cli/prompt_handler.py +123 -17
- ara_cli/tag_extractor.py +8 -7
- ara_cli/template_loader.py +2 -1
- 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.5.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +49 -11
- ara_cli-0.1.14.0.dist-info/RECORD +253 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
- 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 +312 -0
- tests/test_artefact_extraction.py +564 -0
- tests/test_artefact_lister.py +11 -8
- tests/test_chat.py +166 -130
- tests/test_chat_givens_images.py +603 -0
- tests/test_chat_script_runner.py +454 -0
- tests/test_children_contribution_updater.py +98 -0
- tests/test_document_loader_office.py +267 -0
- 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 +416 -214
- tests/test_setup_default_chat_prompt_mode.py +198 -0
- tests/test_tag_extractor.py +95 -49
- tests/test_web_search.py +467 -0
- ara_cli/file_loaders/document_readers.py +0 -233
- ara_cli/file_loaders/file_loaders.py +0 -123
- ara_cli/file_loaders/text_file_loader.py +0 -187
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
- 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.5.dist-info/RECORD +0 -194
- /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
- /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for chat givens loading and image handling functionality.
|
|
3
|
+
|
|
4
|
+
These tests cover the functionality tested by:
|
|
5
|
+
- ara_chat_command_load_file_content.feature (givens and image loading scenarios)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import os
|
|
10
|
+
import tempfile
|
|
11
|
+
import base64
|
|
12
|
+
from unittest.mock import patch, MagicMock, mock_open
|
|
13
|
+
from types import SimpleNamespace
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_default_config():
|
|
17
|
+
"""Default config for test fixtures."""
|
|
18
|
+
return SimpleNamespace(
|
|
19
|
+
ext_code_dirs=[
|
|
20
|
+
{"source_dir": "./src"},
|
|
21
|
+
{"source_dir": "./tests"},
|
|
22
|
+
],
|
|
23
|
+
glossary_dir="./glossary",
|
|
24
|
+
doc_dir="./docs",
|
|
25
|
+
local_prompt_templates_dir="./ara/.araconfig",
|
|
26
|
+
local_ara_templates_dir="./ara/.araconfig/templates/",
|
|
27
|
+
ara_prompt_given_list_includes=[
|
|
28
|
+
"*.businessgoal",
|
|
29
|
+
"*.vision",
|
|
30
|
+
"*.capability",
|
|
31
|
+
"*.keyfeature",
|
|
32
|
+
"*.epic",
|
|
33
|
+
"*.userstory",
|
|
34
|
+
"*.example",
|
|
35
|
+
"*.feature",
|
|
36
|
+
"*.task",
|
|
37
|
+
"*.py",
|
|
38
|
+
"*.md",
|
|
39
|
+
"*.png",
|
|
40
|
+
"*.jpg",
|
|
41
|
+
"*.jpeg",
|
|
42
|
+
],
|
|
43
|
+
llm_config=[
|
|
44
|
+
{"provider": "openai", "model": "openai/gpt-4o", "temperature": 1.0},
|
|
45
|
+
],
|
|
46
|
+
global_dirs=[],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# =============================================================================
|
|
51
|
+
# Tests for _find_givens_files (givens file discovery)
|
|
52
|
+
# =============================================================================
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestFindGivensFiles:
|
|
56
|
+
"""Tests for the _find_givens_files method in Chat class."""
|
|
57
|
+
|
|
58
|
+
@pytest.fixture
|
|
59
|
+
def chat_instance(self):
|
|
60
|
+
"""Creates a chat instance with mocked config."""
|
|
61
|
+
from ara_cli.chat import Chat
|
|
62
|
+
|
|
63
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
64
|
+
chat_file = os.path.join(tmpdir, "test_chat.md")
|
|
65
|
+
with open(chat_file, "w") as f:
|
|
66
|
+
f.write("# ara prompt:\n")
|
|
67
|
+
|
|
68
|
+
mock_config = get_default_config()
|
|
69
|
+
with patch(
|
|
70
|
+
"ara_cli.prompt_handler.ConfigManager.get_config",
|
|
71
|
+
return_value=mock_config,
|
|
72
|
+
):
|
|
73
|
+
chat = Chat(chat_file, reset=False)
|
|
74
|
+
yield chat, tmpdir
|
|
75
|
+
|
|
76
|
+
def test_find_givens_files_explicit_file(self, chat_instance):
|
|
77
|
+
"""Finds explicitly specified givens file."""
|
|
78
|
+
chat, tmpdir = chat_instance
|
|
79
|
+
|
|
80
|
+
# Create a givens file
|
|
81
|
+
givens_file = os.path.join(tmpdir, "custom_givens.md")
|
|
82
|
+
with open(givens_file, "w") as f:
|
|
83
|
+
f.write("[x] some_file.py\n")
|
|
84
|
+
|
|
85
|
+
result = chat._find_givens_files("custom_givens.md")
|
|
86
|
+
assert len(result) == 1
|
|
87
|
+
assert result[0] == givens_file
|
|
88
|
+
|
|
89
|
+
def test_find_givens_files_default_location(self, chat_instance):
|
|
90
|
+
"""Finds default givens config file."""
|
|
91
|
+
chat, tmpdir = chat_instance
|
|
92
|
+
|
|
93
|
+
# Create prompt.data directory with default givens file
|
|
94
|
+
prompt_data_dir = os.path.join(tmpdir, "prompt.data")
|
|
95
|
+
os.makedirs(prompt_data_dir, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
givens_file = os.path.join(prompt_data_dir, "config.prompt_givens.md")
|
|
98
|
+
with open(givens_file, "w") as f:
|
|
99
|
+
f.write("[x] file1.py\n")
|
|
100
|
+
|
|
101
|
+
result = chat._find_givens_files("")
|
|
102
|
+
assert len(result) == 1
|
|
103
|
+
assert result[0] == givens_file
|
|
104
|
+
|
|
105
|
+
def test_find_givens_files_multiple_defaults(self, chat_instance):
|
|
106
|
+
"""Finds both local and global givens files."""
|
|
107
|
+
chat, tmpdir = chat_instance
|
|
108
|
+
|
|
109
|
+
# Create prompt.data directory with both files
|
|
110
|
+
prompt_data_dir = os.path.join(tmpdir, "prompt.data")
|
|
111
|
+
os.makedirs(prompt_data_dir, exist_ok=True)
|
|
112
|
+
|
|
113
|
+
local_givens = os.path.join(prompt_data_dir, "config.prompt_givens.md")
|
|
114
|
+
global_givens = os.path.join(prompt_data_dir, "config.prompt_global_givens.md")
|
|
115
|
+
|
|
116
|
+
with open(local_givens, "w") as f:
|
|
117
|
+
f.write("[x] local_file.py\n")
|
|
118
|
+
with open(global_givens, "w") as f:
|
|
119
|
+
f.write("[x] global_file.py\n")
|
|
120
|
+
|
|
121
|
+
result = chat._find_givens_files("")
|
|
122
|
+
assert len(result) == 2
|
|
123
|
+
|
|
124
|
+
def test_find_givens_files_not_found(self, chat_instance):
|
|
125
|
+
"""Returns empty list when file not found."""
|
|
126
|
+
chat, tmpdir = chat_instance
|
|
127
|
+
|
|
128
|
+
with patch("sys.stdin.readline", return_value="\n"):
|
|
129
|
+
result = chat._find_givens_files("")
|
|
130
|
+
|
|
131
|
+
assert result == []
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# =============================================================================
|
|
135
|
+
# Tests for LOAD_GIVENS command
|
|
136
|
+
# =============================================================================
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TestLoadGivensCommand:
|
|
140
|
+
"""Tests for the do_LOAD_GIVENS command."""
|
|
141
|
+
|
|
142
|
+
@pytest.fixture
|
|
143
|
+
def temp_chat_setup(self):
|
|
144
|
+
"""Creates a temporary chat setup for testing."""
|
|
145
|
+
from ara_cli.chat import Chat
|
|
146
|
+
|
|
147
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
148
|
+
chat_file = os.path.join(tmpdir, "test_chat.md")
|
|
149
|
+
with open(chat_file, "w") as f:
|
|
150
|
+
f.write("# ara prompt:\n")
|
|
151
|
+
|
|
152
|
+
# Create prompt.data directory
|
|
153
|
+
prompt_data_dir = os.path.join(tmpdir, "prompt.data")
|
|
154
|
+
os.makedirs(prompt_data_dir, exist_ok=True)
|
|
155
|
+
|
|
156
|
+
mock_config = get_default_config()
|
|
157
|
+
with patch(
|
|
158
|
+
"ara_cli.prompt_handler.ConfigManager.get_config",
|
|
159
|
+
return_value=mock_config,
|
|
160
|
+
):
|
|
161
|
+
chat = Chat(chat_file, reset=False)
|
|
162
|
+
|
|
163
|
+
yield {
|
|
164
|
+
"chat": chat,
|
|
165
|
+
"tmpdir": tmpdir,
|
|
166
|
+
"chat_file": chat_file,
|
|
167
|
+
"prompt_data_dir": prompt_data_dir,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
def test_load_givens_appends_content(self, temp_chat_setup):
|
|
171
|
+
"""LOAD_GIVENS appends file content to chat."""
|
|
172
|
+
setup = temp_chat_setup
|
|
173
|
+
|
|
174
|
+
# Create givens config with marked entry
|
|
175
|
+
givens_file = os.path.join(setup["prompt_data_dir"], "config.prompt_givens.md")
|
|
176
|
+
|
|
177
|
+
# Create a file to load
|
|
178
|
+
target_file = os.path.join(setup["tmpdir"], "to_load.py")
|
|
179
|
+
with open(target_file, "w") as f:
|
|
180
|
+
f.write("print('loaded content')")
|
|
181
|
+
|
|
182
|
+
# The givens file should reference the target file
|
|
183
|
+
with open(givens_file, "w") as f:
|
|
184
|
+
f.write(f"[x] {target_file}\n")
|
|
185
|
+
|
|
186
|
+
with patch(
|
|
187
|
+
"ara_cli.prompt_handler.load_givens",
|
|
188
|
+
return_value=("loaded givens content\n", []),
|
|
189
|
+
):
|
|
190
|
+
setup["chat"].do_LOAD_GIVENS("")
|
|
191
|
+
|
|
192
|
+
with open(setup["chat_file"], "r") as f:
|
|
193
|
+
content = f.read()
|
|
194
|
+
|
|
195
|
+
assert "loaded givens content" in content
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# =============================================================================
|
|
199
|
+
# Tests for image loading (LOAD_IMAGE command)
|
|
200
|
+
# =============================================================================
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class TestLoadImageCommand:
|
|
204
|
+
"""Tests for the do_LOAD_IMAGE command."""
|
|
205
|
+
|
|
206
|
+
@pytest.fixture
|
|
207
|
+
def chat_with_image(self):
|
|
208
|
+
"""Creates a chat instance with a test image file."""
|
|
209
|
+
from ara_cli.chat import Chat
|
|
210
|
+
|
|
211
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
212
|
+
chat_file = os.path.join(tmpdir, "test_chat.md")
|
|
213
|
+
with open(chat_file, "w") as f:
|
|
214
|
+
f.write("# ara prompt:\n")
|
|
215
|
+
|
|
216
|
+
# Create a fake image file
|
|
217
|
+
image_file = os.path.join(tmpdir, "test_image.png")
|
|
218
|
+
with open(image_file, "wb") as f:
|
|
219
|
+
f.write(b"\x89PNG\r\n\x1a\n" + b"fake_image_data")
|
|
220
|
+
|
|
221
|
+
mock_config = get_default_config()
|
|
222
|
+
with patch(
|
|
223
|
+
"ara_cli.prompt_handler.ConfigManager.get_config",
|
|
224
|
+
return_value=mock_config,
|
|
225
|
+
):
|
|
226
|
+
chat = Chat(chat_file, reset=False)
|
|
227
|
+
|
|
228
|
+
yield {
|
|
229
|
+
"chat": chat,
|
|
230
|
+
"tmpdir": tmpdir,
|
|
231
|
+
"chat_file": chat_file,
|
|
232
|
+
"image_file": image_file,
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
def test_load_image_recognizes_png(self, chat_with_image):
|
|
236
|
+
"""Recognizes PNG as valid image format."""
|
|
237
|
+
setup = chat_with_image
|
|
238
|
+
|
|
239
|
+
with patch.object(
|
|
240
|
+
setup["chat"],
|
|
241
|
+
"find_matching_files_to_load",
|
|
242
|
+
return_value=[setup["image_file"]],
|
|
243
|
+
):
|
|
244
|
+
with patch(
|
|
245
|
+
"ara_cli.commands.load_image_command.LoadImageCommand"
|
|
246
|
+
) as MockCmd:
|
|
247
|
+
mock_instance = MagicMock()
|
|
248
|
+
MockCmd.return_value = mock_instance
|
|
249
|
+
|
|
250
|
+
setup["chat"].do_LOAD_IMAGE("test_image.png")
|
|
251
|
+
|
|
252
|
+
MockCmd.assert_called_once()
|
|
253
|
+
mock_instance.execute.assert_called_once()
|
|
254
|
+
|
|
255
|
+
def test_load_image_rejects_unsupported_format(self, chat_with_image):
|
|
256
|
+
"""Rejects unsupported file formats."""
|
|
257
|
+
setup = chat_with_image
|
|
258
|
+
|
|
259
|
+
# Create a non-image file
|
|
260
|
+
text_file = os.path.join(setup["tmpdir"], "file.txt")
|
|
261
|
+
with open(text_file, "w") as f:
|
|
262
|
+
f.write("not an image")
|
|
263
|
+
|
|
264
|
+
with patch.object(
|
|
265
|
+
setup["chat"], "find_matching_files_to_load", return_value=[text_file]
|
|
266
|
+
):
|
|
267
|
+
# Should report error for unsupported format
|
|
268
|
+
with patch("ara_cli.error_handler.report_error") as mock_error:
|
|
269
|
+
setup["chat"].do_LOAD_IMAGE("file.txt")
|
|
270
|
+
mock_error.assert_called()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class TestLoadImageFileContent:
|
|
274
|
+
"""Tests for image file content loading into chat."""
|
|
275
|
+
|
|
276
|
+
@pytest.fixture
|
|
277
|
+
def temp_chat_file(self):
|
|
278
|
+
"""Creates a temporary chat file."""
|
|
279
|
+
from ara_cli.chat import Chat
|
|
280
|
+
|
|
281
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as f:
|
|
282
|
+
f.write("# ara prompt:\n")
|
|
283
|
+
temp_path = f.name
|
|
284
|
+
|
|
285
|
+
mock_config = get_default_config()
|
|
286
|
+
with patch(
|
|
287
|
+
"ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
|
|
288
|
+
):
|
|
289
|
+
chat = Chat(temp_path, reset=False)
|
|
290
|
+
|
|
291
|
+
yield chat, temp_path
|
|
292
|
+
|
|
293
|
+
# Cleanup
|
|
294
|
+
if os.path.exists(temp_path):
|
|
295
|
+
os.unlink(temp_path)
|
|
296
|
+
|
|
297
|
+
def test_load_image_returns_true(self, temp_chat_file):
|
|
298
|
+
"""load_image returns True for valid image."""
|
|
299
|
+
chat, temp_path = temp_chat_file
|
|
300
|
+
|
|
301
|
+
with patch.object(chat, "load_binary_file", return_value=True) as mock_load:
|
|
302
|
+
result = chat.load_image("test.png", prefix="PREFIX:", suffix=":SUFFIX")
|
|
303
|
+
|
|
304
|
+
mock_load.assert_called_once_with(
|
|
305
|
+
file_path="test.png",
|
|
306
|
+
mime_type="image/png",
|
|
307
|
+
prefix="PREFIX:",
|
|
308
|
+
suffix=":SUFFIX",
|
|
309
|
+
)
|
|
310
|
+
assert result is True
|
|
311
|
+
|
|
312
|
+
def test_load_image_handles_jpg(self, temp_chat_file):
|
|
313
|
+
"""load_image handles JPG extension."""
|
|
314
|
+
chat, temp_path = temp_chat_file
|
|
315
|
+
|
|
316
|
+
with patch.object(chat, "load_binary_file", return_value=True) as mock_load:
|
|
317
|
+
chat.load_image("photo.jpg")
|
|
318
|
+
|
|
319
|
+
mock_load.assert_called_once()
|
|
320
|
+
call_args = mock_load.call_args
|
|
321
|
+
assert call_args.kwargs["mime_type"] == "image/jpeg"
|
|
322
|
+
|
|
323
|
+
def test_load_image_handles_jpeg(self, temp_chat_file):
|
|
324
|
+
"""load_image handles JPEG extension."""
|
|
325
|
+
chat, temp_path = temp_chat_file
|
|
326
|
+
|
|
327
|
+
with patch.object(chat, "load_binary_file", return_value=True) as mock_load:
|
|
328
|
+
chat.load_image("photo.jpeg")
|
|
329
|
+
|
|
330
|
+
mock_load.assert_called_once()
|
|
331
|
+
call_args = mock_load.call_args
|
|
332
|
+
assert call_args.kwargs["mime_type"] == "image/jpeg"
|
|
333
|
+
|
|
334
|
+
def test_load_image_error_for_unsupported_webp(self, temp_chat_file):
|
|
335
|
+
"""WebP is not supported - verifies error handling."""
|
|
336
|
+
chat, temp_path = temp_chat_file
|
|
337
|
+
|
|
338
|
+
with patch('ara_cli.error_handler.report_error') as mock_error:
|
|
339
|
+
result = chat.load_image("image.webp")
|
|
340
|
+
# Should report error for unsupported format
|
|
341
|
+
mock_error.assert_called_once()
|
|
342
|
+
|
|
343
|
+
def test_load_image_unsupported_extension(self, temp_chat_file):
|
|
344
|
+
"""load_image reports error for unsupported format."""
|
|
345
|
+
chat, temp_path = temp_chat_file
|
|
346
|
+
|
|
347
|
+
with patch("ara_cli.error_handler.report_error") as mock_error:
|
|
348
|
+
result = chat.load_image("document.xyz")
|
|
349
|
+
|
|
350
|
+
mock_error.assert_called_once()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# =============================================================================
|
|
354
|
+
# Tests for find_matching_files_to_load (glob patterns)
|
|
355
|
+
# =============================================================================
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class TestFindMatchingFilesToLoad:
|
|
359
|
+
"""Tests for file matching with glob patterns."""
|
|
360
|
+
|
|
361
|
+
@pytest.fixture
|
|
362
|
+
def chat_with_files(self):
|
|
363
|
+
"""Creates a chat with multiple files in directory."""
|
|
364
|
+
from ara_cli.chat import Chat
|
|
365
|
+
|
|
366
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
367
|
+
chat_file = os.path.join(tmpdir, "test_chat.md")
|
|
368
|
+
with open(chat_file, "w") as f:
|
|
369
|
+
f.write("# ara prompt:\n")
|
|
370
|
+
|
|
371
|
+
# Create multiple test files
|
|
372
|
+
for name in ["file1.py", "file2.py", "readme.md", "image.png"]:
|
|
373
|
+
with open(os.path.join(tmpdir, name), "w") as f:
|
|
374
|
+
f.write(f"content of {name}")
|
|
375
|
+
|
|
376
|
+
mock_config = get_default_config()
|
|
377
|
+
with patch(
|
|
378
|
+
"ara_cli.prompt_handler.ConfigManager.get_config",
|
|
379
|
+
return_value=mock_config,
|
|
380
|
+
):
|
|
381
|
+
chat = Chat(chat_file, reset=False)
|
|
382
|
+
|
|
383
|
+
yield chat, tmpdir
|
|
384
|
+
|
|
385
|
+
def test_find_matching_files_single(self, chat_with_files):
|
|
386
|
+
"""Finds single file by name."""
|
|
387
|
+
chat, tmpdir = chat_with_files
|
|
388
|
+
|
|
389
|
+
result = chat.find_matching_files_to_load("file1.py")
|
|
390
|
+
|
|
391
|
+
assert len(result) == 1
|
|
392
|
+
assert result[0].endswith("file1.py")
|
|
393
|
+
|
|
394
|
+
def test_find_matching_files_glob_pattern(self, chat_with_files):
|
|
395
|
+
"""Finds files using glob pattern."""
|
|
396
|
+
chat, tmpdir = chat_with_files
|
|
397
|
+
|
|
398
|
+
result = chat.find_matching_files_to_load("*.py")
|
|
399
|
+
|
|
400
|
+
assert len(result) == 2
|
|
401
|
+
assert any("file1.py" in f for f in result)
|
|
402
|
+
assert any("file2.py" in f for f in result)
|
|
403
|
+
|
|
404
|
+
def test_find_matching_files_no_match(self, chat_with_files):
|
|
405
|
+
"""Returns None when no files match."""
|
|
406
|
+
chat, tmpdir = chat_with_files
|
|
407
|
+
|
|
408
|
+
with patch("ara_cli.error_handler.report_error"):
|
|
409
|
+
result = chat.find_matching_files_to_load("nonexistent.txt")
|
|
410
|
+
|
|
411
|
+
assert result is None
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# =============================================================================
|
|
415
|
+
# Tests for global directory loading
|
|
416
|
+
# =============================================================================
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class TestGlobalDirectoryLoading:
|
|
420
|
+
"""Tests for loading files from global directories."""
|
|
421
|
+
|
|
422
|
+
@pytest.fixture
|
|
423
|
+
def chat_with_global_dir(self):
|
|
424
|
+
"""Creates a chat with global directory configuration."""
|
|
425
|
+
from ara_cli.chat import Chat
|
|
426
|
+
|
|
427
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
428
|
+
chat_file = os.path.join(tmpdir, "test_chat.md")
|
|
429
|
+
with open(chat_file, "w") as f:
|
|
430
|
+
f.write("# ara prompt:\n")
|
|
431
|
+
|
|
432
|
+
# Create global directory with files
|
|
433
|
+
global_dir = os.path.join(tmpdir, "global_files")
|
|
434
|
+
os.makedirs(global_dir, exist_ok=True)
|
|
435
|
+
|
|
436
|
+
global_file = os.path.join(global_dir, "shared_code.py")
|
|
437
|
+
with open(global_file, "w") as f:
|
|
438
|
+
f.write("# Shared code\ndef helper(): pass")
|
|
439
|
+
|
|
440
|
+
mock_config = get_default_config()
|
|
441
|
+
mock_config.global_dirs = [{"source_dir": global_dir}]
|
|
442
|
+
|
|
443
|
+
with patch(
|
|
444
|
+
"ara_cli.prompt_handler.ConfigManager.get_config",
|
|
445
|
+
return_value=mock_config,
|
|
446
|
+
):
|
|
447
|
+
chat = Chat(chat_file, reset=False)
|
|
448
|
+
|
|
449
|
+
yield {
|
|
450
|
+
"chat": chat,
|
|
451
|
+
"tmpdir": tmpdir,
|
|
452
|
+
"global_dir": global_dir,
|
|
453
|
+
"global_file": global_file,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
def test_global_dir_in_config(self, chat_with_global_dir):
|
|
457
|
+
"""Global directory is accessible in config."""
|
|
458
|
+
setup = chat_with_global_dir
|
|
459
|
+
|
|
460
|
+
assert len(setup["chat"].config.global_dirs) == 1
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# =============================================================================
|
|
464
|
+
# Tests for LOAD command with --load-images flag
|
|
465
|
+
# =============================================================================
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class TestLoadWithImages:
|
|
469
|
+
"""Tests for LOAD command with image extraction."""
|
|
470
|
+
|
|
471
|
+
def test_load_command_class_accepts_extract_images(self):
|
|
472
|
+
"""LoadCommand accepts extract_images parameter."""
|
|
473
|
+
from ara_cli.commands.load_command import LoadCommand
|
|
474
|
+
import inspect
|
|
475
|
+
|
|
476
|
+
# Check that LoadCommand.__init__ accepts extract_images parameter
|
|
477
|
+
sig = inspect.signature(LoadCommand.__init__)
|
|
478
|
+
params = list(sig.parameters.keys())
|
|
479
|
+
assert 'extract_images' in params
|
|
480
|
+
|
|
481
|
+
def test_load_command_instantiation_with_extract_images(self):
|
|
482
|
+
"""LoadCommand can be instantiated with extract_images=True."""
|
|
483
|
+
from ara_cli.commands.load_command import LoadCommand
|
|
484
|
+
|
|
485
|
+
# Should not raise an error
|
|
486
|
+
cmd = LoadCommand(
|
|
487
|
+
chat_instance=MagicMock(),
|
|
488
|
+
file_path="test.md",
|
|
489
|
+
prefix="",
|
|
490
|
+
block_delimiter="```",
|
|
491
|
+
extract_images=True,
|
|
492
|
+
output=print
|
|
493
|
+
)
|
|
494
|
+
assert cmd is not None
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
# =============================================================================
|
|
498
|
+
# Tests for binary content appending (base64 encoding)
|
|
499
|
+
# =============================================================================
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class TestBinaryContentAppending:
|
|
503
|
+
"""Tests for binary file content being appended as base64."""
|
|
504
|
+
|
|
505
|
+
@pytest.fixture
|
|
506
|
+
def chat_instance(self):
|
|
507
|
+
"""Creates a chat instance for testing."""
|
|
508
|
+
from ara_cli.chat import Chat
|
|
509
|
+
|
|
510
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
511
|
+
chat_file = os.path.join(tmpdir, "test_chat.md")
|
|
512
|
+
with open(chat_file, "w") as f:
|
|
513
|
+
f.write("# ara prompt:\n")
|
|
514
|
+
|
|
515
|
+
mock_config = get_default_config()
|
|
516
|
+
with patch(
|
|
517
|
+
"ara_cli.prompt_handler.ConfigManager.get_config",
|
|
518
|
+
return_value=mock_config,
|
|
519
|
+
):
|
|
520
|
+
chat = Chat(chat_file, reset=False)
|
|
521
|
+
|
|
522
|
+
yield chat, tmpdir, chat_file
|
|
523
|
+
|
|
524
|
+
def test_binary_file_content_base64_encoded(self, chat_instance):
|
|
525
|
+
"""Binary file content is base64 encoded when appended."""
|
|
526
|
+
chat, tmpdir, chat_file = chat_instance
|
|
527
|
+
|
|
528
|
+
# Create a binary file
|
|
529
|
+
binary_file = os.path.join(tmpdir, "image.png")
|
|
530
|
+
binary_content = b"\x89PNG\r\n\x1a\ntest_binary_data"
|
|
531
|
+
with open(binary_file, "wb") as f:
|
|
532
|
+
f.write(binary_content)
|
|
533
|
+
|
|
534
|
+
expected_base64 = base64.b64encode(binary_content).decode("utf-8")
|
|
535
|
+
|
|
536
|
+
# Mock the open calls to capture what gets written
|
|
537
|
+
with patch(
|
|
538
|
+
"ara_cli.file_loaders.loaders.binary_file_loader.open",
|
|
539
|
+
mock_open(read_data=binary_content),
|
|
540
|
+
) as mock_file:
|
|
541
|
+
chat.load_binary_file(binary_file, mime_type="image/png")
|
|
542
|
+
|
|
543
|
+
# Check that the file was written with base64 content
|
|
544
|
+
write_calls = mock_file().write.call_args_list
|
|
545
|
+
if write_calls:
|
|
546
|
+
written_content = "".join(str(call[0][0]) for call in write_calls)
|
|
547
|
+
assert expected_base64 in written_content or "base64" in written_content
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
# =============================================================================
|
|
551
|
+
# Tests for choose_file_to_load (user file selection)
|
|
552
|
+
# =============================================================================
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
class TestChooseFileToLoad:
|
|
556
|
+
"""Tests for interactive file selection."""
|
|
557
|
+
|
|
558
|
+
@pytest.fixture
|
|
559
|
+
def chat_instance(self):
|
|
560
|
+
"""Creates a chat instance for testing."""
|
|
561
|
+
from ara_cli.chat import Chat
|
|
562
|
+
|
|
563
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
564
|
+
chat_file = os.path.join(tmpdir, "test_chat.md")
|
|
565
|
+
with open(chat_file, "w") as f:
|
|
566
|
+
f.write("# ara prompt:\n")
|
|
567
|
+
|
|
568
|
+
mock_config = get_default_config()
|
|
569
|
+
with patch(
|
|
570
|
+
"ara_cli.prompt_handler.ConfigManager.get_config",
|
|
571
|
+
return_value=mock_config,
|
|
572
|
+
):
|
|
573
|
+
chat = Chat(chat_file, reset=False)
|
|
574
|
+
|
|
575
|
+
yield chat
|
|
576
|
+
|
|
577
|
+
def test_choose_file_to_load_single_file(self, chat_instance):
|
|
578
|
+
"""Single file is returned without prompting."""
|
|
579
|
+
files = ["/path/to/file.py"]
|
|
580
|
+
|
|
581
|
+
result = chat_instance.choose_file_to_load(files, "*.py")
|
|
582
|
+
|
|
583
|
+
assert result == "/path/to/file.py"
|
|
584
|
+
|
|
585
|
+
def test_choose_file_to_load_multiple_files_user_selects(self, chat_instance):
|
|
586
|
+
"""Multiple files prompts user for selection."""
|
|
587
|
+
files = ["/path/to/file1.py", "/path/to/file2.py"]
|
|
588
|
+
|
|
589
|
+
with patch("sys.stdin.readline", return_value="1\n"):
|
|
590
|
+
result = chat_instance.choose_file_to_load(files, "*.py")
|
|
591
|
+
|
|
592
|
+
# User selects option 1 (0-indexed would be file1.py)
|
|
593
|
+
assert result == "/path/to/file1.py"
|
|
594
|
+
|
|
595
|
+
def test_choose_file_to_load_invalid_choice(self, chat_instance):
|
|
596
|
+
"""Invalid choice returns None."""
|
|
597
|
+
files = ["/path/to/file1.py", "/path/to/file2.py"]
|
|
598
|
+
|
|
599
|
+
with patch("sys.stdin.readline", return_value="99\n"):
|
|
600
|
+
with patch("ara_cli.error_handler.report_error"):
|
|
601
|
+
result = chat_instance.choose_file_to_load(files, "*.py")
|
|
602
|
+
|
|
603
|
+
assert result is None
|