diff --git a/CODEOWNERS b/CODEOWNERS index 509b366b71..f51e8cbea4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,3 +2,10 @@ # the repo. Unless a later match takes precedence, # review when someone opens a pull request. * @mbhaskar @Aniruddh25 @seantleonard @mathos1432 @gledis69 @aaronburtle @tarazou9 @ayush3797 @abhishekkumams @aaronpowell @severussundar @ravishetye @rohkhann @neeraj-sharma2592 @sourabh1007 + +code_of_conduct.md @jerrynixon +contributing.md @jerrynixon +license.txt @jerrynixon +readme.md @jerrynixon +security.md @jerrynixon +support.md @jerrynixon diff --git a/SUPPORT.md b/SUPPORT.md index 25297b8704..df2a6e3170 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,18 +1,35 @@ # Support +Data API builder is an open-source project with contributions from Microsoft and the community to build a configuration-based engine in a Docker-friendly container that easily adds a secure and feature-rich Data API over databases. + ## How to file issues and get help -### Azure support tickets +### GitHub Issues + +Engineers who contribute to and work on Data API builder periodically monitor and review issues added to [the repository's issues list](https://github.com/Azure/data-api-builder/issues). The period of review is not set by a service level agreement (SLA), and resolution is not guaranteed. When an issue is raised, a member of the team will respond as soon as possible; please be patient. + +Repository issues track bugs, questions, and feature requests. Creating issues requires a GitHub account; issue templates prompt for details that will help engineers reproduce your issue and more effectively complete an investigation. Repository issues may be added to the backlog by engineers and become triaged in our future roadmap. + +We recommend searching for existing issues similar to yours before creating a new one. Commenting on existing issues helps coalesce recurring issues and helps engineers evaluate severity and find better resolutions. + +### Before Submitting an Issue -While Data API builder is in public preview, it is not eligible for Microsoft support. Please use [GitHub Issues](https://github.com/Azure/data-api-builder/issues) to file bugs and feature requests. +First, please do a search in [open issues](https://github.com/Azure/data-api-builder/issues) to see if the issue or feature request has already been filed. Use this [query](https://github.com/Azure/data-api-builder/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc+label%3Aenhancement+) to search for the most popular feature requests. +If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment. -### GitHub issues +:+1: - upvote -We use [GitHub Issues](https://github.com/azure/data-api-builder/issues) to track bugs, questions, and feature requests. Opening GitHub issues requires you to have a free to create GitHub account. There is a GitHub Issues template to help you provide the information we need to investigate your issue. +:-1: - downvote -We recommend that you search through the existing issues to see if there is a similar issue that you can comment on. If you find an existing issue, please add a comment to the existing issue instead of creating a new one. +### Azure services support tickets -When an issue is raised, a member of the team will respond as soon as possible, so please be patient. +If your solution hosts Data API builder containers using Azure Static Web Apps, Azure Container Apps, Azure Container Instances, Azure Kubernetes Services, or Azure Web Apps for Containers, then the cause of your issue could be that service. If you wish to open a support ticket with that service, do so in your [Azure portal](https://learn.microsoft.com/en-us/azure/azure-portal/supportability/how-to-create-azure-support-request). + +### Azure Database support tickets + +If your solution uses an Azure database, including Azure SQL, Azure Cosmos DB, Azure Database for PostgreSQL, Azure Database for MySQL, and Azure SQL Data Warehouse, then the cause of your issue could be with that database. If you wish to open a support ticket with that database, do so in your [Azure portal](https://learn.microsoft.com/en-us/azure/azure-portal/supportability/how-to-create-azure-support-request). + +Data API builder containers using Azure Static Web Apps, Azure Container Apps, Azure Container Instances, Azure Kubernetes Services, or Azure Web Apps for Containers, then the cause of your issue could be that service. If you wish to open a support ticket with that service, do so in your [Azure portal](https://learn.microsoft.com/en-us/azure/azure-portal/supportability/how-to-create-azure-support-request). ## Resources @@ -23,5 +40,4 @@ When an issue is raised, a member of the team will respond as soon as possible, ## Security issues Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (). You should receive a response within 24 hours. - For more information, review [SECURITY.md](SECURITY.md). diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs index 806c36237e..9a39c763ed 100644 --- a/src/Cli.Tests/EndToEndTests.cs +++ b/src/Cli.Tests/EndToEndTests.cs @@ -178,7 +178,7 @@ public void TestInitializingRestAndGraphQLGlobalSettings() [DataRow(CliBool.None, "cosmosdb_nosql", DatabaseType.CosmosDB_NoSQL, DisplayName = "Init command without '--graphql.multiple-create.enabled' option for cosmosdb_nosql database type")] public void TestEnablingMultipleCreateOperation(CliBool isMultipleCreateEnabled, string dbType, DatabaseType expectedDbType) { - List args = new() { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--connection-string", SAMPLE_TEST_CONN_STRING, "--database-type", dbType }; + List args = new() { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--connection-string", dbType == "postgresql" ? SAMPLE_TEST_PGSQL_CONN_STRING : SAMPLE_TEST_CONN_STRING, "--database-type", dbType }; if (string.Equals("cosmosdb_nosql", dbType, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Cli.Tests/TestHelper.cs b/src/Cli.Tests/TestHelper.cs index 0b3fbe8a16..e0dfeda46f 100644 --- a/src/Cli.Tests/TestHelper.cs +++ b/src/Cli.Tests/TestHelper.cs @@ -13,6 +13,8 @@ public static class TestHelper public const string SAMPLE_TEST_CONN_STRING = "Data Source=<>;Initial Catalog=<>;User ID=<>;Password=<>;"; + public const string SAMPLE_TEST_PGSQL_CONN_STRING = "Host=<>;Database=<>;username=<>;password=<>"; + // test schema for cosmosDB public const string TEST_SCHEMA_FILE = "test-schema.gql"; public const string DAB_DRAFT_SCHEMA_TEST_PATH = "https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"; @@ -157,6 +159,68 @@ public static Process ExecuteDabCommand(string command, string flags) }, ""entities"": {}"; + /// + /// Configuration with unresolved environment variable references on + /// properties of various data types (string, enum, bool, int). + /// + public const string CONFIG_ENV_VARS = @" + { + ""data-source"": { + ""database-type"": ""@env('database-type')"", + ""connection-string"": ""@env('connection-string')"" + }, + ""runtime"": { + ""rest"": { + ""path"": ""/api"", + ""enabled"": false + }, + ""graphql"": { + ""path"": ""/graphql"", + ""enabled"": true, + ""allow-introspection"": true + }, + ""host"": { + ""mode"": ""development"", + ""cors"": { + ""origins"": [], + ""allow-credentials"": false + }, + ""authentication"": { + ""provider"": ""StaticWebApps"" + } + } + }, + ""entities"": { + ""MyEntity"": { + ""source"": { + ""type"": ""stored-procedure"", + ""object"": ""s001.book"", + ""parameters"": { + ""param1"": ""@env('sp_param1_int')"", + ""param2"": ""hello"", + ""param3"": ""@env('sp_param3_bool')"" + } + }, + ""permissions"": [ + { + ""role"": ""anonymous"", + ""actions"": [ + ""execute"" + ] + } + ], + ""rest"": { + ""methods"": [ + ""post"" + ] + }, + ""graphql"": { + ""operation"": ""mutation"" + } + } + } + }"; + /// /// A minimal valid config json without any entities. This config string is used in unit tests. /// diff --git a/src/Cli.Tests/ValidateConfigTests.cs b/src/Cli.Tests/ValidateConfigTests.cs index 54e19a78a7..f80800fb6f 100644 --- a/src/Cli.Tests/ValidateConfigTests.cs +++ b/src/Cli.Tests/ValidateConfigTests.cs @@ -30,6 +30,12 @@ public void TestCleanup() { _fileSystem = null; _runtimeConfigLoader = null; + + // Clear environment variables set in tests. + Environment.SetEnvironmentVariable($"connection-string", null); + Environment.SetEnvironmentVariable($"database-type", null); + Environment.SetEnvironmentVariable($"sp_param1_int", null); + Environment.SetEnvironmentVariable($"sp_param2_bool", null); } /// @@ -107,4 +113,58 @@ public void TestValidateWithEmptyConfig() Assert.Fail($"Unexpected Exception thrown: {ex.Message}"); } } + + /// + /// This method implicitly validates that RuntimeConfigValidator::ValidateConfigSchema(...) successfully + /// executes against a config file referencing environment variables. + /// [CLI] ConfigGenerator::IsConfigValid(...) + /// |_ [Engine] RuntimeConfigValidator::TryValidateConfig(...) + /// |_ [Engine] RuntimeConfigValidator::ValidateConfigSchema(...) + /// ValidateConfigSchema(...) doesn't execute successfully when a RuntimeConfig object has unresolved environment variables. + /// Example: + /// Input file snipppet: + /// "data-source": { + /// "database-type": "@env('DATABASE_TYPE')", // ENUM + /// "connection-string": "@env('CONN_STRING')" // STRING + /// } + /// ... + /// "source": { + /// "type": ""stored-procedure", + /// "object": "s001.book", + /// "parameters": { + /// "param1": "@env('sp_param1_int')", // INT + /// "param2": "@env('sp_param2_bool')" // BOOL + /// } + /// } + /// + [TestMethod] + public void ValidateConfigSchemaWhereConfigReferencesEnvironmentVariables() + { + // Arrange + Environment.SetEnvironmentVariable($"connection-string", SAMPLE_TEST_CONN_STRING); + Environment.SetEnvironmentVariable($"database-type", "mssql"); + Environment.SetEnvironmentVariable($"sp_param1_int", "123"); + Environment.SetEnvironmentVariable($"sp_param2_bool", "true"); + + // Capture console output to get error messaging. + StringWriter writer = new(); + Console.SetOut(writer); + + ((MockFileSystem)_fileSystem!).AddFile( + path: TEST_RUNTIME_CONFIG_FILE, + mockFile: CONFIG_ENV_VARS); + ValidateOptions validateOptions = new(TEST_RUNTIME_CONFIG_FILE); + + // Act + ConfigGenerator.IsConfigValid(validateOptions, _runtimeConfigLoader!, _fileSystem!); + + // Assert + string loggerOutput = writer.ToString(); + Assert.IsFalse( + condition: loggerOutput.Contains("Failed to validate config against schema due to"), + message: "Unexpected errors encountered when validating config schema in RuntimeConfigValidator::ValidateConfigSchema(...)."); + Assert.IsTrue( + condition: loggerOutput.Contains("The config satisfies the schema requirements."), + message: "RuntimeConfigValidator::ValidateConfigSchema(...) didn't communicate successful config schema validation."); + } } diff --git a/src/Config/Azure.DataApiBuilder.Config.csproj b/src/Config/Azure.DataApiBuilder.Config.csproj index 9f900047a0..501f10bc22 100644 --- a/src/Config/Azure.DataApiBuilder.Config.csproj +++ b/src/Config/Azure.DataApiBuilder.Config.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Config/RuntimeConfigLoader.cs b/src/Config/RuntimeConfigLoader.cs index 57b92bedac..2cc823a6bb 100644 --- a/src/Config/RuntimeConfigLoader.cs +++ b/src/Config/RuntimeConfigLoader.cs @@ -14,6 +14,8 @@ using Azure.DataApiBuilder.Service.Exceptions; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using Npgsql; [assembly: InternalsVisibleTo("Azure.DataApiBuilder.Service.Tests")] namespace Azure.DataApiBuilder.Config; @@ -105,11 +107,15 @@ public static bool TryParseConfig(string json, DataSource ds = config.GetDataSourceFromDataSourceName(dataSourceKey); - // Add Application Name for telemetry for MsSQL + // Add Application Name for telemetry for MsSQL or PgSql if (ds.DatabaseType is DatabaseType.MSSQL && replaceEnvVar) { updatedConnection = GetConnectionStringWithApplicationName(connectionValue); } + else if (ds.DatabaseType is DatabaseType.PostgreSQL && replaceEnvVar) + { + updatedConnection = GetPgSqlConnectionStringWithApplicationName(connectionValue); + } ds = ds with { ConnectionString = updatedConnection }; config.UpdateDataSourceNameToDataSource(dataSourceName, ds); @@ -235,4 +241,52 @@ internal static string GetConnectionStringWithApplicationName(string connectionS // Return the updated connection string. return connectionStringBuilder.ConnectionString; } + + /// + /// It adds or replaces a property in the connection string with `Application Name` property. + /// If the connection string already contains the property, it appends the property `Application Name` to the connection string, + /// else add the Application Name property with DataApiBuilder Application Name based on hosted/oss platform. + /// + /// Connection string for connecting to database. + /// Updated connection string with `Application Name` property. + internal static string GetPgSqlConnectionStringWithApplicationName(string connectionString) + { + // If the connection string is null, empty, or whitespace, return it as is. + if (string.IsNullOrWhiteSpace(connectionString)) + { + return connectionString; + } + + string applicationName = ProductInfo.GetDataApiBuilderUserAgent(); + + // Create a StringBuilder from the connection string. + NpgsqlConnectionStringBuilder connectionStringBuilder; + try + { + connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString); + } + catch (Exception ex) + { + throw new DataApiBuilderException( + message: DataApiBuilderException.CONNECTION_STRING_ERROR_MESSAGE, + statusCode: HttpStatusCode.ServiceUnavailable, + subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization, + innerException: ex); + } + + // If the connection string does not contain the `Application Name` property, add it. + // or if the connection string contains the `Application Name` property, replace it with the DataApiBuilder Application Name. + if (connectionStringBuilder.ApplicationName.IsNullOrEmpty()) + { + connectionStringBuilder.ApplicationName = applicationName; + } + else + { + // If the connection string contains the `ApplicationName` property with a value, update the value by adding the DataApiBuilder Application Name. + connectionStringBuilder.ApplicationName += $",{applicationName}"; + } + + // Return the updated connection string. + return connectionStringBuilder.ConnectionString; + } } diff --git a/src/Core/Configurations/RuntimeConfigValidator.cs b/src/Core/Configurations/RuntimeConfigValidator.cs index 7ffb0bd7f7..91c19c420e 100644 --- a/src/Core/Configurations/RuntimeConfigValidator.cs +++ b/src/Core/Configurations/RuntimeConfigValidator.cs @@ -190,7 +190,7 @@ public async Task TryValidateConfig( /// public async Task ValidateConfigSchema(RuntimeConfig runtimeConfig, string configFilePath, ILoggerFactory loggerFactory) { - string jsonData = _fileSystem.File.ReadAllText(configFilePath); + string jsonData = runtimeConfig.ToJson(); ILogger jsonConfigValidatorLogger = loggerFactory.CreateLogger(); JsonConfigSchemaValidator jsonConfigSchemaValidator = new(jsonConfigValidatorLogger, _fileSystem); diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 8a63dc8157..14c22a5b52 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -64,7 +64,6 @@ - - + diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index c6bf8a4a46..8ee6d2a3e2 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -652,11 +652,64 @@ public void MsSqlConnStringSupplementedWithAppNameProperty( message: "DAB did not properly set the 'Application Name' connection string property."); } + /// + /// Validates that DAB supplements the PgSQL database connection strings with the property "ApplicationName" and + /// 1. Adds the property/value "Application Name=dab_oss_Major.Minor.Patch" when the env var DAB_APP_NAME_ENV is not set. + /// 2. Adds the property/value "Application Name=dab_hosted_Major.Minor.Patch" when the env var DAB_APP_NAME_ENV is set to "dab_hosted". + /// (DAB_APP_NAME_ENV is set in hosted scenario or when user sets the value.) + /// NOTE: "#pragma warning disable format" is used here to avoid removing intentional, readability promoting spacing in DataRow display names. + /// + /// connection string provided in the config. + /// Updated connection string with Application Name. + /// Whether DAB_APP_NAME_ENV is set in environment. (Always present in hosted scenario or if user supplies value.) + [DataTestMethod] + [DataRow("Host=foo;Username=testuser;", "Host=foo;Username=testuser;Application Name=", false, DisplayName = "[PGSQL]:DAB adds version 'dab_oss_major_minor_patch' to non-provided connection string property 'ApplicationName']")] + [DataRow("Host=foo;Username=testuser;", "Host=foo;Username=testuser;Application Name=", true, DisplayName = "[PGSQL]:DAB adds DAB_APP_NAME_ENV value 'dab_hosted' and version suffix '_major_minor_patch' to non-provided connection string property 'ApplicationName'.]")] + [DataRow("Host=foo;Username=testuser;Application Name=UserAppName", "Host=foo;Username=testuser;Application Name=UserAppName,", false, DisplayName = "[PGSQL]:DAB appends version 'dab_oss_major_minor_patch' to user supplied 'Application Name' property.]")] + [DataRow("Host=foo;Username=testuser;Application Name=UserAppName", "Host=foo;Username=testuser;Application Name=UserAppName,", true, DisplayName = "[PGSQL]:DAB appends version string 'dab_hosted' and version suffix '_major_minor_patch' to user supplied 'ApplicationName' property.]")] + public void PgSqlConnStringSupplementedWithAppNameProperty( + string configProvidedConnString, + string expectedDabModifiedConnString, + bool dabEnvOverride) + { + // Explicitly set the DAB_APP_NAME_ENV to null to ensure that the DAB_APP_NAME_ENV is not set. + if (dabEnvOverride) + { + Environment.SetEnvironmentVariable(ProductInfo.DAB_APP_NAME_ENV, "dab_hosted"); + } + else + { + Environment.SetEnvironmentVariable(ProductInfo.DAB_APP_NAME_ENV, null); + } + + // Resolve assembly version. Not possible to do in DataRow as DataRows expect compile-time constants. + string resolvedAssemblyVersion = ProductInfo.GetDataApiBuilderUserAgent(); + expectedDabModifiedConnString += resolvedAssemblyVersion; + + RuntimeConfig runtimeConfig = CreateBasicRuntimeConfigWithNoEntity(DatabaseType.PostgreSQL, configProvidedConnString); + + // Act + bool configParsed = RuntimeConfigLoader.TryParseConfig( + runtimeConfig.ToJson(), + out RuntimeConfig updatedRuntimeConfig, + replaceEnvVar: true); + + // Assert + Assert.AreEqual( + expected: true, + actual: configParsed, + message: "Runtime config unexpectedly failed parsing."); + Assert.AreEqual( + expected: expectedDabModifiedConnString, + actual: updatedRuntimeConfig.DataSource.ConnectionString, + message: "DAB did not properly set the 'Application Name' connection string property."); + } + /// /// Validates that DAB doesn't append nor modify /// - the 'Application Name' or 'App' properties in MySQL database connection strings. /// - the 'Application Name' property in - /// PostgreSQL, CosmosDB_PostgreSQl, CosmosDB_NoSQL database connection strings. + /// CosmosDB_PostgreSQL, CosmosDB_NoSQL database connection strings. /// This test validates that this behavior holds true when the DAB_APP_NAME_ENV environment variable /// - is set (dabEnvOverride==true) -> (DAB hosted) /// - is not set (dabEnvOverride==false) -> (DAB OSS). @@ -673,10 +726,6 @@ public void MsSqlConnStringSupplementedWithAppNameProperty( [DataRow(DatabaseType.MySQL, "Something;" , "Something;" , true , DisplayName = "[MYSQL|DAB hosted]:No addition of 'Application Name' or 'App' property to connection string.")] [DataRow(DatabaseType.MySQL, "Something;Application Name=CustAppName;" , "Something;Application Name=CustAppName;" , true , DisplayName = "[MYSQL|DAB hosted]:No modification of customer overridden 'Application Name' property.")] [DataRow(DatabaseType.MySQL, "Something1;App=CustAppName;Something2;" , "Something1;App=CustAppName;Something2;" , true, DisplayName = "[MySQL|DAB hosted]:No modification of customer overridden 'App' property.")] - [DataRow(DatabaseType.PostgreSQL, "Something;" , "Something;" , false, DisplayName = "[PGSQL|DAB OSS]:No addition of 'Application Name' property to connection string.]")] - [DataRow(DatabaseType.PostgreSQL, "Something;Application Name=CustAppName;", "Something;Application Name=CustAppName;", false, DisplayName = "[PGSQL|DAB OSS]:No modification of customer overridden 'Application Name' property.")] - [DataRow(DatabaseType.PostgreSQL, "Something;" , "Something;" , true , DisplayName = "[PGSQL|DAB hosted]:No addition of 'Application Name' property to connection string.")] - [DataRow(DatabaseType.PostgreSQL, "Something;Application Name=CustAppName;", "Something;Application Name=CustAppName;", true , DisplayName = "[PGSQL|DAB hosted]:No modification of customer overridden 'Application Name' property.")] [DataRow(DatabaseType.CosmosDB_NoSQL, "Something;" , "Something;" , false, DisplayName = "[COSMOSDB_NOSQL|DAB OSS]:No addition of 'Application Name' property to connection string.")] [DataRow(DatabaseType.CosmosDB_NoSQL, "Something;Application Name=CustAppName;", "Something;Application Name=CustAppName;", false, DisplayName = "[COSMOSDB_NOSQL|DAB OSS]:No modification of customer overridden 'Application Name' property.")] [DataRow(DatabaseType.CosmosDB_NoSQL, "Something;" , "Something;" , true , DisplayName = "[COSMOSDB_NOSQL|DAB hosted]:No addition of 'Application Name' property to connection string.")] diff --git a/src/Service.Tests/Unittests/RuntimeConfigLoaderJsonDeserializerTests.cs b/src/Service.Tests/Unittests/RuntimeConfigLoaderJsonDeserializerTests.cs index 0d754263bb..d8c6411b1f 100644 --- a/src/Service.Tests/Unittests/RuntimeConfigLoaderJsonDeserializerTests.cs +++ b/src/Service.Tests/Unittests/RuntimeConfigLoaderJsonDeserializerTests.cs @@ -42,12 +42,6 @@ public class RuntimeConfigLoaderJsonDeserializerTests /// Replacement used as key to get environment variable. /// Replacement value. [DataTestMethod] - [DataRow( - new string[] { "@env(')", "@env()", "@env(')'@env('()", "@env('@env()'", "@@eennvv((''''))" }, - new string[] { "@env(')", "@env()", "@env(')'@env('()", "@env('@env()'", "@@eennvv((''''))" }, - true, - true, - DisplayName = "Replacement strings that won't match.")] [DataRow( new string[] { "@env('envVarName')", "@env(@env('envVarName'))", "@en@env('envVarName')", "@env'()@env'@env('envVarName')')')" }, new string[] { "envVarValue", "@env(envVarValue)", "@enenvVarValue", "@env'()@env'envVarValue')')" }, @@ -539,6 +533,7 @@ private static string GetDataSourceConfigForGivenDatabase(string databaseType) { string options = ""; string databaseTypeEnvVariable = ""; + string connectionStringEnvVarName = "DATABASE_CONNECTION_STRING"; switch (databaseType) { @@ -561,6 +556,7 @@ private static string GetDataSourceConfigForGivenDatabase(string databaseType) break; case "postgresql": databaseTypeEnvVariable = $"@env('POSTGRESQL_DB_TYPE')"; + connectionStringEnvVarName = "DATABASE_CONNECTION_STRING_PGSQL"; options = @",""options"": null"; break; case "dwsql": @@ -572,7 +568,7 @@ private static string GetDataSourceConfigForGivenDatabase(string databaseType) return $@" {{ ""database-type"": ""{databaseTypeEnvVariable}"", - ""connection-string"": ""@env('DATABASE_CONNECTION_STRING')"" + ""connection-string"": ""@env('{connectionStringEnvVarName}')"" {options} }}"; } @@ -621,7 +617,8 @@ private static void ClearEnvironmentVariablesFromDictionary(Dictionary;Initial Catalog=<>;User ID=<>;Password=<>;" } + { "DATABASE_CONNECTION_STRING", "Data Source=<>;Initial Catalog=<>;User ID=<>;Password=<>;" }, + { "DATABASE_CONNECTION_STRING_PGSQL", "Host=<>;Database=<>;username=<>;password=<>" } }; /// diff --git a/src/Service.Tests/Unittests/SerializationDeserializationTests.cs b/src/Service.Tests/Unittests/SerializationDeserializationTests.cs index 036b2b5450..451c986a4c 100644 --- a/src/Service.Tests/Unittests/SerializationDeserializationTests.cs +++ b/src/Service.Tests/Unittests/SerializationDeserializationTests.cs @@ -259,9 +259,6 @@ public void TestColumnDefinitionNegativeCases() /// Temporarily ignore test for .net6 due to npgsql issue. /// [TestMethod] -#if NET6_0 - [Ignore] -#endif public void TestDictionaryDatabaseObjectSerializationDeserialization() { InitializeObjects(); diff --git a/src/Service.Tests/Unittests/SqlMetadataProviderUnitTests.cs b/src/Service.Tests/Unittests/SqlMetadataProviderUnitTests.cs index d6b1daa35e..3c7427971d 100644 --- a/src/Service.Tests/Unittests/SqlMetadataProviderUnitTests.cs +++ b/src/Service.Tests/Unittests/SqlMetadataProviderUnitTests.cs @@ -166,8 +166,14 @@ public async Task CheckExceptionForBadConnectionStringForMySql(string connection [DataRow("")] public async Task CheckExceptionForBadConnectionStringForPgSql(string connectionString) { + + // For strings that are an invalid format for the connection string builder, need to + // redirect std error to a string writer for comparison to expected error messaging later. + StringWriter sw = new(); + Console.SetError(sw); + DatabaseEngine = TestCategory.POSTGRESQL; - await CheckExceptionForBadConnectionStringHelperAsync(DatabaseEngine, connectionString); + await CheckExceptionForBadConnectionStringHelperAsync(DatabaseEngine, connectionString, sw); } /// @@ -192,6 +198,10 @@ private static async Task CheckExceptionForBadConnectionStringHelperAsync(string { _queryBuilder = new MySqlQueryBuilder(); } + else if (string.Equals(databaseType, TestCategory.POSTGRESQL)) + { + _queryBuilder = new PostgresQueryBuilder(); + } try { @@ -213,16 +223,13 @@ private static async Task CheckExceptionForBadConnectionStringHelperAsync(string } catch (DataApiBuilderException ex) { - // use contains to correctly cover db/user unique error messaging - // if sw is not null it holds the error messaging - string error = sw is null ? ex.Message : sw.ToString(); - Assert.IsTrue(error.Contains(DataApiBuilderException.CONNECTION_STRING_ERROR_MESSAGE)); + // Combine both the console and exception messages because they both + // may contain the connection string errors this function expects to exist. + string consoleMessages = sw is not null ? sw.ToString() : string.Empty; + string allErrorMessages = ex.Message + " " + consoleMessages; + Assert.IsTrue(allErrorMessages.Contains(DataApiBuilderException.CONNECTION_STRING_ERROR_MESSAGE)); Assert.AreEqual(DataApiBuilderException.SubStatusCodes.ErrorInInitialization, ex.SubStatusCode); Assert.AreEqual(HttpStatusCode.ServiceUnavailable, ex.StatusCode); - if (sw is not null) - { - Assert.IsTrue(error.StartsWith("Deserialization of the configuration file failed during a post-processing step.")); - } } TestHelper.UnsetAllDABEnvironmentVariables();