ara-cli 0.1.9.69__py3-none-any.whl → 0.1.10.8__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.

Potentially problematic release.


This version of ara-cli might be problematic. Click here for more details.

Files changed (150) hide show
  1. ara_cli/__init__.py +18 -2
  2. ara_cli/__main__.py +248 -62
  3. ara_cli/ara_command_action.py +155 -86
  4. ara_cli/ara_config.py +226 -80
  5. ara_cli/ara_subcommands/__init__.py +0 -0
  6. ara_cli/ara_subcommands/autofix.py +26 -0
  7. ara_cli/ara_subcommands/chat.py +27 -0
  8. ara_cli/ara_subcommands/classifier_directory.py +16 -0
  9. ara_cli/ara_subcommands/common.py +100 -0
  10. ara_cli/ara_subcommands/create.py +75 -0
  11. ara_cli/ara_subcommands/delete.py +22 -0
  12. ara_cli/ara_subcommands/extract.py +22 -0
  13. ara_cli/ara_subcommands/fetch_templates.py +14 -0
  14. ara_cli/ara_subcommands/list.py +65 -0
  15. ara_cli/ara_subcommands/list_tags.py +25 -0
  16. ara_cli/ara_subcommands/load.py +48 -0
  17. ara_cli/ara_subcommands/prompt.py +136 -0
  18. ara_cli/ara_subcommands/read.py +47 -0
  19. ara_cli/ara_subcommands/read_status.py +20 -0
  20. ara_cli/ara_subcommands/read_user.py +20 -0
  21. ara_cli/ara_subcommands/reconnect.py +27 -0
  22. ara_cli/ara_subcommands/rename.py +22 -0
  23. ara_cli/ara_subcommands/scan.py +14 -0
  24. ara_cli/ara_subcommands/set_status.py +22 -0
  25. ara_cli/ara_subcommands/set_user.py +22 -0
  26. ara_cli/ara_subcommands/template.py +16 -0
  27. ara_cli/artefact_autofix.py +649 -68
  28. ara_cli/artefact_creator.py +8 -11
  29. ara_cli/artefact_deleter.py +2 -4
  30. ara_cli/artefact_fuzzy_search.py +22 -10
  31. ara_cli/artefact_link_updater.py +4 -4
  32. ara_cli/artefact_lister.py +29 -55
  33. ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
  34. ara_cli/artefact_models/artefact_load.py +11 -3
  35. ara_cli/artefact_models/artefact_model.py +146 -39
  36. ara_cli/artefact_models/artefact_templates.py +70 -44
  37. ara_cli/artefact_models/businessgoal_artefact_model.py +23 -25
  38. ara_cli/artefact_models/epic_artefact_model.py +34 -26
  39. ara_cli/artefact_models/feature_artefact_model.py +203 -64
  40. ara_cli/artefact_models/keyfeature_artefact_model.py +21 -24
  41. ara_cli/artefact_models/serialize_helper.py +1 -1
  42. ara_cli/artefact_models/task_artefact_model.py +83 -15
  43. ara_cli/artefact_models/userstory_artefact_model.py +37 -27
  44. ara_cli/artefact_models/vision_artefact_model.py +23 -42
  45. ara_cli/artefact_reader.py +92 -91
  46. ara_cli/artefact_renamer.py +8 -4
  47. ara_cli/artefact_scan.py +66 -3
  48. ara_cli/chat.py +622 -162
  49. ara_cli/chat_agent/__init__.py +0 -0
  50. ara_cli/chat_agent/agent_communicator.py +62 -0
  51. ara_cli/chat_agent/agent_process_manager.py +211 -0
  52. ara_cli/chat_agent/agent_status_manager.py +73 -0
  53. ara_cli/chat_agent/agent_workspace_manager.py +76 -0
  54. ara_cli/commands/__init__.py +0 -0
  55. ara_cli/commands/command.py +7 -0
  56. ara_cli/commands/extract_command.py +15 -0
  57. ara_cli/commands/load_command.py +65 -0
  58. ara_cli/commands/load_image_command.py +34 -0
  59. ara_cli/commands/read_command.py +117 -0
  60. ara_cli/completers.py +144 -0
  61. ara_cli/directory_navigator.py +37 -4
  62. ara_cli/error_handler.py +134 -0
  63. ara_cli/file_classifier.py +6 -5
  64. ara_cli/file_lister.py +1 -1
  65. ara_cli/file_loaders/__init__.py +0 -0
  66. ara_cli/file_loaders/binary_file_loader.py +33 -0
  67. ara_cli/file_loaders/document_file_loader.py +34 -0
  68. ara_cli/file_loaders/document_reader.py +245 -0
  69. ara_cli/file_loaders/document_readers.py +233 -0
  70. ara_cli/file_loaders/file_loader.py +50 -0
  71. ara_cli/file_loaders/file_loaders.py +123 -0
  72. ara_cli/file_loaders/image_processor.py +89 -0
  73. ara_cli/file_loaders/markdown_reader.py +75 -0
  74. ara_cli/file_loaders/text_file_loader.py +187 -0
  75. ara_cli/global_file_lister.py +51 -0
  76. ara_cli/list_filter.py +1 -1
  77. ara_cli/output_suppressor.py +1 -1
  78. ara_cli/prompt_extractor.py +215 -88
  79. ara_cli/prompt_handler.py +521 -134
  80. ara_cli/prompt_rag.py +2 -2
  81. ara_cli/tag_extractor.py +83 -38
  82. ara_cli/template_loader.py +245 -0
  83. ara_cli/template_manager.py +18 -13
  84. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  85. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  86. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  87. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  88. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  89. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  90. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  91. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  92. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  93. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  94. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  95. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  96. ara_cli/update_config_prompt.py +9 -3
  97. ara_cli/version.py +1 -1
  98. ara_cli-0.1.10.8.dist-info/METADATA +241 -0
  99. ara_cli-0.1.10.8.dist-info/RECORD +193 -0
  100. tests/test_ara_command_action.py +73 -59
  101. tests/test_ara_config.py +341 -36
  102. tests/test_artefact_autofix.py +1060 -0
  103. tests/test_artefact_link_updater.py +3 -3
  104. tests/test_artefact_lister.py +52 -132
  105. tests/test_artefact_renamer.py +2 -2
  106. tests/test_artefact_scan.py +327 -33
  107. tests/test_chat.py +2063 -498
  108. tests/test_file_classifier.py +24 -1
  109. tests/test_file_creator.py +3 -5
  110. tests/test_file_lister.py +1 -1
  111. tests/test_global_file_lister.py +131 -0
  112. tests/test_list_filter.py +2 -2
  113. tests/test_prompt_handler.py +746 -0
  114. tests/test_tag_extractor.py +19 -13
  115. tests/test_template_loader.py +192 -0
  116. tests/test_template_manager.py +5 -4
  117. tests/test_update_config_prompt.py +2 -2
  118. ara_cli/ara_command_parser.py +0 -327
  119. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  120. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  121. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  122. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  123. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  124. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  125. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  126. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  127. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  128. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  129. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  130. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  131. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  132. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  133. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  134. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  135. ara_cli/templates/template.businessgoal +0 -10
  136. ara_cli/templates/template.capability +0 -10
  137. ara_cli/templates/template.epic +0 -15
  138. ara_cli/templates/template.example +0 -6
  139. ara_cli/templates/template.feature +0 -26
  140. ara_cli/templates/template.issue +0 -14
  141. ara_cli/templates/template.keyfeature +0 -15
  142. ara_cli/templates/template.task +0 -6
  143. ara_cli/templates/template.userstory +0 -17
  144. ara_cli/templates/template.vision +0 -14
  145. ara_cli-0.1.9.69.dist-info/METADATA +0 -16
  146. ara_cli-0.1.9.69.dist-info/RECORD +0 -158
  147. tests/test_ara_autofix.py +0 -219
  148. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
  149. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
  150. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,272 @@
