-
Notifications
You must be signed in to change notification settings - Fork 304
Fix PostgreSQL health check connection string parsing #3083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d4424e0
66b22b8
d858f2d
209087a
26096f1
d94b703
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #nullable enable | ||
|
|
||
| using System; | ||
| using Azure.DataApiBuilder.Config.ObjectModel; | ||
| using Azure.DataApiBuilder.Service.HealthCheck; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
| using Moq; | ||
|
|
||
| namespace Azure.DataApiBuilder.Service.Tests.UnitTests | ||
| { | ||
| /// <summary> | ||
| /// Unit tests for health check utility methods. | ||
| /// </summary> | ||
| [TestClass] | ||
| public class HealthCheckUtilitiesUnitTests | ||
| { | ||
| /// <summary> | ||
| /// Tests that PostgreSQL connection strings are properly normalized. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_PostgreSQL_Success() | ||
| { | ||
| // Arrange | ||
| string connectionString = "Host=localhost;Port=5432;Database=testdb;Username=testuser;Password=testpass"; | ||
| DatabaseType dbType = DatabaseType.PostgreSQL; | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(connectionString, dbType); | ||
|
|
||
| // Assert | ||
| Assert.IsNotNull(result); | ||
| Assert.IsTrue(result.Contains("Host=localhost")); | ||
| Assert.IsTrue(result.Contains("Database=testdb")); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tests that MSSQL connection strings are properly normalized. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_MSSQL_Success() | ||
| { | ||
| // Arrange | ||
| string connectionString = "Server=localhost;Database=testdb;User Id=testuser;Password=testpass"; | ||
| DatabaseType dbType = DatabaseType.MSSQL; | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(connectionString, dbType); | ||
|
|
||
| // Assert | ||
| Assert.IsNotNull(result); | ||
| Assert.IsTrue(result.Contains("Data Source=localhost")); | ||
| Assert.IsTrue(result.Contains("Initial Catalog=testdb")); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tests that DWSQL connection strings are properly normalized. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_DWSQL_Success() | ||
| { | ||
| // Arrange | ||
| string connectionString = "Server=localhost;Database=testdb;User Id=testuser;Password=testpass"; | ||
| DatabaseType dbType = DatabaseType.DWSQL; | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(connectionString, dbType); | ||
|
|
||
| // Assert | ||
| Assert.IsNotNull(result); | ||
| Assert.IsTrue(result.Contains("Data Source=localhost")); | ||
| Assert.IsTrue(result.Contains("Initial Catalog=testdb")); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tests that MySQL connection strings are properly normalized. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_MySQL_Success() | ||
| { | ||
| // Arrange | ||
| string connectionString = "Server=localhost;Port=3306;Database=testdb;Uid=testuser;Pwd=testpass"; | ||
| DatabaseType dbType = DatabaseType.MySQL; | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(connectionString, dbType); | ||
|
|
||
| // Assert | ||
| Assert.IsNotNull(result); | ||
| Assert.IsTrue(result.Contains("Server=localhost")); | ||
| Assert.IsTrue(result.Contains("Database=testdb")); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tests that unsupported database types return the original connection string. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_UnsupportedType_ReturnsOriginal() | ||
| { | ||
| // Arrange | ||
| string connectionString = "AccountEndpoint=https://test.documents.azure.com:443/;AccountKey=test"; | ||
| DatabaseType dbType = DatabaseType.CosmosDB_NoSQL; | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(connectionString, dbType); | ||
|
|
||
| // Assert | ||
| Assert.AreEqual(connectionString, result); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tests that malformed connection strings are handled gracefully. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_MalformedString_ReturnsOriginalAndLogs() | ||
| { | ||
| // Arrange | ||
| string malformedConnectionString = "InvalidConnectionString;NoEquals"; | ||
| DatabaseType dbType = DatabaseType.PostgreSQL; | ||
| Mock<ILogger> mockLogger = new Mock<ILogger>(); | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(malformedConnectionString, dbType, mockLogger.Object); | ||
|
|
||
| // Assert | ||
| Assert.AreEqual(malformedConnectionString, result); | ||
| mockLogger.Verify( | ||
| x => x.Log( | ||
| LogLevel.Warning, | ||
| It.IsAny<EventId>(), | ||
| It.Is<It.IsAnyType>((v, t) => true), | ||
| It.IsAny<Exception>(), | ||
| It.Is<Func<It.IsAnyType, Exception?, string>>((v, t) => true)), | ||
| Times.Once); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tests that null logger is handled gracefully. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_MalformedString_NullLogger_ReturnsOriginal() | ||
| { | ||
| // Arrange | ||
| string malformedConnectionString = "InvalidConnectionString;NoEquals"; | ||
| DatabaseType dbType = DatabaseType.MSSQL; | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(malformedConnectionString, dbType, null); | ||
|
|
||
| // Assert | ||
| Assert.AreEqual(malformedConnectionString, result); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tests that PostgreSQL connection strings with lowercase keywords are normalized correctly. | ||
| /// This is the specific bug that was reported - lowercase 'host' was not supported. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_PostgreSQL_LowercaseKeywords_Success() | ||
| { | ||
| // Arrange | ||
| string connectionString = "host=localhost;port=5432;database=mydb;username=myuser;password=mypass"; | ||
| DatabaseType dbType = DatabaseType.PostgreSQL; | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(connectionString, dbType); | ||
|
|
||
| // Assert | ||
| Assert.IsNotNull(result); | ||
| // NpgsqlConnectionStringBuilder should normalize lowercase keywords to proper format | ||
| Assert.IsTrue(result.Contains("Host=localhost") || result.Contains("host=localhost")); | ||
| Assert.IsTrue(result.Contains("Database=mydb") || result.Contains("database=mydb")); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tests that empty connection strings are handled gracefully. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void NormalizeConnectionString_EmptyString_ReturnsEmpty() | ||
| { | ||
| // Arrange | ||
| string connectionString = string.Empty; | ||
| DatabaseType dbType = DatabaseType.PostgreSQL; | ||
|
|
||
| // Act | ||
| string result = HealthCheck.Utilities.NormalizeConnectionString(connectionString, dbType); | ||
|
|
||
| // Assert | ||
| Assert.AreEqual(string.Empty, result); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,8 @@ | |
| using System.Text.Json; | ||
| using Azure.DataApiBuilder.Config.ObjectModel; | ||
| using Microsoft.Data.SqlClient; | ||
| using Microsoft.Extensions.Logging; | ||
| using MySqlConnector; | ||
| using Npgsql; | ||
|
|
||
| namespace Azure.DataApiBuilder.Service.HealthCheck | ||
|
|
@@ -69,5 +71,32 @@ public static string CreateHttpRestQuery(string entityName, int first) | |
| // "EntityName?$first=4" | ||
| return $"/{entityName}?$first={first}"; | ||
| } | ||
|
|
||
| public static string NormalizeConnectionString(string connectionString, DatabaseType dbType, ILogger? logger = null) | ||
| { | ||
| try | ||
| { | ||
| switch (dbType) | ||
| { | ||
| case DatabaseType.PostgreSQL: | ||
| return new NpgsqlConnectionStringBuilder(connectionString).ToString(); | ||
| case DatabaseType.MySQL: | ||
| return new MySqlConnectionStringBuilder(connectionString).ToString(); | ||
| case DatabaseType.MSSQL: | ||
| case DatabaseType.DWSQL: | ||
| return new SqlConnectionStringBuilder(connectionString).ToString(); | ||
| default: | ||
| return connectionString; | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| // Log the exception if a logger is provided | ||
| logger?.LogWarning(ex, "Failed to parse connection string for database type {DatabaseType}. Returning original connection string.", dbType); | ||
| // If the connection string cannot be parsed by the builder, | ||
| // return the original string to avoid failing the health check. | ||
| return connectionString; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot at least log the exception or throw them instead of swallowing silently
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added logging in commit d94b703. The method now accepts an optional |
||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot for sake of completeness, should you also normalize MySQL connection string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added MySQL connection string normalization in commit d94b703. The method now uses
MySqlConnectionStringBuilderfor MySQL databases, consistent with the pattern used for PostgreSQL and MSSQL.