ara-cli 0.1.9.96__py3-none-any.whl → 0.1.10.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.
tests/test_chat.py CHANGED
@@ -8,6 +8,7 @@ import ara_cli
8
8
  from unittest.mock import patch, MagicMock, mock_open
9
9
  from types import SimpleNamespace
10
10
  from ara_cli.chat import Chat
11
+ from ara_cli.error_handler import AraError
11
12
  from ara_cli.template_manager import TemplatePathManager
12
13
  from ara_cli.ara_config import ConfigManager
13
14
  from ara_cli.file_loaders.text_file_loader import TextFileLoader
@@ -49,7 +50,7 @@ def get_default_config():
49
50
  @pytest.fixture
50
51
  def temp_chat_file():
51
52
  """Fixture to create a temporary chat file."""
52
- temp_file = tempfile.NamedTemporaryFile(delete=True, mode='w+', encoding='utf-8')
53
+ temp_file = tempfile.NamedTemporaryFile(delete=True, mode="w+", encoding="utf-8")
53
54
  yield temp_file
54
55
  temp_file.close()
55
56
 
@@ -57,7 +58,7 @@ def temp_chat_file():
57
58
  @pytest.fixture
58
59
  def temp_load_file():
59
60
  """Fixture to create a temporary file to load."""
60
- temp_file = tempfile.NamedTemporaryFile(delete=True, mode='w+', encoding='utf-8')
61
+ temp_file = tempfile.NamedTemporaryFile(delete=True, mode="w+", encoding="utf-8")
61
62
  temp_file.write("This is the content to load.")
62
63
  temp_file.flush()
63
64
  yield temp_file
@@ -65,19 +66,23 @@ def temp_load_file():
65
66
 
66
67
 
67
68
  def test_handle_existing_chat_no_reset(temp_chat_file):
68
- with patch('builtins.input', return_value='n'):
69
+ with patch("builtins.input", return_value="n"):
69
70
  mock_config = get_default_config()
70
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
71
+ with patch(
72
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
73
+ ):
71
74
  chat = Chat(temp_chat_file.name, reset=None)
72
75
  assert chat.chat_name == temp_chat_file.name
73
76
 
74
77
 
75
78
  def test_handle_existing_chat_with_reset(temp_chat_file):
76
- with patch('builtins.input', return_value='y'):
79
+ with patch("builtins.input", return_value="y"):
77
80
  mock_config = get_default_config()
78
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
81
+ with patch(
82
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
83
+ ):
79
84
  chat = Chat(temp_chat_file.name, reset=None)
80
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
85
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
81
86
  content = file.read()
82
87
  assert content.strip() == "# ara prompt:"
83
88
 
@@ -85,33 +90,42 @@ def test_handle_existing_chat_with_reset(temp_chat_file):
85
90
  def test_handle_existing_chat_reset_flag(temp_chat_file):
86
91
  mock_config = get_default_config()
87
92
 
88
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
93
+ with patch(
94
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
95
+ ):
89
96
  Chat(temp_chat_file.name, reset=True)
90
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
97
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
91
98
  content = file.read()
92
99
  assert content.strip() == "# ara prompt:"
93
100
 
94
101
 
95
- @pytest.mark.parametrize("chat_name, expected_file_name", [
96
- ("test", "test_chat.md"),
97
- ("test.md", "test.md"),
98
- ("test_chat", "test_chat.md"),
99
- ("test_chat.md", "test_chat.md"),
100
- ("another_test", "another_test_chat.md"),
101
- ("another_test.md", "another_test.md")
102
- ])
102
+ @pytest.mark.parametrize(
103
+ "chat_name, expected_file_name",
104
+ [
105
+ ("test", "test_chat.md"),
106
+ ("test.md", "test.md"),
107
+ ("test_chat", "test_chat.md"),
108
+ ("test_chat.md", "test_chat.md"),
109
+ ("another_test", "another_test_chat.md"),
110
+ ("another_test.md", "another_test.md"),
111
+ ],
112
+ )
103
113
  def test_initialize_new_chat(chat_name, expected_file_name):
104
114
  with tempfile.TemporaryDirectory() as temp_dir:
105
115
  temp_chat_file_path = os.path.join(temp_dir, "temp_chat_file.md")
106
116
  mock_config = get_default_config()
107
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
117
+ with patch(
118
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
119
+ ):
108
120
  chat_instance = Chat(temp_chat_file_path, reset=False)
109
- created_chat_file = chat_instance.initialize_new_chat(os.path.join(temp_dir, chat_name))
121
+ created_chat_file = chat_instance.initialize_new_chat(
122
+ os.path.join(temp_dir, chat_name)
123
+ )
110
124
 
111
125
  assert created_chat_file.endswith(expected_file_name)
112
126
  assert os.path.exists(created_chat_file)
113
127
 
114
- with open(created_chat_file, 'r', encoding='utf-8') as file:
128
+ with open(created_chat_file, "r", encoding="utf-8") as file:
115
129
  content = file.read()
116
130
 
117
131
  assert content == chat_instance.default_chat_content
@@ -123,36 +137,49 @@ def test_init_with_limited_command_set():
123
137
  temp_chat_file_path = os.path.join(temp_dir, "temp_chat_file.md")
124
138
 
125
139
  mock_config = get_default_config()
126
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
127
- chat_instance = Chat(temp_chat_file_path, reset=False, enable_commands=enable_commands)
140
+ with patch(
141
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
142
+ ):
143
+ chat_instance = Chat(
144
+ temp_chat_file_path, reset=False, enable_commands=enable_commands
145
+ )
128
146
 
129
- assert 'r' in chat_instance.aliases
130
- assert 's' in chat_instance.aliases
131
- assert 'QUIT' in chat_instance.aliases
132
- assert 'q' in chat_instance.aliases
133
- assert 'h' in chat_instance.aliases
147
+ assert "r" in chat_instance.aliases
148
+ assert "s" in chat_instance.aliases
149
+ assert "QUIT" in chat_instance.aliases
150
+ assert "q" in chat_instance.aliases
151
+ assert "h" in chat_instance.aliases
134
152
 
135
153
  assert "shell" in chat_instance.hidden_commands
136
154
  assert getattr(chat_instance, "do_shell") == chat_instance.default
137
155
 
138
156
 
139
- @pytest.mark.parametrize("chat_name, existing_files, expected", [
140
- ("test_chat", ["test_chat"], "test_chat"),
141
- ("test_chat", ["test_chat.md"], "test_chat.md"),
142
- ("test_chat", ["test_chat_chat.md"], "test_chat_chat.md"),
143
- ("new_chat", [], "new_chat_chat.md"),
144
- ])
157
+ @pytest.mark.parametrize(
158
+ "chat_name, existing_files, expected",
159
+ [
160
+ ("test_chat", ["test_chat"], "test_chat"),
161
+ ("test_chat", ["test_chat.md"], "test_chat.md"),
162
+ ("test_chat", ["test_chat_chat.md"], "test_chat_chat.md"),
163
+ ("new_chat", [], "new_chat_chat.md"),
164
+ ],
165
+ )
145
166
  def test_setup_chat(monkeypatch, chat_name, existing_files, expected):
146
167
  def mock_exists(path):
147
168
  return path in existing_files
148
169
 
149
- monkeypatch.setattr(os.path, 'exists', mock_exists)
150
- monkeypatch.setattr(Chat, 'handle_existing_chat', lambda self, chat_file, reset=None: chat_file)
151
- monkeypatch.setattr(Chat, 'initialize_new_chat', lambda self, chat_name: f"{chat_name}_chat.md")
170
+ monkeypatch.setattr(os.path, "exists", mock_exists)
171
+ monkeypatch.setattr(
172
+ Chat, "handle_existing_chat", lambda self, chat_file, reset=None: chat_file
173
+ )
174
+ monkeypatch.setattr(
175
+ Chat, "initialize_new_chat", lambda self, chat_name: f"{chat_name}_chat.md"
176
+ )
152
177
 
153
178
  mock_config = get_default_config()
154
179
 
155
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
180
+ with patch(
181
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
182
+ ):
156
183
  chat_instance = Chat(chat_name)
157
184
  result = chat_instance.setup_chat(chat_name)
158
185
  assert result == expected
@@ -161,7 +188,9 @@ def test_setup_chat(monkeypatch, chat_name, existing_files, expected):
161
188
  def test_disable_commands(temp_chat_file):
162
189
  mock_config = get_default_config()
163
190
 
164
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
191
+ with patch(
192
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
193
+ ):
165
194
  chat = Chat(temp_chat_file.name, reset=False)
166
195
 
167
196
  chat.aliases["q"] = "quit"
@@ -174,7 +203,7 @@ def test_disable_commands(temp_chat_file):
174
203
  chat.disable_commands(commands_to_disable)
175
204
 
176
205
  for command in commands_to_disable:
177
- assert getattr(chat, f'do_{command}') == chat.default
206
+ assert getattr(chat, f"do_{command}") == chat.default
178
207
  assert command in chat.hidden_commands
179
208
 
180
209
  assert "q" not in chat.aliases
@@ -184,15 +213,37 @@ def test_disable_commands(temp_chat_file):
184
213
  assert "r" in chat.aliases
185
214
 
186
215
 
187
- @pytest.mark.parametrize("lines, expected", [
188
- (["This is a line.", "Another line here.", "Yet another line."], None),
189
- (["This is a line.", "# ara prompt:", "Another line here."], "# ara prompt:"),
190
- (["This is a line.", "# ara prompt:", "Another line here.", "# ara response:"], "# ara response:"),
191
- (["This is a line.", " # ara prompt: ", "Another line here.", " # ara response: "], "# ara response:"),
192
- (["# ara prompt:", "# ara response:"], "# ara response:"),
193
- (["# ara response:", "# ara prompt:", "# ara prompt:", "# ara response:"], "# ara response:"),
194
- ([], None)
195
- ])
216
+ @pytest.mark.parametrize(
217
+ "lines, expected",
218
+ [
219
+ (["This is a line.", "Another line here.", "Yet another line."], None),
220
+ (["This is a line.", "# ara prompt:", "Another line here."], "# ara prompt:"),
221
+ (
222
+ [
223
+ "This is a line.",
224
+ "# ara prompt:",
225
+ "Another line here.",
226
+ "# ara response:",
227
+ ],
228
+ "# ara response:",
229
+ ),
230
+ (
231
+ [
232
+ "This is a line.",
233
+ " # ara prompt: ",
234
+ "Another line here.",
235
+ " # ara response: ",
236
+ ],
237
+ "# ara response:",
238
+ ),
239
+ (["# ara prompt:", "# ara response:"], "# ara response:"),
240
+ (
241
+ ["# ara response:", "# ara prompt:", "# ara prompt:", "# ara response:"],
242
+ "# ara response:",
243
+ ),
244
+ ([], None),
245
+ ],
246
+ )
196
247
  def test_get_last_role_marker(lines, expected):
197
248
  assert Chat.get_last_role_marker(lines=lines) == expected
198
249
 
@@ -202,7 +253,9 @@ def test_start_non_interactive(temp_chat_file, capsys):
202
253
  temp_chat_file.write(content)
203
254
  temp_chat_file.flush()
204
255
  mock_config = get_default_config()
205
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
256
+ with patch(
257
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
258
+ ):
206
259
  chat = Chat(temp_chat_file.name, reset=False)
207
260
  chat.start_non_interactive()
208
261
 
@@ -214,10 +267,12 @@ def test_start_non_interactive(temp_chat_file, capsys):
214
267
  def test_start(temp_chat_file):
215
268
  initial_dir = os.getcwd()
216
269
  mock_config = get_default_config()
217
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
270
+ with patch(
271
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
272
+ ):
218
273
  chat = Chat(temp_chat_file.name, reset=False)
219
274
 
220
- with patch('ara_cli.chat.Chat.cmdloop') as mock_cmdloop:
275
+ with patch("ara_cli.chat.Chat.cmdloop") as mock_cmdloop:
221
276
  chat.start()
222
277
  mock_cmdloop.assert_called_once()
223
278
 
@@ -226,103 +281,204 @@ def test_start(temp_chat_file):
226
281
  os.chdir(initial_dir)
227
282
 
228
283
 
229
- @pytest.mark.parametrize("initial_content, expected_content", [
230
- (["This is a line.\n", "Another line here.\n", "Yet another line.\n"],
231
- ["This is a line.\n", "Another line here.\n", "Yet another line.\n", "\n", "# ara prompt:"]),
232
-
233
- (["This is a line.\n", "# ara prompt:\n", "Another line here.\n"],
234
- ["This is a line.\n", "# ara prompt:\n", "Another line here.\n"]),
235
-
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
-
239
- (["This is a line.\n", " # ara prompt: \n", "Another line here.\n", " # ara response: \n"],
240
- ["This is a line.\n", " # ara prompt: \n", "Another line here.\n", " # ara response: \n", "\n", "# ara prompt:"]),
241
-
242
- (["# ara prompt:\n", "# ara response:\n"],
243
- ["# ara prompt:\n", "# ara response:\n", "\n", "# ara prompt:"]),
244
-
245
- (["# ara response:\n", "# ara prompt:\n", "# ara prompt:\n", "# ara response:\n"],
246
- ["# ara response:\n", "# ara prompt:\n", "# ara prompt:\n", "# ara response:\n", "\n", "# ara prompt:"]),
247
- ])
284
+ @pytest.mark.parametrize(
285
+ "initial_content, expected_content",
286
+ [
287
+ (
288
+ ["This is a line.\n", "Another line here.\n", "Yet another line.\n"],
289
+ [
290
+ "This is a line.\n",
291
+ "Another line here.\n",
292
+ "Yet another line.\n",
293
+ "\n",
294
+ "# ara prompt:",
295
+ ],
296
+ ),
297
+ (
298
+ ["This is a line.\n", "# ara prompt:\n", "Another line here.\n"],
299
+ ["This is a line.\n", "# ara prompt:\n", "Another line here.\n"],
300
+ ),
301
+ (
302
+ [
303
+ "This is a line.\n",
304
+ "# ara prompt:\n",
305
+ "Another line here.\n",
306
+ "# ara response:\n",
307
+ ],
308
+ [
309
+ "This is a line.\n",
310
+ "# ara prompt:\n",
311
+ "Another line here.\n",
312
+ "# ara response:\n",
313
+ "\n",
314
+ "# ara prompt:",
315
+ ],
316
+ ),
317
+ (
318
+ [
319
+ "This is a line.\n",
320
+ " # ara prompt: \n",
321
+ "Another line here.\n",
322
+ " # ara response: \n",
323
+ ],
324
+ [
325
+ "This is a line.\n",
326
+ " # ara prompt: \n",
327
+ "Another line here.\n",
328
+ " # ara response: \n",
329
+ "\n",
330
+ "# ara prompt:",
331
+ ],
332
+ ),
333
+ (
334
+ ["# ara prompt:\n", "# ara response:\n"],
335
+ ["# ara prompt:\n", "# ara response:\n", "\n", "# ara prompt:"],
336
+ ),
337
+ (
338
+ [
339
+ "# ara response:\n",
340
+ "# ara prompt:\n",
341
+ "# ara prompt:\n",
342
+ "# ara response:\n",
343
+ ],
344
+ [
345
+ "# ara response:\n",
346
+ "# ara prompt:\n",
347
+ "# ara prompt:\n",
348
+ "# ara response:\n",
349
+ "\n",
350
+ "# ara prompt:",
351
+ ],
352
+ ),
353
+ ],
354
+ )
248
355
  def test_add_prompt_tag_if_needed(temp_chat_file, initial_content, expected_content):
249
356
  temp_chat_file.writelines(initial_content)
250
357
  temp_chat_file.flush()
251
358
 
252
359
  mock_config = get_default_config()
253
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
254
- Chat(temp_chat_file.name, reset=False).add_prompt_tag_if_needed(temp_chat_file.name)
360
+ with patch(
361
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
362
+ ):
363
+ Chat(temp_chat_file.name, reset=False).add_prompt_tag_if_needed(
364
+ temp_chat_file.name
365
+ )
255
366
 
256
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
367
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
257
368
  lines = file.readlines()
258
369
 
259
370
  assert lines == expected_content
260
371
 
261
372
 
262
- @pytest.mark.parametrize("lines, expected", [
263
- (["\n", " ", "# ara prompt:", "Another line here.", " \n"], "Another line here."),
264
- (["This is a line.", "Another line here.", " \n", "\n"], "Another line here."),
265
- (["\n", " \n", " \n"], ""),
266
- (["This is a line.", "Another line here.", "# ara response:", " \n"], "# ara response:"),
267
- ])
373
+ @pytest.mark.parametrize(
374
+ "lines, expected",
375
+ [
376
+ (
377
+ ["\n", " ", "# ara prompt:", "Another line here.", " \n"],
378
+ "Another line here.",
379
+ ),
380
+ (["This is a line.", "Another line here.", " \n", "\n"], "Another line here."),
381
+ (["\n", " \n", " \n"], ""),
382
+ (
383
+ ["This is a line.", "Another line here.", "# ara response:", " \n"],
384
+ "# ara response:",
385
+ ),
386
+ ],
387
+ )
268
388
  def test_get_last_non_empty_line(lines, expected, temp_chat_file):