1
1
  import pytest
2
2
  from unittest.mock import patch, MagicMock, mock_open, call
3
- from ara_cli.artefact_scan import check_file, find_invalid_files, show_results
4
- from pydantic import ValidationError
3
+ from ara_cli.artefact_scan import (
4
+ check_file,
5
+ find_invalid_files,
6
+ show_results,
7
+ is_contribution_valid,
8
+ is_rule_valid,
9
+ check_contribution,
10
+ )
11
+
12
+
13
+ @pytest.mark.parametrize("contribution", [None, False, 0, "", []])
14
+ def test_check_contribution_none_contribution(contribution):
15
+ # Should return (True, None) if contribution is falsey
16
+ result = check_contribution(
17
+ contribution, classified_artefact_info={}, file_path="irrelevant"
18
+ )
19
+ assert result == (True, None)
20
+
21
+
22
+ def test_check_contribution_invalid_contribution(monkeypatch):
23
+ # If is_contribution_valid returns False, should return (False, custom_reason)
24
+ contribution = MagicMock()
25
+ contribution.classifier = "some_cls"
26
+ contribution.artefact_name = "some_art"
27
+ with patch(
28
+ "ara_cli.artefact_scan.is_contribution_valid", return_value=False
29
+ ) as mock_is_contrib, patch("ara_cli.artefact_scan.is_rule_valid") as mock_is_rule:
30
+ result = check_contribution(
31
+ contribution, classified_artefact_info={}, file_path="f"
32
+ )
33
+ assert result == (
34
+ False,
35
+ "Invalid Contribution Reference: The contribution references "
36
+ "'some_cls' artefact 'some_art' which does not exist.",
37
+ )
38
+ mock_is_contrib.assert_called_once_with(contribution, {})
39
+ mock_is_rule.assert_not_called()
40
+
41
+
42
+ def test_check_contribution_invalid_rule(monkeypatch):
43
+ # is_contribution_valid returns True, is_rule_valid returns False
44
+ contribution = MagicMock()
45
+ contribution.classifier = "c"
46
+ contribution.artefact_name = "a"
47
+ contribution.rule = "r"
48
+ with patch(
49
+ "ara_cli.artefact_scan.is_contribution_valid", return_value=True
50
+ ) as mock_is_contrib, patch(
51
+ "ara_cli.artefact_scan.is_rule_valid", return_value=False
52
+ ) as mock_is_rule:
53
+ result = check_contribution(
54
+ contribution, classified_artefact_info={"x": 1}, file_path="myfile"
55
+ )
56
+ assert result == (
57
+ False,
58
+ "Rule Mismatch: The contribution references rule 'r' which the parent c 'a' does not have.",
59
+ )
60
+ mock_is_contrib.assert_called_once_with(contribution, {"x": 1})
61
+ mock_is_rule.assert_called_once_with(contribution, {"x": 1})
62
+
63
+
64
+ def test_check_contribution_all_valid(monkeypatch):
65
+ # Both is_contribution_valid and is_rule_valid return True
66
+ contribution = MagicMock()
67
+ contribution.classifier = "c"
68
+ contribution.artefact_name = "a"
69
+ contribution.rule = "r"
70
+ with patch(
71
+ "ara_cli.artefact_scan.is_contribution_valid", return_value=True
72
+ ) as mock_is_contrib, patch(
73
+ "ara_cli.artefact_scan.is_rule_valid", return_value=True
74
+ ) as mock_is_rule:
75
+ result = check_contribution(
76
+ contribution, classified_artefact_info={1: 2}, file_path="p"
77
+ )
78
+ assert result == (True, None)
79
+ mock_is_contrib.assert_called_once_with(contribution, {1: 2})
80
+ mock_is_rule.assert_called_once_with(contribution, {1: 2})
81
+
82
+
83
+ @pytest.mark.parametrize(
84
+ "contribution_attrs,expected",
85
+ [
86
+ (None, True), # contribution is None
87
+ ({"artefact_name": None, "classifier": "foo"}, True), # artefact_name is None
88
+ (
89
+ {"artefact_name": "", "classifier": "foo"},
90
+ True,
91
+ ), # artefact_name is empty string
92
+ ({"artefact_name": "bar", "classifier": None}, True), # classifier is None
93
+ (
94
+ {"artefact_name": "bar", "classifier": ""},
95
+ True,
96
+ ), # classifier is empty string
97
+ ],
98
+ )
99
+ def test_is_rule_valid_short_circuits(contribution_attrs, expected):
100
+ if contribution_attrs is None:
101
+ contribution = None
102
+ else:
103
+ contribution = MagicMock()
104
+ for k, v in contribution_attrs.items():
105
+ setattr(contribution, k, v)
106
+ with patch("ara_cli.artefact_reader.ArtefactReader.read_artefact") as mock_read:
107
+ result = is_rule_valid(contribution, classified_artefact_info={})
108
+ assert result is expected
109
+ mock_read.assert_not_called()
110
+
111
+
112
+ def test_is_rule_valid_rule_is_none():
113
+ """Should return True if contribution.rule is None or falsey."""
114
+ contribution = MagicMock()
115
+ contribution.artefact_name = "foo"
116
+ contribution.classifier = "bar"
117
+ contribution.rule = None
118
+ with patch("ara_cli.artefact_reader.ArtefactReader.read_artefact") as mock_read:
119
+ result = is_rule_valid(contribution, classified_artefact_info={})
120
+ assert result is True
121
+ mock_read.assert_not_called()
122
+
123
+ # Also test rule as empty string
124
+ contribution.rule = ""
125
+ with patch("ara_cli.artefact_reader.ArtefactReader.read_artefact") as mock_read:
126
+ result = is_rule_valid(contribution, classified_artefact_info={})
127
+ assert result is True
128
+ mock_read.assert_not_called()
129
+
130
+
131
+ @pytest.mark.parametrize(
132
+ "parent,expected",
133
+ [
134
+ (None, True), # parent is None
135
+ (MagicMock(rules=None), False), # parent.rules is None
136
+ ],
137
+ )
138
+ def test_is_rule_valid_parent_or_rules_none(parent, expected):
139
+ contribution = MagicMock()
140
+ contribution.artefact_name = "foo"
141
+ contribution.classifier = "bar"
142
+ contribution.rule = "r1"
143
+ with patch(
144
+ "ara_cli.artefact_reader.ArtefactReader.read_artefact", return_value=parent
145
+ ) as mock_read:
146
+ result = is_rule_valid(contribution, classified_artefact_info={})
147
+ assert result is expected
148
+ mock_read.assert_called_once_with("foo", "bar")
149
+
150
+
151
+ def test_is_rule_valid_rule_not_in_parent_rules():
152
+ """Should return False if rule is not in parent.rules."""
153
+ contribution = MagicMock()
154
+ contribution.artefact_name = "foo"
155
+ contribution.classifier = "bar"
156
+ contribution.rule = "missing_rule"
157
+ parent = MagicMock()
158
+ parent.rules = ["rule1", "rule2"]
159
+ with patch(
160
+ "ara_cli.artefact_reader.ArtefactReader.read_artefact", return_value=parent
161
+ ) as mock_read:
162
+ result = is_rule_valid(contribution, classified_artefact_info={})
163
+ assert result is False
164
+ mock_read.assert_called_once_with("foo", "bar")
165
+
166
+
167
+ def test_is_rule_valid_rule_in_parent_rules():
168
+ """Should return True if rule is in parent.rules."""
169
+ contribution = MagicMock()
170
+ contribution.artefact_name = "foo"
171
+ contribution.classifier = "bar"
172
+ contribution.rule = "my_rule"
173
+ parent = MagicMock()
174
+ parent.rules = ["my_rule", "other_rule"]
175
+ with patch(
176
+ "ara_cli.artefact_reader.ArtefactReader.read_artefact", return_value=parent
177
+ ) as mock_read:
178
+ result = is_rule_valid(contribution, classified_artefact_info={})
179
+ assert result is True
180
+ mock_read.assert_called_once_with("foo", "bar")
181
+
182
+
183
+ @pytest.mark.parametrize(
184
+ "contribution_attrs,expected",
185
+ [
186
+ # contribution is None
187
+ (None, True),
188
+ # artefact_name missing/None/empty
189
+ ({"artefact_name": None, "classifier": "foo"}, True),
190
+ ({"artefact_name": "", "classifier": "foo"}, True),
191
+ # classifier missing/None/empty
192
+ ({"artefact_name": "bar", "classifier": None}, True),
193
+ ({"artefact_name": "bar", "classifier": ""}, True),
194
+ ],
195
+ )
196
+ def test_is_contribution_valid_short_circuits(contribution_attrs, expected):
197
+ classified_artefact_info = {"dummy": []}
198
+ if contribution_attrs is None:
199
+ contribution = None
200
+ else:
201
+ contribution = MagicMock()
202
+ for k, v in contribution_attrs.items():
203
+ setattr(contribution, k, v)
204
+ with patch(
205
+ "ara_cli.artefact_fuzzy_search.extract_artefact_names_of_classifier"
206
+ ) as mock_extract:
207
+ result = is_contribution_valid(contribution, classified_artefact_info)
208
+ assert result is expected
209
+ # The extract function should NOT be called in these cases
210
+ mock_extract.assert_not_called()
211
+
212
+
213
+ def test_is_contribution_valid_references_existing_artefact():
214
+ """
215
+ contribution with valid artefact_name and classifier,
216
+ artefact_name IS in list returned by extract_artefact_names_of_classifier.
217
+ """
218
+ contribution = MagicMock()
219
+ contribution.artefact_name = "valid_art"
220
+ contribution.classifier = "valid_clf"
221
+ classified_artefact_info = {"valid_clf": [{"name": "valid_art"}]}
222
+ with patch(
223
+ "ara_cli.artefact_fuzzy_search.extract_artefact_names_of_classifier",
224
+ return_value=["valid_art"],
225
+ ) as mock_extract:
226
+ result = is_contribution_valid(contribution, classified_artefact_info)
227
+ assert result is True
228
+ mock_extract.assert_called_once_with(
229
+ classified_files=classified_artefact_info, classifier="valid_clf"
230
+ )
231
+
232
+
233
+ def test_is_contribution_valid_references_missing_artefact():
234
+ """
235
+ contribution with valid artefact_name and classifier,
236
+ artefact_name is NOT in list returned by extract_artefact_names_of_classifier.
237
+ """
238
+ contribution = MagicMock()
239
+ contribution.artefact_name = "missing_art"
240
+ contribution.classifier = "some_clf"
241
+ classified_artefact_info = {"some_clf": [{"name": "another"}]}
242
+ with patch(
243
+ "ara_cli.artefact_fuzzy_search.extract_artefact_names_of_classifier",
244
+ return_value=["another", "something_else"],
245
+ ) as mock_extract:
246
+ result = is_contribution_valid(contribution, classified_artefact_info)
247
+ assert result is False
248
+ mock_extract.assert_called_once_with(
249
+ classified_files=classified_artefact_info, classifier="some_clf"
250
+ )
5
251
 
