fishertools 0.2.1__py3-none-any.whl → 0.4.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.
Files changed (69) hide show
  1. fishertools/__init__.py +16 -5
  2. fishertools/errors/__init__.py +11 -3
  3. fishertools/errors/exception_types.py +282 -0
  4. fishertools/errors/explainer.py +87 -1
  5. fishertools/errors/models.py +73 -1
  6. fishertools/errors/patterns.py +40 -0
  7. fishertools/examples/cli_example.py +156 -0
  8. fishertools/examples/learn_example.py +65 -0
  9. fishertools/examples/logger_example.py +176 -0
  10. fishertools/examples/menu_example.py +101 -0
  11. fishertools/examples/storage_example.py +175 -0
  12. fishertools/input_utils.py +185 -0
  13. fishertools/learn/__init__.py +19 -2
  14. fishertools/learn/examples.py +88 -1
  15. fishertools/learn/knowledge_engine.py +321 -0
  16. fishertools/learn/repl/__init__.py +19 -0
  17. fishertools/learn/repl/cli.py +31 -0
  18. fishertools/learn/repl/code_sandbox.py +229 -0
  19. fishertools/learn/repl/command_handler.py +544 -0
  20. fishertools/learn/repl/command_parser.py +165 -0
  21. fishertools/learn/repl/engine.py +479 -0
  22. fishertools/learn/repl/models.py +121 -0
  23. fishertools/learn/repl/session_manager.py +284 -0
  24. fishertools/learn/repl/test_code_sandbox.py +261 -0
  25. fishertools/learn/repl/test_code_sandbox_pbt.py +148 -0
  26. fishertools/learn/repl/test_command_handler.py +224 -0
  27. fishertools/learn/repl/test_command_handler_pbt.py +189 -0
  28. fishertools/learn/repl/test_command_parser.py +160 -0
  29. fishertools/learn/repl/test_command_parser_pbt.py +100 -0
  30. fishertools/learn/repl/test_engine.py +190 -0
  31. fishertools/learn/repl/test_session_manager.py +310 -0
  32. fishertools/learn/repl/test_session_manager_pbt.py +182 -0
  33. fishertools/learn/test_knowledge_engine.py +241 -0
  34. fishertools/learn/test_knowledge_engine_pbt.py +180 -0
  35. fishertools/patterns/__init__.py +46 -0
  36. fishertools/patterns/cli.py +175 -0
  37. fishertools/patterns/logger.py +140 -0
  38. fishertools/patterns/menu.py +99 -0
  39. fishertools/patterns/storage.py +127 -0
  40. fishertools/readme_transformer.py +631 -0
  41. fishertools/safe/__init__.py +6 -1
  42. fishertools/safe/files.py +329 -1
  43. fishertools/transform_readme.py +105 -0
  44. fishertools-0.4.0.dist-info/METADATA +104 -0
  45. fishertools-0.4.0.dist-info/RECORD +131 -0
  46. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/WHEEL +1 -1
  47. tests/test_documentation_properties.py +329 -0
  48. tests/test_documentation_structure.py +349 -0
  49. tests/test_errors/test_exception_types.py +446 -0
  50. tests/test_errors/test_exception_types_pbt.py +333 -0
  51. tests/test_errors/test_patterns.py +52 -0
  52. tests/test_input_utils/__init__.py +1 -0
  53. tests/test_input_utils/test_input_utils.py +65 -0
  54. tests/test_learn/test_examples.py +179 -1
  55. tests/test_learn/test_explain_properties.py +307 -0
  56. tests/test_patterns_cli.py +611 -0
  57. tests/test_patterns_docstrings.py +473 -0
  58. tests/test_patterns_logger.py +465 -0
  59. tests/test_patterns_menu.py +440 -0
  60. tests/test_patterns_storage.py +447 -0
  61. tests/test_readme_enhancements_v0_3_1.py +2036 -0
  62. tests/test_readme_transformer/__init__.py +1 -0
  63. tests/test_readme_transformer/test_readme_infrastructure.py +1023 -0
  64. tests/test_readme_transformer/test_transform_readme_integration.py +431 -0
  65. tests/test_safe/test_files.py +726 -1
  66. fishertools-0.2.1.dist-info/METADATA +0 -256
  67. fishertools-0.2.1.dist-info/RECORD +0 -81
  68. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/licenses/LICENSE +0 -0
  69. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,611 @@
