|
10 | 10 |
|
11 | 11 | from azure.mgmt.web import WebSiteManagementClient |
12 | 12 | from knack.util import CLIError |
| 13 | +from azure.cli.core.azclierror import (InvalidArgumentValueError, |
| 14 | + MutuallyExclusiveArgumentError, |
| 15 | + AzureResponseError) |
13 | 16 | from azure.cli.command_modules.appservice.custom import (set_deployment_user, |
14 | 17 | update_git_token, add_hostname, |
15 | 18 | update_site_configs, |
|
27 | 30 | list_snapshots, |
28 | 31 | restore_snapshot, |
29 | 32 | create_managed_ssl_cert, |
30 | | - add_github_actions) |
| 33 | + add_github_actions, |
| 34 | + update_app_settings, |
| 35 | + update_application_settings_polling) |
31 | 36 |
|
32 | 37 | # pylint: disable=line-too-long |
33 | 38 | from azure.cli.core.profiles import ResourceType |
@@ -463,6 +468,141 @@ def test_create_managed_ssl_cert(self, generic_site_op_mock, client_factory_mock |
463 | 468 | certificate_envelope=cert_def) |
464 | 469 |
|
465 | 470 |
|
| 471 | + def test_update_app_settings_error_handling_no_parameters(self): |
| 472 | + """Test that MutuallyExclusiveArgumentError is raised when neither settings nor slot_settings are provided.""" |
| 473 | + cmd_mock = _get_test_cmd() |
| 474 | + |
| 475 | + # Test missing both parameters - should fail early without calling any services |
| 476 | + with self.assertRaisesRegex(MutuallyExclusiveArgumentError, |
| 477 | + "Please provide either --settings or --slot-settings parameter"): |
| 478 | + update_app_settings(cmd_mock, 'test-rg', 'test-app') |
| 479 | + |
| 480 | + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') |
| 481 | + @mock.patch('azure.cli.command_modules.appservice.custom.shell_safe_json_parse') |
| 482 | + def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse, mock_site_op): |
| 483 | + """Test that InvalidArgumentValueError is raised for invalid setting formats.""" |
| 484 | + cmd_mock = _get_test_cmd() |
| 485 | + |
| 486 | + # Setup minimal mocks needed to reach the error handling code |
| 487 | + mock_app_settings = mock.MagicMock() |
| 488 | + mock_app_settings.properties = {} |
| 489 | + mock_site_op.return_value = mock_app_settings |
| 490 | + |
| 491 | + # Mock shell_safe_json_parse to raise InvalidArgumentValueError (simulating invalid JSON) |
| 492 | + mock_json_parse.side_effect = InvalidArgumentValueError("Invalid JSON format") |
| 493 | + |
| 494 | + # Test invalid format that can't be parsed as JSON or key=value |
| 495 | + invalid_setting = "invalid_format_no_equals_no_json" |
| 496 | + expected_message = r"Invalid setting format.*Expected 'key=value' format or valid JSON" |
| 497 | + |
| 498 | + with self.assertRaisesRegex(InvalidArgumentValueError, expected_message): |
| 499 | + update_app_settings(cmd_mock, 'test-rg', 'test-app', settings=[invalid_setting]) |
| 500 | + |
| 501 | + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') |
| 502 | + @mock.patch('azure.cli.command_modules.appservice.custom.shell_safe_json_parse') |
| 503 | + def test_update_app_settings_error_handling_invalid_format_no_equals(self, mock_json_parse, mock_site_op): |
| 504 | + """Test ValueError path when shell_safe_json_parse raises InvalidArgumentValueError and string contains no '='.""" |
| 505 | + cmd_mock = _get_test_cmd() |
| 506 | + |
| 507 | + # Setup minimal mocks needed to reach the error handling code |
| 508 | + mock_app_settings = mock.MagicMock() |
| 509 | + mock_app_settings.properties = {} |
| 510 | + mock_site_op.return_value = mock_app_settings |
| 511 | + |
| 512 | + # Mock shell_safe_json_parse to raise InvalidArgumentValueError |
| 513 | + mock_json_parse.side_effect = InvalidArgumentValueError("Invalid JSON format") |
| 514 | + |
| 515 | + # Test invalid format with no equals sign - this should trigger ValueError in split('=', 1) |
| 516 | + invalid_setting_no_equals = "invalidformatthatcontainsnoequalsign" |
| 517 | + expected_message = r"Invalid setting format.*Expected 'key=value' format or valid JSON" |
| 518 | + |
| 519 | + with self.assertRaisesRegex(InvalidArgumentValueError, expected_message): |
| 520 | + update_app_settings(cmd_mock, 'test-rg', 'test-app', settings=[invalid_setting_no_equals]) |
| 521 | + |
| 522 | + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') |
| 523 | + @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') |
| 524 | + @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') |
| 525 | + @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') |
| 526 | + @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') |
| 527 | + def test_update_app_settings_success_key_value_format(self, mock_build, mock_settings_op, mock_centauri, |
| 528 | + mock_client_factory, mock_site_op): |
| 529 | + """Test successful processing of key=value format settings.""" |
| 530 | + cmd_mock = _get_test_cmd() |
| 531 | + |
| 532 | + # Setup mocks |
| 533 | + mock_app_settings = mock.MagicMock() |
| 534 | + mock_app_settings.properties = {} |
| 535 | + mock_site_op.return_value = mock_app_settings |
| 536 | + |
| 537 | + mock_client = mock.MagicMock() |
| 538 | + mock_client_factory.return_value = mock_client |
| 539 | + mock_centauri.return_value = False |
| 540 | + mock_settings_op.return_value = mock_app_settings |
| 541 | + mock_build.return_value = {"KEY1": "value1", "KEY2": "value2"} |
| 542 | + |
| 543 | + # Test valid key=value format |
| 544 | + result = update_app_settings(cmd_mock, 'test-rg', 'test-app', |
| 545 | + settings=['KEY1=value1', 'KEY2=value2']) |
| 546 | + |
| 547 | + # Verify the function completed successfully |
| 548 | + self.assertEqual(result["KEY1"], "value1") |
| 549 | + self.assertEqual(result["KEY2"], "value2") |
| 550 | + mock_build.assert_called_once() |
| 551 | + |
| 552 | + @mock.patch('azure.cli.command_modules.appservice.custom.send_raw_request') |
| 553 | + def test_update_application_settings_polling_error_handling(self, mock_send_request): |
| 554 | + """Test that AzureResponseError is raised in polling function when appropriate.""" |
| 555 | + cmd_mock = _get_test_cmd() |
| 556 | + |
| 557 | + # Mock an exception that doesn't have the expected structure |
| 558 | + class MockException(Exception): |
| 559 | + def __init__(self): |
| 560 | + self.response = mock.MagicMock() |
| 561 | + self.response.status_code = 400 # Not 202 |
| 562 | + self.response.headers = {} |
| 563 | + |
| 564 | + # Mock _generic_settings_operation to raise the exception |
| 565 | + with mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') as mock_settings_op, \ |
| 566 | + self.assertRaisesRegex(AzureResponseError, "Failed to update application settings"): |
| 567 | + mock_settings_op.side_effect = MockException() |
| 568 | + update_application_settings_polling(cmd_mock, 'test-rg', 'test-app', |
| 569 | + mock.MagicMock(), None, mock.MagicMock()) |
| 570 | + |
| 571 | + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') |
| 572 | + @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') |
| 573 | + @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') |
| 574 | + @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') |
| 575 | + @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') |
| 576 | + def test_update_app_settings_success_with_slot_settings(self, mock_build, mock_settings_op, mock_centauri, |
| 577 | + mock_client_factory, mock_site_op): |
| 578 | + """Test successful processing with slot settings.""" |
| 579 | + cmd_mock = _get_test_cmd() |
| 580 | + |
| 581 | + # Setup mocks |
| 582 | + mock_app_settings = mock.MagicMock() |
| 583 | + mock_app_settings.properties = {} |
| 584 | + mock_site_op.return_value = mock_app_settings |
| 585 | + |
| 586 | + mock_client = mock.MagicMock() |
| 587 | + mock_slot_config = mock.MagicMock() |
| 588 | + mock_slot_config.app_setting_names = [] |
| 589 | + mock_client.web_apps.list_slot_configuration_names.return_value = mock_slot_config |
| 590 | + mock_client_factory.return_value = mock_client |
| 591 | + mock_centauri.return_value = False |
| 592 | + mock_settings_op.return_value = mock_app_settings |
| 593 | + mock_build.return_value = {"SLOT_KEY": "slot_value"} |
| 594 | + |
| 595 | + # Test with slot settings |
| 596 | + result = update_app_settings(cmd_mock, 'test-rg', 'test-app', |
| 597 | + settings=['REGULAR_KEY=regular_value'], |
| 598 | + slot_settings=['SLOT_KEY=slot_value']) |
| 599 | + |
| 600 | + # Verify slot configuration was updated |
| 601 | + mock_client.web_apps.list_slot_configuration_names.assert_called_once() |
| 602 | + mock_client.web_apps.update_slot_configuration_names.assert_called_once() |
| 603 | + mock_build.assert_called_once() |
| 604 | + |
| 605 | + |
466 | 606 | class FakedResponse: # pylint: disable=too-few-public-methods |
467 | 607 | def __init__(self, status_code): |
468 | 608 | self.status_code = status_code |
|
0 commit comments