ara-cli 0.1.9.75__py3-none-any.whl → 0.1.9.77__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/__main__.py +11 -4
- ara_cli/ara_command_parser.py +300 -92
- ara_cli/artefact_autofix.py +167 -89
- ara_cli/artefact_models/artefact_load.py +11 -3
- ara_cli/artefact_models/task_artefact_model.py +31 -23
- ara_cli/artefact_reader.py +74 -50
- ara_cli/chat.py +68 -1
- ara_cli/version.py +1 -1
- {ara_cli-0.1.9.75.dist-info → ara_cli-0.1.9.77.dist-info}/METADATA +3 -1
- {ara_cli-0.1.9.75.dist-info → ara_cli-0.1.9.77.dist-info}/RECORD +15 -15
- tests/test_artefact_autofix.py +88 -1
- tests/test_chat.py +169 -30
- {ara_cli-0.1.9.75.dist-info → ara_cli-0.1.9.77.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.75.dist-info → ara_cli-0.1.9.77.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.75.dist-info → ara_cli-0.1.9.77.dist-info}/top_level.txt +0 -0
ara_cli/chat.py
CHANGED
|
@@ -34,6 +34,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
|
|
|
34
34
|
".jpg": "image/jpeg",
|
|
35
35
|
".jpeg": "image/jpeg",
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
DOCUMENT_TYPE_EXTENSIONS = [".docx", ".doc", ".odt", ".pdf"]
|
|
37
39
|
|
|
38
40
|
def __init__(
|
|
39
41
|
self,
|
|
@@ -371,9 +373,52 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
|
|
|
371
373
|
with open(self.chat_name, 'a', encoding='utf-8') as chat_file:
|
|
372
374
|
chat_file.write(write_content)
|
|
373
375
|
return True
|
|
376
|
+
|
|
377
|
+
def read_docx(self, file_path):
|
|
378
|
+
import docx
|
|
379
|
+
doc = docx.Document(file_path)
|
|
380
|
+
return '\n'.join(para.text for para in doc.paragraphs)
|
|
381
|
+
|
|
382
|
+
def read_pdf(self, file_path):
|
|
383
|
+
import pymupdf4llm
|
|
384
|
+
return pymupdf4llm.to_markdown(file_path, write_images=False)
|
|
385
|
+
|
|
386
|
+
def read_odt(self, file_path):
|
|
387
|
+
import pymupdf4llm
|
|
388
|
+
return pymupdf4llm.to_markdown(file_path, write_images=False)
|
|
389
|
+
|
|
390
|
+
@file_exists_check
|
|
391
|
+
def load_document_file(self, file_path: str, prefix: str = "", suffix: str = "", block_delimiter: str = "```"):
|
|
392
|
+
import os
|
|
393
|
+
|
|
394
|
+
_, ext = os.path.splitext(file_path)
|
|
395
|
+
ext = ext.lower()
|
|
396
|
+
|
|
397
|
+
text_content = ""
|
|
398
|
+
match ext:
|
|
399
|
+
case ".docx":
|
|
400
|
+
text_content = self.read_docx(file_path)
|
|
401
|
+
case ".pdf":
|
|
402
|
+
text_content = self.read_pdf(file_path)
|
|
403
|
+
case ".odt":
|
|
404
|
+
text_content = self.read_odt(file_path)
|
|
405
|
+
# Add more cases if needed.
|
|
406
|
+
case _:
|
|
407
|
+
print("Unsupported document type.")
|
|
408
|
+
return False
|
|
409
|
+
|
|
410
|
+
if block_delimiter:
|
|
411
|
+
text_content = f"{block_delimiter}\n{text_content}\n{block_delimiter}"
|
|
412
|
+
|
|
413
|
+
write_content = f"{prefix}{text_content}{suffix}\n"
|
|
414
|
+
|
|
415
|
+
with open(self.chat_name, 'a', encoding='utf-8') as chat_file:
|
|
416
|
+
chat_file.write(write_content)
|
|
417
|
+
return True
|
|
374
418
|
|
|
375
419
|
def load_file(self, file_name: str, prefix: str = "", suffix: str = "", block_delimiter: str = ""):
|
|
376
420
|
binary_type_mapping = Chat.BINARY_TYPE_MAPPING
|
|
421
|
+
document_type_extensions = Chat.DOCUMENT_TYPE_EXTENSIONS
|
|
377
422
|
|
|
378
423
|
file_type = None
|
|
379
424
|
file_name_lower = file_name.lower()
|
|
@@ -382,7 +427,16 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
|
|
|
382
427
|
file_type = mime_type
|
|
383
428
|
break
|
|
384
429
|
|
|
385
|
-
|
|
430
|
+
is_file_document = any(file_name_lower.endswith(ext) for ext in document_type_extensions)
|
|
431
|
+
|
|
432
|
+
if is_file_document:
|
|
433
|
+
return self.load_document_file(
|
|
434
|
+
file_name=file_name,
|
|
435
|
+
prefix=prefix,
|
|
436
|
+
suffix=suffix,
|
|
437
|
+
block_delimiter=block_delimiter
|
|
438
|
+
)
|
|
439
|
+
elif file_type:
|
|
386
440
|
return self.load_binary_file(
|
|
387
441
|
file_name=file_name,
|
|
388
442
|
mime_type=file_type,
|
|
@@ -503,6 +557,19 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
|
|
|
503
557
|
return False
|
|
504
558
|
return True
|
|
505
559
|
|
|
560
|
+
@cmd2.with_category(CATEGORY_CHAT_CONTROL)
|
|
561
|
+
def do_LOAD_DOCUMENT(self, file_name):
|
|
562
|
+
"""Load a document file (PDF, DOCX, DOC, ODT) and append its text content to chat file. Can be given the file name in-line. Will attempt to find the file relative to chat file first, then treat the given path as absolute"""
|
|
563
|
+
matching_files = self.find_matching_files_to_load(file_name)
|
|
564
|
+
if not matching_files:
|
|
565
|
+
return
|
|
566
|
+
|
|
567
|
+
for file_path in matching_files:
|
|
568
|
+
prefix = f"\nFile: {file_path}\n"
|
|
569
|
+
self.add_prompt_tag_if_needed(self.chat_name)
|
|
570
|
+
if not os.path.isdir(file_path) and self.load_document_file(file_path, prefix=prefix):
|
|
571
|
+
print(f"Loaded document file {file_path}")
|
|
572
|
+
|
|
506
573
|
@cmd2.with_category(CATEGORY_CHAT_CONTROL)
|
|
507
574
|
def do_LOAD_IMAGE(self, file_name):
|
|
508
575
|
"""Load an image file and append it to chat file. Can be given the file name in-line. Will attempt to find the file relative to chat file first, then treat the given path as absolute"""
|
ara_cli/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version.py
|
|
2
|
-
__version__ = "0.1.9.
|
|
2
|
+
__version__ = "0.1.9.77" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ara_cli
|
|
3
|
-
Version: 0.1.9.
|
|
3
|
+
Version: 0.1.9.77
|
|
4
4
|
Requires-Dist: litellm
|
|
5
5
|
Requires-Dist: llama-index
|
|
6
6
|
Requires-Dist: llama-index-llms-openai
|
|
@@ -13,4 +13,6 @@ Requires-Dist: argcomplete
|
|
|
13
13
|
Requires-Dist: cmd2>=2.5
|
|
14
14
|
Requires-Dist: pydantic
|
|
15
15
|
Requires-Dist: pydantic_ai
|
|
16
|
+
Requires-Dist: python-docx
|
|
17
|
+
Requires-Dist: pymupdf4llm
|
|
16
18
|
Dynamic: requires-dist
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
ara_cli/__init__.py,sha256=0zl7IegxTid26EBGLav_fXZ4CCIV3H5TfAoFQiOHjvg,148
|
|
2
|
-
ara_cli/__main__.py,sha256=
|
|
2
|
+
ara_cli/__main__.py,sha256=ppfq0FIi4x6ONRzP67784A4BPo2labh8Bd_EExuXo4U,2011
|
|
3
3
|
ara_cli/ara_command_action.py,sha256=J613DUTjRxrPG8Jm-fJcIM0QlZTeULmq9Q7DKkDxJHg,22039
|
|
4
|
-
ara_cli/ara_command_parser.py,sha256=
|
|
4
|
+
ara_cli/ara_command_parser.py,sha256=vyxLELnyAZFC2C3v0hH4-r9QBmJ8oIs0hCZukkMFXfc,20136
|
|
5
5
|
ara_cli/ara_config.py,sha256=SgZfQVpqj5JJN4SB0n2IvAH0sKIdS3k1K1Zht2wDywA,8814
|
|
6
|
-
ara_cli/artefact_autofix.py,sha256=
|
|
6
|
+
ara_cli/artefact_autofix.py,sha256=WVTiIR-jo4YKmmz4eS3qTFvl45W1YKwAk1XSuz9QX10,20015
|
|
7
7
|
ara_cli/artefact_creator.py,sha256=0Ory6cB-Ahkw-BDNb8QHnTbp_OHGABdkb9bhwcEdcIc,6063
|
|
8
8
|
ara_cli/artefact_deleter.py,sha256=Co4wwCH3yW8H9NrOq7_2p5571EeHr0TsfE-H8KqoOfY,1900
|
|
9
9
|
ara_cli/artefact_fuzzy_search.py,sha256=iBlDqjZf-_D3VUjFf7ZwkiQbpQDcwRndIU7aG_sRTgE,2668
|
|
10
10
|
ara_cli/artefact_link_updater.py,sha256=nKdxTpDKqWTOAMD8viKmUaklSFGWzJZ8S8E8xW_ADuM,3775
|
|
11
11
|
ara_cli/artefact_lister.py,sha256=jhk4n4eqp7hDIq07q43QzS7-36BM3OfZ4EABxCeOGcw,4764
|
|
12
|
-
ara_cli/artefact_reader.py,sha256=
|
|
12
|
+
ara_cli/artefact_reader.py,sha256=Pho0_Eqm7kD9CNbVMhKb6mkNM0I3iJiCJXbXmVp1DJU,7827
|
|
13
13
|
ara_cli/artefact_renamer.py,sha256=Hnz_3zD9xxnBa1FHyUE6mIktLk_9ttP2rFRvQIkmz-o,4061
|
|
14
14
|
ara_cli/artefact_scan.py,sha256=msPCm-vPWOAZ_e_z5GylXxq1MtNlmJ4zvKrsdOFCWF4,4813
|
|
15
|
-
ara_cli/chat.py,sha256=
|
|
15
|
+
ara_cli/chat.py,sha256=Plje33XcOedSx-nmLCkuFIXSqHPIvMcy5I71xYWuYmU,31956
|
|
16
16
|
ara_cli/classifier.py,sha256=zWskj7rBYdqYBGjksBm46iTgVU5IIf2PZsJr4qeiwVU,1878
|
|
17
17
|
ara_cli/codefusionretriever.py,sha256=fCHgXdIBRzkVAnapX-KI2NQ44XbrrF4tEQmn5J6clUI,1980
|
|
18
18
|
ara_cli/codehierachieretriever.py,sha256=Xd3EgEWWhkSf1TmTWtf8X5_YvyE_4B66nRrqarwSiTU,1182
|
|
@@ -31,9 +31,9 @@ ara_cli/run_file_lister.py,sha256=XbrrDTJXp1LFGx9Lv91SNsEHZPP-PyEMBF_P4btjbDA,23
|
|
|
31
31
|
ara_cli/tag_extractor.py,sha256=TGdaQOVnjy25R0zDsAifB67C5oom0Fwo24s0_fr5A_I,3151
|
|
32
32
|
ara_cli/template_manager.py,sha256=YwrN6AYPpl6ZrW8BVQpVXx8yTRf-oNpJUIKeg4NAggs,6606
|
|
33
33
|
ara_cli/update_config_prompt.py,sha256=Oy9vNTw6UhDohyTEfSKkqE5ifEMPlmWNYkKHgUrK_pY,4607
|
|
34
|
-
ara_cli/version.py,sha256=
|
|
34
|
+
ara_cli/version.py,sha256=YZaVX1MbXXFpamZLvZnUrGSRq0Szw8TohbROLVLDRkA,146
|
|
35
35
|
ara_cli/artefact_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
-
ara_cli/artefact_models/artefact_load.py,sha256=
|
|
36
|
+
ara_cli/artefact_models/artefact_load.py,sha256=IXzWxP-Q_j_oDGMno0m-OuXCQ7Vd5c_NctshGr4ROBw,621
|
|
37
37
|
ara_cli/artefact_models/artefact_mapping.py,sha256=8aD0spBjkJ8toMAmFawc6UTUxB6-tEEViZXv2I-r88Q,1874
|
|
38
38
|
ara_cli/artefact_models/artefact_model.py,sha256=qSbcrmFWAYgBqcNl9QARI1_uLQJm-TPVgP5q2AEFnjE,15983
|
|
39
39
|
ara_cli/artefact_models/artefact_templates.py,sha256=8HNM-TsNvKgTpruOBs751yRDXJypTiJhc1tkWCiYG7s,9830
|
|
@@ -45,7 +45,7 @@ ara_cli/artefact_models/feature_artefact_model.py,sha256=FrR7_xydOmMySAz0QpWgrNF
|
|
|
45
45
|
ara_cli/artefact_models/issue_artefact_model.py,sha256=v6CpKnkqiUh6Wch2kkEmyyW49c8ysdy1qz8l1Ft9uJA,2552
|
|
46
46
|
ara_cli/artefact_models/keyfeature_artefact_model.py,sha256=J9oXLsCAo22AW31D5Z104y02ss0S0O4tPCcd09zYCD0,4066
|
|
47
47
|
ara_cli/artefact_models/serialize_helper.py,sha256=Wks30wy-UrwJURetydKykLgJkdGRgXFHkDT24vHe5tU,595
|
|
48
|
-
ara_cli/artefact_models/task_artefact_model.py,sha256=
|
|
48
|
+
ara_cli/artefact_models/task_artefact_model.py,sha256=1BSMbz9D-RXvdpdd0RlAr9hUx84Rcuysk2YfQC8Qy14,6046
|
|
49
49
|
ara_cli/artefact_models/userstory_artefact_model.py,sha256=2awH31ROtm7j4T44Bv4cylQDYLQtnfgXZMhDu_pgw-k,6435
|
|
50
50
|
ara_cli/artefact_models/vision_artefact_model.py,sha256=frjaUJj-mmIlVHEhzAQztCGs-CtvNu_odSborgztfzo,5251
|
|
51
51
|
ara_cli/templates/agile.artefacts,sha256=nTA8dp98HWKAD-0qhmNpVYIfkVGoJshZqMJGnphiOsE,7932
|
|
@@ -124,14 +124,14 @@ ara_cli/templates/specification_breakdown_files/template.technology.md,sha256=by
|
|
|
124
124
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
125
125
|
tests/test_ara_command_action.py,sha256=JTLqXM9BSMlU33OQgrk_sZnoowFJZKZAx8q-st-wa34,25821
|
|
126
126
|
tests/test_ara_config.py,sha256=pvkdPLTzgLkOijil0HaN0mhLC2Rdu4Fu5RfXEyOlRfs,16672
|
|
127
|
-
tests/test_artefact_autofix.py,sha256=
|
|
127
|
+
tests/test_artefact_autofix.py,sha256=pApZ-N0dW8Ujt-cNLbgvd4bhiIIK8oXb-saLf6QlA-8,25022
|
|
128
128
|
tests/test_artefact_fuzzy_search.py,sha256=5Sh3_l9QK8-WHn6JpGPU1b6h4QEnl2JoMq1Tdp2cj1U,1261
|
|
129
129
|
tests/test_artefact_link_updater.py,sha256=biqbEp2jCOz8giv72hu2P2hDfeJfJ9OrVGdAv5d9cK4,2191
|
|
130
130
|
tests/test_artefact_lister.py,sha256=VCEOCgDgnAOeUUgIoGAbWgz60hf9UT-tdHg18LGfB34,22656
|
|
131
131
|
tests/test_artefact_reader.py,sha256=660K-d8ed-j8hulsUB_7baPD2-hhbg9TffUR5yVc4Uo,927
|
|
132
132
|
tests/test_artefact_renamer.py,sha256=lSnKCCfoFGgKhTdDZrEaeBq1xJAak1QoqH5aSeOe9Ro,3494
|
|
133
133
|
tests/test_artefact_scan.py,sha256=uNWgrt7ieZ4ogKACsPqzAsh59JF2BhTKSag31hpVrTQ,16887
|
|
134
|
-
tests/test_chat.py,sha256=
|
|
134
|
+
tests/test_chat.py,sha256=fUGqpsyilLjwIFNlCAC69pYGEhwRuU6pplywwGJk-K8,54907
|
|
135
135
|
tests/test_classifier.py,sha256=grYGPksydNdPsaEBQxYHZTuTdcJWz7VQtikCKA6BNaQ,1920
|
|
136
136
|
tests/test_directory_navigator.py,sha256=7G0MVrBbtBvbrFUpL0zb_9EkEWi1dulWuHsrQxMJxDY,140
|
|
137
137
|
tests/test_file_classifier.py,sha256=kLWPiePu3F5mkVuI_lK_2QlLh2kXD_Mt2K8KZZ1fAnA,10940
|
|
@@ -141,8 +141,8 @@ tests/test_list_filter.py,sha256=fJA3d_SdaOAUkE7jn68MOVS0THXGghy1fye_64Zvo1U,796
|
|
|
141
141
|
tests/test_tag_extractor.py,sha256=nSiAYlTKZ7TLAOtcJpwK5zTWHhFYU0tI5xKnivLc1dU,2712
|
|
142
142
|
tests/test_template_manager.py,sha256=q-LMHRG4rHkD6ON6YW4cpZxUx9hul6Or8wVVRC2kb-8,4099
|
|
143
143
|
tests/test_update_config_prompt.py,sha256=xsqj1WTn4BsG5Q2t-sNPfu7EoMURFcS-hfb5VSXUnJc,6765
|
|
144
|
-
ara_cli-0.1.9.
|
|
145
|
-
ara_cli-0.1.9.
|
|
146
|
-
ara_cli-0.1.9.
|
|
147
|
-
ara_cli-0.1.9.
|
|
148
|
-
ara_cli-0.1.9.
|
|
144
|
+
ara_cli-0.1.9.77.dist-info/METADATA,sha256=HYjYn2M6mOc2aYugIuai2mhaV8uaDi2xWtT_x-p3aq0,469
|
|
145
|
+
ara_cli-0.1.9.77.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
146
|
+
ara_cli-0.1.9.77.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
|
|
147
|
+
ara_cli-0.1.9.77.dist-info/top_level.txt,sha256=WM4cLHT5DYUaWzLtRj-gu3yVNFpGQ6lLRI3FMmC-38I,14
|
|
148
|
+
ara_cli-0.1.9.77.dist-info/RECORD,,
|
tests/test_artefact_autofix.py
CHANGED
|
@@ -15,6 +15,7 @@ from ara_cli.artefact_autofix import (
|
|
|
15
15
|
_has_valid_contribution,
|
|
16
16
|
set_closest_contribution,
|
|
17
17
|
fix_contribution,
|
|
18
|
+
fix_rule
|
|
18
19
|
)
|
|
19
20
|
from ara_cli.artefact_models.artefact_model import Artefact, ArtefactType, Contribution
|
|
20
21
|
|
|
@@ -60,6 +61,24 @@ def mock_artefact_with_contribution():
|
|
|
60
61
|
return mock_artefact
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def mock_contribution():
|
|
66
|
+
m = MagicMock()
|
|
67
|
+
m.artefact_name = "parent_name"
|
|
68
|
+
m.classifier = "feature"
|
|
69
|
+
m.rule = "my_rule"
|
|
70
|
+
return m
|
|
71
|
+
|
|
72
|
+
@pytest.fixture
|
|
73
|
+
def mock_artefact(mock_contribution):
|
|
74
|
+
m = MagicMock()
|
|
75
|
+
m.contribution = mock_contribution
|
|
76
|
+
m._artefact_type.return_value.value = "requirement"
|
|
77
|
+
m.title = "my_title"
|
|
78
|
+
m.serialize.return_value = "serialized-text"
|
|
79
|
+
return m
|
|
80
|
+
|
|
81
|
+
|
|
63
82
|
def test_read_report_file_success():
|
|
64
83
|
"""Tests successful reading of the report file."""
|
|
65
84
|
mock_content = "# Artefact Check Report\n- `file.feature`: reason"
|
|
@@ -614,4 +633,72 @@ def test_apply_autofix_single_pass(
|
|
|
614
633
|
assert "Single-pass mode enabled" in output
|
|
615
634
|
assert "Attempt 1/1" in output
|
|
616
635
|
assert "Attempt 2/1" not in output
|
|
617
|
-
mock_check_file.assert_called_once()
|
|
636
|
+
mock_check_file.assert_called_once()
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
@patch("ara_cli.artefact_autofix._update_rule")
|
|
640
|
+
@patch("ara_cli.artefact_autofix.populate_classified_artefact_info")
|
|
641
|
+
def test_fix_rule_with_rule(mock_populate, mock_update_rule, mock_artefact, mock_contribution, capsys):
|
|
642
|
+
# Contribution has a rule
|
|
643
|
+
artefact_class = MagicMock()
|
|
644
|
+
artefact_class.deserialize.return_value = mock_artefact
|
|
645
|
+
mock_populate.return_value = {"info": "dummy"}
|
|
646
|
+
|
|
647
|
+
result = fix_rule(
|
|
648
|
+
file_path="dummy.feature",
|
|
649
|
+
artefact_text="text",
|
|
650
|
+
artefact_class=artefact_class,
|
|
651
|
+
classified_artefact_info={},
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
# deserialize called
|
|
655
|
+
artefact_class.deserialize.assert_called_once_with("text")
|
|
656
|
+
# _update_rule called with correct args
|
|
657
|
+
mock_update_rule.assert_called_once_with(
|
|
658
|
+
artefact=mock_artefact,
|
|
659
|
+
name="parent_name",
|
|
660
|
+
classifier="feature",
|
|
661
|
+
classified_file_info={"info": "dummy"},
|
|
662
|
+
delete_if_not_found=True,
|
|
663
|
+
)
|
|
664
|
+
# Feedback message contains rule
|
|
665
|
+
assert "with rule" in capsys.readouterr().out
|
|
666
|
+
# Result is the serialized text
|
|
667
|
+
assert result == "serialized-text"
|
|
668
|
+
|
|
669
|
+
@patch("ara_cli.artefact_autofix._update_rule")
|
|
670
|
+
@patch("ara_cli.artefact_autofix.populate_classified_artefact_info")
|
|
671
|
+
def test_fix_rule_without_rule(mock_populate, mock_update_rule, mock_artefact, mock_contribution, capsys):
|
|
672
|
+
# Contribution rule becomes None after update
|
|
673
|
+
mock_contribution.rule = None
|
|
674
|
+
artefact_class = MagicMock()
|
|
675
|
+
artefact_class.deserialize.return_value = mock_artefact
|
|
676
|
+
mock_populate.return_value = {"info": "dummy"}
|
|
677
|
+
|
|
678
|
+
result = fix_rule(
|
|
679
|
+
file_path="dummy.feature",
|
|
680
|
+
artefact_text="text",
|
|
681
|
+
artefact_class=artefact_class,
|
|
682
|
+
classified_artefact_info={},
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
# Feedback message says "without a rule"
|
|
686
|
+
assert "without a rule" in capsys.readouterr().out
|
|
687
|
+
assert result == "serialized-text"
|
|
688
|
+
|
|
689
|
+
@patch("ara_cli.artefact_autofix.populate_classified_artefact_info")
|
|
690
|
+
def test_fix_rule_contribution_none_raises(mock_populate):
|
|
691
|
+
# artefact.contribution is None: should assert
|
|
692
|
+
artefact = MagicMock()
|
|
693
|
+
artefact.contribution = None
|
|
694
|
+
artefact_class = MagicMock()
|
|
695
|
+
artefact_class.deserialize.return_value = artefact
|
|
696
|
+
mock_populate.return_value = {}
|
|
697
|
+
|
|
698
|
+
with pytest.raises(AssertionError):
|
|
699
|
+
fix_rule(
|
|
700
|
+
file_path="dummy.feature",
|
|
701
|
+
artefact_text="stuff",
|
|
702
|
+
artefact_class=artefact_class,
|
|
703
|
+
classified_artefact_info={},
|
|
704
|
+
)
|
tests/test_chat.py
CHANGED
|
@@ -185,7 +185,7 @@ def test_disable_commands(temp_chat_file):
|
|
|
185
185
|
(["This is a line.", "Another line here.", "Yet another line."], None),
|
|
186
186
|
(["This is a line.", "# ara prompt:", "Another line here."], "# ara prompt:"),
|
|
187
187
|
(["This is a line.", "# ara prompt:", "Another line here.", "# ara response:"], "# ara response:"),
|
|
188
|
-
(["This is a line.", " # ara prompt: ", "Another line here.", " # ara response:
|
|
188
|
+
(["This is a line.", " # ara prompt: ", "Another line here.", " # ara response: "], "# ara response:"),
|
|
189
189
|
(["# ara prompt:", "# ara response:"], "# ara response:"),
|
|
190
190
|
(["# ara response:", "# ara prompt:", "# ara prompt:", "# ara response:"], "# ara response:"),
|
|
191
191
|
([], None)
|
|
@@ -233,8 +233,8 @@ def test_start(temp_chat_file):
|
|
|
233
233
|
(["This is a line.\n", "# ara prompt:\n", "Another line here.\n", "# ara response:\n"],
|
|
234
234
|
["This is a line.\n", "# ara prompt:\n", "Another line here.\n", "# ara response:\n", "\n", "# ara prompt:"]),
|
|
235
235
|
|
|
236
|
-
(["This is a line.\n", " # ara prompt: \n", "Another line here.\n", " # ara response:
|
|
237
|
-
["This is a line.\n", " # ara prompt: \n", "Another line here.\n", " # ara response:
|
|
236
|
+
(["This is a line.\n", " # ara prompt: \n", "Another line here.\n", " # ara response: \n"],
|
|
237
|
+
["This is a line.\n", " # ara prompt: \n", "Another line here.\n", " # ara response: \n", "\n", "# ara prompt:"]),
|
|
238
238
|
|
|
239
239
|
(["# ara prompt:\n", "# ara response:\n"],
|
|
240
240
|
["# ara prompt:\n", "# ara response:\n", "\n", "# ara prompt:"]),
|
|
@@ -257,7 +257,7 @@ def test_add_prompt_tag_if_needed(temp_chat_file, initial_content, expected_cont
|
|
|
257
257
|
|
|
258
258
|
|
|
259
259
|
@pytest.mark.parametrize("lines, expected", [
|
|
260
|
-
(["\n", "
|
|
260
|
+
(["\n", " ", "# ara prompt:", "Another line here.", " \n"], "Another line here."),
|
|
261
261
|
(["This is a line.", "Another line here.", " \n", "\n"], "Another line here."),
|
|
262
262
|
(["\n", " \n", " \n"], ""),
|
|
263
263
|
(["This is a line.", "Another line here.", "# ara response:", " \n"], "# ara response:"),
|
|
@@ -270,7 +270,7 @@ def test_get_last_non_empty_line(lines, expected, temp_chat_file):
|
|
|
270
270
|
assert Chat.get_last_non_empty_line(Chat, file) == expected
|
|
271
271
|
|
|
272
272
|
@pytest.mark.parametrize("lines, expected", [
|
|
273
|
-
(["\n", "
|
|
273
|
+
(["\n", " ", "# ara prompt:", "Another line here.", " \n"], ""),
|
|
274
274
|
(["This is a line.", "Another line here."], "Another line here."),
|
|
275
275
|
(["\n", " \n", " \n"], ""),
|
|
276
276
|
(["This is a line.", "Another line here.", "# ara response:", " \n"], ""),
|
|
@@ -290,7 +290,7 @@ def test_get_last_line(lines, expected, temp_chat_file):
|
|
|
290
290
|
(["Text with image", "(data:image/png;base64,abc123)"],
|
|
291
291
|
"Text with image",
|
|
292
292
|
[{"type": "image_url", "image_url": {"url": "data:image/png;base64,abc123"}}]),
|
|
293
|
-
(["Just text", "Another (data:image/png;base64,xyz789) image"],
|
|
293
|
+
(["Just text", "Another (data:image/png;base64,xyz789) image"],
|
|
294
294
|
"Just text",
|
|
295
295
|
[{"type": "image_url", "image_url": {"url": "data:image/png;base64,xyz789"}}]),
|
|
296
296
|
(["No images here at all"], "No images here at all", []),
|
|
@@ -392,7 +392,7 @@ def test_save_message(temp_chat_file, role, message, initial_content, expected_c
|
|
|
392
392
|
def test_resend_message(temp_chat_file, initial_content, expected_content):
|
|
393
393
|
temp_chat_file.writelines(initial_content)
|
|
394
394
|
temp_chat_file.flush()
|
|
395
|
-
|
|
395
|
+
|
|
396
396
|
mock_config = get_default_config()
|
|
397
397
|
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
398
398
|
chat = Chat(temp_chat_file.name, reset=False)
|
|
@@ -456,7 +456,7 @@ def test_determine_file_path(temp_chat_file):
|
|
|
456
456
|
]
|
|
457
457
|
|
|
458
458
|
with patch('os.path.exists') as mock_exists, \
|
|
459
|
-
|
|
459
|
+
patch('os.path.dirname', return_value="current_directory") as mock_dirname:
|
|
460
460
|
|
|
461
461
|
for file_name, exists_in_current, exists_elsewhere, expected_path in test_cases:
|
|
462
462
|
mock_exists.side_effect = [exists_in_current, exists_elsewhere]
|
|
@@ -501,9 +501,9 @@ def test_load_text_file_file_not_found(temp_chat_file):
|
|
|
501
501
|
with patch("builtins.open", mock_open()) as mock_file:
|
|
502
502
|
result = chat.load_text_file("nonexistent.txt")
|
|
503
503
|
|
|
504
|
-
|
|
504
|
+
assert result is False
|
|
505
505
|
|
|
506
|
-
|
|
506
|
+
mock_file.assert_not_called()
|
|
507
507
|
|
|
508
508
|
|
|
509
509
|
@pytest.mark.parametrize("file_name, mime_type, file_content, expected, path_exists", [
|
|
@@ -516,7 +516,6 @@ def test_load_binary_file(temp_chat_file, file_name, mime_type, file_content, ex
|
|
|
516
516
|
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
517
517
|
chat = Chat(temp_chat_file.name, reset=False)
|
|
518
518
|
|
|
519
|
-
# Mock open to handle both read and write operations
|
|
520
519
|
mock_file = mock_open(read_data=file_content)
|
|
521
520
|
|
|
522
521
|
with patch('builtins.open', mock_file) as mocked_open, \
|
|
@@ -535,36 +534,112 @@ def test_load_binary_file(temp_chat_file, file_name, mime_type, file_content, ex
|
|
|
535
534
|
assert result is False
|
|
536
535
|
|
|
537
536
|
|
|
538
|
-
@pytest.mark.parametrize("file_name,
|
|
539
|
-
(
|
|
540
|
-
|
|
537
|
+
@pytest.mark.parametrize("file_name, loader_path, mock_setup, expected_content", [
|
|
538
|
+
(
|
|
539
|
+
"test.docx",
|
|
540
|
+
"docx.Document",
|
|
541
|
+
lambda mock: setattr(mock.return_value, 'paragraphs', [MagicMock(text="Docx content")]),
|
|
542
|
+
"Docx content"
|
|
543
|
+
),
|
|
544
|
+
pytest.param(
|
|
545
|
+
"test.pdf",
|
|
546
|
+
"pymupdf4llm.to_markdown",
|
|
547
|
+
lambda mock: setattr(mock, 'return_value', "PDF content"),
|
|
548
|
+
"PDF content",
|
|
549
|
+
marks=pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
|
550
|
+
),
|
|
551
|
+
pytest.param(
|
|
552
|
+
"test.odt",
|
|
553
|
+
"pymupdf4llm.to_markdown",
|
|
554
|
+
lambda mock: setattr(mock, 'return_value', "ODT content"),
|
|
555
|
+
"ODT content",
|
|
556
|
+
marks=pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
|
557
|
+
),
|
|
541
558
|
])
|
|
542
|
-
def
|
|
559
|
+
def test_load_document_file(temp_chat_file, file_name, loader_path, mock_setup, expected_content):
|
|
560
|
+
mock_config = get_default_config()
|
|
561
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
562
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
563
|
+
|
|
564
|
+
with patch(loader_path, create=True) as mock_loader, \
|
|
565
|
+
patch("builtins.open", mock_open()) as mock_chat_open:
|
|
566
|
+
|
|
567
|
+
mock_setup(mock_loader)
|
|
568
|
+
|
|
569
|
+
with patch.object(chat, 'determine_file_path', return_value=file_name):
|
|
570
|
+
result = chat.load_document_file(file_name, prefix="Prefix-", suffix="-Suffix", block_delimiter="```")
|
|
571
|
+
|
|
572
|
+
assert result is True
|
|
573
|
+
|
|
574
|
+
if loader_path == "pymupdf4llm.to_markdown":
|
|
575
|
+
mock_loader.assert_called_once_with(file_name, write_images=False)
|
|
576
|
+
else:
|
|
577
|
+
mock_loader.assert_called_once_with(file_name)
|
|
578
|
+
|
|
579
|
+
expected_write = f"Prefix-```\n{expected_content}\n```-Suffix\n"
|
|
580
|
+
mock_chat_open.assert_called_with(chat.chat_name, 'a', encoding='utf-8')
|
|
581
|
+
mock_chat_open().write.assert_called_once_with(expected_write)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def test_load_document_file_unsupported(temp_chat_file, capsys):
|
|
585
|
+
mock_config = get_default_config()
|
|
586
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
587
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
588
|
+
|
|
589
|
+
unsupported_file = "test.txt"
|
|
590
|
+
with patch.object(chat, 'determine_file_path', return_value=unsupported_file):
|
|
591
|
+
result = chat.load_document_file(unsupported_file)
|
|
592
|
+
|
|
593
|
+
assert result is False
|
|
594
|
+
captured = capsys.readouterr()
|
|
595
|
+
assert "Unsupported document type." in captured.out
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
@pytest.mark.parametrize("file_name, file_type, mime_type", [
|
|
599
|
+
("image.png", "binary", "image/png"),
|
|
600
|
+
("document.txt", "text", None),
|
|
601
|
+
("document.docx", "document", None),
|
|
602
|
+
("document.pdf", "document", None),
|
|
603
|
+
("archive.zip", "text", None),
|
|
604
|
+
])
|
|
605
|
+
def test_load_file(temp_chat_file, file_name, file_type, mime_type):
|
|
543
606
|
mock_config = get_default_config()
|
|
544
607
|
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
545
608
|
chat = Chat(temp_chat_file.name, reset=False)
|
|
546
609
|
|
|
547
610
|
with patch.object(chat, 'load_binary_file', return_value=True) as mock_load_binary, \
|
|
548
|
-
patch.object(chat, 'load_text_file', return_value=True) as mock_load_text
|
|
611
|
+
patch.object(chat, 'load_text_file', return_value=True) as mock_load_text, \
|
|
612
|
+
patch.object(chat, 'load_document_file', return_value=True) as mock_load_document:
|
|
549
613
|
|
|
550
|
-
chat.load_file(file_name=file_name)
|
|
614
|
+
chat.load_file(file_name=file_name, prefix="p-", suffix="-s", block_delimiter="b")
|
|
551
615
|
|
|
552
|
-
if
|
|
616
|
+
if file_type == "binary":
|
|
553
617
|
mock_load_binary.assert_called_once_with(
|
|
554
618
|
file_name=file_name,
|
|
555
|
-
mime_type=
|
|
556
|
-
prefix="",
|
|
557
|
-
suffix=""
|
|
619
|
+
mime_type=mime_type,
|
|
620
|
+
prefix="p-",
|
|
621
|
+
suffix="-s"
|
|
558
622
|
)
|
|
559
623
|
mock_load_text.assert_not_called()
|
|
624
|
+
mock_load_document.assert_not_called()
|
|
625
|
+
elif file_type == "document":
|
|
626
|
+
mock_load_binary.assert_not_called()
|
|
627
|
+
mock_load_text.assert_not_called()
|
|
628
|
+
mock_load_document.assert_called_once_with(
|
|
629
|
+
file_name=file_name,
|
|
630
|
+
prefix="p-",
|
|
631
|
+
suffix="-s",
|
|
632
|
+
block_delimiter="b"
|
|
633
|
+
)
|
|
560
634
|
else:
|
|
635
|
+
mock_load_binary.assert_not_called()
|
|
561
636
|
mock_load_text.assert_called_once_with(
|
|
562
637
|
file_name=file_name,
|
|
563
|
-
prefix="",
|
|
564
|
-
suffix="",
|
|
565
|
-
block_delimiter=""
|
|
638
|
+
prefix="p-",
|
|
639
|
+
suffix="-s",
|
|
640
|
+
block_delimiter="b"
|
|
566
641
|
)
|
|
567
|
-
|
|
642
|
+
mock_load_document.assert_not_called()
|
|
568
643
|
|
|
569
644
|
|
|
570
645
|
@pytest.mark.parametrize("files, pattern, user_input, expected_output, expected_file", [
|
|
@@ -788,6 +863,71 @@ def test_complete_LOAD(monkeypatch, temp_chat_file, text, line, begidx, endidx,
|
|
|
788
863
|
assert completions == matching_files
|
|
789
864
|
|
|
790
865
|
|
|
866
|
+
@pytest.mark.parametrize("file_name, is_image, expected_mime", [
|
|
867
|
+
("test.png", True, "image/png"),
|
|
868
|
+
("test.jpg", True, "image/jpeg"),
|
|
869
|
+
("test.jpeg", True, "image/jpeg"),
|
|
870
|
+
("test.txt", False, None)
|
|
871
|
+
])
|
|
872
|
+
def test_load_image(capsys, temp_chat_file, file_name, is_image, expected_mime):
|
|
873
|
+
mock_config = get_default_config()
|
|
874
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
875
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
876
|
+
|
|
877
|
+
with patch.object(chat, 'load_binary_file', return_value=True) as mock_load_binary:
|
|
878
|
+
chat.load_image(file_name=file_name, prefix="p-", suffix="-s")
|
|
879
|
+
|
|
880
|
+
if is_image:
|
|
881
|
+
mock_load_binary.assert_called_once_with(
|
|
882
|
+
file_name=file_name,
|
|
883
|
+
mime_type=expected_mime,
|
|
884
|
+
prefix="p-",
|
|
885
|
+
suffix="-s"
|
|
886
|
+
)
|
|
887
|
+
else:
|
|
888
|
+
mock_load_binary.assert_not_called()
|
|
889
|
+
captured = capsys.readouterr()
|
|
890
|
+
assert f"File {file_name} not recognized as image, could not load" in captured.out
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def test_do_LOAD_DOCUMENT(capsys, temp_chat_file):
|
|
894
|
+
mock_config = get_default_config()
|
|
895
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
896
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
897
|
+
|
|
898
|
+
doc_file = "test.docx"
|
|
899
|
+
with patch.object(chat, 'find_matching_files_to_load', return_value=[doc_file]) as mock_find, \
|
|
900
|
+
patch.object(chat, 'load_document_file', return_value=True) as mock_load, \
|
|
901
|
+
patch.object(chat, 'add_prompt_tag_if_needed') as mock_add_tag:
|
|
902
|
+
|
|
903
|
+
chat.do_LOAD_DOCUMENT(doc_file)
|
|
904
|
+
|
|
905
|
+
mock_find.assert_called_once_with(doc_file)
|
|
906
|
+
mock_add_tag.assert_called_once_with(chat.chat_name)
|
|
907
|
+
mock_load.assert_called_once_with(doc_file, prefix=f"\nFile: {doc_file}\n")
|
|
908
|
+
captured = capsys.readouterr()
|
|
909
|
+
assert f"Loaded document file {doc_file}" in captured.out
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def test_do_LOAD_IMAGE(capsys, temp_chat_file):
|
|
913
|
+
mock_config = get_default_config()
|
|
914
|
+
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
915
|
+
chat = Chat(temp_chat_file.name, reset=False)
|
|
916
|
+
|
|
917
|
+
image_file = "test.png"
|
|
918
|
+
with patch.object(chat, 'find_matching_files_to_load', return_value=[image_file]) as mock_find, \
|
|
919
|
+
patch.object(chat, 'load_image', return_value=True) as mock_load, \
|
|
920
|
+
patch.object(chat, 'add_prompt_tag_if_needed') as mock_add_tag:
|
|
921
|
+
|
|
922
|
+
chat.do_LOAD_IMAGE(image_file)
|
|
923
|
+
|
|
924
|
+
mock_find.assert_called_once_with(image_file)
|
|
925
|
+
mock_add_tag.assert_called_once_with(chat.chat_name)
|
|
926
|
+
mock_load.assert_called_once_with(image_file, prefix=f"\nFile: {image_file}\n")
|
|
927
|
+
captured = capsys.readouterr()
|
|
928
|
+
assert f"Loaded image file {image_file}" in captured.out
|
|
929
|
+
|
|
930
|
+
|
|
791
931
|
@pytest.mark.parametrize("input_chat_name, expected_chat_name", [
|
|
792
932
|
("", "What should be the new chat name? "),
|
|
793
933
|
("new_chat", "new_chat_chat.md"),
|
|
@@ -796,13 +936,13 @@ def test_complete_LOAD(monkeypatch, temp_chat_file, text, line, begidx, endidx,
|
|
|
796
936
|
def test_do_new(monkeypatch, temp_chat_file, input_chat_name, expected_chat_name):
|
|
797
937
|
def mock_input(prompt):
|
|
798
938
|
return "input_chat_name"
|
|
799
|
-
|
|
939
|
+
|
|
800
940
|
monkeypatch.setattr('builtins.input', mock_input)
|
|
801
941
|
|
|
802
942
|
mock_config = get_default_config()
|
|
803
943
|
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
804
944
|
chat = Chat(temp_chat_file.name, reset=False)
|
|
805
|
-
|
|
945
|
+
|
|
806
946
|
with patch.object(Chat, '__init__', return_value=None) as mock_init:
|
|
807
947
|
chat.do_NEW(input_chat_name)
|
|
808
948
|
if input_chat_name == "":
|
|
@@ -1057,10 +1197,9 @@ def test_do_SEND(temp_chat_file):
|
|
|
1057
1197
|
("AnotherTemplate", MagicMock(serialize=MagicMock(return_value="other_content")), "other_content", "Loaded AnotherTemplate artefact template\n"),
|
|
1058
1198
|
])
|
|
1059
1199
|
def test_do_LOAD_TEMPLATE_success(temp_chat_file, template_name, artefact_obj, expected_write, expected_print, capsys):
|
|
1060
|
-
mock_config = MagicMock()
|
|
1200
|
+
mock_config = MagicMock()
|
|
1061
1201
|
with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
|
|
1062
1202
|
chat = Chat(temp_chat_file.name, reset=False)
|
|
1063
|
-
# Patch the artefact loader to return artefact_obj
|
|
1064
1203
|
with patch('ara_cli.artefact_models.artefact_templates.template_artefact_of_type', return_value=artefact_obj) as mock_template_loader, \
|
|
1065
1204
|
patch.object(chat, 'add_prompt_tag_if_needed') as mock_add_prompt_tag, \
|
|
1066
1205
|
patch("builtins.open", mock_open()) as mock_file:
|
|
@@ -1087,4 +1226,4 @@ def test_do_LOAD_TEMPLATE_missing_artefact(temp_chat_file, template_name):
|
|
|
1087
1226
|
chat.do_LOAD_TEMPLATE(template_name)
|
|
1088
1227
|
mock_template_loader.assert_called_once_with(template_name)
|
|
1089
1228
|
mock_add_prompt_tag.assert_not_called()
|
|
1090
|
-
mock_file.assert_not_called()
|
|
1229
|
+
mock_file.assert_not_called()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|