6
252
 
7
253
  def test_check_file_valid():
8
254
  """Tests the happy path where the file is valid and the title matches."""
9
255
  mock_artefact_instance = MagicMock()
10
256
  mock_artefact_instance.title = "dummy_path"
257
+ # Mock contribution to be None to avoid contribution reference check
258
+ mock_artefact_instance.contribution = None
11
259
 
12
260
  mock_artefact_class = MagicMock()
13
261
  mock_artefact_class.deserialize.return_value = mock_artefact_instance
14
262
 
15
263
  with patch("builtins.open", mock_open(read_data="valid content")):
16
264
  is_valid, reason = check_file("dummy_path.feature", mock_artefact_class)
17
-
18
- assert is_valid is True
19
- assert reason is None
265
+
266
+ assert (
267
+ reason is None
268
+ ), f"Reason for invalid found, expected none to be found. The reason found: {reason}"
269
+ assert is_valid is True, "File detected as invalid, expected to be valid"
20
270
 
21
271
 
22
272
  def test_check_file_title_mismatch():
@@ -49,8 +299,7 @@ def test_check_file_value_error():
49
299
 
50
300
  def test_check_file_assertion_error():
51
301
  mock_artefact_class = MagicMock()
52
- mock_artefact_class.deserialize.side_effect = AssertionError(
53
- "Assertion error")
302
+ mock_artefact_class.deserialize.side_effect = AssertionError("Assertion error")
54
303
 