1
+ """
2
+ Property-based tests for the SimpleCLI class in fishertools.patterns.
3
+
4
+ Tests the correctness properties of the SimpleCLI class using hypothesis
5
+ for property-based testing.
6
+
7
+ **Validates: Requirements 11.2, 11.3, 11.5**
8
+ """
9
+
10
+ import pytest
11
+ from unittest.mock import patch, MagicMock
12
+ from io import StringIO
13
+
14
+ from fishertools.patterns.cli import SimpleCLI
15
+
16
+
17
+ class TestSimpleCLIExecutesCorrectHandler:
18
+ """
19
+ Property 8: SimpleCLI Executes Correct Handler
20
+
21
+ For any registered command, calling SimpleCLI with that command should
22
+ execute the corresponding handler function.
23
+
24
+ **Validates: Requirements 11.2, 11.3**
25
+ """
26
+
27
+ def test_executes_single_command(self):
28
+ """Test that SimpleCLI executes a single registered command."""
29
+ cli = SimpleCLI("test", "Test CLI")
30
+ mock_handler = MagicMock()
31
+
32
+ @cli.command("test", "Test command")
33
+ def test_cmd():
34
+ mock_handler()
35
+
36
+ cli.run(["test"])
37
+
38
+ mock_handler.assert_called_once()
39
+
40
+ def test_executes_correct_command_from_multiple(self):
41
+ """Test that SimpleCLI executes the correct command from multiple options."""
42
+ cli = SimpleCLI("test", "Test CLI")
43
+ mock_handler1 = MagicMock()
44
+ mock_handler2 = MagicMock()
45
+ mock_handler3 = MagicMock()
46
+
47
+ @cli.command("cmd1", "First command")
48
+ def cmd1():
49
+ mock_handler1()
50
+
51
+ @cli.command("cmd2", "Second command")
52
+ def cmd2():
53
+ mock_handler2()
54
+
55
+ @cli.command("cmd3", "Third command")
56
+ def cmd3():
57
+ mock_handler3()
58
+
59
+ # Execute cmd2
60
+ cli.run(["cmd2"])
61
+
62
+ # Only cmd2 should have been called
63
+ mock_handler1.assert_not_called()
64
+ mock_handler2.assert_called_once()
65
+ mock_handler3.assert_not_called()
66
+
67
+ def test_executes_command_with_arguments(self):
68
+ """Test that SimpleCLI passes arguments to the command handler."""
69
+ cli = SimpleCLI("test", "Test CLI")
70
+ mock_handler = MagicMock()
71
+
72
+ @cli.command("greet", "Greet someone")
73
+ def greet(name):
74
+ mock_handler(name)
75
+
76
+ cli.run(["greet", "Alice"])
77
+
78
+ mock_handler.assert_called_once_with("Alice")
79
+
80
+ def test_executes_command_with_multiple_arguments(self):
81
+ """Test that SimpleCLI passes multiple arguments to the command handler."""
82
+ cli = SimpleCLI("test", "Test CLI")
83
+ mock_handler = MagicMock()
84
+
85
+ @cli.command("add", "Add two numbers")
86
+ def add(a, b):
87
+ mock_handler(a, b)
88
+
89
+ cli.run(["add", "5", "3"])
90
+
91
+ mock_handler.assert_called_once_with("5", "3")
92
+
93
+ def test_executes_command_with_many_arguments(self):
94
+ """Test that SimpleCLI passes many arguments to the command handler."""
95
+ cli = SimpleCLI("test", "Test CLI")
96
+ mock_handler = MagicMock()
97
+
98
+ @cli.command("multi", "Multi-argument command")
99
+ def multi(a, b, c, d, e):
100
+ mock_handler(a, b, c, d, e)
101
+
102
+ cli.run(["multi", "1", "2", "3", "4", "5"])
103
+
104
+ mock_handler.assert_called_once_with("1", "2", "3", "4", "5")
105
+
106
+ def test_executes_command_with_no_arguments(self):
107
+ """Test that SimpleCLI executes command with no arguments."""
108
+ cli = SimpleCLI("test", "Test CLI")
109
+ mock_handler = MagicMock()
110
+
111
+ @cli.command("status", "Show status")
112
+ def status():
113
+ mock_handler()
114
+
115
+ cli.run(["status"])
116
+
117
+ mock_handler.assert_called_once()
118
+
119
+ def test_command_receives_arguments_as_strings(self):
120
+ """Test that command arguments are passed as strings."""
121
+ cli = SimpleCLI("test", "Test CLI")
122
+ received_args = []
123
+
124
+ @cli.command("echo", "Echo arguments")
125
+ def echo(*args):
126
+ received_args.extend(args)
127
+
128
+ cli.run(["echo", "hello", "123", "true"])
129
+
130
+ # All arguments should be strings
131
+ assert received_args == ["hello", "123", "true"]
132
+ assert all(isinstance(arg, str) for arg in received_args)
133
+
134
+ def test_executes_command_with_special_characters_in_args(self):
135
+ """Test that SimpleCLI handles special characters in arguments."""
136
+ cli = SimpleCLI("test", "Test CLI")
137
+ mock_handler = MagicMock()
138
+
139
+ @cli.command("special", "Handle special chars")
140
+ def special(arg):
141
+ mock_handler(arg)
142
+
143
+ cli.run(["special", "!@#$%^&*()"])
144
+
145
+ mock_handler.assert_called_once_with("!@#$%^&*()")
146
+
147
+ def test_executes_command_with_empty_string_argument(self):
148
+ """Test that SimpleCLI handles empty string arguments."""
149
+ cli = SimpleCLI("test", "Test CLI")
150
+ mock_handler = MagicMock()
151
+
152
+ @cli.command("empty", "Handle empty string")
153
+ def empty(arg):
154
+ mock_handler(arg)
155
+
156
+ cli.run(["empty", ""])
157
+
158
+ mock_handler.assert_called_once_with("")
159
+
160
+ def test_executes_command_with_space_in_argument(self):
161
+ """Test that SimpleCLI handles arguments with spaces."""
162
+ cli = SimpleCLI("test", "Test CLI")
163
+ mock_handler = MagicMock()
164
+
165
+ @cli.command("msg", "Send message")
166
+ def msg(text):
167
+ mock_handler(text)
168
+
169
+ cli.run(["msg", "hello world"])
170
+
171
+ mock_handler.assert_called_once_with("hello world")
172
+
173
+ def test_command_decorator_returns_function(self):
174
+ """Test that command decorator returns the original function."""
175
+ cli = SimpleCLI("test", "Test CLI")
176
+
177
+ @cli.command("test", "Test command")
178
+ def test_func():
179
+ return "result"
180
+
181
+ # The decorator should return the original function
182
+ assert test_func() == "result"
183
+
184
+ def test_multiple_commands_registered_independently(self):
185
+ """Test that multiple commands are registered independently."""
186
+ cli = SimpleCLI("test", "Test CLI")
187
+ results = []
188
+
189
+ @cli.command("cmd1", "First")
190
+ def cmd1():
191
+ results.append("cmd1")
192
+
193
+ @cli.command("cmd2", "Second")
194
+ def cmd2():
195
+ results.append("cmd2")
196
+
197
+ cli.run(["cmd1"])
198
+ cli.run(["cmd2"])
199
+ cli.run(["cmd1"])
200
+
201
+ assert results == ["cmd1", "cmd2", "cmd1"]
202
+
203
+ def test_command_with_return_value(self):
204
+ """Test that command handlers can return values."""
205
+ cli = SimpleCLI("test", "Test CLI")
206
+
207
+ @cli.command("get", "Get value")
208
+ def get_value():
209
+ return 42
210
+
211
+ # The handler should execute without error
212
+ cli.run(["get"])
213
+
214
+
215
+ class TestSimpleCLIHandlesInvalidCommands:
216
+ """
217
+ Property 9: SimpleCLI Handles Invalid Commands
218
+
219
+ For any invalid command, SimpleCLI should handle it gracefully without
220
+ crashing.
221
+
222
+ **Validates: Requirements 11.5**
223
+ """
224
+
225
+ def test_handles_unknown_command(self):
226
+ """Test that SimpleCLI handles unknown commands gracefully."""
227
+ cli = SimpleCLI("test", "Test CLI")
228
+
229
+ @cli.command("known", "Known command")
230
+ def known():
231
+ pass
232
+
233
+ # Should not raise an exception
234
+ with patch('builtins.print') as mock_print:
235
+ cli.run(["unknown"])
236
+
237
+ # Should print an error message
238
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
239
+ assert "Error" in printed_text or "error" in printed_text.lower()
240
+
241
+ def test_handles_invalid_command_gracefully(self):
242
+ """Test that SimpleCLI doesn't crash on invalid commands."""
243
+ cli = SimpleCLI("test", "Test CLI")
244
+
245
+ @cli.command("valid", "Valid command")
246
+ def valid():
247
+ pass
248
+
249
+ # Should not raise an exception
250
+ try:
251
+ cli.run(["invalid"])
252
+ except Exception as e:
253
+ pytest.fail(f"SimpleCLI raised an exception: {e}")
254
+
255
+ def test_shows_help_on_invalid_command(self):
256
+ """Test that SimpleCLI shows help when command is invalid."""
257
+ cli = SimpleCLI("test", "Test CLI")
258
+
259
+ @cli.command("valid", "Valid command")
260
+ def valid():
261
+ pass
262
+
263
+ with patch('builtins.print') as mock_print:
264
+ cli.run(["invalid"])
265
+
266
+ # Should print help information
267
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
268
+ assert "Available commands" in printed_text or "valid" in printed_text.lower()
269
+
270
+ def test_handles_wrong_number_of_arguments(self):
271
+ """Test that SimpleCLI handles wrong number of arguments gracefully."""
272
+ cli = SimpleCLI("test", "Test CLI")
273
+
274
+ @cli.command("add", "Add two numbers")
275
+ def add(a, b):
276
+ pass
277
+
278
+ # Should not raise an exception
279
+ with patch('builtins.print') as mock_print:
280
+ cli.run(["add", "5"]) # Missing second argument
281
+
282
+ # Should print an error message
283
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
284
+ assert "Error" in printed_text or "error" in printed_text.lower()
285
+
286
+ def test_handles_too_many_arguments(self):
287
+ """Test that SimpleCLI handles too many arguments gracefully."""
288
+ cli = SimpleCLI("test", "Test CLI")
289
+
290
+ @cli.command("single", "Single argument command")
291
+ def single(arg):
292
+ pass
293
+
294
+ # Should not raise an exception
295
+ with patch('builtins.print') as mock_print:
296
+ cli.run(["single", "arg1", "arg2", "arg3"])
297
+
298
+ # Should print an error message
299
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
300
+ assert "Error" in printed_text or "error" in printed_text.lower()
301
+
302
+ def test_handles_empty_command_list(self):
303
+ """Test that SimpleCLI handles empty command list gracefully."""
304
+ cli = SimpleCLI("test", "Test CLI")
305
+
306
+ @cli.command("cmd", "Command")
307
+ def cmd():
308
+ pass
309
+
310
+ # Should not raise an exception
311
+ with patch('builtins.print') as mock_print:
312
+ cli.run([])
313
+
314
+ # Should show help
315
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
316
+ assert "Available commands" in printed_text or "test" in printed_text.lower()
317
+
318
+ def test_handles_help_flag(self):
319
+ """Test that SimpleCLI handles --help flag gracefully."""
320
+ cli = SimpleCLI("test", "Test CLI")
321
+
322
+ @cli.command("cmd", "Command")
323
+ def cmd():
324
+ pass
325
+
326
+ # Should not raise an exception
327
+ with patch('builtins.print') as mock_print:
328
+ cli.run(["--help"])
329
+
330
+ # Should show help
331
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
332
+ assert "Available commands" in printed_text
333
+
334
+ def test_handles_short_help_flag(self):
335
+ """Test that SimpleCLI handles -h flag gracefully."""
336
+ cli = SimpleCLI("test", "Test CLI")
337
+
338
+ @cli.command("cmd", "Command")
339
+ def cmd():
340
+ pass
341
+
342
+ # Should not raise an exception
343
+ with patch('builtins.print') as mock_print:
344
+ cli.run(["-h"])
345
+
346
+ # Should show help
347
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
348
+ assert "Available commands" in printed_text
349
+
350
+ def test_handles_command_that_raises_exception(self):
351
+ """Test that SimpleCLI handles exceptions in command handlers."""
352
+ cli = SimpleCLI("test", "Test CLI")
353
+
354
+ @cli.command("fail", "Failing command")
355
+ def fail():
356
+ raise ValueError("Command failed")
357
+
358
+ # Should not raise an exception to the caller
359
+ try:
360
+ cli.run(["fail"])
361
+ except ValueError:
362
+ pytest.fail("SimpleCLI should handle exceptions in handlers")
363
+
364
+ def test_handles_no_commands_registered(self):
365
+ """Test that SimpleCLI handles case with no commands registered."""
366
+ cli = SimpleCLI("test", "Test CLI")
367
+
368
+ # Should not raise an exception
369
+ with patch('builtins.print') as mock_print:
370
+ cli.run(["anything"])
371
+
372
+ # Should show error and help
373
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
374
+ assert "Error" in printed_text or "error" in printed_text.lower()
375
+
376
+ def test_handles_case_sensitive_commands(self):
377
+ """Test that SimpleCLI treats commands as case-sensitive."""
378
+ cli = SimpleCLI("test", "Test CLI")
379
+ mock_handler = MagicMock()
380
+
381
+ @cli.command("Test", "Test command")
382
+ def test_cmd():
383
+ mock_handler()
384
+
385
+ # Try with different case
386
+ with patch('builtins.print') as mock_print:
387
+ cli.run(["test"])
388
+
389
+ # Should not find the command (case-sensitive)
390
+ mock_handler.assert_not_called()
391
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
392
+ assert "Error" in printed_text or "error" in printed_text.lower()
393
+
394
+ def test_handles_command_with_special_characters(self):
395
+ """Test that SimpleCLI handles commands with special characters."""
396
+ cli = SimpleCLI("test", "Test CLI")
397
+ mock_handler = MagicMock()
398
+
399
+ @cli.command("test-cmd", "Test command")
400
+ def test_cmd():
401
+ mock_handler()
402
+
403
+ cli.run(["test-cmd"])
404
+
405
+ mock_handler.assert_called_once()
406
+
407
+ def test_handles_command_with_underscores(self):
408
+ """Test that SimpleCLI handles commands with underscores."""
409
+ cli = SimpleCLI("test", "Test CLI")
410
+ mock_handler = MagicMock()
411
+
412
+ @cli.command("test_cmd", "Test command")
413
+ def test_cmd():
414
+ mock_handler()
415
+
416
+ cli.run(["test_cmd"])
417
+
418
+ mock_handler.assert_called_once()
419
+
420
+
421
+ class TestSimpleCLIHelpDisplay:
422
+ """Test help display functionality."""
423
+
424
+ def test_shows_application_name(self):
425
+ """Test that help displays the application name."""
426
+ cli = SimpleCLI("MyApp", "My application")
427
+
428
+ with patch('builtins.print') as mock_print:
429
+ cli.run(["--help"])
430
+
431
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
432
+ assert "MyApp" in printed_text
433
+
434
+ def test_shows_application_description(self):
435
+ """Test that help displays the application description."""
436
+ cli = SimpleCLI("MyApp", "This is my application")
437
+
438
+ with patch('builtins.print') as mock_print:
439
+ cli.run(["--help"])
440
+
441
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
442
+ assert "This is my application" in printed_text
443
+
444
+ def test_shows_all_commands(self):
445
+ """Test that help displays all registered commands."""
446
+ cli = SimpleCLI("test", "Test CLI")
447
+
448
+ @cli.command("cmd1", "First command")
449
+ def cmd1():
450
+ pass
451
+
452
+ @cli.command("cmd2", "Second command")
453
+ def cmd2():
454
+ pass
455
+
456
+ @cli.command("cmd3", "Third command")
457
+ def cmd3():
458
+ pass
459
+
460
+ with patch('builtins.print') as mock_print:
461
+ cli.run(["--help"])
462
+
463
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
464
+ assert "cmd1" in printed_text
465
+ assert "cmd2" in printed_text
466
+ assert "cmd3" in printed_text
467
+
468
+ def test_shows_command_descriptions(self):
469
+ """Test that help displays command descriptions."""
470
+ cli = SimpleCLI("test", "Test CLI")
471
+
472
+ @cli.command("greet", "Greet someone")
473
+ def greet():
474
+ pass
475
+
476
+ @cli.command("goodbye", "Say goodbye")
477
+ def goodbye():
478
+ pass
479
+
480
+ with patch('builtins.print') as mock_print:
481
+ cli.run(["--help"])
482
+
483
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
484
+ assert "Greet someone" in printed_text
485
+ assert "Say goodbye" in printed_text
486
+
487
+ def test_shows_available_commands_header(self):
488
+ """Test that help shows 'Available commands' header."""
489
+ cli = SimpleCLI("test", "Test CLI")
490
+
491
+ @cli.command("cmd", "Command")
492
+ def cmd():
493
+ pass
494
+
495
+ with patch('builtins.print') as mock_print:
496
+ cli.run(["--help"])
497
+
498
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
499
+ assert "Available commands" in printed_text
500
+
501
+
502
+ class TestSimpleCLIInitialization:
503
+ """Test SimpleCLI initialization."""
504
+
505
+ def test_stores_name(self):
506
+ """Test that SimpleCLI stores the application name."""
507
+ cli = SimpleCLI("MyApp", "Description")
508
+ assert cli.name == "MyApp"
509
+
510
+ def test_stores_description(self):
511
+ """Test that SimpleCLI stores the application description."""
512
+ cli = SimpleCLI("MyApp", "My description")
513
+ assert cli.description == "My description"
514
+
515
+ def test_initializes_empty_commands(self):
516
+ """Test that SimpleCLI initializes with empty commands."""
517
+ cli = SimpleCLI("MyApp", "Description")
518
+ assert cli.commands == {}
519
+
520
+ def test_has_docstring(self):
521
+ """Test that SimpleCLI has a docstring."""
522
+ assert SimpleCLI.__doc__ is not None
523
+ assert len(SimpleCLI.__doc__) > 0
524
+
525
+ def test_command_method_has_docstring(self):
526
+ """Test that command method has a docstring."""
527
+ cli = SimpleCLI("test", "test")
528
+ assert cli.command.__doc__ is not None
529
+ assert len(cli.command.__doc__) > 0
530
+
531
+ def test_run_method_has_docstring(self):
532
+ """Test that run method has a docstring."""
533
+ cli = SimpleCLI("test", "test")
534
+ assert cli.run.__doc__ is not None
535
+ assert len(cli.run.__doc__) > 0
536
+
537
+
538
+ class TestSimpleCLIIntegration:
539
+ """Integration tests for SimpleCLI."""
540
+
541
+ def test_full_workflow(self):
542
+ """Test a complete workflow with SimpleCLI."""
543
+ cli = SimpleCLI("calculator", "Simple calculator")
544
+ results = []
545
+
546
+ @cli.command("add", "Add two numbers")
547
+ def add(a, b):
548
+ results.append(int(a) + int(b))
549
+
550
+ @cli.command("multiply", "Multiply two numbers")
551
+ def multiply(a, b):
552
+ results.append(int(a) * int(b))
553
+
554
+ cli.run(["add", "5", "3"])
555
+ cli.run(["multiply", "4", "2"])
556
+
557
+ assert results == [8, 8]
558
+
559
+ def test_multiple_cli_instances(self):
560
+ """Test that multiple SimpleCLI instances work independently."""
561
+ cli1 = SimpleCLI("app1", "App 1")
562
+ cli2 = SimpleCLI("app2", "App 2")
563
+
564
+ results = []
565
+
566
+ @cli1.command("cmd", "Command")
567
+ def cmd1():
568
+ results.append("cli1")
569
+
570
+ @cli2.command("cmd", "Command")
571
+ def cmd2():
572
+ results.append("cli2")
573
+
574
+ cli1.run(["cmd"])
575
+ cli2.run(["cmd"])
576
+
577
+ assert results == ["cli1", "cli2"]
578
+
579
+ def test_command_with_side_effects(self):
580
+ """Test that commands with side effects work correctly."""
581
+ cli = SimpleCLI("test", "Test CLI")
582
+ state = {"value": 0}
583
+
584
+ @cli.command("increment", "Increment value")
585
+ def increment():
586
+ state["value"] += 1
587
+
588
+ @cli.command("double", "Double value")
589
+ def double():
590
+ state["value"] *= 2
591
+
592
+ cli.run(["increment"])
593
+ cli.run(["double"])
594
+ cli.run(["increment"])
595
+
596
+ assert state["value"] == 3 # (0 + 1) * 2 + 1
597
+
598
+ def test_cli_with_no_arguments_shows_help(self):
599
+ """Test that running CLI with no arguments shows help."""
600
+ cli = SimpleCLI("test", "Test CLI")
601
+
602
+ @cli.command("cmd", "Command")
603
+ def cmd():
604
+ pass
605
+
606
+ with patch('builtins.print') as mock_print:
607
+ cli.run([])
608
+
609
+ printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
610
+ assert "Available commands" in printed_text
611
+