Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/azure-cli/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ Release History

* `az postgres flexible-server create/restore/geo-restore/replica create`: Add new arguments `--federated-client-id` and `--backup-federated-client-id` to support multi-tenant application registration (#33645)
* `az postgresql flexible-server maintenance-event list/show/apply-now/reschedule`: Add commands for maintenance events (#33662)
* Fix #33776: `az postgres flexible-server replica promote`: Populate `sourceServerResourceId` in the PATCH body so that planned switchover promote no longer fails with `MissingRequiredParameter` (#33777)

**Resource**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ def flexible_replica_promote(cmd, client, resource_group_name, name, promote_mod
)
)

# The service requires sourceServerResourceId in the PATCH body when promoting a replica.
# Populate it from the replica server's existing source server resource ID.
if server_object.source_server_resource_id:
params['properties']['sourceServerResourceId'] = server_object.source_server_resource_id

return client.begin_update(resource_group_name, name, params)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import unittest
from unittest.mock import MagicMock, patch


class ReplicaCommandsTest(unittest.TestCase):
"""Unit tests for PostgreSQL flexible-server replica commands."""

def setUp(self):
self.resource_group = 'test-rg'
self.server_name = 'test-replica'
self.source_server_resource_id = (
'/subscriptions/sub-id/resourceGroups/test-rg'
'/providers/Microsoft.DBforPostgreSQL/flexibleServers/source-server'
)

def _build_server_object(self, role='AsyncReplica'):
server = MagicMock()
server.replica.role = role
server.source_server_resource_id = self.source_server_resource_id
return server

def _make_cmd_mock(self):
cmd = MagicMock()
cmd.cli_ctx = MagicMock()
return cmd

@patch('azure.cli.command_modules.postgresql.commands.replica_commands.is_citus_cluster', return_value=False)
@patch('azure.cli.command_modules.postgresql.commands.replica_commands.validate_resource_group')
def test_flexible_replica_promote_switchover_includes_source_server_resource_id(
self, mock_validate_rg, mock_is_citus):
"""Regression test for #33776: planned switchover must populate sourceServerResourceId."""
from azure.cli.command_modules.postgresql.commands.replica_commands import flexible_replica_promote

mock_client = MagicMock()
server_object = self._build_server_object(role='AsyncReplica')
mock_client.get.return_value = server_object

flexible_replica_promote(
cmd=self._make_cmd_mock(),
client=mock_client,
resource_group_name=self.resource_group,
name=self.server_name,
promote_mode='switchover',
promote_option='planned',
)

mock_client.begin_update.assert_called_once()
# begin_update is called as positional: (resource_group, name, params)
call_args = mock_client.begin_update.call_args[0]
params = call_args[2]

# Verify sourceServerResourceId is included in the PATCH body
self.assertEqual(
params['properties']['sourceServerResourceId'],
self.source_server_resource_id,
)
# Verify replica role and promote settings are correct
self.assertEqual(params['properties']['replica']['role'], 'Primary')
self.assertEqual(params['properties']['replica']['promoteMode'], 'switchover')
self.assertEqual(params['properties']['replica']['promoteOption'], 'planned')

@patch('azure.cli.command_modules.postgresql.commands.replica_commands.is_citus_cluster', return_value=False)
@patch('azure.cli.command_modules.postgresql.commands.replica_commands.validate_resource_group')
def test_flexible_replica_promote_standalone_includes_source_server_resource_id(
self, mock_validate_rg, mock_is_citus):
"""Standalone promote also populates sourceServerResourceId in the PATCH body."""
from azure.cli.command_modules.postgresql.commands.replica_commands import flexible_replica_promote

mock_client = MagicMock()
server_object = self._build_server_object(role='AsyncReplica')
mock_client.get.return_value = server_object

flexible_replica_promote(
cmd=self._make_cmd_mock(),
client=mock_client,
resource_group_name=self.resource_group,
name=self.server_name,
promote_mode='standalone',
promote_option='planned',
)

mock_client.begin_update.assert_called_once()
call_args = mock_client.begin_update.call_args[0]
params = call_args[2]

self.assertEqual(
params['properties']['sourceServerResourceId'],
self.source_server_resource_id,
)
self.assertEqual(params['properties']['replica']['role'], 'None')

@patch('azure.cli.command_modules.postgresql.commands.replica_commands.is_citus_cluster', return_value=False)
@patch('azure.cli.command_modules.postgresql.commands.replica_commands.validate_resource_group')
def test_flexible_replica_promote_no_source_server_resource_id_does_not_fail(
self, mock_validate_rg, mock_is_citus):
"""If source_server_resource_id is absent on the server object, no KeyError is raised."""
from azure.cli.command_modules.postgresql.commands.replica_commands import flexible_replica_promote

mock_client = MagicMock()
server_object = self._build_server_object(role='AsyncReplica')
server_object.source_server_resource_id = None
mock_client.get.return_value = server_object

flexible_replica_promote(
cmd=self._make_cmd_mock(),
client=mock_client,
resource_group_name=self.resource_group,
name=self.server_name,
promote_mode='switchover',
promote_option='forced',
)

mock_client.begin_update.assert_called_once()
call_args = mock_client.begin_update.call_args[0]
params = call_args[2]

# sourceServerResourceId should NOT be injected when the server has none
self.assertNotIn('sourceServerResourceId', params['properties'])


if __name__ == '__main__':
unittest.main()
Loading