55
304
  with patch("builtins.open", mock_open(read_data="invalid content")):
56
305
  is_valid, reason = check_file("dummy_path", mock_artefact_class)
@@ -76,36 +325,46 @@ def test_check_file_unexpected_error():
76
325
  assert is_valid is False
77
326
  assert "Unexpected error: Exception('Unexpected error')" in reason
78
327
 
328
+
79
329
  # Tests for find_invalid_files
80
330
 
81
331
 
82
332
  def test_find_invalid_files():
333
+ """Tests finding invalid files with proper mocking of check_file."""
83
334
  mock_artefact_class = MagicMock()
84
- with patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping", {"test_classifier": mock_artefact_class}):
85
- artefact_files = {
86
- "test_classifier": [
87
- {"file_path": "file1.txt"}, # Should be checked
88
- {"file_path": "file2.txt"}, # Should be checked
89
- {"file_path": "templates/file3.txt"}, # Should be skipped
90
- {"file_path": "some/path/file.data"} # Should be skipped
91
- ]
92
- }
335
+ classified_info = {
336
+ "test_classifier": [
337
+ {"file_path": "file1.txt"}, # Should be checked
338
+ {"file_path": "file2.txt"}, # Should be checked
339
+ {"file_path": "templates/file3.txt"}, # Should be skipped
340
+ {"file_path": "some/path/file.data"}, # Should be skipped
341
+ ]
342
+ }
93
343
 