269
- temp_chat_file.writelines(line + '\n' for line in lines)
389
+ temp_chat_file.writelines(line + "\n" for line in lines)
270
390
  temp_chat_file.flush()
271
391
 
272
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
392
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
273
393
  assert Chat.get_last_non_empty_line(Chat, file) == expected
274
394
 
275
395
 
276
- @pytest.mark.parametrize("lines, expected", [
277
- (["\n", " ", "# ara prompt:", "Another line here.", " \n"], ""),
278
- (["This is a line.", "Another line here."], "Another line here."),
279
- (["\n", " \n", " \n"], ""),
280
- (["This is a line.", "Another line here.", "# ara response:", " \n"], ""),
281
- ([], ""),
282
- ([""], "")
283
- ])
396
+ @pytest.mark.parametrize(
397
+ "lines, expected",
398
+ [
399
+ (["\n", " ", "# ara prompt:", "Another line here.", " \n"], ""),
400
+ (["This is a line.", "Another line here."], "Another line here."),
401
+ (["\n", " \n", " \n"], ""),
402
+ (["This is a line.", "Another line here.", "# ara response:", " \n"], ""),
403
+ ([], ""),
404
+ ([""], ""),
405
+ ],
406
+ )
284
407
  def test_get_last_line(lines, expected, temp_chat_file):
285
- temp_chat_file.writelines(line + '\n' for line in lines)
408
+ temp_chat_file.writelines(line + "\n" for line in lines)
286
409
  temp_chat_file.flush()
287
410
 
288
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
411
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
289
412
  assert Chat.get_last_line(Chat, file) == expected
290
413
 
291
414
 
292
- @pytest.mark.parametrize("chat_history, expected_text_content, expected_image_data_list", [
293
- (["Message 1", "Message 2"], "Message 1\nMessage 2", []),
294
- (["Text with image", "(data:image/png;base64,abc123)"],
295
- "Text with image",
296
- [{"type": "image_url", "image_url": {"url": "data:image/png;base64,abc123"}}]),
297
- (["Just text", "Another (data:image/png;base64,xyz789) image"],
298
- "Just text",
299
- [{"type": "image_url", "image_url": {"url": "data:image/png;base64,xyz789"}}]),
300
- (["No images here at all"], "No images here at all", []),
301
- ])
302
- def test_assemble_prompt(temp_chat_file, chat_history, expected_text_content, expected_image_data_list):
415
+ @pytest.mark.parametrize(
416
+ "chat_history, expected_text_content, expected_image_data_list",
417
+ [
418
+ (["Message 1", "Message 2"], "Message 1\nMessage 2", []),
419
+ (
420
+ ["Text with image", "(data:image/png;base64,abc123)"],
421
+ "Text with image",
422
+ [
423
+ {
424
+ "type": "image_url",
425
+ "image_url": {"url": "data:image/png;base64,abc123"},
426
+ }
427
+ ],
428
+ ),
429
+ (
430
+ ["Just text", "Another (data:image/png;base64,xyz789) image"],
431
+ "Just text",
432
+ [
433
+ {
434
+ "type": "image_url",
435
+ "image_url": {"url": "data:image/png;base64,xyz789"},
436
+ }
437
+ ],
438
+ ),
439
+ (["No images here at all"], "No images here at all", []),
440
+ ],
441
+ )
442
+ def test_assemble_prompt(
443
+ temp_chat_file, chat_history, expected_text_content, expected_image_data_list
444
+ ):
303
445
  mock_config = get_default_config()
304
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
446
+ with patch(
447
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
448
+ ):
305
449
  chat = Chat(temp_chat_file.name, reset=False)
306
450
  chat.chat_history = chat_history
307
451
 
308
- with patch('ara_cli.prompt_handler.append_images_to_message', return_value="mocked combined content") as mock_append:
309
- combined_content = chat.assemble_prompt()
310
-
311
- assert combined_content == [{'content': 'You are a helpful assistant that can process both text and images.', 'role': 'system'}, 'mocked combined content']
452
+ with patch('ara_cli.prompt_handler.append_images_to_message', return_value="mocked combined content") as mock_append, \
453
+ patch('ara_cli.prompt_handler.prepend_system_prompt', return_value=[{'role': 'system', 'content': 'You are a helpful assistant that can process both text and images.'}]) as mock_prepend:
454
+ chat.assemble_prompt()
312
455
 
313
456
  mock_append.assert_called_once_with(
314
- {'role': 'user', 'content': [{'type': 'text', 'text': expected_text_content}]},
315
- expected_image_data_list)
457
+ {
458
+ "role": "user",
459
+ "content": [{"type": "text", "text": expected_text_content}],
460
+ },
461
+ expected_image_data_list,
462
+ )
463
+
464
+ mock_prepend.assert_called_once()
316
465
 
317
466
 
318
- @pytest.mark.parametrize("chat_history, last_line_in_file, expected_written_content", [
319
- (["Message 1", "Message 2"], "Some other line", "\n# ara response:\n"),
320
- (["Message 1", "Message 2"], "Some other line\n", "# ara response:\n"),
321
- (["Message 1", "Message 2"], "# ara response:", ""),
322
- ])
323
- def test_send_message(temp_chat_file, chat_history, last_line_in_file, expected_written_content):
467
+ @pytest.mark.parametrize(
468
+ "chat_history, last_line_in_file, expected_written_content",
469
+ [
470
+ (["Message 1", "Message 2"], "Some other line", "\n# ara response:\n"),
471
+ (["Message 1", "Message 2"], "Some other line\n", "# ara response:\n"),
472
+ (["Message 1", "Message 2"], "# ara response:", ""),
473
+ ],
474
+ )
475
+ def test_send_message(
476
+ temp_chat_file, chat_history, last_line_in_file, expected_written_content
477
+ ):
324
478
  mock_config = get_default_config()
325
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
479
+ with patch(
480
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
481
+ ):
326
482
  chat = Chat(temp_chat_file.name, reset=False)
327
483
  chat.chat_history = chat_history
328
484
 
@@ -333,9 +489,9 @@ def test_send_message(temp_chat_file, chat_history, last_line_in_file, expected_
333
489
 
334
490
  mock_chunks = [mock_chunk1, mock_chunk2]
335
491
 
336
- with patch('ara_cli.chat.send_prompt', return_value=mock_chunks), \
337
- patch.object(chat, 'get_last_line', return_value=last_line_in_file), \
338
- patch.object(chat, 'assemble_prompt', return_value="mocked prompt"):
492
+ with patch("ara_cli.chat.send_prompt", return_value=mock_chunks), patch.object(
493
+ chat, "get_last_line", return_value=last_line_in_file
494
+ ), patch.object(chat, "assemble_prompt", return_value="mocked prompt"):
339
495
 
340
496
  m = mock_open(read_data=last_line_in_file)
341
497
  with patch("builtins.open", m):
@@ -347,67 +503,138 @@ def test_send_message(temp_chat_file, chat_history, last_line_in_file, expected_
347
503
  assert "response_part_2" in written_content
348
504
 
349
505
 
350
- @pytest.mark.parametrize("role, message, initial_content, expected_content", [
351
- ("ara prompt", "This is a new prompt message.",
352
- ["Existing content.\n"],
353
- ["Existing content.\n", "\n", "# ara prompt:\nThis is a new prompt message.\n"]),
354
-
355
- ("ara response", "This is a new response message.",
356
- ["# ara prompt:\nThis is a prompt.\n"],
357
- ["# ara prompt:\nThis is a prompt.\n", "\n", "# ara response:\nThis is a new response message.\n"]),
358
-
359
- ("ara prompt", "This is another prompt.",
360
- ["# ara response:\nThis is a response.\n"],
361
- ["# ara response:\nThis is a response.\n", "\n", "# ara prompt:\nThis is another prompt.\n"]),
362
-
363
- ("ara response", "Another response here.",
364
- ["# ara prompt:\nPrompt here.\n", "# ara response:\nFirst response.\n"],
365
- ["# ara prompt:\nPrompt here.\n", "# ara response:\nFirst response.\n", "\n", "# ara response:\nAnother response here.\n"]),
366
-
367
- ("ara prompt", "Final prompt message.",
368
- ["# ara prompt:\nInitial prompt.\n", "# ara response:\nResponse here.\n"],
369
- ["# ara prompt:\nInitial prompt.\n", "# ara response:\nResponse here.\n", "\n", "# ara prompt:\nFinal prompt message.\n"])
370
- ])
506
+ @pytest.mark.parametrize(
507
+ "role, message, initial_content, expected_content",
508
+ [
509
+ (
510
+ "ara prompt",
511
+ "This is a new prompt message.",
512
+ ["Existing content.\n"],
513
+ [
514
+ "Existing content.\n",
515
+ "\n",
516
+ "# ara prompt:\nThis is a new prompt message.\n",
517
+ ],
518
+ ),
519
+ (
520
+ "ara response",
521
+ "This is a new response message.",
522
+ ["# ara prompt:\nThis is a prompt.\n"],
523
+ [
524
+ "# ara prompt:\nThis is a prompt.\n",
525
+ "\n",
526
+ "# ara response:\nThis is a new response message.\n",
527
+ ],
528
+ ),
529
+ (
530
+ "ara prompt",
531
+ "This is another prompt.",
532
+ ["# ara response:\nThis is a response.\n"],
533
+ [
534
+ "# ara response:\nThis is a response.\n",
535
+ "\n",
536
+ "# ara prompt:\nThis is another prompt.\n",
537
+ ],
538
+ ),
539
+ (
540
+ "ara response",
541
+ "Another response here.",
542
+ ["# ara prompt:\nPrompt here.\n", "# ara response:\nFirst response.\n"],
543
+ [
544
+ "# ara prompt:\nPrompt here.\n",
545
+ "# ara response:\nFirst response.\n",
546
+ "\n",
547
+ "# ara response:\nAnother response here.\n",
548
+ ],
549
+ ),
550
+ (
551
+ "ara prompt",
552
+ "Final prompt message.",
553
+ ["# ara prompt:\nInitial prompt.\n", "# ara response:\nResponse here.\n"],
554
+ [
555
+ "# ara prompt:\nInitial prompt.\n",
556
+ "# ara response:\nResponse here.\n",
557
+ "\n",
558
+ "# ara prompt:\nFinal prompt message.\n",
559
+ ],
560
+ ),
561
+ ],
562
+ )
371
563
  def test_save_message(temp_chat_file, role, message, initial_content, expected_content):
372
564
  temp_chat_file.writelines(initial_content)
373
565
  temp_chat_file.flush()
374
566
 
375
567
  mock_config = get_default_config()
376
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
568
+ with patch(
569
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
570
+ ):
377
571
  chat_instance = Chat(temp_chat_file.name, reset=False)
378
572
  chat_instance.save_message(role, message)
379
573
 
380
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
574
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
381
575
  lines = file.readlines()
382
576
 
383
- assert ''.join(lines) == ''.join(expected_content)
577
+ assert "".join(lines) == "".join(expected_content)
384
578
 
385
579
 
386
- @pytest.mark.parametrize("initial_content, expected_content", [
387
- (["# ara prompt:\nPrompt message.\n", "# ara response:\nResponse message.\n"],
388
- ["# ara prompt:\nPrompt message.\n"]),
389
- (["# ara prompt:\nPrompt message 1.\n", "# ara response:\nResponse message 1.\n", "# ara prompt:\nPrompt message 2.\n", "# ara response:\nResponse message 2.\n"],
390
- ["# ara prompt:\nPrompt message 1.\n", "# ara response:\nResponse message 1.\n", "# ara prompt:\nPrompt message 2.\n"]),
391
- (["# ara prompt:\nOnly prompt message.\n"],
392
- ["# ara prompt:\nOnly prompt message.\n"]),
393
- (["# ara prompt:\nPrompt message.\n", "# ara response:\nResponse message.\n", "# ara prompt:\nAnother prompt message.\n"],
394
- ["# ara prompt:\nPrompt message.\n", "# ara response:\nResponse message.\n", "# ara prompt:\nAnother prompt message.\n"]),
395
- ])
580
+ @pytest.mark.parametrize(
581
+ "initial_content, expected_content",
582
+ [
583
+ (
584
+ [
585
+ "# ara prompt:\nPrompt message.\n",
586
+ "# ara response:\nResponse message.\n",
587
+ ],
588
+ ["# ara prompt:\nPrompt message.\n"],
589
+ ),
590
+ (
591
+ [
592
+ "# ara prompt:\nPrompt message 1.\n",
593
+ "# ara response:\nResponse message 1.\n",
594
+ "# ara prompt:\nPrompt message 2.\n",
595
+ "# ara response:\nResponse message 2.\n",
596
+ ],
597
+ [
598
+ "# ara prompt:\nPrompt message 1.\n",
599
+ "# ara response:\nResponse message 1.\n",
600
+ "# ara prompt:\nPrompt message 2.\n",
601
+ ],
602
+ ),
603
+ (
604
+ ["# ara prompt:\nOnly prompt message.\n"],
605
+ ["# ara prompt:\nOnly prompt message.\n"],
606
+ ),
607
+ (
608
+ [
609
+ "# ara prompt:\nPrompt message.\n",
610
+ "# ara response:\nResponse message.\n",
611
+ "# ara prompt:\nAnother prompt message.\n",
612
+ ],
613
+ [
614
+ "# ara prompt:\nPrompt message.\n",
615
+ "# ara response:\nResponse message.\n",
616
+ "# ara prompt:\nAnother prompt message.\n",
617
+ ],
618
+ ),
619
+ ],
620
+ )
396
621
  def test_resend_message(temp_chat_file, initial_content, expected_content):
397
622
  temp_chat_file.writelines(initial_content)
398
623
  temp_chat_file.flush()
399
624
 
400
625
  mock_config = get_default_config()
401
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
626
+ with patch(
627
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
628
+ ):
402
629
  chat = Chat(temp_chat_file.name, reset=False)
403
630
 
404
- with patch.object(chat, 'send_message') as mock_send_message:
631
+ with patch.object(chat, "send_message") as mock_send_message:
405
632
  chat.resend_message()
406
633
 
407
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
634
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
408
635
  lines = file.readlines()
409
636
 
410
- assert ''.join(lines) == ''.join(expected_content)
637
+ assert "".join(lines) == "".join(expected_content)
411
638
  mock_send_message.assert_called_once()
412
639
 
413
640
 
@@ -416,76 +643,64 @@ def test_resend_message_empty(temp_chat_file):
416
643
  temp_chat_file.flush()
417
644
 
418
645
  mock_config = get_default_config()
419
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
646
+ with patch(
647
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
648
+ ):
420
649
  chat = Chat(temp_chat_file.name, reset=False)
421
650
 
422
- with patch.object(chat, 'send_message') as mock_send_message:
651
+ with patch.object(chat, "send_message") as mock_send_message:
423
652
  chat.resend_message()
424
653
 
425
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
654
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
426
655
  lines = file.readlines()
427
656
 
428
- assert ''.join(lines) == ''
429
- assert ''.join(chat.chat_history) == ''
657
+ assert "".join(lines) == ""
658
+ assert "".join(chat.chat_history) == ""
430
659
  mock_send_message.assert_not_called()
431
660
 
432
661
 
433
- @pytest.mark.parametrize("strings, expected_content", [
434
- (["Line 1", "Line 2", "Line 3"], "Line 1\nLine 2\nLine 3\n"),
435
- (["Single line"], "Single line\n"),
436
- (["First line", "", "Third line"], "First line\n\nThird line\n"),
437
- ([], "\n"),
438
- ])
662
+ @pytest.mark.parametrize(
663
+ "strings, expected_content",
664
+ [
665
+ (["Line 1", "Line 2", "Line 3"], "Line 1\nLine 2\nLine 3\n"),
666
+ (["Single line"], "Single line\n"),
667
+ (["First line", "", "Third line"], "First line\n\nThird line\n"),
668
+ ([], "\n"),
669
+ ],
670
+ )
439
671
  def test_append_strings(temp_chat_file, strings, expected_content):
440
672
  mock_config = get_default_config()
441
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
673
+ with patch(
674
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
675
+ ):
442
676
  chat_instance = Chat(temp_chat_file.name, reset=False)
443
677
  chat_instance.append_strings(strings)
444
678
 
445
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
679
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
446
680
  content = file.read()
447
681
 
448
682
  assert content == expected_content
449
683
 
450
684
 
451
- def test_determine_file_path(temp_chat_file):
452
- mock_config = get_default_config()
453
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
454
- chat = Chat(temp_chat_file.name, reset=False)
455
-
456
- test_cases = [
457
- ("existing_in_current_dir.txt", True, True, "current_directory/existing_in_current_dir.txt"),
458
- ("existing_elsewhere.txt", False, True, "existing_elsewhere.txt"),
459
- ("non_existent.txt", False, False, None),
460
- ]
461
-
462
- with patch('os.path.exists') as mock_exists, \
463
- patch('os.path.dirname', return_value="current_directory") as mock_dirname:
464
-
465
- for file_name, exists_in_current, exists_elsewhere, expected_path in test_cases:
466
- mock_exists.side_effect = [exists_in_current, exists_elsewhere]
467
-
468
- result = chat.determine_file_path(file_name)
469
-
470
- assert result == expected_path
471
-
472
- mock_exists.reset_mock()
473
-
474
-
475
- @pytest.mark.parametrize("file_name, expected_content", [
476
- ("document.txt", "Hello World\n"),
477
- ("another_document.txt", "Another World\n"),
478
- ])
685
+ @pytest.mark.parametrize(
686
+ "file_name, expected_content",
687
+ [
688
+ ("document.txt", "Hello World\n"),
689
+ ("another_document.txt", "Another World\n"),
690
+ ],
691
+ )
479
692
  def test_load_text_file(temp_chat_file, file_name, expected_content):
480
693
  # Create a mock config
481
694
  mock_config = MagicMock()
482
695
 
483
696
  # Patch the get_config method to return the mock config
484
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
697
+ with patch(
698
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
699
+ ):
485
700
  chat = Chat(temp_chat_file.name, reset=False)
486
701
 
487
702
  # Mock the TextFileLoader
488
- with patch.object(TextFileLoader, 'load', return_value=True) as mock_load:
703
+ with patch.object(TextFileLoader, "load", return_value=True) as mock_load:
489
704
  # Call the load_text_file method
490
705
  result = chat.load_text_file(file_name)
491
706
 
@@ -501,7 +716,7 @@ def test_load_text_file(temp_chat_file, file_name, expected_content):
501
716
  [
502
717
  True,
503
718
  # False # TODO: @file_exists_check decorator should be fixed
504
- ]
719
+ ],
505
720
  )
