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,312 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for artefact_converter.py
|
|
3
|
+
|
|
4
|
+
Provides full test coverage for the AraArtefactConverter class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
from unittest.mock import patch, MagicMock, mock_open
|
|
11
|
+
from ara_cli.artefact_converter import AraArtefactConverter
|
|
12
|
+
from ara_cli.error_handler import AraError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def mock_file_system():
|
|
17
|
+
"""Mock file system for testing."""
|
|
18
|
+
mock_fs = MagicMock()
|
|
19
|
+
mock_fs.path = MagicMock()
|
|
20
|
+
mock_fs.path.join = os.path.join
|
|
21
|
+
mock_fs.path.exists = MagicMock(return_value=False)
|
|
22
|
+
return mock_fs
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def converter(mock_file_system):
|
|
27
|
+
"""Create converter instance with mocked dependencies."""
|
|
28
|
+
with patch("ara_cli.artefact_converter.ArtefactReader") as mock_reader, patch(
|
|
29
|
+
"ara_cli.artefact_converter.ArtefactCreator"
|
|
30
|
+
):
|
|
31
|
+
converter = AraArtefactConverter(file_system=mock_file_system)
|
|
32
|
+
converter.reader = mock_reader.return_value
|
|
33
|
+
return converter
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestAraArtefactConverterInit:
|
|
37
|
+
"""Tests for AraArtefactConverter initialization."""
|
|
38
|
+
|
|
39
|
+
def test_uses_provided_file_system(self, mock_file_system):
|
|
40
|
+
"""Uses provided file system when given."""
|
|
41
|
+
with patch("ara_cli.artefact_converter.ArtefactReader"), patch(
|
|
42
|
+
"ara_cli.artefact_converter.ArtefactCreator"
|
|
43
|
+
):
|
|
44
|
+
converter = AraArtefactConverter(file_system=mock_file_system)
|
|
45
|
+
assert converter.file_system == mock_file_system
|
|
46
|
+
|
|
47
|
+
def test_uses_os_as_default_file_system(self):
|
|
48
|
+
"""Uses os module as default file system."""
|
|
49
|
+
with patch("ara_cli.artefact_converter.ArtefactReader"), patch(
|
|
50
|
+
"ara_cli.artefact_converter.ArtefactCreator"
|
|
51
|
+
):
|
|
52
|
+
converter = AraArtefactConverter()
|
|
53
|
+
assert converter.file_system == os
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TestValidateClassifiers:
|
|
57
|
+
"""Tests for classifier validation."""
|
|
58
|
+
|
|
59
|
+
@patch("ara_cli.artefact_converter.Classifier.is_valid_classifier")
|
|
60
|
+
def test_raises_for_invalid_old_classifier(self, mock_is_valid, converter):
|
|
61
|
+
"""Raises ValueError for invalid old classifier."""
|
|
62
|
+
mock_is_valid.side_effect = lambda x: x != "invalid"
|
|
63
|
+
|
|
64
|
+
with pytest.raises(ValueError) as exc_info:
|
|
65
|
+
converter._validate_classifiers("invalid", "feature")
|
|
66
|
+
|
|
67
|
+
assert "Invalid classifier: invalid" in str(exc_info.value)
|
|
68
|
+
|
|
69
|
+
@patch("ara_cli.artefact_converter.Classifier.is_valid_classifier")
|
|
70
|
+
def test_raises_for_invalid_new_classifier(self, mock_is_valid, converter):
|
|
71
|
+
"""Raises ValueError for invalid new classifier."""
|
|
72
|
+
mock_is_valid.side_effect = lambda x: x != "invalid"
|
|
73
|
+
|
|
74
|
+
with pytest.raises(ValueError) as exc_info:
|
|
75
|
+
converter._validate_classifiers("feature", "invalid")
|
|
76
|
+
|
|
77
|
+
assert "Invalid classifier: invalid" in str(exc_info.value)
|
|
78
|
+
|
|
79
|
+
@patch(
|
|
80
|
+
"ara_cli.artefact_converter.Classifier.is_valid_classifier", return_value=True
|
|
81
|
+
)
|
|
82
|
+
def test_passes_for_valid_classifiers(self, mock_is_valid, converter):
|
|
83
|
+
"""Passes validation for valid classifiers."""
|
|
84
|
+
converter._validate_classifiers("feature", "userstory")
|
|
85
|
+
assert mock_is_valid.call_count == 2
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestResolveTargetContent:
|
|
89
|
+
"""Tests for target content resolution."""
|
|
90
|
+
|
|
91
|
+
def test_raises_when_target_exists_without_flags(self, converter):
|
|
92
|
+
"""Raises ValueError when target exists and no override/merge flags."""
|
|
93
|
+
converter.reader.read_artefact_data.return_value = (None, {"exists": True})
|
|
94
|
+
|
|
95
|
+
with pytest.raises(ValueError) as exc_info:
|
|
96
|
+
converter._resolve_target_content(
|
|
97
|
+
"test", "feature", merge=False, override=False
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
assert "already existing" in str(exc_info.value)
|
|
101
|
+
|
|
102
|
+
def test_returns_content_when_merge_flag_set(self, converter):
|
|
103
|
+
"""Returns existing content when merge flag is set."""
|
|
104
|
+
converter.reader.read_artefact_data.return_value = (
|
|
105
|
+
"existing content",
|
|
106
|
+
{"exists": True},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
result = converter._resolve_target_content(
|
|
110
|
+
"test", "feature", merge=True, override=False
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
assert result == "existing content"
|
|
114
|
+
|
|
115
|
+
def test_returns_none_when_override_flag_set(self, converter):
|
|
116
|
+
"""Returns None when override flag is set (different path)."""
|
|
117
|
+
converter.reader.read_artefact_data.return_value = (None, {"exists": True})
|
|
118
|
+
|
|
119
|
+
# Override skips the existence check entirely
|
|
120
|
+
result = converter._resolve_target_content(
|
|
121
|
+
"test", "feature", merge=False, override=True
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# This won't raise because override=True skips the check
|
|
125
|
+
assert result is None
|
|
126
|
+
|
|
127
|
+
def test_returns_none_when_no_existing_target(self, converter):
|
|
128
|
+
"""Returns None when no existing target artefact."""
|
|
129
|
+
converter.reader.read_artefact_data.return_value = (None, None)
|
|
130
|
+
|
|
131
|
+
result = converter._resolve_target_content(
|
|
132
|
+
"test", "feature", merge=False, override=False
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
assert result is None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestGetTargetClass:
|
|
139
|
+
"""Tests for getting target artefact class."""
|
|
140
|
+
|
|
141
|
+
def test_returns_class_for_valid_classifier(self, converter):
|
|
142
|
+
"""Returns artefact class for valid classifier."""
|
|
143
|
+
# Using a known classifier
|
|
144
|
+
result = converter._get_target_class("feature")
|
|
145
|
+
assert result is not None
|
|
146
|
+
|
|
147
|
+
def test_raises_for_invalid_classifier(self, converter):
|
|
148
|
+
"""Raises ValueError for invalid classifier string."""
|
|
149
|
+
with pytest.raises(ValueError):
|
|
150
|
+
converter._get_target_class("definitely_not_a_type")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class TestGetPrompt:
|
|
154
|
+
"""Tests for prompt generation."""
|
|
155
|
+
|
|
156
|
+
@patch("ara_cli.artefact_converter.LLMSingleton")
|
|
157
|
+
def test_uses_fallback_when_langfuse_unavailable(self, mock_singleton, converter):
|
|
158
|
+
"""Uses fallback prompt when Langfuse is unavailable."""
|
|
159
|
+
mock_singleton.get_instance.return_value.langfuse = None
|
|
160
|
+
|
|
161
|
+
result = converter._get_prompt(
|
|
162
|
+
old_classifier="feature",
|
|
163
|
+
new_classifier="userstory",
|
|
164
|
+
artefact_name="test",
|
|
165
|
+
content="content",
|
|
166
|
+
target_content_existing=None,
|
|
167
|
+
merge=False,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
assert "Convert the following feature artefact" in result
|
|
171
|
+
assert "content" in result
|
|
172
|
+
|
|
173
|
+
@patch("ara_cli.artefact_converter.LLMSingleton")
|
|
174
|
+
def test_merge_prompt_includes_both_contents(self, mock_singleton, converter):
|
|
175
|
+
"""Merge prompt includes source and target content."""
|
|
176
|
+
mock_singleton.get_instance.return_value.langfuse = None
|
|
177
|
+
|
|
178
|
+
result = converter._get_prompt(
|
|
179
|
+
old_classifier="feature",
|
|
180
|
+
new_classifier="userstory",
|
|
181
|
+
artefact_name="test",
|
|
182
|
+
content="source content",
|
|
183
|
+
target_content_existing="target content",
|
|
184
|
+
merge=True,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
assert "Merge" in result
|
|
188
|
+
assert "source content" in result
|
|
189
|
+
assert "target content" in result
|
|
190
|
+
|
|
191
|
+
@patch("ara_cli.artefact_converter.LLMSingleton")
|
|
192
|
+
def test_uses_langfuse_prompt_when_available(self, mock_singleton, converter):
|
|
193
|
+
"""Uses Langfuse prompt when available."""
|
|
194
|
+
mock_langfuse = MagicMock()
|
|
195
|
+
mock_prompt = MagicMock()
|
|
196
|
+
mock_prompt.compile.return_value = "langfuse prompt"
|
|
197
|
+
mock_langfuse.get_prompt.return_value = mock_prompt
|
|
198
|
+
mock_singleton.get_instance.return_value.langfuse = mock_langfuse
|
|
199
|
+
|
|
200
|
+
result = converter._get_prompt(
|
|
201
|
+
old_classifier="feature",
|
|
202
|
+
new_classifier="userstory",
|
|
203
|
+
artefact_name="test",
|
|
204
|
+
content="content",
|
|
205
|
+
target_content_existing=None,
|
|
206
|
+
merge=False,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
assert result == "langfuse prompt"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class TestRunConversionAgent:
|
|
213
|
+
"""Tests for running the conversion agent."""
|
|
214
|
+
|
|
215
|
+
@patch("ara_cli.llm_utils.create_pydantic_ai_agent")
|
|
216
|
+
def test_returns_converted_artefact(self, mock_create_agent, converter):
|
|
217
|
+
"""Returns converted artefact from agent."""
|
|
218
|
+
mock_result = MagicMock()
|
|
219
|
+
mock_result.output = "converted artefact"
|
|
220
|
+
mock_agent = MagicMock()
|
|
221
|
+
mock_agent.run_sync.return_value = mock_result
|
|
222
|
+
mock_create_agent.return_value = mock_agent
|
|
223
|
+
|
|
224
|
+
result = converter._run_conversion_agent("prompt", MagicMock)
|
|
225
|
+
|
|
226
|
+
assert result == "converted artefact"
|
|
227
|
+
|
|
228
|
+
@patch("ara_cli.llm_utils.create_pydantic_ai_agent")
|
|
229
|
+
def test_raises_ara_error_on_failure(self, mock_create_agent, converter):
|
|
230
|
+
"""Raises AraError when agent fails."""
|
|
231
|
+
mock_agent = MagicMock()
|
|
232
|
+
mock_agent.run_sync.side_effect = Exception("LLM error")
|
|
233
|
+
mock_create_agent.return_value = mock_agent
|
|
234
|
+
|
|
235
|
+
with pytest.raises(AraError) as exc_info:
|
|
236
|
+
converter._run_conversion_agent("prompt", MagicMock)
|
|
237
|
+
|
|
238
|
+
assert "LLM conversion failed" in str(exc_info.value)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class TestWriteArtefact:
|
|
242
|
+
"""Tests for writing converted artefacts."""
|
|
243
|
+
|
|
244
|
+
@patch("ara_cli.artefact_converter.DirectoryNavigator")
|
|
245
|
+
@patch(
|
|
246
|
+
"ara_cli.artefact_converter.Classifier.get_sub_directory",
|
|
247
|
+
return_value="features",
|
|
248
|
+
)
|
|
249
|
+
@patch("builtins.open", new_callable=mock_open)
|
|
250
|
+
@patch("os.makedirs")
|
|
251
|
+
@patch("shutil.rmtree")
|
|
252
|
+
def test_writes_artefact_file(
|
|
253
|
+
self,
|
|
254
|
+
mock_rmtree,
|
|
255
|
+
mock_makedirs,
|
|
256
|
+
mock_file,
|
|
257
|
+
mock_subdir,
|
|
258
|
+
mock_navigator,
|
|
259
|
+
converter,
|
|
260
|
+
mock_file_system,
|
|
261
|
+
):
|
|
262
|
+
"""Writes artefact content to file."""
|
|
263
|
+
mock_file_system.path.exists.return_value = False
|
|
264
|
+
|
|
265
|
+
converter._write_artefact(
|
|
266
|
+
"feature", "test", "content", merge=False, override=False
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
mock_file.assert_called()
|
|
270
|
+
mock_file().write.assert_called_with("content")
|
|
271
|
+
|
|
272
|
+
@patch("ara_cli.artefact_converter.DirectoryNavigator")
|
|
273
|
+
@patch(
|
|
274
|
+
"ara_cli.artefact_converter.Classifier.get_sub_directory",
|
|
275
|
+
return_value="features",
|
|
276
|
+
)
|
|
277
|
+
def test_raises_when_file_exists_without_flags(
|
|
278
|
+
self, mock_subdir, mock_navigator, converter, mock_file_system
|
|
279
|
+
):
|
|
280
|
+
"""Raises ValueError when target file exists without override/merge."""
|
|
281
|
+
mock_file_system.path.exists.return_value = True
|
|
282
|
+
|
|
283
|
+
with pytest.raises(ValueError) as exc_info:
|
|
284
|
+
converter._write_artefact(
|
|
285
|
+
"feature", "test", "content", merge=False, override=False
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
assert "already exists" in str(exc_info.value)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class TestConvert:
|
|
292
|
+
"""Integration tests for the convert method."""
|
|
293
|
+
|
|
294
|
+
@patch(
|
|
295
|
+
"ara_cli.artefact_converter.Classifier.is_valid_classifier", return_value=True
|
|
296
|
+
)
|
|
297
|
+
def test_raises_when_source_not_found(self, mock_is_valid, converter):
|
|
298
|
+
"""Raises AraError when source artefact not found."""
|
|
299
|
+
converter.reader.read_artefact_data.return_value = (None, None)
|
|
300
|
+
|
|
301
|
+
with pytest.raises(AraError) as exc_info:
|
|
302
|
+
converter.convert("feature", "test", "userstory")
|
|
303
|
+
|
|
304
|
+
assert "not found" in str(exc_info.value)
|
|
305
|
+
|
|
306
|
+
@patch("ara_cli.artefact_converter.Classifier.is_valid_classifier")
|
|
307
|
+
def test_raises_for_invalid_classifier(self, mock_is_valid, converter):
|
|
308
|
+
"""Raises ValueError for invalid classifier."""
|
|
309
|
+
mock_is_valid.return_value = False
|
|
310
|
+
|
|
311
|
+
with pytest.raises(ValueError):
|
|
312
|
+
converter.convert("invalid", "test", "userstory")
|