344
+ with patch(
345
+ "ara_cli.artefact_models.artefact_mapping.artefact_type_mapping",
346
+ {"test_classifier": mock_artefact_class},
347
+ ):
94
348
  with patch("ara_cli.artefact_scan.check_file") as mock_check_file:
95
349
  mock_check_file.side_effect = [
96
- (True, None), # for file1.txt
97
- (False, "Invalid content") # for file2.txt
350
+ (True, None), # for file1.txt
351
+ (False, "Invalid content"), # for file2.txt
98
352
  ]
99
353
 
100
- invalid_files = find_invalid_files(
101
- artefact_files, "test_classifier")
354
+ invalid_files = find_invalid_files(classified_info, "test_classifier")
355
+
102
356
  assert len(invalid_files) == 1
103
357
  assert invalid_files[0] == ("file2.txt", "Invalid content")
104
358
  assert mock_check_file.call_count == 2
105
- mock_check_file.assert_has_calls([
106
- call("file1.txt", mock_artefact_class),
107
- call("file2.txt", mock_artefact_class)
108
- ], any_order=False)
359
+
360
+ # Check that check_file was called with the correct parameters
361
+ mock_check_file.assert_has_calls(
362
+ [
363
+ call("file1.txt", mock_artefact_class, classified_info),
364
+ call("file2.txt", mock_artefact_class, classified_info),
365
+ ],
366
+ any_order=False,
367
+ )
109
368
 