506
721
  def test_load_binary_file(temp_chat_file, path_exists):
507
722
  """
@@ -515,24 +730,30 @@ def test_load_binary_file(temp_chat_file, path_exists):
515
730
  file_content = b"fake-binary-data"
516
731
 
517
732
  mock_config = get_default_config()
518
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
733
+ with patch(
734
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
735
+ ):
519
736
  chat = Chat(temp_chat_file.name, reset=False)
520
737
 
521
738
  # Path to the actual file to be loaded
522
739
  path_to_load = file_name if path_exists else None
523
740
 
524
741
  # We patch open within the loader's module
525
- with patch('ara_cli.file_loaders.binary_file_loader.open', mock_open(read_data=file_content)) as mock_loader_open, \
526
- patch.object(chat, 'determine_file_path', return_value=path_to_load):
742
+ with patch(
743
+ "ara_cli.file_loaders.binary_file_loader.open",
744
+ mock_open(read_data=file_content),
745
+ ) as mock_loader_open:
527
746
 
528
- result = chat.load_binary_file(file_name, mime_type=mime_type, prefix="PRE-", suffix="-POST")
747
+ result = chat.load_binary_file(
748
+ file_name, mime_type=mime_type, prefix="PRE-", suffix="-POST"
749
+ )
529
750
 
530
751
  if path_exists:
531
752
  assert result is True
532
753
  # Check read call for the image
533
- mock_loader_open.assert_any_call(file_name, 'rb')
754
+ mock_loader_open.assert_any_call(file_name, "rb")
534
755
  # Check write call to the chat file
535
- mock_loader_open.assert_any_call(chat.chat_name, 'a', encoding='utf-8')
756
+ mock_loader_open.assert_any_call(chat.chat_name, "a", encoding="utf-8")
536
757
 
537
758
  # Assuming the loader formats it as a base64 markdown image
538
759
  base64_encoded = base64.b64encode(file_content).decode("utf-8")
@@ -548,88 +769,117 @@ def test_load_binary_file(temp_chat_file, path_exists):
548
769
  mock_loader_open.assert_not_called()
549
770
 
550
771
 
551
- @pytest.mark.parametrize("file_name, module_to_mock, mock_setup, expected_content", [
552
- (
553
- "test.docx",
554
- "docx",
555
- lambda mock: setattr(mock.Document.return_value, 'paragraphs', [MagicMock(text="Docx content")]),
556
- "Docx content"
557
- ),
558
- pytest.param(
559
- "test.pdf",
560
- "pymupdf4llm",
561
- lambda mock: setattr(mock, 'to_markdown', MagicMock(return_value="PDF content")),
562
- "PDF content",
563
- marks=pytest.mark.filterwarnings("ignore::DeprecationWarning")
564
- ),
565
- pytest.param(
566
- "test.odt",
567
- "pymupdf4llm",
568
- lambda mock: setattr(mock, 'to_markdown', MagicMock(return_value="ODT content")),
569
- "ODT content",
570
- marks=pytest.mark.filterwarnings("ignore::DeprecationWarning")
571
- ),
572
- ])
573
- def test_load_document_file(temp_chat_file, file_name, module_to_mock, mock_setup, expected_content):
772
+ @pytest.mark.parametrize(
773
+ "file_name, module_to_mock, mock_setup, expected_content",
774
+ [
775
+ (
776
+ "test.docx",
777
+ "docx",
778
+ lambda mock: setattr(
779
+ mock.Document.return_value,
780
+ "paragraphs",
781
+ [MagicMock(text="Docx content")],
782
+ ),
783
+ "Docx content",
784
+ ),
785
+ pytest.param(
786
+ "test.pdf",
787
+ "pymupdf4llm",
788
+ lambda mock: setattr(
789
+ mock, "to_markdown", MagicMock(return_value="PDF content")
790
+ ),
791
+ "PDF content",
792
+ marks=pytest.mark.filterwarnings("ignore::DeprecationWarning"),
793
+ ),
794
+ pytest.param(
795
+ "test.odt",
796
+ "pymupdf4llm",
797
+ lambda mock: setattr(
798
+ mock, "to_markdown", MagicMock(return_value="ODT content")
799
+ ),
800
+ "ODT content",
801
+ marks=pytest.mark.filterwarnings("ignore::DeprecationWarning"),
802
+ ),
803
+ ],
804
+ )
805
+ def test_load_document_file(
806
+ temp_chat_file, file_name, module_to_mock, mock_setup, expected_content
807
+ ):
574
808
  mock_config = get_default_config()
575
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
809
+ with patch(
810
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
811
+ ):
576
812
  chat = Chat(temp_chat_file.name, reset=False)
577
813
 
578
814
  # Patch the dependency in sys.modules before it's imported inside the method
579
- with patch.dict('sys.modules', {module_to_mock: MagicMock()}) as mock_modules:
815
+ with patch.dict("sys.modules", {module_to_mock: MagicMock()}) as mock_modules:
580
816
  mock_setup(mock_modules[module_to_mock])
581
817
 
582
- with patch("ara_cli.file_loaders.document_file_loader.open", mock_open()) as mock_chat_open, \
583
- patch.object(chat, 'determine_file_path', return_value=file_name):
818
+ with patch(
819
+ "ara_cli.file_loaders.document_file_loader.open", mock_open()
820
+ ) as mock_chat_open:
584
821
  # FIX: Call with a positional argument `file_name` as the decorator expects, not a keyword `file_path`.
585
822
  result = chat.load_document_file(
586
- file_name, prefix="Prefix-", suffix="-Suffix", block_delimiter="```")
823
+ file_name, prefix="Prefix-", suffix="-Suffix", block_delimiter="```"
824
+ )
587
825
 
588
826
  assert result is True
589
827
  expected_write = f"Prefix-```\n{expected_content}\n```-Suffix\n"
590
- mock_chat_open.assert_called_with(
591
- chat.chat_name, 'a', encoding='utf-8')
828
+ mock_chat_open.assert_called_with(chat.chat_name, "a", encoding="utf-8")
592
829
  mock_chat_open().write.assert_called_once_with(expected_write)
593
830
 
594
831
 
595
832
  def test_load_document_file_unsupported(temp_chat_file, capsys):
596
833
  mock_config = get_default_config()
597
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
834
+ with patch(
835
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
836
+ ):
598
837
  chat = Chat(temp_chat_file.name, reset=False)
599
838
 
600
839
  unsupported_file = "test.txt"
601
- with patch.object(chat, 'determine_file_path', return_value=unsupported_file):
602
- result = chat.load_document_file(unsupported_file)
840
+ result = chat.load_document_file(unsupported_file)
603
841
 
604
- assert result is False
605
- captured = capsys.readouterr()
606
- assert "Unsupported document type." in captured.out
842
+ assert result is False
843
+ captured = capsys.readouterr()
844
+ assert "Unsupported document type." in captured.out
607
845
 
608
846
 
609
- @pytest.mark.parametrize("file_name, file_type, mime_type", [
610
- ("image.png", "binary", "image/png"),
611
- ("document.txt", "text", None),
612
- ("document.docx", "document", None),
613
- ("document.pdf", "document", None),
614
- ("archive.zip", "text", None),
615
- ])
847
+ @pytest.mark.parametrize(
848
+ "file_name, file_type, mime_type",
849
+ [
850
+ ("image.png", "binary", "image/png"),
851
+ ("document.txt", "text", None),
852
+ ("document.docx", "document", None),
853
+ ("document.pdf", "document", None),
854
+ ("archive.zip", "text", None),
855
+ ],
856
+ )
616
857
  def test_load_file(temp_chat_file, file_name, file_type, mime_type):
617
858
  mock_config = get_default_config()
618
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
859
+ with patch(
860
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
861
+ ):
619
862
  chat = Chat(temp_chat_file.name, reset=False)
620
863
 
621
- with patch.object(chat, 'load_binary_file', return_value=True) as mock_load_binary, \
622
- patch.object(chat, 'load_text_file', return_value=True) as mock_load_text, \
623
- patch.object(chat, 'load_document_file', return_value=True) as mock_load_document:
624
-
625
- chat.load_file(file_name=file_name, prefix="p-", suffix="-f", block_delimiter="b", extract_images=False)
864
+ with patch.object(
865
+ chat, "load_binary_file", return_value=True
866
+ ) as mock_load_binary, patch.object(
867
+ chat, "load_text_file", return_value=True
868
+ ) as mock_load_text, patch.object(
869
+ chat, "load_document_file", return_value=True
870
+ ) as mock_load_document:
871
+
872
+ chat.load_file(
873
+ file_name=file_name,
874
+ prefix="p-",
875
+ suffix="-f",
876
+ block_delimiter="b",
877
+ extract_images=False,
878
+ )
626
879
 
627
880
  if file_type == "binary":
628
881
  mock_load_binary.assert_called_once_with(
629
- file_path=file_name,
630
- mime_type=mime_type,
631
- prefix="p-",
632
- suffix="-f"
882
+ file_path=file_name, mime_type=mime_type, prefix="p-", suffix="-f"
633
883
  )
634
884
  mock_load_text.assert_not_called()
635
885
  mock_load_document.assert_not_called()
@@ -641,7 +891,7 @@ def test_load_file(temp_chat_file, file_name, file_type, mime_type):
641
891
  prefix="p-",
642
892
  suffix="-f",
643
893
  block_delimiter="b",
644
- extract_images=False
894
+ extract_images=False,
645
895
  )
646
896
  else:
647
897
  mock_load_binary.assert_not_called()
@@ -650,30 +900,63 @@ def test_load_file(temp_chat_file, file_name, file_type, mime_type):
650
900
  prefix="p-",
651
901
  suffix="-f",
652
902
  block_delimiter="b",
653
- extract_images=False
903
+ extract_images=False,
654
904
  )
655
905
  mock_load_document.assert_not_called()
656
906
 
657
907
 
658
- @pytest.mark.parametrize("files, pattern, user_input, expected_output, expected_file", [
659
- (["file1.md"], "*.md", "", None, "file1.md"),
660
- (["file1.md", "file2.md"], "*.md", "1", "1: file1.md\n2: file2.md\n", "file1.md"),
661
- (["file1.md", "file2.md"], "*.md", "2", "1: file1.md\n2: file2.md\n", "file2.md"),
662
- (["file1.md", "file2.md"], "*.md", "3", "1: file1.md\n2: file2.md\nInvalid choice. Aborting load.\n", None),
663
- (["file1.md", "file2.md"], "*.md", "invalid", "1: file1.md\n2: file2.md\nInvalid input. Aborting load.\n", None),
664
- (["file1.md", "file2.md"], "*", "1", "1: file1.md\n2: file2.md\n", "file1.md"),
665
- (["global_file1.md", "global_file2.md"], "global/*", "2", "1: global_file1.md\n2: global_file2.md\n", "global_file2.md"),
666
- ])
667
- def test_choose_file_to_load(monkeypatch, capsys, files, pattern, user_input, expected_output, expected_file):
908
+ @pytest.mark.parametrize(
909
+ "files, pattern, user_input, expected_output, expected_file",
910
+ [
911
+ # Single file cases - should return directly without prompting
912
+ (["file1.md"], "*.md", "", None, "file1.md"),
913
+ (["single_file.txt"], "pattern", "", None, "single_file.txt"),
914
+ # Multiple files with normal pattern - should prompt user
915
+ (
916
+ ["file1.md", "file2.md"],
917
+ "*.md",
918
+ "1",
919
+ "1: file1.md\n2: file2.md\n",
920
+ "file1.md",
921
+ ),
922
+ (
923
+ ["file1.md", "file2.md"],
924
+ "*.md",
925
+ "2",
926
+ "1: file1.md\n2: file2.md\n",
927
+ "file2.md",
928
+ ),
929
+ # Special patterns that force prompting even with single file
930
+ (["single_file.md"], "*", "1", "1: single_file.md\n", "single_file.md"),
931
+ (["single_file.md"], "global/*", "1", "1: single_file.md\n", "single_file.md"),
932
+ # Multiple files with special patterns
933
+ (["file1.md", "file2.md"], "*", "1", "1: file1.md\n2: file2.md\n", "file1.md"),
934
+ (
935
+ ["global_file1.md", "global_file2.md"],
936
+ "global/*",
937
+ "2",
938
+ "1: global_file1.md\n2: global_file2.md\n",
939
+ "global_file2.md",
940
+ ),
941
+ ],
942
+ )
943
+ def test_choose_file_to_load_valid_cases(
944
+ monkeypatch, capsys, files, pattern, user_input, expected_output, expected_file
945
+ ):
946
+ """Test choose_file_to_load with valid inputs and successful selections"""
947
+
668
948
  def mock_input(prompt):
669
949
  return user_input
670
950
 
671
- monkeypatch.setattr('builtins.input', mock_input)
951
+ monkeypatch.setattr("builtins.input", mock_input)
672
952
 
673
953
  mock_config = get_default_config()
674
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
954
+ with patch(
955
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
956
+ ):
675
957
  with patch("builtins.open", mock_open()):
676
958
  chat = Chat("dummy_chat_name", reset=False)
959
+
677
960
  file_path = chat.choose_file_to_load(files, pattern)
678
961
 
679
962
  captured = capsys.readouterr()
@@ -684,17 +967,340 @@ def test_choose_file_to_load(monkeypatch, capsys, files, pattern, user_input, ex
684
967
  assert file_path == expected_file
685
968
 
686
969
 
687
- @pytest.mark.parametrize("directory, pattern, file_type, existing_files, user_input, expected_output, expected_loaded_file", [
688
- ("prompt.data", "*.rules.md", "rules", ["rules1.md"], "", "Loaded rules from rules1.md", "rules1.md"),
689
- ("prompt.data", "*.rules.md", "rules", ["rules1.md", "rules2.md"], "1", "Loaded rules from rules1.md", "rules1.md"),
690
- ("prompt.data", "*.rules.md", "rules", ["rules1.md", "rules2.md"], "2", "Loaded rules from rules2.md", "rules2.md"),
691
- ("prompt.data", "*.rules.md", "rules", ["rules1.md", "rules2.md"], "3", "Invalid choice. Aborting load.", None),
692
- ("prompt.data", "*.rules.md", "rules", ["rules1.md", "rules2.md"], "invalid", "Invalid input. Aborting load.", None),
693
- ("prompt.data", "*.rules.md", "rules", [], "", "No rules file found.", None),
694
- ("prompt.data", "*", "rules", ["rules1.md", "rules2.md"], "1", "Loaded rules from rules1.md", "rules1.md"),
695
- ("prompt.data", "global/*", "rules", ["global_rules1.md", "global_rules2.md"], "2", "Loaded rules from global_rules2.md", "global_rules2.md"),
696
- ])
697
- def test_load_helper(monkeypatch, capsys, temp_chat_file, directory, pattern, file_type, existing_files, user_input, expected_output, expected_loaded_file):
970
+ @pytest.mark.parametrize(
971
+ "files, pattern, user_input, expected_error_message",
972
+ [
973
+ # Choice index out of range (too high)
974
+ (["file1.md", "file2.md"], "*.md", "3", "Invalid choice. Aborting load."),
975
+ (
976
+ ["file1.md", "file2.md", "file3.md"],
977
+ "*.md",
978
+ "4",
979
+ "Invalid choice. Aborting load.",
980
+ ),
981
+ # Choice index out of range (zero)
982
+ (["file1.md", "file2.md"], "*.md", "0", "Invalid choice. Aborting load."),
983
+ # Choice index out of range (negative)
984
+ (["file1.md", "file2.md"], "*.md", "-1", "Invalid choice. Aborting load."),
985
+ (["file1.md", "file2.md"], "*.md", "-5", "Invalid choice. Aborting load."),
986
+ # Special patterns with out of range choices
987
+ (["file1.md"], "*", "2", "Invalid choice. Aborting load."),
988
+ (["file1.md"], "global/*", "0", "Invalid choice. Aborting load."),
989
+ ],
990
+ )
991
+ @patch("ara_cli.error_handler.report_error")
992
+ def test_choose_file_to_load_invalid_choice_index(
993
+ mock_report_error,
994
+ monkeypatch,
995
+ capsys,
996
+ files,
997
+ pattern,
998
+ user_input,
999
+ expected_error_message,
1000
+ ):
1001
+ """Test choose_file_to_load with invalid choice indices"""
1002
+
1003
+ def mock_input(prompt):
1004
+ return user_input
1005
+
1006
+ monkeypatch.setattr("builtins.input", mock_input)
1007
+
1008
+ mock_config = get_default_config()
1009
+ with patch(
1010
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1011
+ ):
1012
+ with patch("builtins.open", mock_open()):
1013
+ chat = Chat("dummy_chat_name", reset=False)
1014
+
1015
+ file_path = chat.choose_file_to_load(files, pattern)
1016
+
1017
+ # Verify error was reported
1018
+ mock_report_error.assert_called_once()
1019
+ error_call = mock_report_error.call_args[0][0]
1020
+ assert isinstance(error_call, ValueError)
1021
+ assert str(error_call) == expected_error_message
1022
+
1023
+ # Verify None was returned
1024
+ assert file_path is None
1025
+
1026
+
1027
+ @pytest.mark.parametrize(
1028
+ "files, pattern, user_input, expected_error_message",
1029
+ [
1030
+ # Non-numeric input
1031
+ (["file1.md", "file2.md"], "*.md", "invalid", "Invalid input. Aborting load."),
1032
+ (["file1.md", "file2.md"], "*.md", "abc", "Invalid input. Aborting load."),
1033
+ (["file1.md", "file2.md"], "*.md", "1.5", "Invalid input. Aborting load."),
1034
+ # Empty input
1035
+ (["file1.md", "file2.md"], "*.md", "", "Invalid input. Aborting load."),
1036
+ # Special characters
1037
+ (["file1.md", "file2.md"], "*.md", "!", "Invalid input. Aborting load."),
1038
+ (["file1.md", "file2.md"], "*.md", "@#$", "Invalid input. Aborting load."),
1039
+ # Special patterns with invalid input
1040
+ (
1041
+ ["file1.md", "file2.md"],
1042
+ "*",
1043
+ "not_a_number",
1044
+ "Invalid input. Aborting load.",
1045
+ ),
1046
+ (["file1.md", "file2.md"], "global/*", "xyz", "Invalid input. Aborting load."),
1047
+ ],
1048
+ )
1049
+ @patch("ara_cli.error_handler.report_error")
1050
+ def test_choose_file_to_load_invalid_input_format(
1051
+ mock_report_error,
1052
+ monkeypatch,
1053
+ capsys,
1054
+ files,
1055
+ pattern,
1056
+ user_input,
1057
+ expected_error_message,
1058
+ ):
1059
+ """Test choose_file_to_load with non-numeric and invalid input formats"""
1060
+
1061
+ def mock_input(prompt):
1062
+ return user_input
1063
+
1064
+ monkeypatch.setattr("builtins.input", mock_input)
1065
+
1066
+ mock_config = get_default_config()
1067
+ with patch(
1068
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1069
+ ):
1070
+ with patch("builtins.open", mock_open()):
1071
+ chat = Chat("dummy_chat_name", reset=False)
1072
+
1073
+ file_path = chat.choose_file_to_load(files, pattern)
1074
+
1075
+ # Verify error was reported
1076
+ mock_report_error.assert_called_once()
1077
+ error_call = mock_report_error.call_args[0][0]
1078
+ assert isinstance(error_call, ValueError)
1079
+ assert str(error_call) == expected_error_message
1080
+
1081
+ # Verify None was returned
1082
+ assert file_path is None
1083
+
1084
+
1085
+ def test_choose_file_to_load_files_are_sorted(monkeypatch, capsys):
1086
+ """Test that files are sorted before displaying to user"""
1087
+ files = ["zebra.md", "alpha.md", "beta.md"]
1088
+ expected_order = ["alpha.md", "beta.md", "zebra.md"]
1089
+
1090
+ def mock_input(prompt):
1091
+ return "2" # Choose the second file (beta.md after sorting)
1092
+
1093
+ monkeypatch.setattr("builtins.input", mock_input)
1094
+
1095
+ mock_config = get_default_config()
1096
+ with patch(
1097
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1098
+ ):
1099
+ with patch("builtins.open", mock_open()):
1100
+ chat = Chat("dummy_chat_name", reset=False)
1101
+
1102
+ file_path = chat.choose_file_to_load(files, "*.md")
1103
+
1104
+ captured = capsys.readouterr()
1105
+
1106
+ # Verify files are displayed in sorted order
1107
+ assert "1: alpha.md" in captured.out
1108
+ assert "2: beta.md" in captured.out
1109
+ assert "3: zebra.md" in captured.out
1110
+
1111
+ # Verify correct file was selected (beta.md, which is index 1 after sorting)
1112
+ assert file_path == "beta.md"
1113
+
1114
+
1115
+ def test_choose_file_to_load_basename_displayed(monkeypatch, capsys):
1116
+ """Test that only basenames are displayed to user, not full paths"""
1117
+ files = ["/long/path/to/file1.md", "/another/long/path/file2.md"]
1118
+
1119
+ def mock_input(prompt):
1120
+ return "1"
1121
+
1122
+ monkeypatch.setattr("builtins.input", mock_input)
1123
+
1124
+ mock_config = get_default_config()
1125
+ with patch(
1126
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1127
+ ):
1128
+ with patch("builtins.open", mock_open()):
1129
+ chat = Chat("dummy_chat_name", reset=False)
1130
+
1131
+ file_path = chat.choose_file_to_load(files, "*.md")
1132
+
1133
+ captured = capsys.readouterr()
1134
+
1135
+ # Verify only basenames are shown, not full paths
1136
+ assert "1: file2.md" in captured.out
1137
+ assert "2: file1.md" in captured.out
1138
+ assert "/long/path/to/" not in captured.out
1139
+ assert "/another/long/path/" not in captured.out
1140
+
1141
+ # But full path should be returned
1142
+ assert file_path == "/another/long/path/file2.md"
1143
+
1144
+
1145
+ def test_choose_file_to_load_empty_files_list(monkeypatch):
1146
+ """Test choose_file_to_load with empty files list"""
1147
+
1148
+ def mock_input(prompt):
1149
+ return "1"
1150
+
1151
+ monkeypatch.setattr("builtins.input", mock_input)
1152
+
1153
+ mock_config = get_default_config()
1154
+ with patch(
1155
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1156
+ ):
1157
+ with patch("builtins.open", mock_open()):
1158
+ chat = Chat("dummy_chat_name", reset=False)
1159
+
1160
+ # With empty list, should go to else branch and try to access files[0]
1161
+ # This will raise IndexError, but let's test the actual behavior
1162
+ with pytest.raises(IndexError):
1163
+ chat.choose_file_to_load([], "pattern")
1164
+
1165
+
1166
+ def test_choose_file_to_load_input_prompt_message(monkeypatch, capsys):
1167
+ """Test that the correct prompt message is displayed"""
1168
+ files = ["file1.md", "file2.md"]
1169
+ expected_prompt = "Please choose a file to load (enter number): "
1170
+
1171
+ def mock_input(prompt):
1172
+ assert prompt == expected_prompt
1173
+ return "1"
1174
+
1175
+ monkeypatch.setattr("builtins.input", mock_input)
1176
+
1177
+ mock_config = get_default_config()
1178
+ with patch(
1179
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1180
+ ):
1181
+ with patch("builtins.open", mock_open()):
1182
+ chat = Chat("dummy_chat_name", reset=False)
1183
+
1184
+ chat.choose_file_to_load(files, "*.md")
1185
+
1186
+
1187
+ @pytest.mark.parametrize(
1188
+ "directory, pattern, file_type, existing_files, exclude_pattern, excluded_files, user_input, expected_output, expected_loaded_file",
1189
+ [
1190
+ # Basic successful load - single file
1191
+ (
1192
+ "prompt.data",
1193
+ "*.rules.md",
1194
+ "rules",
1195
+ ["rules1.md"],
1196
+ None,
1197
+ [],
1198
+ "",
1199
+ "Loaded rules from rules1.md",
1200
+ "rules1.md",
1201
+ ),
1202
+ # Multiple files - user chooses first
1203
+ (
1204
+ "prompt.data",
1205
+ "*.rules.md",
1206
+ "rules",
1207
+ ["rules1.md", "rules2.md"],
1208
+ None,
1209
+ [],
1210
+ "1",
1211
+ "Loaded rules from rules1.md",
1212
+ "rules1.md",
1213
+ ),
1214
+ # Multiple files - user chooses second
1215
+ (
1216
+ "prompt.data",
1217
+ "*.rules.md",
1218
+ "rules",
1219
+ ["rules1.md", "rules2.md"],
1220
+ None,
1221
+ [],
1222
+ "2",
1223
+ "Loaded rules from rules2.md",
1224
+ "rules2.md",
1225
+ ),
1226
+ # Multiple files - invalid choice (out of range)
1227
+ (
1228
+ "prompt.data",
1229
+ "*.rules.md",
1230
+ "rules",
1231
+ ["rules1.md", "rules2.md"],
1232
+ None,
1233
+ [],
1234
+ "3",
1235
+ "Invalid choice. Aborting load.",
1236
+ None,
1237
+ ),
1238
+ # Multiple files - invalid input (non-numeric)
1239
+ (
1240
+ "prompt.data",
1241
+ "*.rules.md",
1242
+ "rules",
1243
+ ["rules1.md", "rules2.md"],
1244
+ None,
1245
+ [],
1246
+ "invalid",
1247
+ "Invalid input. Aborting load.",
1248
+ None,
1249
+ ),
1250
+ # No matching files
1251
+ (
1252
+ "prompt.data",
1253
+ "*.rules.md",
1254
+ "rules",
1255
+ [],
1256
+ None,
1257
+ [],
1258
+ "",
1259
+ "No rules file found.",
1260
+ None,
1261
+ ),
1262
+ # Global pattern with multiple files
1263
+ (
1264
+ "prompt.data",
1265
+ "*",
1266
+ "rules",
1267
+ ["rules1.md", "rules2.md"],
1268
+ None,
1269
+ [],
1270
+ "1",
1271
+ "Loaded rules from rules1.md",
1272
+ "rules1.md",
1273
+ ),
1274
+ # Global/* pattern with multiple files
1275
+ (
1276
+ "prompt.data",
1277
+ "global/*",
1278
+ "rules",
1279
+ ["global_rules1.md", "global_rules2.md"],
1280
+ None,
1281
+ [],
1282
+ "2",
1283
+ "Loaded rules from global_rules2.md",
1284
+ "global_rules2.md",
1285
+ ),
1286
+ ],
1287
+ )
1288
+ def test_load_helper_basic_scenarios(
1289
+ monkeypatch,
1290
+ capsys,
1291
+ temp_chat_file,
1292
+ directory,
1293
+ pattern,
1294
+ file_type,
1295
+ existing_files,
1296
+ exclude_pattern,
1297
+ excluded_files,
1298
+ user_input,
1299
+ expected_output,
1300
+ expected_loaded_file,
1301
+ ):
1302
+ """Test _load_helper basic scenarios without exclusions"""
1303
+
698
1304
  def mock_glob(file_pattern):
699
1305
  return existing_files
700
1306
 
@@ -704,30 +1310,84 @@ def test_load_helper(monkeypatch, capsys, temp_chat_file, directory, pattern, fi
704
1310
  def mock_load_file(self, file_path):
705
1311
  return True
706
1312
 
707
- monkeypatch.setattr(glob, 'glob', mock_glob)
708
- monkeypatch.setattr('builtins.input', mock_input)
709
- monkeypatch.setattr(Chat, 'load_file', mock_load_file)
710
- monkeypatch.setattr(Chat, 'add_prompt_tag_if_needed', lambda self, chat_file: None)
1313
+ monkeypatch.setattr(glob, "glob", mock_glob)
1314
+ monkeypatch.setattr("builtins.input", mock_input)
1315
+ monkeypatch.setattr(Chat, "load_file", mock_load_file)
1316
+ monkeypatch.setattr(Chat, "add_prompt_tag_if_needed", lambda self, chat_file: None)
711
1317
 
712
1318
  mock_config = get_default_config()
713
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1319
+ with patch(
1320
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1321
+ ):
714
1322
  chat = Chat(temp_chat_file.name, reset=False)
715
- chat._load_helper(directory, pattern, file_type)
716
1323
 
717
- captured = capsys.readouterr()
1324
+ chat._load_helper(directory, pattern, file_type, exclude_pattern)
718
1325
 
719
- assert expected_output in captured.out
1326
+ captured = capsys.readouterr()
1327
+ # Check both stdout and stderr since error messages go to stderr
1328
+ output = captured.out + captured.err
1329
+ assert expected_output in output
720
1330
 
721
1331
  if expected_loaded_file:
722
- assert expected_loaded_file in captured.out
1332
+ assert expected_loaded_file in output
723
1333
 
724
1334
 
725
- @pytest.mark.parametrize("directory, pattern, file_type, existing_files, exclude_pattern, excluded_files, user_input, expected_output, expected_loaded_file", [
726
- ("prompt.data", "*.rules.md", "rules", ["rules1.md", "rules2.md"], "*.exclude.md", ["rules2.md"], "1", "Loaded rules from rules1.md", "rules1.md"),
727
- ("prompt.data", "*.rules.md", "rules", ["rules1.md", "rules2.md"], "*.exclude.md", ["rules1.md"], "2", "Loaded rules from rules2.md", "rules2.md"),
728
- ("prompt.data", "*.rules.md", "rules", ["rules1.md", "rules2.md"], "*.exclude.md", ["rules1.md", "rules2.md"], "", "No rules file found.", None),
729
- ])
730
- def test_load_helper_with_exclude(monkeypatch, capsys, temp_chat_file, directory, pattern, file_type, existing_files, exclude_pattern, excluded_files, user_input, expected_output, expected_loaded_file):
1335
+ @pytest.mark.parametrize(
1336
+ "directory, pattern, file_type, existing_files, exclude_pattern, excluded_files, user_input, expected_output, expected_loaded_file",
1337
+ [
1338
+ # Exclude some files - one remaining
1339
+ (
1340
+ "prompt.data",
1341
+ "*.rules.md",
1342
+ "rules",
1343
+ ["rules1.md", "rules2.md"],
1344
+ "*.exclude.md",
1345
+ ["rules2.md"],
1346
+ "",
1347
+ "Loaded rules from rules1.md",
1348
+ "rules1.md",
1349
+ ),
1350
+ # Exclude some files - multiple remaining, user chooses
1351
+ (
1352
+ "prompt.data",
1353
+ "*.rules.md",
1354
+ "rules",
1355
+ ["rules1.md", "rules2.md", "rules3.md"],
1356
+ "*.exclude.md",
1357
+ ["rules2.md"],
1358
+ "2",
1359
+ "Loaded rules from rules3.md",
1360
+ "rules3.md",
1361
+ ),
1362
+ # Exclude all files
1363
+ (
1364
+ "prompt.data",
1365
+ "*.rules.md",
1366
+ "rules",
1367
+ ["rules1.md", "rules2.md"],
1368
+ "*.exclude.md",
1369
+ ["rules1.md", "rules2.md"],
1370
+ "",
1371
+ "No rules file found.",
1372
+ None,
1373
+ ),
1374
+ ],
1375
+ )
1376
+ def test_load_helper_with_exclusions(
1377
+ monkeypatch,
1378
+ capsys,
1379
+ temp_chat_file,
1380
+ directory,
1381
+ pattern,
1382
+ file_type,
1383
+ existing_files,
1384
+ exclude_pattern,
1385
+ excluded_files,
1386
+ user_input,
1387
+ expected_output,
1388
+ expected_loaded_file,
1389
+ ):
1390
+ """Test _load_helper with file exclusions"""
731
1391
 
732
1392
  def mock_glob(file_pattern):
733
1393
  if file_pattern == exclude_pattern:
@@ -740,27 +1400,158 @@ def test_load_helper_with_exclude(monkeypatch, capsys, temp_chat_file, directory
740
1400
  def mock_load_file(self, file_path):
741
1401
  return True
742
1402
 
743
- monkeypatch.setattr(glob, 'glob', mock_glob)
744
- monkeypatch.setattr('builtins.input', mock_input)
745
- monkeypatch.setattr(Chat, 'load_file', mock_load_file)
746
- monkeypatch.setattr(Chat, 'add_prompt_tag_if_needed', lambda self, chat_file: None)
1403
+ monkeypatch.setattr(glob, "glob", mock_glob)
1404
+ monkeypatch.setattr("builtins.input", mock_input)
1405
+ monkeypatch.setattr(Chat, "load_file", mock_load_file)
1406
+ monkeypatch.setattr(Chat, "add_prompt_tag_if_needed", lambda self, chat_file: None)
747
1407
 
748
1408
  mock_config = get_default_config()
749
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1409
+ with patch(
1410
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1411
+ ):
750
1412
  chat = Chat(temp_chat_file.name, reset=False)
1413
+
751
1414
  chat._load_helper(directory, pattern, file_type, exclude_pattern)
752
1415
 
753
1416
  captured = capsys.readouterr()
754
-
755
- assert expected_output in captured.out
1417
+ # Check both stdout and stderr since error messages go to stderr
1418
+ output = captured.out + captured.err
1419
+ assert expected_output in output
756
1420
 
757
1421
  if expected_loaded_file:
758
- assert expected_loaded_file in captured.out
1422
+ assert expected_loaded_file in output
1423
+
1424
+
1425
+ def test_load_helper_load_file_fails(monkeypatch, capsys, temp_chat_file):
1426
+ """Test _load_helper when load_file returns False"""
1427
+
1428
+ def mock_glob(file_pattern):
1429
+ return ["rules1.md"]
1430
+
1431
+ def mock_load_file(self, file_path):
1432
+ return False # Simulate load failure
1433
+
1434
+ monkeypatch.setattr(glob, "glob", mock_glob)
1435
+ monkeypatch.setattr(Chat, "load_file", mock_load_file)
1436
+ monkeypatch.setattr(Chat, "add_prompt_tag_if_needed", lambda self, chat_file: None)
1437
+
1438
+ mock_config = get_default_config()
1439
+ with patch(
1440
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1441
+ ):
1442
+ chat = Chat(temp_chat_file.name, reset=False)
1443
+
1444
+ chat._load_helper("prompt.data", "*.rules.md", "rules")
1445
+
1446
+ captured = capsys.readouterr()
1447
+ output = captured.out + captured.err
1448
+ # Should not print "Loaded" message when load_file returns False
1449
+ assert "Loaded rules from" not in output
1450
+
1451
+
1452
+ def test_load_helper_choose_file_returns_none(temp_chat_file):
1453
+ """Test _load_helper when choose_file_to_load returns None"""
1454
+ mock_config = get_default_config()
1455
+ with patch(
1456
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1457
+ ):
1458
+ chat = Chat(temp_chat_file.name, reset=False)
1459
+
1460
+ # Mock these to ensure they're not called when choose_file_to_load returns None
1461
+ with patch("glob.glob", return_value=["rules1.md", "rules2.md"]), patch.object(
1462
+ chat, "choose_file_to_load", return_value=None
1463
+ ) as mock_choose, patch.object(
1464
+ chat, "add_prompt_tag_if_needed"
1465
+ ) as mock_add_prompt_tag, patch.object(
1466
+ chat, "load_file"
1467
+ ) as mock_load_file:
1468
+
1469
+ chat._load_helper("prompt.data", "*.rules.md", "rules")
1470
+
1471
+ # Verify that subsequent methods are not called when choose_file_to_load returns None
1472
+ mock_choose.assert_called_once()
1473
+ mock_add_prompt_tag.assert_not_called()
1474
+ mock_load_file.assert_not_called()
1475
+
1476
+
1477
+ def test_load_helper_directory_path_construction(temp_chat_file):
1478
+ """Test that _load_helper constructs directory paths correctly"""
1479
+ expected_directory_path = os.path.join(
1480
+ os.path.dirname(temp_chat_file.name), "custom_dir"
1481
+ )
1482
+ expected_file_pattern = os.path.join(expected_directory_path, "*.custom.md")
1483
+
1484
+ def mock_glob(file_pattern):
1485
+ # Verify the correct path is being used
1486
+ assert file_pattern == expected_file_pattern
1487
+ return ["custom1.md"]
1488
+
1489
+ mock_config = get_default_config()
1490
+ with patch(
1491
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1492
+ ):
1493
+ chat = Chat(temp_chat_file.name, reset=False)
1494
+
1495
+ with patch("glob.glob", side_effect=mock_glob), patch.object(
1496
+ chat, "load_file", return_value=True
1497
+ ), patch.object(chat, "add_prompt_tag_if_needed"):
1498
+
1499
+ chat._load_helper("custom_dir", "*.custom.md", "custom")
1500
+
1501
+
1502
+ def test_load_helper_calls_add_prompt_tag_before_load(temp_chat_file):
1503
+ """Test that _load_helper calls add_prompt_tag_if_needed before loading file"""
1504
+ call_order = []
1505
+
1506
+ def mock_add_prompt_tag(chat_file):
1507
+ call_order.append("add_prompt_tag")
1508
+
1509
+ def mock_load_file(file_path):
1510
+ call_order.append("load_file")
1511
+ return True
1512
+
1513
+ mock_config = get_default_config()
1514
+ with patch(
1515
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1516
+ ):
1517
+ chat = Chat(temp_chat_file.name, reset=False)
1518
+
1519
+ with patch("glob.glob", return_value=["rules1.md"]), patch.object(
1520
+ chat, "add_prompt_tag_if_needed", side_effect=mock_add_prompt_tag
1521
+ ), patch.object(chat, "load_file", side_effect=mock_load_file):
1522
+
1523
+ chat._load_helper("prompt.data", "*.rules.md", "rules")
1524
+
1525
+ # Verify correct call order
1526
+ assert call_order == ["add_prompt_tag", "load_file"]
1527
+
1528
+
1529
+ @patch("ara_cli.error_handler.report_error")
1530
+ def test_load_helper_reports_error_when_no_files_found(
1531
+ mock_report_error, temp_chat_file
1532
+ ):
1533
+ """Test that _load_helper reports error when no matching files are found"""
1534
+ mock_config = get_default_config()
1535
+ with patch(
1536
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1537
+ ):
1538
+ chat = Chat(temp_chat_file.name, reset=False)
1539
+
1540
+ with patch("glob.glob", return_value=[]): # No files found
1541
+ chat._load_helper("prompt.data", "*.rules.md", "rules")
1542
+
1543
+ # Verify error is reported with correct message
1544
+ mock_report_error.assert_called_once()
1545
+ error_call = mock_report_error.call_args[0]
1546
+ assert isinstance(error_call[0], AraError)
1547
+ assert "No rules file found." in str(error_call[0])
759
1548
 
760
1549
 
761
1550
  def test_help_menu_with_aliases(temp_chat_file, capsys):
762
1551
  mock_config = get_default_config()
763
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1552
+ with patch(
1553
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1554
+ ):
764
1555
  chat = Chat(temp_chat_file.name, reset=False)
765
1556
 
766
1557
  chat._help_menu(verbose=False)
@@ -774,7 +1565,9 @@ def test_help_menu_with_aliases(temp_chat_file, capsys):
774
1565
 
775
1566
  def test_do_quit(temp_chat_file, capsys):
776
1567
  mock_config = get_default_config()
777
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1568
+ with patch(
1569
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1570
+ ):
778
1571
  chat = Chat(temp_chat_file.name, reset=False)
779
1572
 
780
1573
  result = chat.do_quit("")
@@ -786,46 +1579,63 @@ def test_do_quit(temp_chat_file, capsys):
786
1579
 
787
1580
  def test_onecmd_plus_hooks(temp_chat_file):
788
1581
  mock_config = get_default_config()
789
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1582
+ with patch(
1583
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1584
+ ):
790
1585
  chat = Chat(temp_chat_file.name, reset=False)
791
1586
 
792
1587
  command = "dummy command"
793
1588
 
794
- with patch.object(chat, 'full_input', create=True):
795
- with patch.object(cmd2.Cmd, 'onecmd_plus_hooks', return_value=True) as mock_super_onecmd_plus_hooks:
1589
+ with patch.object(chat, "full_input", create=True):
1590
+ with patch.object(
1591
+ cmd2.Cmd, "onecmd_plus_hooks", return_value=True
1592
+ ) as mock_super_onecmd_plus_hooks:
796
1593
  result = chat.onecmd_plus_hooks(command, 20)
797
1594
 
798
- mock_super_onecmd_plus_hooks.assert_called_once_with(command, orig_rl_history_length=20)
1595
+ mock_super_onecmd_plus_hooks.assert_called_once_with(
1596
+ command, orig_rl_history_length=20
1597
+ )
799
1598
  assert result is True
800
1599
 
801
1600
 
802
1601
  def test_default(temp_chat_file):
803
1602
  mock_config = get_default_config()
804
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1603
+ with patch(
1604
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1605
+ ):
805
1606
  chat = Chat(temp_chat_file.name, reset=False)
806
1607
  chat.full_input = "sample input"
807
1608
  chat.default(chat.full_input)
808
1609
  assert chat.message_buffer == ["sample input"]
809
1610
 
810
- @patch('ara_cli.commands.load_command.LoadCommand')
811
- @pytest.mark.parametrize("file_name_arg, load_images_arg, matching_files", [
812
- ("test.txt", "", ["/path/to/test.txt"]),
813
- ("*.txt", "", ["/path/to/a.txt", "/path/to/b.txt"]),
814
- ("doc.pdf", "--load-images", ["/path/to/doc.pdf"]),
815
- ("nonexistent.txt", "", [])
816
- ])
817
- def test_do_LOAD(MockLoadCommand, temp_chat_file, file_name_arg, load_images_arg, matching_files):
1611
+
1612
+ @patch("ara_cli.commands.load_command.LoadCommand")
1613
+ @pytest.mark.parametrize(
1614
+ "file_name_arg, load_images_arg, matching_files",
1615
+ [
1616
+ ("test.txt", "", ["/path/to/test.txt"]),
1617
+ ("*.txt", "", ["/path/to/a.txt", "/path/to/b.txt"]),
1618
+ ("doc.pdf", "--load-images", ["/path/to/doc.pdf"]),
1619
+ ("nonexistent.txt", "", []),
1620
+ ],
1621
+ )
1622
+ def test_do_LOAD(
1623
+ MockLoadCommand, temp_chat_file, file_name_arg, load_images_arg, matching_files
1624
+ ):
818
1625
  from ara_cli.chat import load_parser
1626
+
819
1627
  args_str = f"{file_name_arg} {load_images_arg}".strip()
820
1628
  args = load_parser.parse_args(args_str.split() if args_str else [])
821
1629
 
822
1630
  mock_config = get_default_config()
823
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1631
+ with patch(
1632
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1633
+ ):
824
1634
  chat = Chat(temp_chat_file.name, reset=False)
825
1635
  # FIX: Mock add_prompt_tag_if_needed to prevent IndexError on the empty temp file.
826
1636
  chat.add_prompt_tag_if_needed = MagicMock()
827
1637
 
828
- with patch.object(chat, 'find_matching_files_to_load', return_value=matching_files):
1638
+ with patch.object(chat, "find_matching_files_to_load", return_value=matching_files):
829
1639
  chat.onecmd_plus_hooks(f"LOAD {args_str}", orig_rl_history_length=0)
830
1640
 
831
1641
  if not matching_files:
@@ -833,16 +1643,17 @@ def test_do_LOAD(MockLoadCommand, temp_chat_file, file_name_arg, load_images_arg
833
1643
  else:
834
1644
  # Check that the tag was prepared for each file loaded
835
1645
  assert chat.add_prompt_tag_if_needed.call_count == len(matching_files)
836
-
1646
+
837
1647
  # Check that the LoadCommand was instantiated and executed for each file
838
1648
  assert MockLoadCommand.call_count == len(matching_files)
839
1649
  for i, file_path in enumerate(matching_files):
840
1650
  _, kwargs = MockLoadCommand.call_args_list[i]
841
- assert kwargs['chat_instance'] == chat
842
- assert kwargs['file_path'] == file_path
843
- assert kwargs['extract_images'] == args.load_images
1651
+ assert kwargs["chat_instance"] == chat
1652
+ assert kwargs["file_path"] == file_path
1653
+ assert kwargs["extract_images"] == args.load_images
844
1654
  assert MockLoadCommand.return_value.execute.call_count == len(matching_files)
845
1655
 
1656
+
846
1657
  def test_do_LOAD_interactive(monkeypatch, capsys, temp_chat_file, temp_load_file):
847
1658
  def mock_glob(file_pattern):
848
1659
  return [temp_load_file.name]
@@ -850,12 +1661,14 @@ def test_do_LOAD_interactive(monkeypatch, capsys, temp_chat_file, temp_load_file
850
1661
  def mock_input(prompt):
851
1662
  return temp_load_file.name
852
1663
 
853
- monkeypatch.setattr(glob, 'glob', mock_glob)
854
- monkeypatch.setattr('builtins.input', mock_input)
855
- monkeypatch.setattr(Chat, 'add_prompt_tag_if_needed', lambda self, chat_file: None)
1664
+ monkeypatch.setattr(glob, "glob", mock_glob)
1665
+ monkeypatch.setattr("builtins.input", mock_input)
1666
+ monkeypatch.setattr(Chat, "add_prompt_tag_if_needed", lambda self, chat_file: None)
856
1667
 
857
1668
  mock_config = get_default_config()
858
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1669
+ with patch(
1670
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1671
+ ):
859
1672
  chat = Chat(temp_chat_file.name, reset=False)
860
1673
  chat.do_LOAD("")
861
1674
 
@@ -863,68 +1676,200 @@ def test_do_LOAD_interactive(monkeypatch, capsys, temp_chat_file, temp_load_file
863
1676
  assert f"Loaded contents of file {temp_load_file.name}" in captured.out
864
1677
 
865
1678
 
866
- @pytest.mark.parametrize("text, line, begidx, endidx, matching_files", [
867
- ("file", "LOAD file", 5, 9, ["file1.md", "file2.txt"]),
868
- ("path/to/file", "LOAD path/to/file", 5, 18, ["path/to/file1.md", "path/to/file2.txt"]),
869
- ("nonexistent", "LOAD nonexistent", 5, 16, []),
870
- ])
871
- def test_complete_LOAD(monkeypatch, temp_chat_file, text, line, begidx, endidx, matching_files):
1679
+ @pytest.mark.parametrize(
1680
+ "text, line, begidx, endidx, matching_files",
1681
+ [
1682
+ ("file", "LOAD file", 5, 9, ["file1.md", "file2.txt"]),
1683
+ (
1684
+ "path/to/file",
1685
+ "LOAD path/to/file",
1686
+ 5,
1687
+ 18,
1688
+ ["path/to/file1.md", "path/to/file2.txt"],
1689
+ ),
1690
+ ("nonexistent", "LOAD nonexistent", 5, 16, []),
1691
+ ],
1692
+ )
1693
+ def test_complete_LOAD(
1694
+ monkeypatch, temp_chat_file, text, line, begidx, endidx, matching_files
1695
+ ):
872
1696
  def mock_glob(pattern):
873
1697
  return matching_files
874
1698
 
875
- monkeypatch.setattr(glob, 'glob', mock_glob)
1699
+ monkeypatch.setattr(glob, "glob", mock_glob)
876
1700
 
877
1701
  mock_config = get_default_config()
878
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1702
+ with patch(
1703
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1704
+ ):
879
1705
  chat = Chat(temp_chat_file.name, reset=False)
880
1706
  completions = chat.complete_LOAD(text, line, begidx, endidx)
881
1707
 
882
1708
  assert completions == matching_files
883
1709
 
884
1710
 
885
- @pytest.mark.parametrize("file_name, is_image, expected_mime", [
886
- ("test.png", True, "image/png"),
887
- ("test.jpg", True, "image/jpeg"),
888
- ("test.jpeg", True, "image/jpeg"),
889
- ("test.txt", False, None)
890
- ])
891
- def test_load_image(capsys, temp_chat_file, file_name, is_image, expected_mime):
1711
+ @pytest.mark.parametrize(
1712
+ "file_name, expected_mime_type",
1713
+ [
1714
+ ("test.png", "image/png"),
1715
+ ("test.jpg", "image/jpeg"),
1716
+ ("test.jpeg", "image/jpeg"),
1717
+ ("TEST.PNG", "image/png"), # Test case insensitive
1718
+ ("path/to/image.JPG", "image/jpeg"), # Test with path
1719
+ ],
1720
+ )
1721
+ @patch("ara_cli.error_handler.report_error")
1722
+ def test_load_image_success(
1723
+ mock_report_error, temp_chat_file, file_name, expected_mime_type
1724
+ ):
1725
+ """Test load_image successfully loads supported image files"""
1726
+ mock_config = get_default_config()
1727
+ with patch(
1728
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1729
+ ):
1730
+ chat = Chat(temp_chat_file.name, reset=False)
1731
+
1732
+ with patch.object(chat, "load_binary_file", return_value=True) as mock_load_binary:
1733
+ result = chat.load_image(
1734
+ file_name=file_name, prefix="prefix-", suffix="-suffix"
1735
+ )
1736
+
1737
+ mock_load_binary.assert_called_once_with(
1738
+ file_path=file_name,
1739
+ mime_type=expected_mime_type,
1740
+ prefix="prefix-",
1741
+ suffix="-suffix",
1742
+ )
1743
+ assert result is True
1744
+ mock_report_error.assert_not_called()
1745
+
1746
+
1747
+ @pytest.mark.parametrize(
1748
+ "file_name",
1749
+ [
1750
+ "document.txt",
1751
+ "archive.zip",
1752
+ "video.mp4",
1753
+ "audio.wav",
1754
+ "script.py",
1755
+ "image.gif", # Not in BINARY_TYPE_MAPPING
1756
+ "image.bmp", # Not in BINARY_TYPE_MAPPING
1757
+ "", # Empty filename
1758
+ "no_extension",
1759
+ ],
1760
+ )
1761
+ @patch("ara_cli.error_handler.report_error")
1762
+ def test_load_image_unsupported_file_types(
1763
+ mock_report_error, temp_chat_file, file_name
1764
+ ):
1765
+ """Test load_image reports error for unsupported file types"""
1766
+ mock_config = get_default_config()
1767
+ with patch(
1768
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1769
+ ):
1770
+ chat = Chat(temp_chat_file.name, reset=False)
1771
+
1772
+ with patch.object(chat, "load_binary_file") as mock_load_binary:
1773
+ result = chat.load_image(file_name=file_name)
1774
+
1775
+ mock_load_binary.assert_not_called()
1776
+ mock_report_error.assert_called_once()
1777
+
1778
+ # Verify the error message and type
1779
+ error_call = mock_report_error.call_args[0]
1780
+ assert isinstance(error_call[0], AraError)
1781
+ assert f"File {file_name} not recognized as image, could not load" in str(
1782
+ error_call[0]
1783
+ )
1784
+ assert result is None
1785
+
1786
+
1787
+ @patch("ara_cli.error_handler.report_error")
1788
+ def test_load_image_load_binary_file_fails(mock_report_error, temp_chat_file):
1789
+ """Test load_image when load_binary_file returns False"""
1790
+ mock_config = get_default_config()
1791
+ with patch(
1792
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1793
+ ):
1794
+ chat = Chat(temp_chat_file.name, reset=False)
1795
+
1796
+ with patch.object(chat, "load_binary_file", return_value=False) as mock_load_binary:
1797
+ result = chat.load_image(file_name="test.png", prefix="pre-", suffix="-post")
1798
+
1799
+ mock_load_binary.assert_called_once_with(
1800
+ file_path="test.png", mime_type="image/png", prefix="pre-", suffix="-post"
1801
+ )
1802
+ assert result is False
1803
+ mock_report_error.assert_not_called()
1804
+
1805
+
1806
+ @patch("ara_cli.error_handler.report_error")
1807
+ def test_load_image_default_parameters(mock_report_error, temp_chat_file):
1808
+ """Test load_image with default prefix and suffix parameters"""
892
1809
  mock_config = get_default_config()
893
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1810
+ with patch(
1811
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1812
+ ):
894
1813
  chat = Chat(temp_chat_file.name, reset=False)
895
1814
 
896
- with patch.object(chat, 'load_binary_file', return_value=True) as mock_load_binary:
897
- chat.load_image(file_name=file_name, prefix="p-", suffix="-f")
1815
+ with patch.object(chat, "load_binary_file", return_value=True) as mock_load_binary:
1816
+ result = chat.load_image(file_name="image.jpeg")
1817
+
1818
+ mock_load_binary.assert_called_once_with(
1819
+ file_path="image.jpeg", mime_type="image/jpeg", prefix="", suffix=""
1820
+ )
1821
+ assert result is True
1822
+ mock_report_error.assert_not_called()
1823
+
1824
+
1825
+ @patch("ara_cli.error_handler.report_error")
1826
+ def test_load_image_binary_type_mapping_usage(mock_report_error, temp_chat_file):
1827
+ """Test that load_image correctly uses Chat.BINARY_TYPE_MAPPING"""
1828
+ mock_config = get_default_config()
1829
+ with patch(
1830
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1831
+ ):
1832
+ chat = Chat(temp_chat_file.name, reset=False)
1833
+
1834
+ # Verify the mapping is used correctly by testing each supported extension
1835
+ original_mapping = Chat.BINARY_TYPE_MAPPING.copy()
1836
+
1837
+ with patch.object(chat, "load_binary_file", return_value=True) as mock_load_binary:
1838
+ for extension, expected_mime in original_mapping.items():
1839
+ mock_load_binary.reset_mock()
1840
+ test_filename = f"test{extension}"
1841
+
1842
+ chat.load_image(file_name=test_filename)
898
1843
 
899
- if is_image:
900
- # FIX: The called method's parameter is `file_path`, not `file_name`.
901
1844
  mock_load_binary.assert_called_once_with(
902
- file_path=file_name,
903
- mime_type=expected_mime,
904
- prefix="p-",
905
- suffix="-f"
1845
+ file_path=test_filename, mime_type=expected_mime, prefix="", suffix=""
906
1846
  )
907
- else:
908
- mock_load_binary.assert_not_called()
909
- captured = capsys.readouterr()
910
- assert f"File {file_name} not recognized as image, could not load" in captured.out
1847
+
1848
+ mock_report_error.assert_not_called()
911
1849
 
912
1850
 
913
- @patch('ara_cli.commands.load_image_command.LoadImageCommand')
914
- @pytest.mark.parametrize("image_file, should_load, expected_mime", [
915
- ("test.png", True, "image/png"),
916
- ("test.jpg", True, "image/jpeg"),
917
- ("test.txt", False, None)
918
- ])
919
- def test_do_LOAD_IMAGE(MockLoadImageCommand, capsys, temp_chat_file, image_file, should_load, expected_mime):
1851
+ @patch("ara_cli.commands.load_image_command.LoadImageCommand")
1852
+ @pytest.mark.parametrize(
1853
+ "image_file, should_load, expected_mime",
1854
+ [
1855
+ ("test.png", True, "image/png"),
1856
+ ("test.jpg", True, "image/jpeg"),
1857
+ ("test.txt", False, None),
1858
+ ],
1859
+ )
1860
+ def test_do_LOAD_IMAGE(
1861
+ MockLoadImageCommand, capsys, temp_chat_file, image_file, should_load, expected_mime
1862
+ ):
920
1863
  matching_files = [f"/path/to/{image_file}"]
921
1864
 
922
1865
  mock_config = get_default_config()
923
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1866
+ with patch(
1867
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1868
+ ):
924
1869
  chat = Chat(temp_chat_file.name, reset=False)
925
1870
  chat.add_prompt_tag_if_needed = MagicMock()
926
1871
 
927
- with patch.object(chat, 'find_matching_files_to_load', return_value=matching_files):
1872
+ with patch.object(chat, "find_matching_files_to_load", return_value=matching_files):
928
1873
  chat.do_LOAD_IMAGE(image_file)
929
1874
 
930
1875
  if should_load:
@@ -934,7 +1879,7 @@ def test_do_LOAD_IMAGE(MockLoadImageCommand, capsys, temp_chat_file, image_file,
934
1879
  file_path=matching_files[0],
935
1880
  mime_type=expected_mime,
936
1881
  prefix=f"\nFile: {matching_files[0]}\n",
937
- output=chat.poutput
1882
+ output=chat.poutput,
938
1883
  )
939
1884
  MockLoadImageCommand.return_value.execute.assert_called_once()
940
1885
  else:
@@ -943,45 +1888,59 @@ def test_do_LOAD_IMAGE(MockLoadImageCommand, capsys, temp_chat_file, image_file,
943
1888
  chat.add_prompt_tag_if_needed.assert_called_once()
944
1889
  MockLoadImageCommand.assert_not_called()
945
1890
  captured = capsys.readouterr()
946
- assert f"File {matching_files[0]} not recognized as image, could not load" in captured.err
1891
+ assert (
1892
+ f"File {matching_files[0]} not recognized as image, could not load"
1893
+ in captured.err
1894
+ )
947
1895
 
948
1896
 
949
- @pytest.mark.parametrize("input_chat_name, expected_chat_name", [
950
- ("", "What should be the new chat name? "),
951
- ("new_chat", "new_chat_chat.md"),
952
- ("new_chat.md", "new_chat.md"),
953
- ])
1897
+ @pytest.mark.parametrize(
1898
+ "input_chat_name, expected_chat_name",
1899
+ [
1900
+ ("", "What should be the new chat name? "),
1901
+ ("new_chat", "new_chat_chat.md"),
1902
+ ("new_chat.md", "new_chat.md"),
1903
+ ],
1904
+ )
954
1905
  def test_do_new(monkeypatch, temp_chat_file, input_chat_name, expected_chat_name):
955
1906
  def mock_input(prompt):
956
1907
  return "input_chat_name"
957
1908
 
958
- monkeypatch.setattr('builtins.input', mock_input)
1909
+ monkeypatch.setattr("builtins.input", mock_input)
959
1910
 
960
1911
  mock_config = get_default_config()
961
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1912
+ with patch(
1913
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1914
+ ):
962
1915
  chat = Chat(temp_chat_file.name, reset=False)
963
1916
 
964
- with patch.object(Chat, '__init__', return_value=None) as mock_init:
1917
+ with patch.object(Chat, "__init__", return_value=None) as mock_init:
965
1918
  chat.do_NEW(input_chat_name)
966
1919
  if input_chat_name == "":
967
- mock_init.assert_called_with(os.path.join(os.path.dirname(temp_chat_file.name), "input_chat_name"))
1920
+ mock_init.assert_called_with(
1921
+ os.path.join(os.path.dirname(temp_chat_file.name), "input_chat_name")
1922
+ )
968
1923
  else:
969
- mock_init.assert_called_with(os.path.join(os.path.dirname(temp_chat_file.name), input_chat_name))
1924
+ mock_init.assert_called_with(
1925
+ os.path.join(os.path.dirname(temp_chat_file.name), input_chat_name)
1926
+ )
970
1927
 
971
1928
 
972
1929
  def test_do_RERUN(temp_chat_file):
973
1930
  initial_content = [
974
1931
  "# ara prompt:\nPrompt message.\n",
975
- "# ara response:\nResponse message.\n"
1932
+ "# ara response:\nResponse message.\n",
976
1933
  ]
977
1934
  temp_chat_file.writelines(initial_content)
978
1935
  temp_chat_file.flush()
979
1936
 
980
1937
  mock_config = get_default_config()
981
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1938
+ with patch(
1939
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1940
+ ):
982
1941
  chat = Chat(temp_chat_file.name, reset=False)
983
1942
 
984
- with patch.object(chat, 'resend_message') as mock_resend_message:
1943
+ with patch.object(chat, "resend_message") as mock_resend_message:
985
1944
  chat.do_RERUN("")
986
1945
  mock_resend_message.assert_called_once()
987
1946
 
@@ -992,15 +1951,17 @@ def test_do_CLEAR(temp_chat_file, capsys):
992
1951
  temp_chat_file.flush()
993
1952
 
994
1953
  mock_config = get_default_config()
995
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1954
+ with patch(
1955
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1956
+ ):
996
1957
  chat = Chat(temp_chat_file.name, reset=False)
997
1958
 
998
- with patch('builtins.input', return_value='y'):
1959
+ with patch("builtins.input", return_value="y"):
999
1960
  chat.do_CLEAR(None)
1000
1961
 
1001
1962
  captured = capsys.readouterr()
1002
1963
 
1003
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
1964
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
1004
1965
  content = file.read()
1005
1966
 
1006
1967
  assert content.strip() == "# ara prompt:"
@@ -1013,118 +1974,204 @@ def test_do_CLEAR_abort(temp_chat_file, capsys):
1013
1974
  temp_chat_file.flush()
1014
1975
 
1015
1976
  mock_config = get_default_config()
1016
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1977
+ with patch(
1978
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
1979
+ ):
1017
1980
  chat = Chat(temp_chat_file.name, reset=False)
1018
1981
 
1019
- with patch('builtins.input', return_value='n'):
1982
+ with patch("builtins.input", return_value="n"):
1020
1983
  chat.do_CLEAR(None)
1021
1984
 
1022
1985
  captured = capsys.readouterr()
1023
1986
 
1024
- with open(temp_chat_file.name, 'r', encoding='utf-8') as file:
1987
+ with open(temp_chat_file.name, "r", encoding="utf-8") as file:
1025
1988
  content = file.read()
1026
1989
 
1027
1990
  assert content.strip() == initial_content
1028
1991
  assert "Cleared content of" not in captured.out
1029
1992
 
1030
1993
 
1031
- @pytest.mark.parametrize("rules_name, expected_directory, expected_pattern", [
1032
- ("", "prompt.data", "*.rules.md"),
1033
- ("global/test_rule", "mocked_global_directory/prompt-modules/rules/", "test_rule"),
1034
- ("local_rule", "mocked_local_directory/custom-prompt-modules/rules", "local_rule")
1035
- ])
1036
- def test_do_LOAD_RULES(monkeypatch, temp_chat_file, rules_name, expected_directory, expected_pattern):
1994
+ @pytest.mark.parametrize(
1995
+ "rules_name",
1996
+ [
1997
+ "",
1998
+ "global/test_rule",
1999
+ "local_rule",
2000
+ ],
2001
+ )
2002
+ def test_do_LOAD_RULES(temp_chat_file, rules_name):
1037
2003
  mock_config = get_default_config()
1038
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2004
+ with patch(
2005
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2006
+ ):
1039
2007
  chat = Chat(temp_chat_file.name, reset=False)
1040
2008
 
1041
- with patch.object(chat, '_load_template_helper') as mock_load_template_helper:
2009
+ with patch.object(chat.template_loader, "load_template") as mock_load_template:
1042
2010
  chat.do_LOAD_RULES(rules_name)
1043
- mock_load_template_helper.assert_called_once_with(
1044
- rules_name, "rules", "*.rules.md")
1045
-
1046
-
1047
- @pytest.mark.parametrize("intention_name, expected_directory, expected_pattern", [
1048
- ("", "prompt.data", "*.intention.md"),
1049
- ("global/test_intention",
1050
- "mocked_global_directory/prompt-modules/intentions/", "test_intention"),
1051
- ("local_intention",
1052
- "mocked_local_directory/custom-prompt-modules/intentions", "local_intention")
1053
- ])
1054
- def test_do_LOAD_INTENTION(monkeypatch, temp_chat_file, intention_name, expected_directory, expected_pattern):
2011
+ mock_load_template.assert_called_once_with(
2012
+ rules_name, "rules", chat.chat_name, "*.rules.md"
2013
+ )
2014
+
2015
+
2016
+ @pytest.mark.parametrize(
2017
+ "intention_name",
2018
+ [
2019
+ "",
2020
+ "global/test_intention",
2021
+ "local_intention",
2022
+ ],
2023
+ )
2024
+ def test_do_LOAD_INTENTION(temp_chat_file, intention_name):
1055
2025
  mock_config = get_default_config()
1056
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2026
+ with patch(
2027
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2028
+ ):
1057
2029
  chat = Chat(temp_chat_file.name, reset=False)
1058
2030
 
1059
- with patch.object(chat, '_load_template_helper') as mock_load_template_helper:
2031
+ with patch.object(chat.template_loader, "load_template") as mock_load_template:
1060
2032
  chat.do_LOAD_INTENTION(intention_name)
1061
- mock_load_template_helper.assert_called_once_with(
1062
- intention_name, "intention", "*.intention.md")
2033
+ mock_load_template.assert_called_once_with(
2034
+ intention_name, "intention", chat.chat_name, "*.intention.md"
2035
+ )
1063
2036
 
1064
2037
 
1065
- @pytest.mark.parametrize("blueprint_name, expected_directory, expected_pattern", [
1066
- ("global/test_blueprint",
1067
- "mocked_global_directory/prompt-modules/blueprints/", "test_blueprint"),
1068
- ("local_blueprint",
1069
- "mocked_local_directory/custom-prompt-modules/blueprints", "local_blueprint")
1070
- ])
1071
- def test_do_LOAD_BLUEPRINT(monkeypatch, temp_chat_file, blueprint_name, expected_directory, expected_pattern):
2038
+ @pytest.mark.parametrize(
2039
+ "blueprint_name",
2040
+ [
2041
+ "global/test_blueprint",
2042
+ "local_blueprint",
2043
+ ],
2044
+ )
2045
+ def test_do_LOAD_BLUEPRINT(temp_chat_file, blueprint_name):
1072
2046
  mock_config = get_default_config()
1073
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2047
+ with patch(
2048
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2049
+ ):
1074
2050
  chat = Chat(temp_chat_file.name, reset=False)
1075
2051
 
1076
- with patch.object(chat, '_load_template_from_global_or_local') as mock_load_template:
2052
+ with patch.object(chat.template_loader, "load_template") as mock_load_template:
1077
2053
  chat.do_LOAD_BLUEPRINT(blueprint_name)
1078
- mock_load_template.assert_called_once_with(blueprint_name, "blueprint")
2054
+ mock_load_template.assert_called_once_with(
2055
+ blueprint_name, "blueprint", chat.chat_name
2056
+ )
1079
2057
 
1080
2058
 
1081
- @pytest.mark.parametrize("commands_name, expected_directory, expected_pattern", [
1082
- ("", "prompt.data", "*.commands.md"),
1083
- ("global/test_command",
1084
- "mocked_global_directory/prompt-modules/commands/", "test_command"),
1085
- ("local_command", "mocked_local_directory/custom-prompt-modules/commands", "local_command")
1086
- ])
1087
- def test_do_LOAD_COMMANDS(monkeypatch, temp_chat_file, commands_name, expected_directory, expected_pattern):
2059
+ @pytest.mark.parametrize(
2060
+ "commands_name",
2061
+ [
2062
+ "",
2063
+ "global/test_command",
2064
+ "local_command",
2065
+ ],
2066
+ )
2067
+ def test_do_LOAD_COMMANDS(temp_chat_file, commands_name):
1088
2068
  mock_config = get_default_config()
1089
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2069
+ with patch(
2070
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2071
+ ):
1090
2072
  chat = Chat(temp_chat_file.name, reset=False)
1091
2073
 
1092
- with patch.object(chat, '_load_template_helper') as mock_load_template_helper:
2074
+ with patch.object(chat.template_loader, "load_template") as mock_load_template:
1093
2075
  chat.do_LOAD_COMMANDS(commands_name)
1094
- mock_load_template_helper.assert_called_once_with(
1095
- commands_name, "commands", "*.commands.md")
1096
-
1097
-
1098
- @pytest.mark.parametrize("template_name, template_type, default_pattern, custom_template_subdir, expected_directory, expected_pattern", [
1099
- ("local_command", "commands", "*.commands.md", "custom-prompt-modules",
1100
- "/mocked_local_templates_path/custom-prompt-modules/commands", "local_command"),
1101
- ("local_command", "commands", "*.commands.md", "mocked_custom_modules_path",
1102
- "/mocked_local_templates_path/mocked_custom_modules_path/commands", "local_command"),
1103
- ("local_rule", "rules", "*.rules.md", "custom-prompt-modules",
1104
- "/mocked_local_templates_path/custom-prompt-modules/rules", "local_rule"),
1105
- ("local_rule", "rules", "*.rules.md", "mocked_custom_modules_path",
1106
- "/mocked_local_templates_path/mocked_custom_modules_path/rules", "local_rule"),
1107
- ("local_intention", "intention", "*.intentions.md", "custom-prompt-modules",
1108
- "/mocked_local_templates_path/custom-prompt-modules/intentions", "local_intention"),
1109
- ("local_intention", "intention", "*.intentions.md", "mocked_custom_modules_path",
1110
- "/mocked_local_templates_path/mocked_custom_modules_path/intentions", "local_intention"),
1111
- ("local_blueprint", "blueprint", "*.blueprints.md", "custom-prompt-modules",
1112
- "/mocked_local_templates_path/custom-prompt-modules/blueprints", "local_blueprint"),
1113
- ("local_blueprint", "blueprint", "*.blueprints.md", "mocked_custom_modules_path",
1114
- "/mocked_local_templates_path/mocked_custom_modules_path/blueprints", "local_blueprint")
1115
- ])
1116
- def test_load_template_local(monkeypatch, temp_chat_file, template_name, template_type, default_pattern, custom_template_subdir, expected_directory, expected_pattern):
1117
- expected_base_dir = os.path.abspath(
1118
- os.path.join(os.path.dirname(__file__), "../"))
2076
+ mock_load_template.assert_called_once_with(
2077
+ commands_name, "commands", chat.chat_name, "*.commands.md"
2078
+ )
2079
+
2080
+
2081
+ @pytest.mark.parametrize(
2082
+ "template_name, template_type, default_pattern, custom_template_subdir, expected_directory, expected_pattern",
2083
+ [
2084
+ (
2085
+ "local_command",
2086
+ "commands",
2087
+ "*.commands.md",
2088
+ "custom-prompt-modules",
2089
+ "/mocked_local_templates_path/custom-prompt-modules/commands",
2090
+ "local_command",
2091
+ ),
2092
+ (
2093
+ "local_command",
2094
+ "commands",
2095
+ "*.commands.md",
2096
+ "mocked_custom_modules_path",
2097
+ "/mocked_local_templates_path/mocked_custom_modules_path/commands",
2098
+ "local_command",
2099
+ ),
2100
+ (
2101
+ "local_rule",
2102
+ "rules",
2103
+ "*.rules.md",
2104
+ "custom-prompt-modules",
2105
+ "/mocked_local_templates_path/custom-prompt-modules/rules",
2106
+ "local_rule",
2107
+ ),
2108
+ (
2109
+ "local_rule",
2110
+ "rules",
2111
+ "*.rules.md",
2112
+ "mocked_custom_modules_path",
2113
+ "/mocked_local_templates_path/mocked_custom_modules_path/rules",
2114
+ "local_rule",
2115
+ ),
2116
+ (
2117
+ "local_intention",
2118
+ "intention",
2119
+ "*.intentions.md",
2120
+ "custom-prompt-modules",
2121
+ "/mocked_local_templates_path/custom-prompt-modules/intentions",
2122
+ "local_intention",
2123
+ ),
2124
+ (
2125
+ "local_intention",
2126
+ "intention",
2127
+ "*.intentions.md",
2128
+ "mocked_custom_modules_path",
2129
+ "/mocked_local_templates_path/mocked_custom_modules_path/intentions",
2130
+ "local_intention",
2131
+ ),
2132
+ (
2133
+ "local_blueprint",
2134
+ "blueprint",
2135
+ "*.blueprints.md",
2136
+ "custom-prompt-modules",
2137
+ "/mocked_local_templates_path/custom-prompt-modules/blueprints",
2138
+ "local_blueprint",
2139
+ ),
2140
+ (
2141
+ "local_blueprint",
2142
+ "blueprint",
2143
+ "*.blueprints.md",
2144
+ "mocked_custom_modules_path",
2145
+ "/mocked_local_templates_path/mocked_custom_modules_path/blueprints",
2146
+ "local_blueprint",
2147
+ ),
2148
+ ],
2149
+ )
2150
+ def test_load_template_local(
2151
+ monkeypatch,
2152
+ temp_chat_file,
2153
+ template_name,
2154
+ template_type,
2155
+ default_pattern,
2156
+ custom_template_subdir,
2157
+ expected_directory,
2158
+ expected_pattern,
2159
+ ):
2160
+ expected_base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
1119
2161
  expected_directory_abs = expected_base_dir + expected_directory
1120
2162
  mock_config = get_default_config()
1121
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2163
+ with patch(
2164
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2165
+ ):
1122
2166
  chat = Chat(temp_chat_file.name, reset=False)
1123
2167
 
1124
2168
  mock_local_templates_path = "mocked_local_templates_path"
1125
2169
 
1126
- monkeypatch.setattr(ConfigManager, 'get_config', lambda: MagicMock(
1127
- local_prompt_templates_dir=mock_local_templates_path))
2170
+ monkeypatch.setattr(
2171
+ ConfigManager,
2172
+ "get_config",
2173
+ lambda: MagicMock(local_prompt_templates_dir=mock_local_templates_path),
2174
+ )
1128
2175
 
1129
2176
  config = chat.config
1130
2177
  config.local_prompt_templates_dir = mock_local_templates_path
@@ -1132,98 +2179,158 @@ def test_load_template_local(monkeypatch, temp_chat_file, template_name, templat
1132
2179
 
1133
2180
  chat.config = config
1134
2181
 
1135
- with patch.object(chat, '_load_helper') as mock_load_helper:
2182
+ with patch.object(chat, "_load_helper") as mock_load_helper:
1136
2183
  chat._load_template_from_global_or_local(template_name, template_type)
1137
2184
  mock_load_helper.assert_called_once_with(
1138
- expected_directory_abs, expected_pattern, template_type)
1139
-
1140
-
1141
- @pytest.mark.parametrize("template_name, template_type, default_pattern, expected_directory, expected_pattern", [
1142
- ("global/test_command", "commands", "*.commands.md",
1143
- "mocked_template_base_path/prompt-modules/commands/", "test_command"),
1144
- ("global/test_rule", "rules", "*.rules.md",
1145
- "mocked_template_base_path/prompt-modules/rules/", "test_rule"),
1146
- ("global/test_intention", "intention", "*.intentions.md",
1147
- "mocked_template_base_path/prompt-modules/intentions/", "test_intention"),
1148
- ("global/test_blueprint", "blueprint", "*.blueprints.md",
1149
- "mocked_template_base_path/prompt-modules/blueprints/", "test_blueprint"),
1150
- ])
1151
- def test_load_template_from_global(monkeypatch, temp_chat_file, template_name, template_type, default_pattern, expected_directory, expected_pattern):
2185
+ expected_directory_abs, expected_pattern, template_type
2186
+ )
2187
+
2188
+
2189
+ @pytest.mark.parametrize(
2190
+ "template_name, template_type, default_pattern, expected_directory, expected_pattern",
2191
+ [
2192
+ (
2193
+ "global/test_command",
2194
+ "commands",
2195
+ "*.commands.md",
2196
+ "mocked_template_base_path/prompt-modules/commands/",
2197
+ "test_command",
2198
+ ),
2199
+ (
2200
+ "global/test_rule",
2201
+ "rules",
2202
+ "*.rules.md",
2203
+ "mocked_template_base_path/prompt-modules/rules/",
2204
+ "test_rule",
2205
+ ),
2206
+ (
2207
+ "global/test_intention",
2208
+ "intention",
2209
+ "*.intentions.md",
2210
+ "mocked_template_base_path/prompt-modules/intentions/",
2211
+ "test_intention",
2212
+ ),
2213
+ (
2214
+ "global/test_blueprint",
2215
+ "blueprint",
2216
+ "*.blueprints.md",
2217
+ "mocked_template_base_path/prompt-modules/blueprints/",
2218
+ "test_blueprint",
2219
+ ),
2220
+ ],
2221
+ )
2222
+ def test_load_template_from_global(
2223
+ monkeypatch,
2224
+ temp_chat_file,
2225
+ template_name,
2226
+ template_type,
2227
+ default_pattern,
2228
+ expected_directory,
2229
+ expected_pattern,
2230
+ ):
1152
2231
  mock_config = get_default_config()
1153
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2232
+ with patch(
2233
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2234
+ ):
1154
2235
  chat = Chat(temp_chat_file.name, reset=False)
1155
2236
 
1156
2237
  mock_template_base_path = "mocked_template_base_path"
1157
2238
 
1158
2239
  monkeypatch.setattr(
1159
- TemplatePathManager, 'get_template_base_path', lambda: mock_template_base_path)
2240
+ TemplatePathManager, "get_template_base_path", lambda: mock_template_base_path
2241
+ )
1160
2242
 
1161
2243
  config = chat.config
1162
2244
  chat.config = config
1163
2245
 
1164
- with patch.object(chat, '_load_helper') as mock_load_helper:
2246
+ with patch.object(chat, "_load_helper") as mock_load_helper:
1165
2247
  chat._load_template_from_global_or_local(template_name, template_type)
1166
2248
  mock_load_helper.assert_called_once_with(
1167
- expected_directory, expected_pattern, template_type)
1168
-
1169
-
1170
- @pytest.mark.parametrize("template_name, template_type, default_pattern", [
1171
- ("global/test_command", "commands", "*.commands.md"),
1172
- ("local_command", "commands", "*.commands.md"),
2249
+ expected_directory, expected_pattern, template_type
2250
+ )
1173
2251
 
1174
- ("global/test_rule", "rules", "*.rules.md"),
1175
- ("local_rule", "rules", "*.rules.md"),
1176
2252
 
1177
- ("global/test_intention", "intention", "*.intentions.md"),
1178
- ("local_intention", "intention", "*.intentions.md")
1179
- ])
1180
- def test_load_template_helper_load_from_template_dirs(monkeypatch, temp_chat_file, template_name, template_type, default_pattern):
2253
+ @pytest.mark.parametrize(
2254
+ "template_name, template_type, default_pattern",
2255
+ [
2256
+ ("global/test_command", "commands", "*.commands.md"),
2257
+ ("local_command", "commands", "*.commands.md"),
2258
+ ("global/test_rule", "rules", "*.rules.md"),
2259
+ ("local_rule", "rules", "*.rules.md"),
2260
+ ("global/test_intention", "intention", "*.intentions.md"),
2261
+ ("local_intention", "intention", "*.intentions.md"),
2262
+ ],
2263
+ )
2264
+ def test_load_template_helper_load_from_template_dirs(
2265
+ monkeypatch, temp_chat_file, template_name, template_type, default_pattern
2266
+ ):
1181
2267
  mock_config = get_default_config()
1182
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2268
+ with patch(
2269
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2270
+ ):
1183
2271
  chat = Chat(temp_chat_file.name, reset=False)
1184
2272
 
1185
- with patch.object(chat, "_load_template_from_global_or_local") as mock_load_template:
1186
- chat._load_template_helper(
1187
- template_name, template_type, default_pattern)
2273
+ with patch.object(
2274
+ chat, "_load_template_from_global_or_local"
2275
+ ) as mock_load_template:
2276
+ chat._load_template_helper(template_name, template_type, default_pattern)
1188
2277
 
1189
2278
  mock_load_template.assert_called_once_with(
1190
- template_name=template_name, template_type=template_type)
1191
-
1192
-
1193
- @pytest.mark.parametrize("template_name, template_type, default_pattern", [
1194
- (None, "commands", "*.commands.md"),
1195
- ("", "commands", "*.commands.md"),
2279
+ template_name=template_name, template_type=template_type
2280
+ )
1196
2281
 
1197
- (None, "rules", "*.rules.md"),
1198
- ("", "rules", "*.rules.md"),
1199
2282
 
1200
- (None, "intention", "*.intention.md"),
1201
- ("", "intention", "*.intention.md"),
1202
- ])
1203
- def test_load_template_helper_load_default_pattern(monkeypatch, temp_chat_file, template_name, template_type, default_pattern):
2283
+ @pytest.mark.parametrize(
2284
+ "template_name, template_type, default_pattern",
2285
+ [
2286
+ (None, "commands", "*.commands.md"),
2287
+ ("", "commands", "*.commands.md"),
2288
+ (None, "rules", "*.rules.md"),
2289
+ ("", "rules", "*.rules.md"),
2290
+ (None, "intention", "*.intention.md"),
2291
+ ("", "intention", "*.intention.md"),
2292
+ ],
2293
+ )
2294
+ def test_load_template_helper_load_default_pattern(
2295
+ monkeypatch, temp_chat_file, template_name, template_type, default_pattern
2296
+ ):
1204
2297
  mock_config = get_default_config()
1205
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2298
+ with patch(
2299
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2300
+ ):
1206
2301
  chat = Chat(temp_chat_file.name, reset=False)
1207
2302
 
1208
2303
  with patch.object(chat, "_load_helper") as mock_load_helper:
1209
- chat._load_template_helper(
1210
- template_name, template_type, default_pattern)
2304
+ chat._load_template_helper(template_name, template_type, default_pattern)
1211
2305
 
1212
2306
  mock_load_helper.assert_called_once_with(
1213
- "prompt.data", default_pattern, template_type)
2307
+ "prompt.data", default_pattern, template_type
2308
+ )
1214
2309
 
1215
2310
 
1216
- @pytest.mark.parametrize("force_flag, write_flag, expected_force, expected_write", [
1217
- (False, False, False, False),
1218
- (True, False, True, False),
1219
- (False, True, False, True),
1220
- (True, True, True, True),
1221
- ])
1222
- @patch('ara_cli.commands.extract_command.ExtractCommand')
1223
- def test_do_EXTRACT_with_flags(MockExtractCommand, temp_chat_file, force_flag, write_flag, expected_force, expected_write):
2311
+ @pytest.mark.parametrize(
2312
+ "force_flag, write_flag, expected_force, expected_write",
2313
+ [
2314
+ (False, False, False, False),
2315
+ (True, False, True, False),
2316
+ (False, True, False, True),
2317
+ (True, True, True, True),
2318
+ ],
2319
+ )
2320
+ @patch("ara_cli.commands.extract_command.ExtractCommand")
2321
+ def test_do_EXTRACT_with_flags(
2322
+ MockExtractCommand,
2323
+ temp_chat_file,
2324
+ force_flag,
2325
+ write_flag,
2326
+ expected_force,
2327
+ expected_write,
2328
+ ):
1224
2329
  """Test do_EXTRACT with different flag combinations"""
1225
2330
  mock_config = get_default_config()
1226
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2331
+ with patch(
2332
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2333
+ ):
1227
2334
  chat = Chat(temp_chat_file.name, reset=False)
1228
2335
 
1229
2336
  # Build command string with flags
@@ -1232,31 +2339,36 @@ def test_do_EXTRACT_with_flags(MockExtractCommand, temp_chat_file, force_flag, w
1232
2339
  command_parts.append("-f")
1233
2340
  if write_flag:
1234
2341
  command_parts.append("-w")
1235
-
2342
+
1236
2343
  command_string = " ".join(command_parts)
1237
-
2344
+
1238
2345
  chat.onecmd_plus_hooks(command_string, orig_rl_history_length=0)
1239
2346
 
1240
2347
  MockExtractCommand.assert_called_once_with(
1241
2348
  file_name=chat.chat_name,
1242
2349
  force=expected_force,
1243
2350
  write=expected_write,
1244
- output=chat.poutput
2351
+ output=chat.poutput,
1245
2352
  )
1246
2353
  MockExtractCommand.return_value.execute.assert_called_once()
1247
2354
 
1248
2355
 
1249
- @pytest.mark.parametrize("command_string", [
1250
- "EXTRACT --force",
1251
- "EXTRACT --write",
1252
- "EXTRACT -f -w",
1253
- "EXTRACT --force --write",
1254
- ])
1255
- @patch('ara_cli.commands.extract_command.ExtractCommand')
2356
+ @pytest.mark.parametrize(
2357
+ "command_string",
2358
+ [
2359
+ "EXTRACT --force",
2360
+ "EXTRACT --write",
2361
+ "EXTRACT -f -w",
2362
+ "EXTRACT --force --write",
2363
+ ],
2364
+ )
2365
+ @patch("ara_cli.commands.extract_command.ExtractCommand")
1256
2366
  def test_do_EXTRACT_long_form_flags(MockExtractCommand, temp_chat_file, command_string):
1257
2367
  """Test do_EXTRACT with long-form flag variations"""
1258
2368
  mock_config = get_default_config()
1259
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2369
+ with patch(
2370
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2371
+ ):
1260
2372
  chat = Chat(temp_chat_file.name, reset=False)
1261
2373
 
1262
2374
  chat.onecmd_plus_hooks(command_string, orig_rl_history_length=0)
@@ -1265,50 +2377,53 @@ def test_do_EXTRACT_long_form_flags(MockExtractCommand, temp_chat_file, command_
1265
2377
  MockExtractCommand.return_value.execute.assert_called_once()
1266
2378
 
1267
2379
 
1268
- @patch('ara_cli.commands.extract_command.ExtractCommand')
2380
+ @patch("ara_cli.commands.extract_command.ExtractCommand")
1269
2381
  def test_do_EXTRACT_no_flags(MockExtractCommand, temp_chat_file):
1270
2382
  """Test do_EXTRACT with no flags (default behavior)"""
1271
2383
  mock_config = get_default_config()
1272
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2384
+ with patch(
2385
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2386
+ ):
1273
2387
  chat = Chat(temp_chat_file.name, reset=False)
1274
2388
 
1275
2389
  chat.onecmd_plus_hooks("EXTRACT", orig_rl_history_length=0)
1276
2390
 
1277
2391
  MockExtractCommand.assert_called_once_with(
1278
- file_name=chat.chat_name,
1279
- force=False,
1280
- write=False,
1281
- output=chat.poutput
2392
+ file_name=chat.chat_name, force=False, write=False, output=chat.poutput
1282
2393
  )
1283
2394
  MockExtractCommand.return_value.execute.assert_called_once()
1284
2395
 
1285
2396
 
1286
- @patch('ara_cli.commands.extract_command.ExtractCommand')
2397
+ @patch("ara_cli.commands.extract_command.ExtractCommand")
1287
2398
  def test_do_EXTRACT_command_instantiation(MockExtractCommand, temp_chat_file):
1288
2399
  """Test that ExtractCommand is properly instantiated with correct parameters"""
1289
2400
  mock_config = get_default_config()
1290
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2401
+ with patch(
2402
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2403
+ ):
1291
2404
  chat = Chat(temp_chat_file.name, reset=False)
1292
2405
 
1293
2406
  chat.onecmd_plus_hooks("EXTRACT -f", orig_rl_history_length=0)
1294
2407
 
1295
2408
  # Verify the command was instantiated with the correct chat instance attributes
1296
2409
  call_args = MockExtractCommand.call_args
1297
- assert call_args[1]['file_name'] == chat.chat_name
1298
- assert call_args[1]['output'] == chat.poutput
1299
- assert isinstance(call_args[1]['force'], bool)
1300
- assert isinstance(call_args[1]['write'], bool)
2410
+ assert call_args[1]["file_name"] == chat.chat_name
2411
+ assert call_args[1]["output"] == chat.poutput
2412
+ assert isinstance(call_args[1]["force"], bool)
2413
+ assert isinstance(call_args[1]["write"], bool)
1301
2414
 
1302
2415
 
1303
- @patch('ara_cli.commands.extract_command.ExtractCommand')
2416
+ @patch("ara_cli.commands.extract_command.ExtractCommand")
1304
2417
  def test_do_EXTRACT_command_execution(MockExtractCommand, temp_chat_file):
1305
2418
  """Test that ExtractCommand.execute() is called"""
1306
2419
  mock_config = get_default_config()
1307
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2420
+ with patch(
2421
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2422
+ ):
1308
2423
  chat = Chat(temp_chat_file.name, reset=False)
1309
2424
 
1310
2425
  mock_command_instance = MockExtractCommand.return_value
1311
-
2426
+
1312
2427
  chat.onecmd_plus_hooks("EXTRACT", orig_rl_history_length=0)
1313
2428
 
1314
2429
  mock_command_instance.execute.assert_called_once_with()
@@ -1316,54 +2431,205 @@ def test_do_EXTRACT_command_execution(MockExtractCommand, temp_chat_file):
1316
2431
 
1317
2432
  def test_do_SEND(temp_chat_file):
1318
2433
  mock_config = get_default_config()
1319
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2434
+ with patch(
2435
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2436
+ ):
1320
2437
  chat = Chat(temp_chat_file.name, reset=False)
1321
2438
  chat.message_buffer = ["Message part 1", "Message part 2"]
1322
2439
 
1323
- with patch.object(chat, 'save_message') as mock_save_message:
1324
- with patch.object(chat, 'send_message') as mock_send_message:
2440
+ with patch.object(chat, "save_message") as mock_save_message:
2441
+ with patch.object(chat, "send_message") as mock_send_message:
1325
2442
  chat.do_SEND(None)
1326
2443
  mock_save_message.assert_called_once_with(
1327
- Chat.ROLE_PROMPT, "Message part 1\nMessage part 2")
2444
+ Chat.ROLE_PROMPT, "Message part 1\nMessage part 2"
2445
+ )
1328
2446
  mock_send_message.assert_called_once()
1329
2447
 
1330
2448
 
1331
- @pytest.mark.parametrize("template_name, artefact_obj, expected_write, expected_print", [
1332
- ("TestTemplate", MagicMock(serialize=MagicMock(return_value="serialized_content")),
1333
- "serialized_content", "Loaded TestTemplate artefact template\n"),
1334
- ("AnotherTemplate", MagicMock(serialize=MagicMock(return_value="other_content")),
1335
- "other_content", "Loaded AnotherTemplate artefact template\n"),
1336
- ])
1337
- def test_do_LOAD_TEMPLATE_success(temp_chat_file, template_name, artefact_obj, expected_write, expected_print, capsys):
2449
+ @pytest.mark.parametrize(
2450
+ "template_name, artefact_obj, expected_write, expected_print",
2451
+ [
2452
+ (
2453
+ "TestTemplate",
2454
+ MagicMock(serialize=MagicMock(return_value="serialized_content")),
2455
+ "serialized_content",
2456
+ "Loaded TestTemplate artefact template\n",
2457
+ ),
2458
+ (
2459
+ "AnotherTemplate",
2460
+ MagicMock(serialize=MagicMock(return_value="other_content")),
2461
+ "other_content",
2462
+ "Loaded AnotherTemplate artefact template\n",
2463
+ ),
2464
+ (
2465
+ "",
2466
+ MagicMock(serialize=MagicMock(return_value="empty_content")),
2467
+ "empty_content",
2468
+ "Loaded artefact template\n",
2469
+ ),
2470
+ ],
2471
+ )
2472
+ def test_do_LOAD_TEMPLATE_success(
2473
+ temp_chat_file, template_name, artefact_obj, expected_write, expected_print, capsys
2474
+ ):
1338
2475
  mock_config = MagicMock()
1339
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2476
+ with patch(
2477
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2478
+ ):
1340
2479
  chat = Chat(temp_chat_file.name, reset=False)
1341
- with patch('ara_cli.artefact_models.artefact_templates.template_artefact_of_type', return_value=artefact_obj) as mock_template_loader, \
1342
- patch.object(chat, 'add_prompt_tag_if_needed') as mock_add_prompt_tag, \
1343
- patch("builtins.open", mock_open()) as mock_file:
2480
+
2481
+ with patch(
2482
+ "ara_cli.artefact_models.artefact_templates.template_artefact_of_type",
2483
+ return_value=artefact_obj,
2484
+ ) as mock_template_loader, patch.object(
2485
+ chat, "add_prompt_tag_if_needed"
2486
+ ) as mock_add_prompt_tag, patch(
2487
+ "builtins.open", mock_open()
2488
+ ) as mock_file:
2489
+
1344
2490
  chat.do_LOAD_TEMPLATE(template_name)
2491
+
1345
2492
  mock_template_loader.assert_called_once_with(template_name)
1346
2493
  artefact_obj.serialize.assert_called_once_with()
1347
2494
  mock_add_prompt_tag.assert_called_once_with(chat.chat_name)
1348
- mock_file.assert_called_with(chat.chat_name, 'a', encoding='utf-8')
2495
+ mock_file.assert_called_with(chat.chat_name, "a", encoding="utf-8")
1349
2496
  mock_file().write.assert_called_once_with(expected_write)
2497
+
1350
2498
  out = capsys.readouterr()
1351
2499
  assert expected_print in out.out
1352
2500
 
1353
2501
 
1354
- @pytest.mark.parametrize("template_name", [
1355
- ("MissingTemplate"),
1356
- (""),
1357
- ])
1358
- def test_do_LOAD_TEMPLATE_missing_artefact(temp_chat_file, template_name):
2502
+ @pytest.mark.parametrize(
2503
+ "template_name",
2504
+ [
2505
+ "MissingTemplate",
2506
+ "",
2507
+ "NonExistentTemplate",
2508
+ ],
2509
+ )
2510
+ @patch("ara_cli.error_handler.report_error")
2511
+ def test_do_LOAD_TEMPLATE_missing_artefact(
2512
+ mock_report_error, temp_chat_file, template_name
2513
+ ):
1359
2514
  mock_config = MagicMock()
1360
- with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
2515
+ with patch(
2516
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2517
+ ):
1361
2518
  chat = Chat(temp_chat_file.name, reset=False)
1362
- with patch('ara_cli.artefact_models.artefact_templates.template_artefact_of_type', return_value=None) as mock_template_loader, \
1363
- patch.object(chat, 'add_prompt_tag_if_needed') as mock_add_prompt_tag, \
1364
- patch("builtins.open", mock_open()) as mock_file:
1365
- with pytest.raises(ValueError, match=f"No template for '{template_name}' found."):
1366
- chat.do_LOAD_TEMPLATE(template_name)
2519
+
2520
+ with patch(
2521
+ "ara_cli.artefact_models.artefact_templates.template_artefact_of_type",
2522
+ return_value=None,
2523
+ ) as mock_template_loader, patch.object(
2524
+ chat, "add_prompt_tag_if_needed"
2525
+ ) as mock_add_prompt_tag, patch(
2526
+ "builtins.open", mock_open()
2527
+ ) as mock_file:
2528
+
2529
+ chat.do_LOAD_TEMPLATE(template_name)
2530
+
1367
2531
  mock_template_loader.assert_called_once_with(template_name)
2532
+ mock_report_error.assert_called_once()
2533
+
2534
+ # Verify the error details
2535
+ error_call = mock_report_error.call_args[0][0]
2536
+ assert isinstance(error_call, ValueError)
2537
+ assert str(error_call) == f"No template for '{template_name}' found."
2538
+
2539
+ # Verify subsequent operations are not called
1368
2540
  mock_add_prompt_tag.assert_not_called()
1369
2541
  mock_file.assert_not_called()
2542
+
2543
+
2544
+ def test_do_LOAD_TEMPLATE_string_join_behavior(temp_chat_file):
2545
+ """Test that template_name is properly joined when passed as argument"""
2546
+ mock_config = MagicMock()
2547
+ with patch(
2548
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2549
+ ):
2550
+ chat = Chat(temp_chat_file.name, reset=False)
2551
+
2552
+ mock_artefact = MagicMock(serialize=MagicMock(return_value="test_content"))
2553
+
2554
+ with patch(
2555
+ "ara_cli.artefact_models.artefact_templates.template_artefact_of_type",
2556
+ return_value=mock_artefact,
2557
+ ) as mock_template_loader, patch.object(chat, "add_prompt_tag_if_needed"), patch(
2558
+ "builtins.open", mock_open()
2559
+ ):
2560
+
2561
+ # Test with string argument (normal case)
2562
+ chat.do_LOAD_TEMPLATE("TestTemplate")
2563
+ mock_template_loader.assert_called_with("TestTemplate")
2564
+
2565
+ # Reset mock for next test
2566
+ mock_template_loader.reset_mock()
2567
+
2568
+ # Test with list-like argument (edge case)
2569
+ chat.do_LOAD_TEMPLATE(["Test", "Template"])
2570
+ mock_template_loader.assert_called_with("TestTemplate")
2571
+
2572
+
2573
+ def test_do_LOAD_TEMPLATE_file_operations(temp_chat_file):
2574
+ """Test file operations are performed in correct order"""
2575
+ mock_config = MagicMock()
2576
+ with patch(
2577
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2578
+ ):
2579
+ chat = Chat(temp_chat_file.name, reset=False)
2580
+
2581
+ mock_artefact = MagicMock(serialize=MagicMock(return_value="test_content"))
2582
+ call_order = []
2583
+
2584
+ def mock_add_prompt_tag(chat_name):
2585
+ call_order.append("add_prompt_tag")
2586
+
2587
+ def mock_write(content):
2588
+ call_order.append("write")
2589
+
2590
+ with patch(
2591
+ "ara_cli.artefact_models.artefact_templates.template_artefact_of_type",
2592
+ return_value=mock_artefact,
2593
+ ), patch.object(
2594
+ chat, "add_prompt_tag_if_needed", side_effect=mock_add_prompt_tag
2595
+ ), patch(
2596
+ "builtins.open", mock_open()
2597
+ ) as mock_file:
2598
+
2599
+ mock_file().write.side_effect = mock_write
2600
+
2601
+ chat.do_LOAD_TEMPLATE("TestTemplate")
2602
+
2603
+ # Verify operations happen in correct order
2604
+ assert call_order == ["add_prompt_tag", "write"]
2605
+
2606
+ # Verify file is opened with correct parameters
2607
+ mock_file.assert_called_with(chat.chat_name, "a", encoding="utf-8")
2608
+
2609
+
2610
+ def test_do_LOAD_TEMPLATE_serialize_called_correctly(temp_chat_file):
2611
+ """Test that artefact.serialize() is called and its result is used"""
2612
+ mock_config = MagicMock()
2613
+ with patch(
2614
+ "ara_cli.prompt_handler.ConfigManager.get_config", return_value=mock_config
2615
+ ):
2616
+ chat = Chat(temp_chat_file.name, reset=False)
2617
+
2618
+ expected_content = "unique_serialized_content_12345"
2619
+ mock_artefact = MagicMock()
2620
+ mock_artefact.serialize.return_value = expected_content
2621
+
2622
+ with patch(
2623
+ "ara_cli.artefact_models.artefact_templates.template_artefact_of_type",
2624
+ return_value=mock_artefact,
2625
+ ), patch.object(chat, "add_prompt_tag_if_needed"), patch(
2626
+ "builtins.open", mock_open()
2627
+ ) as mock_file:
2628
+
2629
+ chat.do_LOAD_TEMPLATE("TestTemplate")
2630
+
2631
+ # Verify serialize was called exactly once
2632
+ mock_artefact.serialize.assert_called_once_with()
2633
+
2634
+ # Verify the serialized content was written to file
2635
+ mock_file().write.assert_called_once_with(expected_content)