From 76c8f85fdf3bc6d595083445d513762771571c82 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Tue, 16 Sep 2025 14:30:53 -0700 Subject: [PATCH 01/16] Add live tests for postgres --- .../Azure.Mcp.Tools.Postgres.LiveTests.csproj | 17 + .../PostgresCommandTests.cs | 394 ++++++++++++++++++ .../tests/test-resources-post.ps1 | 94 +++++ .../tests/test-resources.bicep | 105 +++++ 4 files changed, 610 insertions(+) create mode 100644 tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/Azure.Mcp.Tools.Postgres.LiveTests.csproj create mode 100644 tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs create mode 100644 tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 create mode 100644 tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/Azure.Mcp.Tools.Postgres.LiveTests.csproj b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/Azure.Mcp.Tools.Postgres.LiveTests.csproj new file mode 100644 index 000000000..0f06a032a --- /dev/null +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/Azure.Mcp.Tools.Postgres.LiveTests.csproj @@ -0,0 +1,17 @@ + + + true + Exe + + + + + + + + + + + + + diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs new file mode 100644 index 000000000..8a4e7b614 --- /dev/null +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -0,0 +1,394 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Net; +using System.Text.Json; +using Azure.Mcp.Tests; +using Azure.Mcp.Tests.Client; +using Azure.Mcp.Tests.Client.Helpers; +using Xunit; + +namespace Azure.Mcp.Tools.Postgres.LiveTests; + +public class PostgresCommandTests(ITestOutputHelper output) : CommandTestsBase(output) +{ + + [Fact] + public async Task Should_ListDatabases_Successfully() + { + // Use the deployed test PostgreSQL server + var serverName = Settings.ResourceBaseName + "-postgres"; + + var result = await CallToolAsync( + "azmcp_postgres_database_list", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName }, + { "server", serverName } + }); + + // Should successfully retrieve the list of databases + var databases = result.AssertProperty("databases"); + Assert.Equal(JsonValueKind.Array, databases.ValueKind); + + // Should contain at least the default postgres database + var databaseArray = databases.EnumerateArray().ToList(); + Assert.True(databaseArray.Count >= 1, "Should contain at least the default postgres database"); + + // Verify that postgres database exists (default database) + var postgresDb = databaseArray.FirstOrDefault(db => + db.GetProperty("name").GetString() == "postgres"); + Assert.NotEqual(default, postgresDb); + + // Verify database properties + if (postgresDb.ValueKind != JsonValueKind.Undefined) + { + var dbName = postgresDb.GetProperty("name").GetString(); + Assert.Equal("postgres", dbName); + } + } + + [Fact] + public async Task Should_QueryDatabase_Successfully() + { + // Use the deployed test PostgreSQL server + var serverName = Settings.ResourceBaseName + "-postgres"; + var databaseName = "postgres"; // Default database + + var result = await CallToolAsync( + "azmcp_postgres_database_query", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName }, + { "server", serverName }, + { "database", databaseName }, + { "query", "SELECT version()" } + }); + + // Should successfully execute the query + var queryResult = result.AssertProperty("result"); + Assert.Equal(JsonValueKind.Object, queryResult.ValueKind); + + // Verify query result structure + Assert.True(queryResult.TryGetProperty("columns", out _)); + Assert.True(queryResult.TryGetProperty("rows", out _)); + + var columns = queryResult.GetProperty("columns"); + Assert.Equal(JsonValueKind.Array, columns.ValueKind); + Assert.True(columns.GetArrayLength() >= 1, "Should have at least one column"); + + var rows = queryResult.GetProperty("rows"); + Assert.Equal(JsonValueKind.Array, rows.ValueKind); + Assert.True(rows.GetArrayLength() >= 1, "Should have at least one row"); + } + + [Fact] + public async Task Should_ListServers_Successfully() + { + var result = await CallToolAsync( + "azmcp_postgres_server_list", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName } + }); + + // Should successfully retrieve the list of servers + var servers = result.AssertProperty("servers"); + Assert.Equal(JsonValueKind.Array, servers.ValueKind); + + // Should contain at least one server (our test server) + var serverArray = servers.EnumerateArray().ToList(); + Assert.True(serverArray.Count >= 1, "Should contain at least one PostgreSQL server"); + + // Verify server properties + var firstServer = serverArray.First(); + Assert.Equal(JsonValueKind.Object, firstServer.ValueKind); + + // Verify required properties exist + Assert.True(firstServer.TryGetProperty("name", out _)); + Assert.True(firstServer.TryGetProperty("id", out _)); + Assert.True(firstServer.TryGetProperty("type", out _)); + Assert.True(firstServer.TryGetProperty("location", out _)); + + var serverType = firstServer.GetProperty("type").GetString(); + Assert.Contains("Microsoft.DBforPostgreSQL", serverType); + } + + [Fact] + public async Task Should_GetServerConfig_Successfully() + { + // Use the deployed test PostgreSQL server + var serverName = Settings.ResourceBaseName + "-postgres"; + + var result = await CallToolAsync( + "azmcp_postgres_server_config_get", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName }, + { "server", serverName } + }); + + // Should successfully retrieve server configuration + var configs = result.AssertProperty("configurations"); + Assert.Equal(JsonValueKind.Array, configs.ValueKind); + + // Should contain configuration parameters + var configArray = configs.EnumerateArray().ToList(); + Assert.True(configArray.Count > 0, "Should contain configuration parameters"); + + // Verify configuration structure + var firstConfig = configArray.First(); + Assert.Equal(JsonValueKind.Object, firstConfig.ValueKind); + + // Verify required properties exist + Assert.True(firstConfig.TryGetProperty("name", out _)); + Assert.True(firstConfig.TryGetProperty("value", out _)); + } + + [Fact] + public async Task Should_GetServerParam_Successfully() + { + // Use the deployed test PostgreSQL server + var serverName = Settings.ResourceBaseName + "-postgres"; + var parameterName = "log_statement"; // A common PostgreSQL parameter + + var result = await CallToolAsync( + "azmcp_postgres_server_param_get", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName }, + { "server", serverName }, + { "parameter-name", parameterName } + }); + + // Should successfully retrieve the parameter + var parameter = result.AssertProperty("parameter"); + Assert.Equal(JsonValueKind.Object, parameter.ValueKind); + + // Verify parameter properties + var paramName = parameter.GetProperty("name").GetString(); + Assert.Equal(parameterName, paramName); + + Assert.True(parameter.TryGetProperty("value", out _)); + Assert.True(parameter.TryGetProperty("dataType", out _)); + } + + [Fact] + public async Task Should_ListTables_Successfully() + { + // Use the deployed test PostgreSQL server + var serverName = Settings.ResourceBaseName + "-postgres"; + var databaseName = "postgres"; // Default database + + var result = await CallToolAsync( + "azmcp_postgres_table_list", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName }, + { "server", serverName }, + { "database", databaseName } + }); + + // Should successfully retrieve the list of tables + var tables = result.AssertProperty("tables"); + Assert.Equal(JsonValueKind.Array, tables.ValueKind); + + // PostgreSQL default database may or may not have user tables, + // but the command should execute successfully + var tableArray = tables.EnumerateArray().ToList(); + + // If there are tables, verify their structure + if (tableArray.Count > 0) + { + var firstTable = tableArray.First(); + Assert.Equal(JsonValueKind.Object, firstTable.ValueKind); + + // Verify table properties + Assert.True(firstTable.TryGetProperty("tableName", out _)); + Assert.True(firstTable.TryGetProperty("schemaName", out _)); + } + } + + [Fact] + public async Task Should_GetTableSchema_Successfully() + { + // First, create a test table to ensure we have something to query + var serverName = Settings.ResourceBaseName + "-postgres"; + var databaseName = "postgres"; + + // Create a test table first + await CallToolAsync( + "azmcp_postgres_database_query", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName }, + { "server", serverName }, + { "database", databaseName }, + { "query", "CREATE TABLE IF NOT EXISTS test_schema_table (id SERIAL PRIMARY KEY, name VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)" } + }); + + // Now get the schema for the test table + var result = await CallToolAsync( + "azmcp_postgres_table_schema_get", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName }, + { "server", serverName }, + { "database", databaseName }, + { "table", "test_schema_table" } + }); + + // Should successfully retrieve the table schema + var schema = result.AssertProperty("schema"); + Assert.Equal(JsonValueKind.Object, schema.ValueKind); + + // Verify schema structure + Assert.True(schema.TryGetProperty("tableName", out _)); + Assert.True(schema.TryGetProperty("columns", out _)); + + var columns = schema.GetProperty("columns"); + Assert.Equal(JsonValueKind.Array, columns.ValueKind); + Assert.True(columns.GetArrayLength() >= 3, "Should have at least 3 columns (id, name, created_at)"); + + // Verify column structure + var firstColumn = columns.EnumerateArray().First(); + Assert.Equal(JsonValueKind.Object, firstColumn.ValueKind); + Assert.True(firstColumn.TryGetProperty("columnName", out _)); + Assert.True(firstColumn.TryGetProperty("dataType", out _)); + + // Cleanup: Drop the test table + await CallToolAsync( + "azmcp_postgres_database_query", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resource-group", Settings.ResourceGroupName }, + { "server", serverName }, + { "database", databaseName }, + { "query", "DROP TABLE IF EXISTS test_schema_table" } + }); + } + + [Theory] + [InlineData("--invalid-param", new string[0])] + [InlineData("--subscription", new[] { "invalidSub" })] + [InlineData("--subscription", new[] { "sub", "--resource-group", "rg" })] // Missing server + public async Task Should_Return400_WithInvalidDatabaseListInput(string firstArg, string[] remainingArgs) + { + var allArgs = new[] { firstArg }.Concat(remainingArgs); + var argsString = string.Join(" ", allArgs); + + try + { + var result = await CallToolAsync("azmcp_postgres_database_list", + new Dictionary { { "args", argsString } }); + + // If we get here, the command didn't fail as expected + Assert.Fail("Expected command to fail with invalid input, but it succeeded"); + } + catch (Exception ex) + { + // Expected behavior - the command should fail with invalid input + Assert.NotNull(ex.Message); + Assert.NotEmpty(ex.Message); + } + } + + [Theory] + [InlineData("--invalid-param")] + [InlineData("--subscription invalidSub")] + [InlineData("--subscription sub --resource-group rg")] // Missing server and database + [InlineData("--subscription sub --resource-group rg --server server1")] // Missing database and query + public async Task Should_Return400_WithInvalidQueryInput(string args) + { + try + { + var result = await CallToolAsync("azmcp_postgres_database_query", + new Dictionary { { "args", args } }); + + // If we get here, the command didn't fail as expected + Assert.Fail("Expected command to fail with invalid input, but it succeeded"); + } + catch (Exception ex) + { + // Expected behavior - the command should fail with invalid input + Assert.NotNull(ex.Message); + Assert.NotEmpty(ex.Message); + } + } + + [Theory] + [InlineData("--invalid-param")] + [InlineData("--subscription invalidSub")] + [InlineData("--subscription sub --resource-group rg")] // Missing server + public async Task Should_Return400_WithInvalidServerListInput(string args) + { + try + { + var result = await CallToolAsync("azmcp_postgres_server_list", + new Dictionary { { "args", args } }); + + // If we get here, the command didn't fail as expected + Assert.Fail("Expected command to fail with invalid input, but it succeeded"); + } + catch (Exception ex) + { + // Expected behavior - the command should fail with invalid input + Assert.NotNull(ex.Message); + Assert.NotEmpty(ex.Message); + } + } + + [Theory] + [InlineData("--invalid-param")] + [InlineData("--subscription invalidSub")] + [InlineData("--subscription sub --resource-group rg")] // Missing server + public async Task Should_Return400_WithInvalidServerConfigGetInput(string args) + { + try + { + var result = await CallToolAsync("azmcp_postgres_server_config_get", + new Dictionary { { "args", args } }); + + // If we get here, the command didn't fail as expected + Assert.Fail("Expected command to fail with invalid input, but it succeeded"); + } + catch (Exception ex) + { + // Expected behavior - the command should fail with invalid input + Assert.NotNull(ex.Message); + Assert.NotEmpty(ex.Message); + } + } + + [Theory] + [InlineData("--invalid-param")] + [InlineData("--subscription invalidSub")] + [InlineData("--subscription sub --resource-group rg")] // Missing server and parameter-name + [InlineData("--subscription sub --resource-group rg --server server1")] // Missing parameter-name + public async Task Should_Return400_WithInvalidServerParamGetInput(string args) + { + try + { + var result = await CallToolAsync("azmcp_postgres_server_param_get", + new Dictionary { { "args", args } }); + + // If we get here, the command didn't fail as expected + Assert.Fail("Expected command to fail with invalid input, but it succeeded"); + } + catch (Exception ex) + { + // Expected behavior - the command should fail with invalid input + Assert.NotNull(ex.Message); + Assert.NotEmpty(ex.Message); + } + } +} diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 new file mode 100644 index 000000000..2128b30ca --- /dev/null +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 @@ -0,0 +1,94 @@ +param( + [string] $TenantId, + [string] $TestApplicationId, + [string] $ResourceGroupName, + [string] $BaseName, + [hashtable] $DeploymentOutputs +) + +$ErrorActionPreference = "Stop" + +. "$PSScriptRoot/../../../eng/common/scripts/common.ps1" +. "$PSScriptRoot/../../../eng/scripts/helpers/TestResourcesHelpers.ps1" + +$testSettings = New-TestSettings @PSBoundParameters -OutputPath $PSScriptRoot + +$postgresServerName = "$($testSettings.ResourceBaseName)-postgres" + +Write-Host "Verifying PostgreSQL Server deployment: $postgresServerName" -ForegroundColor Yellow + +# Get the PostgreSQL server details to verify deployment +try { + $postgresServer = Get-AzPostgreSqlFlexibleServer -ResourceGroupName $ResourceGroupName -Name $postgresServerName + + if ($postgresServer) { + Write-Host "PostgreSQL Server '$postgresServerName' deployed successfully" -ForegroundColor Green + Write-Host " Server: $($postgresServer.Name)" -ForegroundColor Gray + Write-Host " FQDN: $($postgresServer.FullyQualifiedDomainName)" -ForegroundColor Gray + Write-Host " Location: $($postgresServer.Location)" -ForegroundColor Gray + Write-Host " Version: $($postgresServer.Version)" -ForegroundColor Gray + Write-Host " State: $($postgresServer.State)" -ForegroundColor Gray + + # List databases + try { + $databases = Get-AzPostgreSqlFlexibleServerDatabase -ResourceGroupName $ResourceGroupName -ServerName $postgresServerName + Write-Host " Databases:" -ForegroundColor Gray + foreach ($db in $databases) { + Write-Host " - $($db.Name) (Charset: $($db.Charset), Collation: $($db.Collation))" -ForegroundColor Gray + } + } + catch { + Write-Warning "Could not list databases: $($_.Exception.Message)" + } + + # List firewall rules + try { + $firewallRules = Get-AzPostgreSqlFlexibleServerFirewallRule -ResourceGroupName $ResourceGroupName -ServerName $postgresServerName + Write-Host " Firewall Rules:" -ForegroundColor Gray + foreach ($rule in $firewallRules) { + Write-Host " - $($rule.Name): $($rule.StartIpAddress) - $($rule.EndIpAddress)" -ForegroundColor Gray + } + } + catch { + Write-Warning "Could not list firewall rules: $($_.Exception.Message)" + } + + # Wait for server to be ready + Write-Host "Waiting for PostgreSQL server to be ready..." -ForegroundColor Yellow + $maxWaitTime = 300 # 5 minutes + $waitInterval = 15 # 15 seconds + $elapsedTime = 0 + + do { + Start-Sleep -Seconds $waitInterval + $elapsedTime += $waitInterval + $currentServer = Get-AzPostgreSqlFlexibleServer -ResourceGroupName $ResourceGroupName -Name $postgresServerName + Write-Host " Server state: $($currentServer.State)" -ForegroundColor Gray + + if ($currentServer.State -eq "Ready") { + Write-Host "PostgreSQL server is ready!" -ForegroundColor Green + break + } + + if ($elapsedTime -ge $maxWaitTime) { + Write-Warning "Timeout waiting for PostgreSQL server to be ready. Current state: $($currentServer.State)" + break + } + } while ($currentServer.State -ne "Ready") + + # Prepare test data + Write-Host "Preparing test data..." -ForegroundColor Yellow + + # The connection string and data preparation would typically be done here + # However, since we're using MCP tools for testing, the actual data preparation + # will be done as part of the live tests themselves + + Write-Host "PostgreSQL test resources setup completed successfully!" -ForegroundColor Green + } else { + Write-Error "PostgreSQL Server '$postgresServerName' not found" + } +} +catch { + Write-Error "Error verifying PostgreSQL Server deployment: $($_.Exception.Message)" + throw +} diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep new file mode 100644 index 000000000..49d21fd08 --- /dev/null +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep @@ -0,0 +1,105 @@ +targetScope = 'resourceGroup' + +@minLength(3) +@maxLength(17) +@description('The base resource name. PostgreSQL Server names have a max length restriction.') +param baseName string = resourceGroup().name + +@description('The location of the resource. By default, this is the same as the resource group.') +param location string = resourceGroup().location == 'westus' ? 'westus2' : resourceGroup().location + +@description('The client OID to grant access to test resources.') +param testApplicationOid string + +@description('PostgreSQL Server administrator login name.') +param postgresAdminLogin string = 'mcptestadmin' + +@description('PostgreSQL Server administrator password.') +@secure() +param postgresAdminPassword string = newGuid() + +// PostgreSQL Flexible Server resource +resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-12-01-preview' = { + name: '${baseName}-postgres' + location: location + sku: { + name: 'Standard_B1ms' + tier: 'Burstable' + } + properties: { + administratorLogin: postgresAdminLogin + administratorLoginPassword: postgresAdminPassword + version: '15' + storage: { + storageSizeGB: 32 + iops: 120 + tier: 'P4' + } + backup: { + backupRetentionDays: 7 + geoRedundantBackup: 'Disabled' + } + network: { + publicNetworkAccess: 'Enabled' + } + highAvailability: { + mode: 'Disabled' + } + } +} + +// Firewall rule to allow all Azure services +resource allowAllAzureServicesRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-12-01-preview' = { + parent: postgresServer + name: 'AllowAllAzureServicesAndResourcesWithinAzureIps' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '0.0.0.0' + } +} + +// Firewall rule to allow all IPs (for testing purposes) +resource allowAllIpsRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-12-01-preview' = { + parent: postgresServer + name: 'AllowAllIps' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '255.255.255.255' + } +} + +// Test database +resource testDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-12-01-preview' = { + parent: postgresServer + name: 'testdb' + properties: { + charset: 'utf8' + collation: 'en_US.utf8' + } +} + +// PostgreSQL Contributor role definition +resource postgresContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { + scope: subscription() + // This is the PostgreSQL Contributor role + // Lets you manage PostgreSQL servers, but not access to them + // See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#postgresql-contributor + name: 'b24988ac-6180-42a0-ab88-20f7382dd24c' +} + +// Role assignment for test application +resource appPostgresRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(postgresContributorRoleDefinition.id, testApplicationOid, postgresServer.id) + scope: postgresServer + properties: { + principalId: testApplicationOid + roleDefinitionId: postgresContributorRoleDefinition.id + description: 'PostgreSQL Contributor for testApplicationOid' + } +} + +// Output values for tests +output postgresServerName string = postgresServer.name +output postgresServerFqdn string = postgresServer.properties.fullyQualifiedDomainName +output testDatabaseName string = testDatabase.name +output adminLogin string = postgresAdminLogin From 55fd34646a188355efcd9ab2431d7ee85702f099 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Tue, 16 Sep 2025 14:54:47 -0700 Subject: [PATCH 02/16] update location --- tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep index 49d21fd08..7161246fa 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep @@ -6,7 +6,7 @@ targetScope = 'resourceGroup' param baseName string = resourceGroup().name @description('The location of the resource. By default, this is the same as the resource group.') -param location string = resourceGroup().location == 'westus' ? 'westus2' : resourceGroup().location +param location string = 'westus3' @description('The client OID to grant access to test resources.') param testApplicationOid string From 3f23c208666177be55e6d2a615035508060c839f Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Tue, 16 Sep 2025 15:29:43 -0700 Subject: [PATCH 03/16] update --- .../PostgresCommandTests.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs index 8a4e7b614..9a701f695 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -25,6 +25,7 @@ public async Task Should_ListDatabases_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" }, { "server", serverName } }); @@ -62,6 +63,7 @@ public async Task Should_QueryDatabase_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" }, { "server", serverName }, { "database", databaseName }, { "query", "SELECT version()" } @@ -92,7 +94,8 @@ public async Task Should_ListServers_Successfully() new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName } + { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" } }); // Should successfully retrieve the list of servers @@ -129,6 +132,7 @@ public async Task Should_GetServerConfig_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" }, { "server", serverName } }); @@ -162,6 +166,7 @@ public async Task Should_GetServerParam_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" }, { "server", serverName }, { "parameter-name", parameterName } }); @@ -191,6 +196,7 @@ public async Task Should_ListTables_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" }, { "server", serverName }, { "database", databaseName } }); @@ -229,6 +235,7 @@ await CallToolAsync( { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" }, { "server", serverName }, { "database", databaseName }, { "query", "CREATE TABLE IF NOT EXISTS test_schema_table (id SERIAL PRIMARY KEY, name VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)" } @@ -241,6 +248,7 @@ await CallToolAsync( { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" }, { "server", serverName }, { "database", databaseName }, { "table", "test_schema_table" } @@ -271,6 +279,7 @@ await CallToolAsync( { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, + { "user", "mcptestadmin" }, { "server", serverName }, { "database", databaseName }, { "query", "DROP TABLE IF EXISTS test_schema_table" } From 50bafecc06ff4818e4d72c5cd8e71707dc3886c2 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Tue, 16 Sep 2025 16:07:22 -0700 Subject: [PATCH 04/16] update --- .../Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs index 9a701f695..8f23b779d 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -168,7 +168,7 @@ public async Task Should_GetServerParam_Successfully() { "resource-group", Settings.ResourceGroupName }, { "user", "mcptestadmin" }, { "server", serverName }, - { "parameter-name", parameterName } + { "param", parameterName } }); // Should successfully retrieve the parameter From d322caf9ab1d65cb4b5cd41fe1259c1227629a23 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Tue, 16 Sep 2025 16:48:18 -0700 Subject: [PATCH 05/16] update --- .../PostgresCommandTests.cs | 75 +++++++------------ 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs index 8f23b779d..9e320e012 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -30,7 +30,7 @@ public async Task Should_ListDatabases_Successfully() }); // Should successfully retrieve the list of databases - var databases = result.AssertProperty("databases"); + var databases = result.AssertProperty("Databases"); Assert.Equal(JsonValueKind.Array, databases.ValueKind); // Should contain at least the default postgres database @@ -70,20 +70,16 @@ public async Task Should_QueryDatabase_Successfully() }); // Should successfully execute the query - var queryResult = result.AssertProperty("result"); - Assert.Equal(JsonValueKind.Object, queryResult.ValueKind); - - // Verify query result structure - Assert.True(queryResult.TryGetProperty("columns", out _)); - Assert.True(queryResult.TryGetProperty("rows", out _)); - - var columns = queryResult.GetProperty("columns"); - Assert.Equal(JsonValueKind.Array, columns.ValueKind); - Assert.True(columns.GetArrayLength() >= 1, "Should have at least one column"); - - var rows = queryResult.GetProperty("rows"); - Assert.Equal(JsonValueKind.Array, rows.ValueKind); - Assert.True(rows.GetArrayLength() >= 1, "Should have at least one row"); + var queryResult = result.AssertProperty("QueryResult"); + Assert.Equal(JsonValueKind.Array, queryResult.ValueKind); + + // Should have at least one element (the column headers) + Assert.True(queryResult.GetArrayLength() >= 1, "Should have at least column headers"); + + // First element should be column names + var firstElement = queryResult.EnumerateArray().First(); + Assert.Equal(JsonValueKind.String, firstElement.ValueKind); + Assert.Contains("version", firstElement.GetString()!, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -99,7 +95,7 @@ public async Task Should_ListServers_Successfully() }); // Should successfully retrieve the list of servers - var servers = result.AssertProperty("servers"); + var servers = result.AssertProperty("Servers"); Assert.Equal(JsonValueKind.Array, servers.ValueKind); // Should contain at least one server (our test server) @@ -137,20 +133,14 @@ public async Task Should_GetServerConfig_Successfully() }); // Should successfully retrieve server configuration - var configs = result.AssertProperty("configurations"); - Assert.Equal(JsonValueKind.Array, configs.ValueKind); - - // Should contain configuration parameters - var configArray = configs.EnumerateArray().ToList(); - Assert.True(configArray.Count > 0, "Should contain configuration parameters"); - - // Verify configuration structure - var firstConfig = configArray.First(); - Assert.Equal(JsonValueKind.Object, firstConfig.ValueKind); - - // Verify required properties exist - Assert.True(firstConfig.TryGetProperty("name", out _)); - Assert.True(firstConfig.TryGetProperty("value", out _)); + var config = result.AssertProperty("Configuration"); + Assert.Equal(JsonValueKind.String, config.ValueKind); + + // Should contain configuration information + var configString = config.GetString(); + Assert.NotNull(configString); + Assert.NotEmpty(configString); + Assert.Contains("Server Name:", configString); } [Fact] @@ -172,15 +162,12 @@ public async Task Should_GetServerParam_Successfully() }); // Should successfully retrieve the parameter - var parameter = result.AssertProperty("parameter"); - Assert.Equal(JsonValueKind.Object, parameter.ValueKind); + var parameterValue = result.AssertProperty("ParameterValue"); + Assert.Equal(JsonValueKind.String, parameterValue.ValueKind); - // Verify parameter properties - var paramName = parameter.GetProperty("name").GetString(); - Assert.Equal(parameterName, paramName); - - Assert.True(parameter.TryGetProperty("value", out _)); - Assert.True(parameter.TryGetProperty("dataType", out _)); + // Verify parameter value is not null or empty + var paramValue = parameterValue.GetString(); + Assert.NotNull(paramValue); } [Fact] @@ -202,22 +189,18 @@ public async Task Should_ListTables_Successfully() }); // Should successfully retrieve the list of tables - var tables = result.AssertProperty("tables"); + var tables = result.AssertProperty("Tables"); Assert.Equal(JsonValueKind.Array, tables.ValueKind); // PostgreSQL default database may or may not have user tables, // but the command should execute successfully var tableArray = tables.EnumerateArray().ToList(); - // If there are tables, verify their structure + // If there are tables, verify they are strings (table names) if (tableArray.Count > 0) { var firstTable = tableArray.First(); - Assert.Equal(JsonValueKind.Object, firstTable.ValueKind); - - // Verify table properties - Assert.True(firstTable.TryGetProperty("tableName", out _)); - Assert.True(firstTable.TryGetProperty("schemaName", out _)); + Assert.Equal(JsonValueKind.String, firstTable.ValueKind); } } @@ -255,7 +238,7 @@ await CallToolAsync( }); // Should successfully retrieve the table schema - var schema = result.AssertProperty("schema"); + var schema = result.AssertProperty("Schema"); Assert.Equal(JsonValueKind.Object, schema.ValueKind); // Verify schema structure From 9863bcb25a2b858514e8feeb433205441ad2d427 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Tue, 16 Sep 2025 17:54:07 -0700 Subject: [PATCH 06/16] update --- .../PostgresCommandTests.cs | 41 +++++++++---------- .../tests/test-resources.bicep | 17 ++++++++ 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs index 9e320e012..c48d705f1 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -25,7 +25,7 @@ public async Task Should_ListDatabases_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" }, + { "user", Settings.PrincipalName }, { "server", serverName } }); @@ -63,7 +63,7 @@ public async Task Should_QueryDatabase_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" }, + { "user", Settings.PrincipalName }, { "server", serverName }, { "database", databaseName }, { "query", "SELECT version()" } @@ -91,7 +91,7 @@ public async Task Should_ListServers_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" } + { "user", Settings.PrincipalName } }); // Should successfully retrieve the list of servers @@ -128,7 +128,7 @@ public async Task Should_GetServerConfig_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" }, + { "user", Settings.PrincipalName }, { "server", serverName } }); @@ -156,7 +156,7 @@ public async Task Should_GetServerParam_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" }, + { "user", Settings.PrincipalName }, { "server", serverName }, { "param", parameterName } }); @@ -183,7 +183,7 @@ public async Task Should_ListTables_Successfully() { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" }, + { "user", Settings.PrincipalName }, { "server", serverName }, { "database", databaseName } }); @@ -218,7 +218,7 @@ await CallToolAsync( { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" }, + { "user", Settings.PrincipalName }, { "server", serverName }, { "database", databaseName }, { "query", "CREATE TABLE IF NOT EXISTS test_schema_table (id SERIAL PRIMARY KEY, name VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)" } @@ -231,7 +231,7 @@ await CallToolAsync( { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" }, + { "user", Settings.PrincipalName }, { "server", serverName }, { "database", databaseName }, { "table", "test_schema_table" } @@ -239,21 +239,20 @@ await CallToolAsync( // Should successfully retrieve the table schema var schema = result.AssertProperty("Schema"); - Assert.Equal(JsonValueKind.Object, schema.ValueKind); + Assert.Equal(JsonValueKind.Array, schema.ValueKind); - // Verify schema structure - Assert.True(schema.TryGetProperty("tableName", out _)); - Assert.True(schema.TryGetProperty("columns", out _)); + // Schema should contain column information as strings + var schemaArray = schema.EnumerateArray().ToList(); + Assert.True(schemaArray.Count >= 1, "Should have at least one schema entry"); - var columns = schema.GetProperty("columns"); - Assert.Equal(JsonValueKind.Array, columns.ValueKind); - Assert.True(columns.GetArrayLength() >= 3, "Should have at least 3 columns (id, name, created_at)"); + // Verify that each schema entry is a string + var firstEntry = schemaArray.First(); + Assert.Equal(JsonValueKind.String, firstEntry.ValueKind); - // Verify column structure - var firstColumn = columns.EnumerateArray().First(); - Assert.Equal(JsonValueKind.Object, firstColumn.ValueKind); - Assert.True(firstColumn.TryGetProperty("columnName", out _)); - Assert.True(firstColumn.TryGetProperty("dataType", out _)); + // The schema should contain information about our test table columns + var schemaContent = string.Join(" ", schemaArray.Select(s => s.GetString())); + Assert.Contains("id", schemaContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("name", schemaContent, StringComparison.OrdinalIgnoreCase); // Cleanup: Drop the test table await CallToolAsync( @@ -262,7 +261,7 @@ await CallToolAsync( { { "subscription", Settings.SubscriptionId }, { "resource-group", Settings.ResourceGroupName }, - { "user", "mcptestadmin" }, + { "user", Settings.PrincipalName }, { "server", serverName }, { "database", databaseName }, { "query", "DROP TABLE IF EXISTS test_schema_table" } diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep index 7161246fa..b906ad76c 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep @@ -45,6 +45,22 @@ resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-12-01-pr highAvailability: { mode: 'Disabled' } + authConfig: { + activeDirectoryAuth: 'Enabled' + passwordAuth: 'Disabled' + tenantId: tenant().tenantId + } + } +} + +// Configure Entra ID administrator for PostgreSQL server +resource postgresAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2023-12-01-preview' = { + parent: postgresServer + name: testApplicationOid + properties: { + principalType: 'ServicePrincipal' + principalName: testApplicationOid + tenantId: tenant().tenantId } } @@ -103,3 +119,4 @@ output postgresServerName string = postgresServer.name output postgresServerFqdn string = postgresServer.properties.fullyQualifiedDomainName output testDatabaseName string = testDatabase.name output adminLogin string = postgresAdminLogin +output entraIdAdminObjectId string = testApplicationOid From a2b1716a8e029de1deafdad5c5391d60eeaff4f3 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 10:05:21 -0700 Subject: [PATCH 07/16] updates --- AzureMcp.sln | 15 + .../Azure.Mcp.Tools.Postgres.LiveTests.csproj | 4 +- .../PostgresCommandTests.cs | 439 +++++++----------- .../tests/test-resources.bicep | 94 ++-- 4 files changed, 227 insertions(+), 325 deletions(-) diff --git a/AzureMcp.sln b/AzureMcp.sln index a1e570226..73ab42398 100644 --- a/AzureMcp.sln +++ b/AzureMcp.sln @@ -402,6 +402,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{37B0CE47 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.Postgres.UnitTests", "tools\Azure.Mcp.Tools.Postgres\tests\Azure.Mcp.Tools.Postgres.UnitTests\Azure.Mcp.Tools.Postgres.UnitTests.csproj", "{6CFDFF50-A41A-4A5E-A66A-670DB707495F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.Postgres.LiveTests", "tools\Azure.Mcp.Tools.Postgres\tests\Azure.Mcp.Tools.Postgres.LiveTests\Azure.Mcp.Tools.Postgres.LiveTests.csproj", "{A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7F099BC4-FAF9-0D5C-A860-45BB7185CE13}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Mcp.Tools.Quota.LiveTests", "tools\Azure.Mcp.Tools.Quota\tests\Azure.Mcp.Tools.Quota.LiveTests\Azure.Mcp.Tools.Quota.LiveTests.csproj", "{2C7B96D1-085F-4BCA-8D7D-3047765E3492}" @@ -1518,6 +1520,18 @@ Global {6CFDFF50-A41A-4A5E-A66A-670DB707495F}.Release|x64.Build.0 = Release|Any CPU {6CFDFF50-A41A-4A5E-A66A-670DB707495F}.Release|x86.ActiveCfg = Release|Any CPU {6CFDFF50-A41A-4A5E-A66A-670DB707495F}.Release|x86.Build.0 = Release|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Debug|x64.ActiveCfg = Debug|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Debug|x64.Build.0 = Debug|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Debug|x86.ActiveCfg = Debug|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Debug|x86.Build.0 = Debug|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Release|Any CPU.Build.0 = Release|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Release|x64.ActiveCfg = Release|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Release|x64.Build.0 = Release|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Release|x86.ActiveCfg = Release|Any CPU + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32}.Release|x86.Build.0 = Release|Any CPU {2C7B96D1-085F-4BCA-8D7D-3047765E3492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2C7B96D1-085F-4BCA-8D7D-3047765E3492}.Debug|Any CPU.Build.0 = Debug|Any CPU {2C7B96D1-085F-4BCA-8D7D-3047765E3492}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1947,6 +1961,7 @@ Global {7C1FC1A3-5E52-45A8-B136-0934ADD5580C} = {BE4DDD77-798F-8692-0C2A-2A9637256ED2} {37B0CE47-14C8-F5BF-BDDD-13EEBE580A88} = {898C8C6E-FC1C-58E1-FB62-BBE77ED42888} {6CFDFF50-A41A-4A5E-A66A-670DB707495F} = {37B0CE47-14C8-F5BF-BDDD-13EEBE580A88} + {A8B9C7D6-E5F4-43A2-B1E8-9C7D6E5F4A32} = {37B0CE47-14C8-F5BF-BDDD-13EEBE580A88} {7F099BC4-FAF9-0D5C-A860-45BB7185CE13} = {4CBFCE90-00AC-3F19-DC8C-C81357708F76} {2C7B96D1-085F-4BCA-8D7D-3047765E3492} = {7F099BC4-FAF9-0D5C-A860-45BB7185CE13} {B3167A7D-CF85-4A5F-B1CF-F14013782F1D} = {7F099BC4-FAF9-0D5C-A860-45BB7185CE13} diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/Azure.Mcp.Tools.Postgres.LiveTests.csproj b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/Azure.Mcp.Tools.Postgres.LiveTests.csproj index 0f06a032a..a9f4f4b09 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/Azure.Mcp.Tools.Postgres.LiveTests.csproj +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/Azure.Mcp.Tools.Postgres.LiveTests.csproj @@ -13,5 +13,7 @@ + + - + \ No newline at end of file diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs index c48d705f1..7971d0655 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -1,385 +1,286 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Net; using System.Text.Json; +using Azure.Mcp.Core.Services.Azure.ResourceGroup; +using Azure.Mcp.Core.Services.Azure.Subscription; +using Azure.Mcp.Core.Services.Azure.Tenant; +using Azure.Mcp.Core.Services.Caching; using Azure.Mcp.Tests; using Azure.Mcp.Tests.Client; using Azure.Mcp.Tests.Client.Helpers; +using Azure.Mcp.Tools.Postgres.Services; +using Microsoft.Extensions.Caching.Memory; using Xunit; namespace Azure.Mcp.Tools.Postgres.LiveTests; -public class PostgresCommandTests(ITestOutputHelper output) : CommandTestsBase(output) +public class PostgresCommandTests : CommandTestsBase { + private const string ServersKey = "servers"; + private const string DatabasesKey = "databases"; + private const string TablesKey = "tables"; + private const string ConfigKey = "config"; + private const string QueryResultKey = "queryResult"; - [Fact] - public async Task Should_ListDatabases_Successfully() - { - // Use the deployed test PostgreSQL server - var serverName = Settings.ResourceBaseName + "-postgres"; - - var result = await CallToolAsync( - "azmcp_postgres_database_list", - new() - { - { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName }, - { "server", serverName } - }); + private readonly PostgresService _postgresService; - // Should successfully retrieve the list of databases - var databases = result.AssertProperty("Databases"); - Assert.Equal(JsonValueKind.Array, databases.ValueKind); - - // Should contain at least the default postgres database - var databaseArray = databases.EnumerateArray().ToList(); - Assert.True(databaseArray.Count >= 1, "Should contain at least the default postgres database"); - - // Verify that postgres database exists (default database) - var postgresDb = databaseArray.FirstOrDefault(db => - db.GetProperty("name").GetString() == "postgres"); - Assert.NotEqual(default, postgresDb); - - // Verify database properties - if (postgresDb.ValueKind != JsonValueKind.Undefined) - { - var dbName = postgresDb.GetProperty("name").GetString(); - Assert.Equal("postgres", dbName); - } + public PostgresCommandTests(ITestOutputHelper output) : base(output) + { + var memoryCache = new MemoryCache(Microsoft.Extensions.Options.Options.Create(new MemoryCacheOptions())); + var cacheService = new CacheService(memoryCache); + var tenantService = new TenantService(cacheService); + var subscriptionService = new SubscriptionService(cacheService, tenantService); + var resourceGroupService = new ResourceGroupService(cacheService, subscriptionService); + _postgresService = new PostgresService(resourceGroupService); } [Fact] - public async Task Should_QueryDatabase_Successfully() + public async Task Should_list_postgres_servers() { - // Use the deployed test PostgreSQL server - var serverName = Settings.ResourceBaseName + "-postgres"; - var databaseName = "postgres"; // Default database - + // act var result = await CallToolAsync( - "azmcp_postgres_database_query", + "azmcp_postgres_server_list", new() { - { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName }, - { "server", serverName }, - { "database", databaseName }, - { "query", "SELECT version()" } + { "subscription", Settings.SubscriptionId } }); - // Should successfully execute the query - var queryResult = result.AssertProperty("QueryResult"); - Assert.Equal(JsonValueKind.Array, queryResult.ValueKind); + // assert + var serversArray = result.AssertProperty(ServersKey); + Assert.Equal(JsonValueKind.Array, serversArray.ValueKind); + Assert.NotEmpty(serversArray.EnumerateArray()); - // Should have at least one element (the column headers) - Assert.True(queryResult.GetArrayLength() >= 1, "Should have at least column headers"); - - // First element should be column names - var firstElement = queryResult.EnumerateArray().First(); - Assert.Equal(JsonValueKind.String, firstElement.ValueKind); - Assert.Contains("version", firstElement.GetString()!, StringComparison.OrdinalIgnoreCase); + // Check that our test server is in the list + var serverName = $"{Settings.ResourceBaseName}-postgres"; + Assert.Contains(serversArray.EnumerateArray(), server => + server.GetString()?.Contains(serverName) == true); } [Fact] - public async Task Should_ListServers_Successfully() + public async Task Should_list_postgres_databases() { + var serverName = $"{Settings.ResourceBaseName}-postgres"; + + // act var result = await CallToolAsync( - "azmcp_postgres_server_list", + "azmcp_postgres_database_list", new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName } + { "resourceGroup", Settings.ResourceBaseName }, + { "server", serverName } }); - // Should successfully retrieve the list of servers - var servers = result.AssertProperty("Servers"); - Assert.Equal(JsonValueKind.Array, servers.ValueKind); - - // Should contain at least one server (our test server) - var serverArray = servers.EnumerateArray().ToList(); - Assert.True(serverArray.Count >= 1, "Should contain at least one PostgreSQL server"); - - // Verify server properties - var firstServer = serverArray.First(); - Assert.Equal(JsonValueKind.Object, firstServer.ValueKind); - - // Verify required properties exist - Assert.True(firstServer.TryGetProperty("name", out _)); - Assert.True(firstServer.TryGetProperty("id", out _)); - Assert.True(firstServer.TryGetProperty("type", out _)); - Assert.True(firstServer.TryGetProperty("location", out _)); + // assert + var databasesArray = result.AssertProperty(DatabasesKey); + Assert.Equal(JsonValueKind.Array, databasesArray.ValueKind); + Assert.NotEmpty(databasesArray.EnumerateArray()); - var serverType = firstServer.GetProperty("type").GetString(); - Assert.Contains("Microsoft.DBforPostgreSQL", serverType); + // Check that our test database is in the list + Assert.Contains(databasesArray.EnumerateArray(), db => + db.GetString() == "testdb"); } [Fact] - public async Task Should_GetServerConfig_Successfully() + public async Task Should_get_postgres_server_config() { - // Use the deployed test PostgreSQL server - var serverName = Settings.ResourceBaseName + "-postgres"; + var serverName = $"{Settings.ResourceBaseName}-postgres"; + // act var result = await CallToolAsync( "azmcp_postgres_server_config_get", new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName }, + { "resourceGroup", Settings.ResourceBaseName }, { "server", serverName } }); - // Should successfully retrieve server configuration - var config = result.AssertProperty("Configuration"); - Assert.Equal(JsonValueKind.String, config.ValueKind); + // assert + var configProperty = result.AssertProperty(ConfigKey); + Assert.Equal(JsonValueKind.Object, configProperty.ValueKind); - // Should contain configuration information - var configString = config.GetString(); - Assert.NotNull(configString); - Assert.NotEmpty(configString); - Assert.Contains("Server Name:", configString); + // Verify some expected configuration properties exist + Assert.True(configProperty.TryGetProperty("sku", out _)); + Assert.True(configProperty.TryGetProperty("properties", out _)); } [Fact] - public async Task Should_GetServerParam_Successfully() + public async Task Should_execute_simple_database_query() { - // Use the deployed test PostgreSQL server - var serverName = Settings.ResourceBaseName + "-postgres"; - var parameterName = "log_statement"; // A common PostgreSQL parameter + var serverName = $"{Settings.ResourceBaseName}-postgres"; + var databaseName = "testdb"; + // act - Execute a simple query to test connectivity var result = await CallToolAsync( - "azmcp_postgres_server_param_get", + "azmcp_postgres_database_query", new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName }, + { "resourceGroup", Settings.ResourceBaseName }, { "server", serverName }, - { "param", parameterName } + { "database", databaseName }, + { "query", "SELECT version();" } }); - // Should successfully retrieve the parameter - var parameterValue = result.AssertProperty("ParameterValue"); - Assert.Equal(JsonValueKind.String, parameterValue.ValueKind); + // assert + var queryResult = result.AssertProperty(QueryResultKey); + Assert.Equal(JsonValueKind.Array, queryResult.ValueKind); + Assert.NotEmpty(queryResult.EnumerateArray()); - // Verify parameter value is not null or empty - var paramValue = parameterValue.GetString(); - Assert.NotNull(paramValue); + // Verify the result contains version information + var firstRow = queryResult.EnumerateArray().First(); + Assert.True(firstRow.TryGetProperty("version", out var versionProperty)); + Assert.Contains("PostgreSQL", versionProperty.GetString()!); } [Fact] - public async Task Should_ListTables_Successfully() + public async Task Should_list_tables_in_database() { - // Use the deployed test PostgreSQL server - var serverName = Settings.ResourceBaseName + "-postgres"; - var databaseName = "postgres"; // Default database + var serverName = $"{Settings.ResourceBaseName}-postgres"; + var databaseName = "testdb"; + // First create a test table + await CallToolAsync( + "azmcp_postgres_database_query", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resourceGroup", Settings.ResourceBaseName }, + { "server", serverName }, + { "database", databaseName }, + { "query", "CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(100));" } + }); + + // act - List tables var result = await CallToolAsync( "azmcp_postgres_table_list", new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName }, + { "resourceGroup", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName } }); - // Should successfully retrieve the list of tables - var tables = result.AssertProperty("Tables"); - Assert.Equal(JsonValueKind.Array, tables.ValueKind); + // assert + var tablesArray = result.AssertProperty(TablesKey); + Assert.Equal(JsonValueKind.Array, tablesArray.ValueKind); - // PostgreSQL default database may or may not have user tables, - // but the command should execute successfully - var tableArray = tables.EnumerateArray().ToList(); - - // If there are tables, verify they are strings (table names) - if (tableArray.Count > 0) - { - var firstTable = tableArray.First(); - Assert.Equal(JsonValueKind.String, firstTable.ValueKind); - } + // Check that our test table is in the list + Assert.Contains(tablesArray.EnumerateArray(), table => + table.GetString() == "test_table"); } [Fact] - public async Task Should_GetTableSchema_Successfully() + public async Task Should_get_table_schema() { - // First, create a test table to ensure we have something to query - var serverName = Settings.ResourceBaseName + "-postgres"; - var databaseName = "postgres"; + var serverName = $"{Settings.ResourceBaseName}-postgres"; + var databaseName = "testdb"; + var tableName = "test_table"; - // Create a test table first + // Ensure test table exists await CallToolAsync( "azmcp_postgres_database_query", new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName }, + { "resourceGroup", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, - { "query", "CREATE TABLE IF NOT EXISTS test_schema_table (id SERIAL PRIMARY KEY, name VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)" } + { "query", "CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(100));" } }); - // Now get the schema for the test table + // act var result = await CallToolAsync( "azmcp_postgres_table_schema_get", new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName }, + { "resourceGroup", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, - { "table", "test_schema_table" } + { "table", tableName } }); - // Should successfully retrieve the table schema - var schema = result.AssertProperty("Schema"); - Assert.Equal(JsonValueKind.Array, schema.ValueKind); - - // Schema should contain column information as strings - var schemaArray = schema.EnumerateArray().ToList(); - Assert.True(schemaArray.Count >= 1, "Should have at least one schema entry"); - - // Verify that each schema entry is a string - var firstEntry = schemaArray.First(); - Assert.Equal(JsonValueKind.String, firstEntry.ValueKind); + // assert + var schemaProperty = result.AssertProperty("schema"); + Assert.Equal(JsonValueKind.Array, schemaProperty.ValueKind); + Assert.NotEmpty(schemaProperty.EnumerateArray()); + + // Verify expected columns are present + var columns = schemaProperty.EnumerateArray().ToList(); + Assert.Contains(columns, col => + col.TryGetProperty("column_name", out var nameProperty) && + nameProperty.GetString() == "id"); + Assert.Contains(columns, col => + col.TryGetProperty("column_name", out var nameProperty) && + nameProperty.GetString() == "name"); + } - // The schema should contain information about our test table columns - var schemaContent = string.Join(" ", schemaArray.Select(s => s.GetString())); - Assert.Contains("id", schemaContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("name", schemaContent, StringComparison.OrdinalIgnoreCase); + [Fact] + public async Task Should_insert_and_query_data() + { + var serverName = $"{Settings.ResourceBaseName}-postgres"; + var databaseName = "testdb"; - // Cleanup: Drop the test table + // Create test table await CallToolAsync( "azmcp_postgres_database_query", new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", Settings.ResourceGroupName }, - { "user", Settings.PrincipalName }, + { "resourceGroup", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, - { "query", "DROP TABLE IF EXISTS test_schema_table" } + { "query", "CREATE TABLE IF NOT EXISTS live_test_data (id SERIAL PRIMARY KEY, test_name VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);" } }); - } - [Theory] - [InlineData("--invalid-param", new string[0])] - [InlineData("--subscription", new[] { "invalidSub" })] - [InlineData("--subscription", new[] { "sub", "--resource-group", "rg" })] // Missing server - public async Task Should_Return400_WithInvalidDatabaseListInput(string firstArg, string[] remainingArgs) - { - var allArgs = new[] { firstArg }.Concat(remainingArgs); - var argsString = string.Join(" ", allArgs); - - try - { - var result = await CallToolAsync("azmcp_postgres_database_list", - new Dictionary { { "args", argsString } }); - - // If we get here, the command didn't fail as expected - Assert.Fail("Expected command to fail with invalid input, but it succeeded"); - } - catch (Exception ex) - { - // Expected behavior - the command should fail with invalid input - Assert.NotNull(ex.Message); - Assert.NotEmpty(ex.Message); - } - } + // Insert test data + var testId = Guid.NewGuid().ToString(); + await CallToolAsync( + "azmcp_postgres_database_query", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resourceGroup", Settings.ResourceBaseName }, + { "server", serverName }, + { "database", databaseName }, + { "query", $"INSERT INTO live_test_data (test_name) VALUES ('test_{testId}');" } + }); - [Theory] - [InlineData("--invalid-param")] - [InlineData("--subscription invalidSub")] - [InlineData("--subscription sub --resource-group rg")] // Missing server and database - [InlineData("--subscription sub --resource-group rg --server server1")] // Missing database and query - public async Task Should_Return400_WithInvalidQueryInput(string args) - { - try - { - var result = await CallToolAsync("azmcp_postgres_database_query", - new Dictionary { { "args", args } }); - - // If we get here, the command didn't fail as expected - Assert.Fail("Expected command to fail with invalid input, but it succeeded"); - } - catch (Exception ex) - { - // Expected behavior - the command should fail with invalid input - Assert.NotNull(ex.Message); - Assert.NotEmpty(ex.Message); - } - } + // act - Query the inserted data + var result = await CallToolAsync( + "azmcp_postgres_database_query", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resourceGroup", Settings.ResourceBaseName }, + { "server", serverName }, + { "database", databaseName }, + { "query", $"SELECT * FROM live_test_data WHERE test_name = 'test_{testId}';" } + }); - [Theory] - [InlineData("--invalid-param")] - [InlineData("--subscription invalidSub")] - [InlineData("--subscription sub --resource-group rg")] // Missing server - public async Task Should_Return400_WithInvalidServerListInput(string args) - { - try - { - var result = await CallToolAsync("azmcp_postgres_server_list", - new Dictionary { { "args", args } }); - - // If we get here, the command didn't fail as expected - Assert.Fail("Expected command to fail with invalid input, but it succeeded"); - } - catch (Exception ex) - { - // Expected behavior - the command should fail with invalid input - Assert.NotNull(ex.Message); - Assert.NotEmpty(ex.Message); - } - } + // assert + var queryResult = result.AssertProperty(QueryResultKey); + Assert.Equal(JsonValueKind.Array, queryResult.ValueKind); + Assert.NotEmpty(queryResult.EnumerateArray()); - [Theory] - [InlineData("--invalid-param")] - [InlineData("--subscription invalidSub")] - [InlineData("--subscription sub --resource-group rg")] // Missing server - public async Task Should_Return400_WithInvalidServerConfigGetInput(string args) - { - try - { - var result = await CallToolAsync("azmcp_postgres_server_config_get", - new Dictionary { { "args", args } }); - - // If we get here, the command didn't fail as expected - Assert.Fail("Expected command to fail with invalid input, but it succeeded"); - } - catch (Exception ex) - { - // Expected behavior - the command should fail with invalid input - Assert.NotNull(ex.Message); - Assert.NotEmpty(ex.Message); - } - } + var firstRow = queryResult.EnumerateArray().First(); + Assert.True(firstRow.TryGetProperty("test_name", out var testNameProperty)); + Assert.Equal($"test_{testId}", testNameProperty.GetString()); - [Theory] - [InlineData("--invalid-param")] - [InlineData("--subscription invalidSub")] - [InlineData("--subscription sub --resource-group rg")] // Missing server and parameter-name - [InlineData("--subscription sub --resource-group rg --server server1")] // Missing parameter-name - public async Task Should_Return400_WithInvalidServerParamGetInput(string args) - { - try - { - var result = await CallToolAsync("azmcp_postgres_server_param_get", - new Dictionary { { "args", args } }); - - // If we get here, the command didn't fail as expected - Assert.Fail("Expected command to fail with invalid input, but it succeeded"); - } - catch (Exception ex) - { - // Expected behavior - the command should fail with invalid input - Assert.NotNull(ex.Message); - Assert.NotEmpty(ex.Message); - } + // Clean up + await CallToolAsync( + "azmcp_postgres_database_query", + new() + { + { "subscription", Settings.SubscriptionId }, + { "resourceGroup", Settings.ResourceBaseName }, + { "server", serverName }, + { "database", databaseName }, + { "query", $"DELETE FROM live_test_data WHERE test_name = 'test_{testId}';" } + }); } -} +} \ No newline at end of file diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep index b906ad76c..7529386be 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep @@ -1,24 +1,23 @@ targetScope = 'resourceGroup' @minLength(3) -@maxLength(17) -@description('The base resource name. PostgreSQL Server names have a max length restriction.') +@maxLength(50) +@description('The base resource name.') param baseName string = resourceGroup().name @description('The location of the resource. By default, this is the same as the resource group.') -param location string = 'westus3' +param location string = resourceGroup().location + +@description('The tenant ID to which the application and resources belong.') +param tenantId string = '72f988bf-86f1-41af-91ab-2d7cd011db47' @description('The client OID to grant access to test resources.') param testApplicationOid string -@description('PostgreSQL Server administrator login name.') -param postgresAdminLogin string = 'mcptestadmin' - -@description('PostgreSQL Server administrator password.') -@secure() -param postgresAdminPassword string = newGuid() +@description('The admin username for the PostgreSQL server.') +param adminUsername string = 'mcp_admin' -// PostgreSQL Flexible Server resource +// Create a PostgreSQL Flexible Server with Entra ID authentication resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-12-01-preview' = { name: '${baseName}-postgres' location: location @@ -27,12 +26,17 @@ resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-12-01-pr tier: 'Burstable' } properties: { - administratorLogin: postgresAdminLogin - administratorLoginPassword: postgresAdminPassword version: '15' + administratorLogin: adminUsername + administratorLoginPassword: null // No password when using Entra ID only + authConfig: { + activeDirectoryAuth: 'Enabled' + passwordAuth: 'Disabled' // Disable local auth, use Entra ID only + tenantId: tenantId + } storage: { + autoGrow: 'Enabled' storageSizeGB: 32 - iops: 120 tier: 'P4' } backup: { @@ -45,36 +49,36 @@ resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-12-01-pr highAvailability: { mode: 'Disabled' } - authConfig: { - activeDirectoryAuth: 'Enabled' - passwordAuth: 'Disabled' - tenantId: tenant().tenantId + maintenanceWindow: { + customWindow: 'Disabled' + dayOfWeek: 0 + startHour: 0 + startMinute: 0 } } } -// Configure Entra ID administrator for PostgreSQL server -resource postgresAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2023-12-01-preview' = { +// Create a test database +resource testDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-12-01-preview' = { parent: postgresServer - name: testApplicationOid + name: 'testdb' properties: { - principalType: 'ServicePrincipal' - principalName: testApplicationOid - tenantId: tenant().tenantId + charset: 'UTF8' + collation: 'en_US.utf8' } } -// Firewall rule to allow all Azure services -resource allowAllAzureServicesRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-12-01-preview' = { +// Allow Azure services to access the server +resource allowAzureServicesRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-12-01-preview' = { parent: postgresServer - name: 'AllowAllAzureServicesAndResourcesWithinAzureIps' + name: 'AllowAzureServices' properties: { startIpAddress: '0.0.0.0' endIpAddress: '0.0.0.0' } } -// Firewall rule to allow all IPs (for testing purposes) +// Allow all IPs for testing (adjust as needed for security) resource allowAllIpsRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-12-01-preview' = { parent: postgresServer name: 'AllowAllIps' @@ -84,39 +88,19 @@ resource allowAllIpsRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRule } } -// Test database -resource testDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-12-01-preview' = { +// Create Entra ID administrator for the PostgreSQL server +resource postgresAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2023-12-01-preview' = { parent: postgresServer - name: 'testdb' - properties: { - charset: 'utf8' - collation: 'en_US.utf8' - } -} - -// PostgreSQL Contributor role definition -resource postgresContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { - scope: subscription() - // This is the PostgreSQL Contributor role - // Lets you manage PostgreSQL servers, but not access to them - // See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#postgresql-contributor - name: 'b24988ac-6180-42a0-ab88-20f7382dd24c' -} - -// Role assignment for test application -resource appPostgresRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(postgresContributorRoleDefinition.id, testApplicationOid, postgresServer.id) - scope: postgresServer + name: testApplicationOid properties: { - principalId: testApplicationOid - roleDefinitionId: postgresContributorRoleDefinition.id - description: 'PostgreSQL Contributor for testApplicationOid' + principalType: 'ServicePrincipal' + principalName: testApplicationOid + tenantId: tenantId } } -// Output values for tests +// Outputs for tests to use output postgresServerName string = postgresServer.name output postgresServerFqdn string = postgresServer.properties.fullyQualifiedDomainName output testDatabaseName string = testDatabase.name -output adminLogin string = postgresAdminLogin -output entraIdAdminObjectId string = testApplicationOid +output tenantId string = tenantId \ No newline at end of file From e72221741ae8057e98f6575363e8248338b21112 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 10:16:06 -0700 Subject: [PATCH 08/16] update --- .../Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs index 7971d0655..2cbcd16fa 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -283,4 +283,4 @@ await CallToolAsync( { "query", $"DELETE FROM live_test_data WHERE test_name = 'test_{testId}';" } }); } -} \ No newline at end of file +} From f24902cd08c2ee0188a5f36b9e34d288e2eb7cb8 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 10:35:07 -0700 Subject: [PATCH 09/16] update --- tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep index 7529386be..a08321dc8 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep @@ -6,7 +6,7 @@ targetScope = 'resourceGroup' param baseName string = resourceGroup().name @description('The location of the resource. By default, this is the same as the resource group.') -param location string = resourceGroup().location +param location string = 'westus3' @description('The tenant ID to which the application and resources belong.') param tenantId string = '72f988bf-86f1-41af-91ab-2d7cd011db47' @@ -103,4 +103,4 @@ resource postgresAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/admini output postgresServerName string = postgresServer.name output postgresServerFqdn string = postgresServer.properties.fullyQualifiedDomainName output testDatabaseName string = testDatabase.name -output tenantId string = tenantId \ No newline at end of file +output tenantId string = tenantId From 0360cbaa4a60f9a32654c0ba848db772bc35aa61 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 11:03:04 -0700 Subject: [PATCH 10/16] update --- .../PostgresCommandTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs index 2cbcd16fa..dfcb9bce7 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -43,7 +43,9 @@ public async Task Should_list_postgres_servers() "azmcp_postgres_server_list", new() { - { "subscription", Settings.SubscriptionId } + { "subscription", Settings.SubscriptionId }, + { "resourceGroup", Settings.ResourceBaseName }, + { "user", "testuser" } }); // assert From 3aa6a15685782ef09c09d22297fd0ea74126ab96 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 11:50:02 -0700 Subject: [PATCH 11/16] update --- .../PostgresCommandTests.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs index dfcb9bce7..fa796dd4a 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Postgres/tests/Azure.Mcp.Tools.Postgres.LiveTests/PostgresCommandTests.cs @@ -44,7 +44,7 @@ public async Task Should_list_postgres_servers() new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "user", "testuser" } }); @@ -70,7 +70,7 @@ public async Task Should_list_postgres_databases() new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName } }); @@ -95,7 +95,7 @@ public async Task Should_get_postgres_server_config() new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName } }); @@ -120,7 +120,7 @@ public async Task Should_execute_simple_database_query() new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, { "query", "SELECT version();" } @@ -149,7 +149,7 @@ await CallToolAsync( new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, { "query", "CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(100));" } @@ -161,7 +161,7 @@ await CallToolAsync( new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName } }); @@ -188,7 +188,7 @@ await CallToolAsync( new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, { "query", "CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(100));" } @@ -200,7 +200,7 @@ await CallToolAsync( new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, { "table", tableName } @@ -233,7 +233,7 @@ await CallToolAsync( new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, { "query", "CREATE TABLE IF NOT EXISTS live_test_data (id SERIAL PRIMARY KEY, test_name VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);" } @@ -246,7 +246,7 @@ await CallToolAsync( new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, { "query", $"INSERT INTO live_test_data (test_name) VALUES ('test_{testId}');" } @@ -258,7 +258,7 @@ await CallToolAsync( new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, { "query", $"SELECT * FROM live_test_data WHERE test_name = 'test_{testId}';" } @@ -279,7 +279,7 @@ await CallToolAsync( new() { { "subscription", Settings.SubscriptionId }, - { "resourceGroup", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceBaseName }, { "server", serverName }, { "database", databaseName }, { "query", $"DELETE FROM live_test_data WHERE test_name = 'test_{testId}';" } From 812894f5ca0fc8f55ee0452f74c40e6223ac9f67 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 12:43:26 -0700 Subject: [PATCH 12/16] update --- .../tests/test-resources-post.ps1 | 85 +++++++++++++++++-- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 index 2128b30ca..39d98741e 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 @@ -76,13 +76,84 @@ try { } } while ($currentServer.State -ne "Ready") - # Prepare test data - Write-Host "Preparing test data..." -ForegroundColor Yellow - - # The connection string and data preparation would typically be done here - # However, since we're using MCP tools for testing, the actual data preparation - # will be done as part of the live tests themselves - + # Prepare test data (create sample table & rows if possible) + Write-Host "Preparing PostgreSQL test data..." -ForegroundColor Yellow + + $psqlExists = Get-Command psql -ErrorAction SilentlyContinue + if (-not $psqlExists) { + Write-Warning "psql is not available on this agent. Skipping data seed step. (Install PostgreSQL client to enable)" + } + else { + try { + # Acquire an access token for AAD auth to PostgreSQL Flexible Server (oss-rdbms resource) + $token = (az account get-access-token --resource-type oss-rdbms --query accessToken -o tsv 2>$null) + if (-not $token) { + Write-Warning "Failed to obtain access token for PostgreSQL. Skipping data seed." + } + else { + $dbName = "testdb" + $aadUser = $postgresServer.properties.administratorLogin + if (-not $aadUser) { $aadUser = 'mcp_admin' } + $fqdn = $postgresServer.FullyQualifiedDomainName + + # Use access token as password (AAD auth). Token can be large; set env var for psql. + $env:PGPASSWORD = $token + + $psqlBaseArgs = @('-h', $fqdn, '-d', $dbName, '-U', $aadUser, '-v', 'ON_ERROR_STOP=1', '-w') + + # Detect table existence + $tableExists = $false + try { + $checkSql = "SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name='todos';" + $checkResult = & psql @psqlBaseArgs -t -c $checkSql 2>$null + if ($LASTEXITCODE -eq 0 -and ($checkResult.Trim() -eq '1')) { $tableExists = $true } + } catch { } + + if (-not $tableExists) { + Write-Host "Creating sample table 'todos'..." -ForegroundColor Gray + $createSql = @' +CREATE TABLE IF NOT EXISTS public.todos ( + id SERIAL PRIMARY KEY, + title TEXT NOT NULL, + is_completed BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS ix_todos_completed ON public.todos(is_completed); +'@ + & psql @psqlBaseArgs -c $createSql + if ($LASTEXITCODE -ne 0) { throw "Failed to create table" } + + Write-Host "Inserting seed rows into 'todos'..." -ForegroundColor Gray + $insertSql = @' +INSERT INTO public.todos (title, is_completed) VALUES + ('Learn MCP Postgres commands', false), + ('Create sample data', true), + ('Verify list & query tests', false) +ON CONFLICT DO NOTHING; +'@ + & psql @psqlBaseArgs -c $insertSql + if ($LASTEXITCODE -ne 0) { throw "Failed to insert seed data" } + } + else { + Write-Host "Table 'todos' already exists; skipping creation." -ForegroundColor Gray + } + + # Provide row count for logging + try { + $countSql = "SELECT COUNT(*) FROM public.todos;" + $rowCount = & psql @psqlBaseArgs -t -c $countSql 2>$null + if ($LASTEXITCODE -eq 0) { Write-Host "Seed verification: todos row count = $($rowCount.Trim())" -ForegroundColor Gray } + } catch { } + } + } + catch { + Write-Warning "PostgreSQL data seed step failed: $($_.Exception.Message)" + } + finally { + Remove-Item Env:PGPASSWORD -ErrorAction SilentlyContinue | Out-Null + } + } + Write-Host "PostgreSQL test resources setup completed successfully!" -ForegroundColor Green } else { Write-Error "PostgreSQL Server '$postgresServerName' not found" From d655a33f898826a40e126d1486be8ee2260906db Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 15:16:55 -0700 Subject: [PATCH 13/16] update --- .../tests/test-resources-post.ps1 | 71 ++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 index 39d98741e..69a5f349f 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 @@ -76,6 +76,72 @@ try { } } while ($currentServer.State -ne "Ready") + # Configure Entra ID administrator (moved from template to avoid early provisioning race) + Write-Host "Configuring Entra ID administrator (if needed)..." -ForegroundColor Yellow + $aadConfigured = $false + try { + # Try PowerShell cmdlet first (if available in current Az module version) + $getAdminCmd = Get-Command -Name Get-AzPostgreSqlFlexibleServerActiveDirectoryAdministrator -ErrorAction SilentlyContinue + $setAdminCmd = Get-Command -Name Set-AzPostgreSqlFlexibleServerActiveDirectoryAdministrator -ErrorAction SilentlyContinue + if ($getAdminCmd -and $setAdminCmd) { + $existingAdmin = Get-AzPostgreSqlFlexibleServerActiveDirectoryAdministrator -Name $postgresServerName -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue + if ($existingAdmin) { + Write-Host " Entra ID administrator already configured: $($existingAdmin.PrincipalName)" -ForegroundColor Gray + $aadConfigured = $true + } else { + $retries = 0; $maxRetries = 5 + while (-not $aadConfigured -and $retries -lt $maxRetries) { + try { + Write-Host " Attempting to set AAD admin via Az module (try $($retries+1)/$maxRetries)..." -ForegroundColor Gray + Set-AzPostgreSqlFlexibleServerActiveDirectoryAdministrator -Name $postgresServerName -ResourceGroupName $ResourceGroupName -PrincipalType ServicePrincipal -PrincipalName $TestApplicationId -TenantId $TenantId -ErrorAction Stop | Out-Null + Write-Host " Entra ID administrator configured (Az module)." -ForegroundColor Green + $aadConfigured = $true + } catch { + $retries++ + if ($retries -ge $maxRetries) { + Write-Warning " Failed to configure AAD admin via Az module after $maxRetries attempts: $($_.Exception.Message)" + } else { + Start-Sleep -Seconds 15 + } + } + } + } + } + } catch { } + + if (-not $aadConfigured) { + # Fallback to az CLI + $azExists = Get-Command az -ErrorAction SilentlyContinue + if (-not $azExists) { + Write-Warning " az CLI not available; cannot configure Entra ID administrator. AAD auth may fail." + } else { + $retries = 0; $maxRetries = 5 + while (-not $aadConfigured -and $retries -lt $maxRetries) { + try { + Write-Host " Attempting to set AAD admin via az CLI (try $($retries+1)/$maxRetries)..." -ForegroundColor Gray + az postgres flexible-server ad-admin create --resource-group $ResourceGroupName --server-name $postgresServerName --object-id $TestApplicationId --principal-name $TestApplicationId --principal-type ServicePrincipal --tenant $TenantId 1>$null 2>$null + if ($LASTEXITCODE -eq 0) { + Write-Host " Entra ID administrator configured (az CLI)." -ForegroundColor Green + $aadConfigured = $true + } else { + throw "az CLI returned exit code $LASTEXITCODE" + } + } catch { + $retries++ + if ($retries -ge $maxRetries) { + Write-Warning " Failed to configure AAD admin via az CLI after $maxRetries attempts: $($_.Exception.Message)" + } else { + Start-Sleep -Seconds 15 + } + } + } + } + } + + if (-not $aadConfigured) { + Write-Warning "Proceeding without confirmed Entra ID administrator. Data seeding using AAD may fail." + } + # Prepare test data (create sample table & rows if possible) Write-Host "Preparing PostgreSQL test data..." -ForegroundColor Yellow @@ -92,8 +158,9 @@ try { } else { $dbName = "testdb" - $aadUser = $postgresServer.properties.administratorLogin - if (-not $aadUser) { $aadUser = 'mcp_admin' } + # Derive AAD user for token-based auth. For service principal connections to PostgreSQL Flexible Server, + # the principal name is the object id / app id we configured as admin. Use TestApplicationId. + $aadUser = $TestApplicationId $fqdn = $postgresServer.FullyQualifiedDomainName # Use access token as password (AAD auth). Token can be large; set env var for psql. From a2c12fe41e2127dd853c6b41a0a2a35a8d345cbb Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 15:22:35 -0700 Subject: [PATCH 14/16] update --- .../tests/test-resources.bicep | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep index a08321dc8..aab966666 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep @@ -88,19 +88,10 @@ resource allowAllIpsRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRule } } -// Create Entra ID administrator for the PostgreSQL server -resource postgresAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2023-12-01-preview' = { - parent: postgresServer - name: testApplicationOid - properties: { - principalType: 'ServicePrincipal' - principalName: testApplicationOid - tenantId: tenantId - } -} // Outputs for tests to use output postgresServerName string = postgresServer.name output postgresServerFqdn string = postgresServer.properties.fullyQualifiedDomainName output testDatabaseName string = testDatabase.name output tenantId string = tenantId +output testApplicationOid string = testApplicationOid From 576be4aafd1a67a57d6306ad005353d5a7c05e52 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 16:04:06 -0700 Subject: [PATCH 15/16] update --- .../tests/test-resources.bicep | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep index aab966666..68aba6ab1 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep @@ -95,3 +95,14 @@ output postgresServerFqdn string = postgresServer.properties.fullyQualifiedDomai output testDatabaseName string = testDatabase.name output tenantId string = tenantId output testApplicationOid string = testApplicationOid + +// Create Entra ID administrator for the PostgreSQL server +resource postgresAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2023-12-01-preview' = { + parent: postgresServer + name: testApplicationOid + properties: { + principalType: 'ServicePrincipal' + principalName: testApplicationOid + tenantId: tenantId + } +} From 5151f499dda3d0454f9f0bda502f9d9983b5de83 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Thu, 18 Sep 2025 16:42:46 -0700 Subject: [PATCH 16/16] update --- .../tests/test-resources-post.ps1 | 12 +++++++++++- .../tests/test-resources.bicep | 11 ----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 index 69a5f349f..c45bc85db 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources-post.ps1 @@ -116,10 +116,20 @@ try { Write-Warning " az CLI not available; cannot configure Entra ID administrator. AAD auth may fail." } else { $retries = 0; $maxRetries = 5 + # Determine object id: if $TestApplicationId looks like a GUID assume object id; otherwise attempt to resolve + $objectId = $null + if ($TestApplicationId -match '^[0-9a-fA-F-]{36}$') { $objectId = $TestApplicationId } + if (-not $objectId) { + try { + $spLookup = az ad sp list --display-name $TestApplicationId --query "[0].id" -o tsv 2>$null + if ($spLookup) { $objectId = $spLookup } + } catch { } + } + if (-not $objectId) { $objectId = $TestApplicationId } while (-not $aadConfigured -and $retries -lt $maxRetries) { try { Write-Host " Attempting to set AAD admin via az CLI (try $($retries+1)/$maxRetries)..." -ForegroundColor Gray - az postgres flexible-server ad-admin create --resource-group $ResourceGroupName --server-name $postgresServerName --object-id $TestApplicationId --principal-name $TestApplicationId --principal-type ServicePrincipal --tenant $TenantId 1>$null 2>$null + az postgres flexible-server ad-admin create --resource-group $ResourceGroupName --server-name $postgresServerName --object-id $objectId --principal-name $TestApplicationId --principal-type ServicePrincipal --tenant $TenantId 1>$null 2>$null if ($LASTEXITCODE -eq 0) { Write-Host " Entra ID administrator configured (az CLI)." -ForegroundColor Green $aadConfigured = $true diff --git a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep index 68aba6ab1..aab966666 100644 --- a/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep +++ b/tools/Azure.Mcp.Tools.Postgres/tests/test-resources.bicep @@ -95,14 +95,3 @@ output postgresServerFqdn string = postgresServer.properties.fullyQualifiedDomai output testDatabaseName string = testDatabase.name output tenantId string = tenantId output testApplicationOid string = testApplicationOid - -// Create Entra ID administrator for the PostgreSQL server -resource postgresAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2023-12-01-preview' = { - parent: postgresServer - name: testApplicationOid - properties: { - principalType: 'ServicePrincipal' - principalName: testApplicationOid - tenantId: tenantId - } -}