110
369
 
111
370
  def test_show_results_no_issues(capsys):
@@ -114,18 +373,20 @@ def test_show_results_no_issues(capsys):
114
373
  show_results(invalid_artefacts)
115
374
  captured = capsys.readouterr()
116
375
  assert captured.out == "All files are good!\n"
117
- m.assert_called_once_with("incompatible_artefacts_report.md", "w")
376
+ m.assert_called_once_with(
377
+ "incompatible_artefacts_report.md", "w", encoding="utf-8"
378
+ )
118
379
  handle = m()
119
- handle.write.assert_has_calls([
120
- call("# Artefact Check Report\n\n"),
121
- call("No problems found.\n")
122
- ], any_order=False)
380
+ handle.write.assert_has_calls(
381
+ [call("# Artefact Check Report\n\n"), call("No problems found.\n")],
382
+ any_order=False,
383
+ )
123
384
 
124
385
 
125
386
  def test_show_results_with_issues(capsys):
126
387
  invalid_artefacts = {
127
388
  "classifier1": [("file1.txt", "reason1"), ("file2.txt", "reason2")],
128
- "classifier2": [("file3.txt", "reason3")]
389
+ "classifier2": [("file3.txt", "reason3")],
129
390
  }
130
391
  with patch("builtins.open", mock_open()) as m:
131
392
  show_results(invalid_artefacts)
@@ -141,7 +402,9 @@ def test_show_results_with_issues(capsys):
141
402
  "\t\treason3\n"
142
403
  )
143
404
  assert captured.out == expected_output
144
- m.assert_called_once_with("incompatible_artefacts_report.md", "w")
405
+ m.assert_called_once_with(
406
+ "incompatible_artefacts_report.md", "w", encoding="utf-8"
407
+ )
145
408
  handle = m()
146
409
  expected_writes = [
147
410
  call("# Artefact Check Report\n\n"),
@@ -151,6 +414,37 @@ def test_show_results_with_issues(capsys):
151
414
  call("\n"),
152
415
  call("## classifier2\n"),
153
416
  call("- `file3.txt`: reason3\n"),
154
- call("\n")
417
+ call("\n"),
155
418
  ]
156
- handle.write.assert_has_calls(expected_writes, any_order=False)
419
+ handle.write.assert_has_calls(expected_writes, any_order=False)
420
+
421
+
422
+ def test_check_file_with_invalid_contribution():
423
+ """Tests file with invalid contribution reference."""
424
+ mock_artefact_instance = MagicMock()
425
+ mock_artefact_instance.title = "dummy_path"
426
+
427
+ # Set up invalid contribution
428
+ mock_contribution = MagicMock()
429
+ mock_contribution.classifier = "test_classifier"
430
+ mock_contribution.artefact_name = "non_existing_artefact"
431
+ mock_artefact_instance.contribution = mock_contribution
432
+
433
+ mock_artefact_class = MagicMock()
434
+ mock_artefact_class.deserialize.return_value = mock_artefact_instance
435
+
436
+ # Mock classified_artefact_info
437
+ classified_info = {"test_classifier": [{"name": "existing_artefact"}]}
438
+
439
+ # Mock extract_artefact_names_of_classifier to return a list without the referenced artefact
440
+ with patch("builtins.open", mock_open(read_data="valid content")):
441
+ with patch(
442
+ "ara_cli.artefact_fuzzy_search.extract_artefact_names_of_classifier",
443
+ return_value=["existing_artefact"],
444
+ ):
445
+ is_valid, reason = check_file(
446
+ "dummy_path.feature", mock_artefact_class, classified_info
447
+ )
448
+
449
+ assert is_valid is False
450
+ assert "Invalid Contribution Reference" in reason