test_utils.py
1 from pathlib import Path 2 from unittest.mock import MagicMock 3 import click 4 import pytest 5 6 7 @pytest.fixture 8 def mock_click_confirm(mocker): 9 """Mock click.confirm for yes/no questions""" 10 return mocker.patch("cli.utils.click.confirm") 11 12 13 @pytest.fixture 14 def mock_click_prompt(mocker): 15 """Mock click.prompt for general questions""" 16 return mocker.patch("cli.utils.click.prompt") 17 18 19 @pytest.fixture 20 def mock_click_echo(mocker): 21 """Mock click.echo for output""" 22 return mocker.patch("cli.utils.click.echo") 23 24 25 class TestAskYesNoQuestion: 26 """Tests for ask_yes_no_question function""" 27 28 def test_ask_yes_no_question_default_false(self, mock_click_confirm): 29 """Test yes/no question with default False""" 30 from cli.utils import ask_yes_no_question 31 32 mock_click_confirm.return_value = False 33 result = ask_yes_no_question("Continue?", default=False) 34 35 assert result is False 36 mock_click_confirm.assert_called_once_with("Continue?", default=False) 37 38 def test_ask_yes_no_question_default_true(self, mock_click_confirm): 39 """Test yes/no question with default True""" 40 from cli.utils import ask_yes_no_question 41 42 mock_click_confirm.return_value = True 43 result = ask_yes_no_question("Proceed?", default=True) 44 45 assert result is True 46 mock_click_confirm.assert_called_once_with("Proceed?", default=True) 47 48 def test_ask_yes_no_question_user_response(self, mock_click_confirm): 49 """Test yes/no question with user response""" 50 from cli.utils import ask_yes_no_question 51 52 mock_click_confirm.return_value = True 53 result = ask_yes_no_question("Are you sure?") 54 55 assert result is True 56 mock_click_confirm.assert_called_once_with("Are you sure?", default=False) 57 58 59 class TestAskQuestion: 60 """Tests for ask_question function""" 61 62 def test_ask_question_basic(self, mock_click_prompt): 63 """Test basic question asking""" 64 from cli.utils import ask_question 65 66 mock_click_prompt.return_value = "test_answer" 67 result = ask_question("What is your name?") 68 69 assert result == "test_answer" 70 mock_click_prompt.assert_called_once_with( 71 "What is your name?", 72 default=None, 73 hide_input=False, 74 type=None, 75 show_choices=None, 76 ) 77 78 def test_ask_question_with_default(self, mock_click_prompt): 79 """Test question with default value""" 80 from cli.utils import ask_question 81 82 mock_click_prompt.return_value = "default_value" 83 result = ask_question("Enter value:", default="default_value") 84 85 assert result == "default_value" 86 mock_click_prompt.assert_called_once_with( 87 "Enter value:", 88 default="default_value", 89 hide_input=False, 90 type=None, 91 show_choices=None, 92 ) 93 94 def test_ask_question_hide_input(self, mock_click_prompt): 95 """Test question with hidden input (password)""" 96 from cli.utils import ask_question 97 98 mock_click_prompt.return_value = "secret" 99 result = ask_question("Enter password:", hide_input=True) 100 101 assert result == "secret" 102 mock_click_prompt.assert_called_once_with( 103 "Enter password:", 104 default=None, 105 hide_input=True, 106 type=None, 107 show_choices=None, 108 ) 109 110 def test_ask_question_with_type(self, mock_click_prompt): 111 """Test question with type parameter""" 112 from cli.utils import ask_question 113 114 mock_click_prompt.return_value = 42 115 result = ask_question("Enter number:", type=int) 116 117 assert result == 42 118 mock_click_prompt.assert_called_once_with( 119 "Enter number:", 120 default=None, 121 hide_input=False, 122 type=int, 123 show_choices=None, 124 ) 125 126 def test_ask_question_with_show_choices(self, mock_click_prompt): 127 """Test question with show_choices parameter""" 128 from cli.utils import ask_question 129 130 mock_click_prompt.return_value = "option1" 131 result = ask_question("Select option:", show_choices=True) 132 133 assert result == "option1" 134 mock_click_prompt.assert_called_once_with( 135 "Select option:", 136 default=None, 137 hide_input=False, 138 type=None, 139 show_choices=True, 140 ) 141 142 143 class TestAskIfNotProvided: 144 """Tests for ask_if_not_provided function""" 145 146 def test_key_exists_returns_value(self, mock_click_prompt): 147 """Test when key exists in options, returns existing value""" 148 from cli.utils import ask_if_not_provided 149 150 options = {"name": "existing_value"} 151 result = ask_if_not_provided(options, "name", "Enter name:") 152 153 assert result == "existing_value" 154 mock_click_prompt.assert_not_called() 155 156 def test_key_missing_interactive_regular_question(self, mock_click_prompt): 157 """Test when key is missing, asks question in interactive mode""" 158 from cli.utils import ask_if_not_provided 159 160 mock_click_prompt.return_value = "new_value" 161 options = {} 162 result = ask_if_not_provided(options, "name", "Enter name:") 163 164 assert result == "new_value" 165 assert options["name"] == "new_value" 166 mock_click_prompt.assert_called_once() 167 168 def test_key_missing_non_interactive_uses_default(self, mock_click_prompt): 169 """Test when key is missing in non-interactive mode, uses default""" 170 from cli.utils import ask_if_not_provided 171 172 options = {} 173 result = ask_if_not_provided( 174 options, "name", "Enter name:", default="default_name", none_interactive=True 175 ) 176 177 assert result == "default_name" 178 assert options["name"] == "default_name" 179 mock_click_prompt.assert_not_called() 180 181 def test_key_missing_bool_question(self, mock_click_confirm): 182 """Test when key is missing and is_bool=True""" 183 from cli.utils import ask_if_not_provided 184 185 mock_click_confirm.return_value = True 186 options = {} 187 result = ask_if_not_provided( 188 options, "enabled", "Enable feature?", default=True, is_bool=True 189 ) 190 191 assert result is True 192 assert options["enabled"] is True 193 mock_click_confirm.assert_called_once_with("Enable feature?", default=True) 194 195 def test_key_missing_bool_question_default_false(self, mock_click_confirm): 196 """Test bool question with non-bool default falls back to False""" 197 from cli.utils import ask_if_not_provided 198 199 mock_click_confirm.return_value = False 200 options = {} 201 result = ask_if_not_provided( 202 options, "enabled", "Enable feature?", default="not_bool", is_bool=True 203 ) 204 205 assert result is False 206 assert options["enabled"] is False 207 mock_click_confirm.assert_called_once_with("Enable feature?", default=False) 208 209 def test_key_missing_with_choices(self, mock_click_prompt): 210 """Test when key is missing with choices parameter""" 211 from cli.utils import ask_if_not_provided 212 213 mock_click_prompt.return_value = "option2" 214 options = {} 215 choices = ["option1", "option2", "option3"] 216 result = ask_if_not_provided( 217 options, "choice", "Select option:", choices=choices 218 ) 219 220 assert result == "option2" 221 assert options["choice"] == "option2" 222 mock_click_prompt.assert_called_once() 223 # Verify that click.Choice was used 224 call_args = mock_click_prompt.call_args 225 assert call_args[1]["show_choices"] is True 226 227 def test_key_missing_with_hide_input(self, mock_click_prompt): 228 """Test when key is missing with hide_input parameter""" 229 from cli.utils import ask_if_not_provided 230 231 mock_click_prompt.return_value = "secret" 232 options = {} 233 result = ask_if_not_provided( 234 options, "password", "Enter password:", hide_input=True 235 ) 236 237 assert result == "secret" 238 assert options["password"] == "secret" 239 mock_click_prompt.assert_called_once() 240 call_args = mock_click_prompt.call_args 241 assert call_args[1]["hide_input"] is True 242 243 def test_key_none_value_asks_question(self, mock_click_prompt): 244 """Test when key exists but value is None, asks question""" 245 from cli.utils import ask_if_not_provided 246 247 mock_click_prompt.return_value = "new_value" 248 options = {"name": None} 249 result = ask_if_not_provided(options, "name", "Enter name:") 250 251 assert result == "new_value" 252 assert options["name"] == "new_value" 253 mock_click_prompt.assert_called_once() 254 255 256 class TestGetCliRootDir: 257 """Tests for get_cli_root_dir function""" 258 259 def test_get_cli_root_dir_returns_path(self): 260 """Test that get_cli_root_dir returns a valid Path""" 261 from cli.utils import get_cli_root_dir 262 263 result = get_cli_root_dir() 264 265 assert isinstance(result, Path) 266 assert result.exists() 267 268 def test_get_cli_root_dir_is_parent_of_cli(self): 269 """Test that returned path is parent of cli directory""" 270 from cli.utils import get_cli_root_dir 271 272 result = get_cli_root_dir() 273 cli_dir = result / "cli" 274 275 assert cli_dir.exists() 276 assert cli_dir.is_dir() 277 278 279 class TestLoadTemplate: 280 """Tests for load_template function""" 281 282 def test_load_template_success(self, tmp_path, mocker): 283 """Test successful template loading""" 284 from cli.utils import load_template 285 286 # Mock get_cli_root_dir to return tmp_path 287 mocker.patch("cli.utils.get_cli_root_dir", return_value=tmp_path) 288 289 # Create a template file 290 templates_dir = tmp_path / "templates" 291 templates_dir.mkdir() 292 template_file = templates_dir / "test_template.txt" 293 template_file.write_text("Hello, World!") 294 295 result = load_template("test_template.txt") 296 297 assert result == "Hello, World!" 298 299 def test_load_template_file_not_found(self, tmp_path, mocker): 300 """Test FileNotFoundError when template doesn't exist""" 301 from cli.utils import load_template 302 303 mocker.patch("cli.utils.get_cli_root_dir", return_value=tmp_path) 304 305 with pytest.raises(FileNotFoundError) as exc_info: 306 load_template("nonexistent.txt") 307 308 assert "does not exist" in str(exc_info.value) 309 310 def test_load_template_with_parser(self, tmp_path, mocker): 311 """Test template loading with parser function""" 312 from cli.utils import load_template 313 314 mocker.patch("cli.utils.get_cli_root_dir", return_value=tmp_path) 315 316 templates_dir = tmp_path / "templates" 317 templates_dir.mkdir() 318 template_file = templates_dir / "test_template.txt" 319 template_file.write_text("hello world") 320 321 def uppercase_parser(content, suffix): 322 return content.upper() + suffix 323 324 result = load_template("test_template.txt", uppercase_parser, "!!!") 325 326 assert result == "HELLO WORLD!!!" 327 328 def test_load_template_without_parser(self, tmp_path, mocker): 329 """Test template loading without parser returns raw content""" 330 from cli.utils import load_template 331 332 mocker.patch("cli.utils.get_cli_root_dir", return_value=tmp_path) 333 334 templates_dir = tmp_path / "templates" 335 templates_dir.mkdir() 336 template_file = templates_dir / "test_template.txt" 337 template_file.write_text("Raw content\nMultiple lines") 338 339 result = load_template("test_template.txt") 340 341 assert result == "Raw content\nMultiple lines" 342 343 344 class TestGetFormattedNames: 345 """Tests for get_formatted_names function""" 346 347 def test_get_formatted_names_camel_case(self): 348 """Test formatting from camelCase""" 349 from cli.utils import get_formatted_names 350 351 result = get_formatted_names("myTestName") 352 353 assert result["KEBAB_CASE_NAME"] == "my-test-name" 354 assert result["PASCAL_CASE_NAME"] == "MyTestName" 355 assert result["SNAKE_CASE_NAME"] == "my_test_name" 356 assert result["SNAKE_UPPER_CASE_NAME"] == "MY_TEST_NAME" 357 assert result["SPACED_NAME"] == "my test name" 358 assert result["SPACED_CAPITALIZED_NAME"] == "My Test Name" 359 360 def test_get_formatted_names_snake_case(self): 361 """Test formatting from snake_case""" 362 from cli.utils import get_formatted_names 363 364 result = get_formatted_names("my_test_name") 365 366 assert result["KEBAB_CASE_NAME"] == "my-test-name" 367 assert result["PASCAL_CASE_NAME"] == "MyTestName" 368 assert result["SNAKE_CASE_NAME"] == "my_test_name" 369 assert result["SNAKE_UPPER_CASE_NAME"] == "MY_TEST_NAME" 370 assert result["SPACED_NAME"] == "my test name" 371 assert result["SPACED_CAPITALIZED_NAME"] == "My Test Name" 372 373 def test_get_formatted_names_kebab_case(self): 374 """Test formatting from kebab-case""" 375 from cli.utils import get_formatted_names 376 377 result = get_formatted_names("my-test-name") 378 379 assert result["KEBAB_CASE_NAME"] == "my-test-name" 380 assert result["PASCAL_CASE_NAME"] == "MyTestName" 381 assert result["SNAKE_CASE_NAME"] == "my_test_name" 382 assert result["SNAKE_UPPER_CASE_NAME"] == "MY_TEST_NAME" 383 assert result["SPACED_NAME"] == "my test name" 384 assert result["SPACED_CAPITALIZED_NAME"] == "My Test Name" 385 386 def test_get_formatted_names_spaces(self): 387 """Test formatting from spaced name""" 388 from cli.utils import get_formatted_names 389 390 result = get_formatted_names("my test name") 391 392 assert result["KEBAB_CASE_NAME"] == "my-test-name" 393 assert result["PASCAL_CASE_NAME"] == "MyTestName" 394 assert result["SNAKE_CASE_NAME"] == "my_test_name" 395 assert result["SNAKE_UPPER_CASE_NAME"] == "MY_TEST_NAME" 396 assert result["SPACED_NAME"] == "my test name" 397 assert result["SPACED_CAPITALIZED_NAME"] == "My Test Name" 398 399 def test_get_formatted_names_acronym(self): 400 """Test formatting with acronyms like API""" 401 from cli.utils import get_formatted_names 402 403 result = get_formatted_names("APIKey") 404 405 assert result["KEBAB_CASE_NAME"] == "api-key" 406 assert result["PASCAL_CASE_NAME"] == "ApiKey" 407 assert result["SNAKE_CASE_NAME"] == "api_key" 408 assert result["SNAKE_UPPER_CASE_NAME"] == "API_KEY" 409 assert result["SPACED_NAME"] == "api key" 410 assert result["SPACED_CAPITALIZED_NAME"] == "API Key" 411 412 def test_get_formatted_names_all_caps_acronym(self): 413 """Test formatting with all caps acronym""" 414 from cli.utils import get_formatted_names 415 416 result = get_formatted_names("API") 417 418 assert result["KEBAB_CASE_NAME"] == "api" 419 assert result["PASCAL_CASE_NAME"] == "Api" 420 assert result["SNAKE_CASE_NAME"] == "api" 421 assert result["SNAKE_UPPER_CASE_NAME"] == "API" 422 assert result["SPACED_NAME"] == "api" 423 assert result["SPACED_CAPITALIZED_NAME"] == "API" 424 425 def test_get_formatted_names_mixed_separators(self): 426 """Test formatting with mixed separators""" 427 from cli.utils import get_formatted_names 428 429 result = get_formatted_names("my-test_name") 430 431 assert result["KEBAB_CASE_NAME"] == "my-test-name" 432 assert result["PASCAL_CASE_NAME"] == "MyTestName" 433 assert result["SNAKE_CASE_NAME"] == "my_test_name" 434 435 def test_get_formatted_names_pascal_case(self): 436 """Test formatting from PascalCase""" 437 from cli.utils import get_formatted_names 438 439 result = get_formatted_names("MyTestName") 440 441 assert result["KEBAB_CASE_NAME"] == "my-test-name" 442 assert result["PASCAL_CASE_NAME"] == "MyTestName" 443 assert result["SNAKE_CASE_NAME"] == "my_test_name" 444 assert result["SPACED_CAPITALIZED_NAME"] == "My Test Name" 445 446 447 class TestGetModulePath: 448 """Tests for get_module_path function""" 449 450 def test_get_module_path_valid_module(self, mocker): 451 """Test getting path for a valid module""" 452 from cli.utils import get_module_path 453 454 mock_module = MagicMock() 455 mock_module.__path__ = ["/path/to/module"] 456 mock_import = mocker.patch("cli.utils.importlib.import_module", return_value=mock_module) 457 458 result = get_module_path("test_module") 459 460 assert result == "/path/to/module" 461 mock_import.assert_called_once_with("test_module") 462 463 464 class TestErrorExit: 465 """Tests for error_exit function""" 466 467 def test_error_exit_with_message(self, mock_click_echo): 468 """Test error_exit with a message""" 469 from cli.utils import error_exit 470 471 with pytest.raises(click.Abort): 472 error_exit("Something went wrong") 473 474 mock_click_echo.assert_called_once() 475 call_args = mock_click_echo.call_args 476 assert "Something went wrong" in str(call_args) 477 assert call_args[1]["err"] is True 478 479 def test_error_exit_without_message(self, mock_click_echo): 480 """Test error_exit without a message""" 481 from cli.utils import error_exit 482 483 with pytest.raises(click.Abort): 484 error_exit() 485 486 mock_click_echo.assert_not_called() 487 488 489 class TestGetSamCliHomeDir: 490 """Tests for get_sam_cli_home_dir function""" 491 492 def test_get_sam_cli_home_dir_with_absolute_env_var(self, tmp_path, monkeypatch): 493 """Test with SAM_CLI_HOME set to absolute path""" 494 from cli.utils import get_sam_cli_home_dir 495 496 sam_home = tmp_path / "sam_home" 497 monkeypatch.setenv("SAM_CLI_HOME", str(sam_home)) 498 499 result = get_sam_cli_home_dir() 500 501 assert result == sam_home.resolve() 502 assert sam_home.exists() 503 504 def test_get_sam_cli_home_dir_with_relative_env_var(self, tmp_path, monkeypatch): 505 """Test with SAM_CLI_HOME set to relative path""" 506 from cli.utils import get_sam_cli_home_dir 507 508 monkeypatch.chdir(tmp_path) 509 monkeypatch.setenv("SAM_CLI_HOME", "relative/sam") 510 511 result = get_sam_cli_home_dir() 512 513 expected = (tmp_path / "relative" / "sam").resolve() 514 assert result == expected 515 assert result.exists() 516 517 def test_get_sam_cli_home_dir_without_env_var(self, tmp_path, monkeypatch): 518 """Test without SAM_CLI_HOME env var (uses default .sam)""" 519 from cli.utils import get_sam_cli_home_dir 520 521 monkeypatch.chdir(tmp_path) 522 monkeypatch.delenv("SAM_CLI_HOME", raising=False) 523 524 result = get_sam_cli_home_dir() 525 526 expected = (tmp_path / ".sam").resolve() 527 assert result == expected 528 assert result.exists() 529 530 def test_get_sam_cli_home_dir_creates_directory(self, tmp_path, monkeypatch): 531 """Test that directory is created if it doesn't exist""" 532 from cli.utils import get_sam_cli_home_dir 533 534 sam_home = tmp_path / "new" / "sam" / "home" 535 monkeypatch.setenv("SAM_CLI_HOME", str(sam_home)) 536 537 result = get_sam_cli_home_dir() 538 539 assert result == sam_home.resolve() 540 assert sam_home.exists() 541 assert sam_home.is_dir() 542 543 def test_get_sam_cli_home_dir_oserror_handling(self, tmp_path, monkeypatch, mocker): 544 """Test OSError handling when directory creation fails""" 545 from cli.utils import get_sam_cli_home_dir 546 547 sam_home = tmp_path / "sam_home" 548 monkeypatch.setenv("SAM_CLI_HOME", str(sam_home)) 549 550 mock_path = mocker.patch("cli.utils.Path") 551 mock_instance = MagicMock() 552 mock_instance.mkdir.side_effect = OSError("Permission denied") 553 mock_path.return_value = mock_instance 554 mock_path.cwd.return_value = tmp_path 555 556 with pytest.raises(click.Abort): 557 get_sam_cli_home_dir() 558 559 560 class TestIndentMultilineString: 561 """Tests for indent_multiline_string function""" 562 563 def test_indent_multiline_string_default_indent(self): 564 """Test with default 4-space indent""" 565 from cli.utils import indent_multiline_string 566 567 text = "line1\nline2\nline3" 568 result = indent_multiline_string(text) 569 570 assert result == "line1\n line2\n line3" 571 572 def test_indent_multiline_string_custom_indent(self): 573 """Test with custom indent level""" 574 from cli.utils import indent_multiline_string 575 576 text = "line1\nline2" 577 result = indent_multiline_string(text, indent=2) 578 579 assert result == "line1\n line2" 580 581 def test_indent_multiline_string_with_initial_indent(self): 582 """Test with initial_indent=True""" 583 from cli.utils import indent_multiline_string 584 585 text = "line1\nline2" 586 result = indent_multiline_string(text, indent=4, initial_indent=True) 587 588 assert result == "\n line1\n line2" 589 590 def test_indent_multiline_string_single_line(self): 591 """Test with single line""" 592 from cli.utils import indent_multiline_string 593 594 text = "single line" 595 result = indent_multiline_string(text) 596 597 assert result == "single line" 598 599 600 class TestWaitForServer: 601 """Tests for wait_for_server function""" 602 603 def test_wait_for_server_success(self, mocker): 604 """Test successful server connection""" 605 from cli.utils import wait_for_server 606 607 mock_response = MagicMock() 608 mock_response.status_code = 200 609 mock_get = mocker.patch("cli.utils.requests.get", return_value=mock_response) 610 611 result = wait_for_server("http://localhost:8000", timeout=5) 612 613 assert result is True 614 mock_get.assert_called_with("http://localhost:8000") 615 616 def test_wait_for_server_timeout(self, mocker): 617 """Test server connection timeout""" 618 from cli.utils import wait_for_server 619 620 mock_get = mocker.patch("cli.utils.requests.get", side_effect=Exception("Connection refused")) 621 mocker.patch("cli.utils.sleep") 622 623 result = wait_for_server("http://localhost:8000", timeout=1) 624 625 assert result is False 626 assert mock_get.call_count >= 2 627 628 629 class TestCreateAndValidateDatabase: 630 """Tests for create_and_validate_database function""" 631 632 def test_create_and_validate_database_sqlite(self, tmp_path, mocker, mock_click_echo): 633 """Test SQLite database creation and validation""" 634 from cli.utils import create_and_validate_database 635 636 db_file = tmp_path / "test.db" 637 database_url = f"sqlite:///{db_file}" 638 639 mock_engine = MagicMock() 640 mock_connection = MagicMock() 641 mock_engine.connect.return_value.__enter__.return_value = mock_connection 642 mock_create_engine = mocker.patch("cli.utils.create_engine", return_value=mock_engine) 643 mocker.patch("cli.utils.event") 644 645 result = create_and_validate_database(database_url, "test_db") 646 647 assert result is True 648 mock_create_engine.assert_called_once_with(database_url) 649 mock_engine.dispose.assert_called_once() 650 assert db_file.parent.exists() 651 652 def test_create_and_validate_database_postgresql_with_psycopg2(self, mocker, mock_click_echo): 653 """Test PostgreSQL database with psycopg2 available""" 654 from cli.utils import create_and_validate_database 655 656 database_url = "postgresql://user:pass@localhost/db" 657 658 mock_engine = MagicMock() 659 mock_connection = MagicMock() 660 mock_engine.connect.return_value.__enter__.return_value = mock_connection 661 mock_create_engine = mocker.patch("cli.utils.create_engine", return_value=mock_engine) 662 mocker.patch.dict("sys.modules", {"psycopg2": MagicMock()}) 663 664 result = create_and_validate_database(database_url, "postgres_db") 665 666 assert result is True 667 mock_create_engine.assert_called_once_with(database_url) 668 mock_engine.dispose.assert_called_once() 669 670 def test_create_and_validate_database_postgresql_without_psycopg2(self, mocker, mock_click_echo): 671 """Test PostgreSQL database without psycopg2 raises ValueError""" 672 from cli.utils import create_and_validate_database 673 674 database_url = "postgresql://user:pass@localhost/db" 675 676 import builtins 677 real_import = builtins.__import__ 678 679 def mock_import(name, *args, **kwargs): 680 if name == "psycopg2": 681 raise ImportError("No module named psycopg2") 682 return real_import(name, *args, **kwargs) 683 684 mocker.patch("builtins.__import__", side_effect=mock_import) 685 686 with pytest.raises(ValueError) as exc_info: 687 create_and_validate_database(database_url, "postgres_db") 688 689 assert "psycopg2" in str(exc_info.value) 690 691 def test_create_and_validate_database_generic(self, mocker, mock_click_echo): 692 """Test generic database URL""" 693 from cli.utils import create_and_validate_database 694 695 database_url = "mysql://user:pass@localhost/db" 696 697 mock_engine = MagicMock() 698 mock_connection = MagicMock() 699 mock_engine.connect.return_value.__enter__.return_value = mock_connection 700 mock_create_engine = mocker.patch("cli.utils.create_engine", return_value=mock_engine) 701 702 result = create_and_validate_database(database_url, "mysql_db") 703 704 assert result is True 705 mock_create_engine.assert_called_once_with(database_url) 706 mock_engine.dispose.assert_called_once() 707 708 def test_create_and_validate_database_connection_failure(self, mocker, mock_click_echo): 709 """Test database connection failure""" 710 from cli.utils import create_and_validate_database 711 712 database_url = "sqlite:///test.db" 713 714 mock_engine = MagicMock() 715 mock_engine.connect.side_effect = Exception("Connection failed") 716 mocker.patch("cli.utils.create_engine", return_value=mock_engine) 717 mocker.patch("cli.utils.event") 718 719 with pytest.raises(ValueError) as exc_info: 720 create_and_validate_database(database_url, "test_db") 721 722 assert "Connection failed" in str(exc_info.value)