ara-cli 0.1.13.3__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 +1 -1
- ara_cli/ara_command_action.py +162 -112
- ara_cli/ara_config.py +1 -1
- ara_cli/ara_subcommands/convert.py +66 -2
- ara_cli/ara_subcommands/prompt.py +266 -106
- ara_cli/artefact_autofix.py +2 -2
- ara_cli/artefact_converter.py +152 -53
- 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 +23 -15
- ara_cli/children_contribution_updater.py +737 -0
- ara_cli/classifier.py +34 -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 +24 -0
- 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/output_suppressor.py +53 -0
- ara_cli/prompt_handler.py +123 -17
- ara_cli/tag_extractor.py +8 -7
- ara_cli/version.py +1 -1
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +18 -12
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/RECORD +58 -45
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
- tests/test_artefact_converter.py +1 -46
- tests/test_artefact_lister.py +11 -8
- tests/test_chat.py +4 -4
- tests/test_chat_givens_images.py +1 -1
- tests/test_children_contribution_updater.py +98 -0
- tests/test_document_loader_office.py +267 -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
- 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/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.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import MagicMock, patch, ANY
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
# Mock the new dependencies to avoid import errors during test collection if they are not installed
|
|
7
|
+
sys.modules["pandas"] = MagicMock()
|
|
8
|
+
sys.modules["openpyxl"] = MagicMock()
|
|
9
|
+
sys.modules["pptx"] = MagicMock()
|
|
10
|
+
sys.modules["pptx.presentation"] = MagicMock()
|
|
11
|
+
sys.modules["pptx.enum.shapes"] = MagicMock()
|
|
12
|
+
|
|
13
|
+
from ara_cli.file_loaders.readers.excel_reader import ExcelReader
|
|
14
|
+
from ara_cli.file_loaders.readers.pptx_reader import PptxReader
|
|
15
|
+
from ara_cli.file_loaders.factories.document_reader_factory import DocumentReaderFactory
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestExcelReader(unittest.TestCase):
|
|
19
|
+
@patch("pandas.read_excel")
|
|
20
|
+
def test_read_excel_success(self, mock_read_excel):
|
|
21
|
+
# Setup mock dataframe
|
|
22
|
+
mock_df = MagicMock()
|
|
23
|
+
mock_df.empty = False
|
|
24
|
+
mock_df.to_markdown.return_value = "| Col1 | Col2 |\n|---|---|\n| Val1 | Val2 |"
|
|
25
|
+
mock_df.fillna.return_value = mock_df
|
|
26
|
+
|
|
27
|
+
# Setup read_excel return value (dict of sheets)
|
|
28
|
+
mock_read_excel.return_value = {"Sheet1": mock_df}
|
|
29
|
+
|
|
30
|
+
reader = ExcelReader("test.xlsx")
|
|
31
|
+
content = reader.read()
|
|
32
|
+
|
|
33
|
+
self.assertIn("### Sheet: Sheet1", content)
|
|
34
|
+
self.assertIn("| Col1 | Col2 |", content)
|
|
35
|
+
mock_read_excel.assert_called_once_with("test.xlsx", sheet_name=None)
|
|
36
|
+
mock_df.to_markdown.assert_called_once()
|
|
37
|
+
|
|
38
|
+
@patch("pandas.read_excel")
|
|
39
|
+
def test_read_excel_empty_sheet(self, mock_read_excel):
|
|
40
|
+
mock_df = MagicMock()
|
|
41
|
+
mock_df.empty = True
|
|
42
|
+
|
|
43
|
+
mock_read_excel.return_value = {"SheetEmpty": mock_df}
|
|
44
|
+
|
|
45
|
+
reader = ExcelReader("test.xlsx")
|
|
46
|
+
content = reader.read()
|
|
47
|
+
|
|
48
|
+
self.assertIn("### Sheet: SheetEmpty", content)
|
|
49
|
+
self.assertIn("_Empty Sheet_", content)
|
|
50
|
+
|
|
51
|
+
@patch("pandas.read_excel")
|
|
52
|
+
def test_read_excel_error(self, mock_read_excel):
|
|
53
|
+
mock_read_excel.side_effect = Exception("File not found")
|
|
54
|
+
|
|
55
|
+
reader = ExcelReader("test.xlsx")
|
|
56
|
+
content = reader.read()
|
|
57
|
+
|
|
58
|
+
self.assertIn("Error reading Excel file: File not found", content)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TestPptxReader(unittest.TestCase):
|
|
62
|
+
@patch("pptx.Presentation")
|
|
63
|
+
def test_read_pptx_success(self, mock_presentation):
|
|
64
|
+
# Setup mock presentation
|
|
65
|
+
mock_prs = MagicMock()
|
|
66
|
+
mock_slide = MagicMock()
|
|
67
|
+
|
|
68
|
+
# Setup shapes
|
|
69
|
+
mock_shape_title = MagicMock()
|
|
70
|
+
mock_shape_title.has_text_frame = True
|
|
71
|
+
mock_shape_title.text_frame.text = "Slide Title"
|
|
72
|
+
mock_shape_title.top = 0
|
|
73
|
+
mock_shape_title.left = 0
|
|
74
|
+
|
|
75
|
+
mock_shape_content = MagicMock()
|
|
76
|
+
mock_shape_content.has_text_frame = True
|
|
77
|
+
mock_shape_content.text_frame.paragraphs = [
|
|
78
|
+
MagicMock(text="Bullet 1"),
|
|
79
|
+
MagicMock(text="Bullet 2"),
|
|
80
|
+
]
|
|
81
|
+
mock_shape_content.top = 100
|
|
82
|
+
mock_shape_content.left = 0
|
|
83
|
+
|
|
84
|
+
# slide.shapes must be a mock that is iterable AND has a title attribute
|
|
85
|
+
mock_shapes = MagicMock()
|
|
86
|
+
mock_shapes.__iter__.return_value = [mock_shape_title, mock_shape_content]
|
|
87
|
+
mock_shapes.title = mock_shape_title
|
|
88
|
+
|
|
89
|
+
mock_slide.shapes = mock_shapes
|
|
90
|
+
|
|
91
|
+
mock_slide.shapes = mock_shapes
|
|
92
|
+
mock_prs.slides = [mock_slide]
|
|
93
|
+
|
|
94
|
+
mock_presentation.return_value = mock_prs
|
|
95
|
+
|
|
96
|
+
reader = PptxReader("test.pptx")
|
|
97
|
+
content = reader.read()
|
|
98
|
+
|
|
99
|
+
self.assertIn("## Slide 1", content)
|
|
100
|
+
self.assertIn("### Slide Title", content)
|
|
101
|
+
self.assertIn("- Bullet 1", content)
|
|
102
|
+
self.assertIn("- Bullet 2", content)
|
|
103
|
+
|
|
104
|
+
@patch("pptx.Presentation")
|
|
105
|
+
def test_read_pptx_with_images(self, mock_presentation):
|
|
106
|
+
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
|
107
|
+
|
|
108
|
+
# Setup mock presentation
|
|
109
|
+
mock_prs = MagicMock()
|
|
110
|
+
mock_slide = MagicMock()
|
|
111
|
+
|
|
112
|
+
# Setup shapes
|
|
113
|
+
mock_shape_image = MagicMock()
|
|
114
|
+
mock_shape_image.has_text_frame = False
|
|
115
|
+
mock_shape_image.shape_type = MSO_SHAPE_TYPE.PICTURE
|
|
116
|
+
mock_shape_image.image.blob = b"fake_image_data"
|
|
117
|
+
mock_shape_image.image.ext = "png"
|
|
118
|
+
mock_shape_image.top = 0
|
|
119
|
+
mock_shape_image.left = 0
|
|
120
|
+
|
|
121
|
+
# Mock shapes as list is fine here if we don't access .title, but cleaner to be consistent if code checks .title
|
|
122
|
+
mock_shapes = MagicMock()
|
|
123
|
+
mock_shapes.__iter__.return_value = [mock_shape_image]
|
|
124
|
+
# If code checks slide.shapes.title, we should ensure it doesn't crash or has it
|
|
125
|
+
# The code does: try: if shape == slide.shapes.title: ... except AttributeError: ...
|
|
126
|
+
# So it handles missing title attribute.
|
|
127
|
+
|
|
128
|
+
mock_slide.shapes = mock_shapes
|
|
129
|
+
mock_prs.slides = [mock_slide]
|
|
130
|
+
mock_presentation.return_value = mock_prs
|
|
131
|
+
|
|
132
|
+
# Mock create_image_data_dir and save_and_describe_image
|
|
133
|
+
with patch.object(
|
|
134
|
+
PptxReader, "create_image_data_dir"
|
|
135
|
+
) as mock_create_dir, patch.object(
|
|
136
|
+
PptxReader, "save_and_describe_image"
|
|
137
|
+
) as mock_save_describe:
|
|
138
|
+
|
|
139
|
+
mock_create_dir.return_value = "fake/dir"
|
|
140
|
+
mock_save_describe.return_value = (
|
|
141
|
+
"fake/dir/1.png",
|
|
142
|
+
"A fake image description",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
reader = PptxReader("test.pptx")
|
|
146
|
+
content = reader.read(extract_images=True)
|
|
147
|
+
|
|
148
|
+
self.assertIn("## Slide 1", content)
|
|
149
|
+
self.assertIn("Image: fake/dir/1.png", content)
|
|
150
|
+
self.assertIn("[A fake image description]", content)
|
|
151
|
+
|
|
152
|
+
@patch("pptx.Presentation")
|
|
153
|
+
def test_read_pptx_groups_and_placeholders(self, mock_presentation):
|
|
154
|
+
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
|
155
|
+
|
|
156
|
+
mock_prs = MagicMock()
|
|
157
|
+
mock_slide = MagicMock()
|
|
158
|
+
|
|
159
|
+
# 1. Group Shape containing an image
|
|
160
|
+
mock_group = MagicMock()
|
|
161
|
+
mock_group.shape_type = MSO_SHAPE_TYPE.GROUP
|
|
162
|
+
|
|
163
|
+
mock_sub_image = MagicMock()
|
|
164
|
+
mock_sub_image.shape_type = MSO_SHAPE_TYPE.PICTURE
|
|
165
|
+
mock_sub_image.image.blob = b"group_img"
|
|
166
|
+
mock_sub_image.image.ext = "jpg"
|
|
167
|
+
|
|
168
|
+
mock_group.shapes = [mock_sub_image]
|
|
169
|
+
mock_group.top = 0
|
|
170
|
+
mock_group.left = 0
|
|
171
|
+
|
|
172
|
+
# 2. Placeholder with image
|
|
173
|
+
mock_placeholder = MagicMock()
|
|
174
|
+
mock_placeholder.shape_type = 0 # Not PICTURE
|
|
175
|
+
mock_placeholder.is_placeholder = True
|
|
176
|
+
mock_placeholder.image.blob = b"place_img"
|
|
177
|
+
mock_placeholder.image.ext = "png"
|
|
178
|
+
mock_placeholder.top = 100
|
|
179
|
+
mock_placeholder.left = 0
|
|
180
|
+
|
|
181
|
+
# 3. Shape that raises error on image access (code robustness check)
|
|
182
|
+
mock_broken_image = MagicMock()
|
|
183
|
+
mock_broken_image.shape_type = MSO_SHAPE_TYPE.PICTURE
|
|
184
|
+
type(mock_broken_image.image).blob = unittest.mock.PropertyMock(
|
|
185
|
+
side_effect=Exception("Corrupt")
|
|
186
|
+
)
|
|
187
|
+
mock_broken_image.top = 200
|
|
188
|
+
mock_broken_image.left = 0
|
|
189
|
+
|
|
190
|
+
# 4. Shape with text that looks like a title but raises AttributeError (line 47 coverage)
|
|
191
|
+
mock_title_ish = MagicMock()
|
|
192
|
+
mock_title_ish.has_text_frame = True
|
|
193
|
+
mock_title_ish.text_frame.text = "A Title"
|
|
194
|
+
mock_title_ish.text_frame.paragraphs = [MagicMock(text="A Title")]
|
|
195
|
+
mock_title_ish.is_placeholder = (
|
|
196
|
+
False # Important: default valid MagicMock is truthy!
|
|
197
|
+
)
|
|
198
|
+
# Ensure checking slide.shapes.title raises AttributeError
|
|
199
|
+
mock_slide.shapes.title = unittest.mock.PropertyMock(
|
|
200
|
+
side_effect=AttributeError("No title")
|
|
201
|
+
)
|
|
202
|
+
mock_title_ish.top = 300
|
|
203
|
+
mock_title_ish.left = 0
|
|
204
|
+
|
|
205
|
+
# Configure slide shapes iteration
|
|
206
|
+
mock_slide.shapes.__iter__.return_value = [
|
|
207
|
+
mock_group,
|
|
208
|
+
mock_placeholder,
|
|
209
|
+
mock_broken_image,
|
|
210
|
+
mock_title_ish,
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
mock_prs.slides = [mock_slide]
|
|
214
|
+
mock_presentation.return_value = mock_prs
|
|
215
|
+
|
|
216
|
+
with patch.object(
|
|
217
|
+
PptxReader, "create_image_data_dir", return_value="dir"
|
|
218
|
+
), patch.object(
|
|
219
|
+
PptxReader, "save_and_describe_image", return_value=("path", "desc")
|
|
220
|
+
):
|
|
221
|
+
|
|
222
|
+
reader = PptxReader("test.pptx")
|
|
223
|
+
# Force AttributeError on slide.shapes.title access during loop
|
|
224
|
+
# The code does: `if shape == slide.shapes.title:`
|
|
225
|
+
# We need checking `slide.shapes.title` to fail
|
|
226
|
+
type(mock_slide.shapes).title = unittest.mock.PropertyMock(
|
|
227
|
+
side_effect=AttributeError
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
content = reader.read(extract_images=True)
|
|
231
|
+
|
|
232
|
+
# Should match group image
|
|
233
|
+
# Should match placeholder image
|
|
234
|
+
# Should handle broken image gracefully (print warning)
|
|
235
|
+
# Should handle title attribute error (continue)
|
|
236
|
+
|
|
237
|
+
# Since save_and_describe_image is mocked, we just check count or content existence
|
|
238
|
+
# We expect 2 valid images (group + placeholder)
|
|
239
|
+
self.assertEqual(content.count("Image: path"), 2)
|
|
240
|
+
# Since AttributeError on title check, correct behavior is falling back to paragraph iteration
|
|
241
|
+
self.assertIn("- A Title", content)
|
|
242
|
+
|
|
243
|
+
@patch("pptx.Presentation")
|
|
244
|
+
def test_read_pptx_top_level_error(self, mock_presentation):
|
|
245
|
+
mock_presentation.side_effect = Exception("File corrupt")
|
|
246
|
+
reader = PptxReader("test.pptx")
|
|
247
|
+
content = reader.read()
|
|
248
|
+
self.assertEqual(content, "Error reading PowerPoint file: File corrupt")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class TestDocumentReaderFactory(unittest.TestCase):
|
|
252
|
+
def test_create_reader(self):
|
|
253
|
+
from ara_cli.file_loaders.readers.docx_reader import DocxReader
|
|
254
|
+
|
|
255
|
+
self.assertIsInstance(
|
|
256
|
+
DocumentReaderFactory.create_reader("test.xlsx"), ExcelReader
|
|
257
|
+
)
|
|
258
|
+
self.assertIsInstance(
|
|
259
|
+
DocumentReaderFactory.create_reader("test.pptx"), PptxReader
|
|
260
|
+
)
|
|
261
|
+
self.assertIsInstance(
|
|
262
|
+
DocumentReaderFactory.create_reader("test.DOCX"), DocxReader
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
unittest.main()
|