Skip to content

Commit

Permalink
Adds some integration tests for schema management (#157)
Browse files Browse the repository at this point in the history
* Add SchemaManager.Core.UnitTests project. Add tests for SqlSchemaManager

* Add integration test project, tests for BaseSchemaRunner

* Add integration tests for schema initializer, schema upgrade runner

* Fix db cleanup

* Dispose Connection

* PR feedback

* Filter out integration tests for now

* Revert changes to SqlServer.Web project

* PR feedback

* Prefix assembly name with Microsoft.Health
  • Loading branch information
YazanMSFT authored Mar 1, 2021
1 parent 81c9b23 commit 76f116e
Show file tree
Hide file tree
Showing 21 changed files with 548 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dotnetbuildtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
- name: Build with dotnet
run: dotnet build --configuration Release
- name: Run UnitTests with dotnet
run: dotnet test --configuration Release
run: dotnet test --configuration Release --filter FullyQualifiedName!~Integration
18 changes: 16 additions & 2 deletions Microsoft.Health.sln
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Client", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Client.UnitTests", "src\Microsoft.Health.Client.UnitTests\Microsoft.Health.Client.UnitTests.csproj", "{D4DD763B-5246-47F4-A618-211AA820B65E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchemaManager.Core", "tools\SchemaManager.Core\SchemaManager.Core.csproj", "{72816760-BDE2-4CAF-AF41-F7BEE8E26158}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SchemaManager.Core", "tools\SchemaManager.Core\SchemaManager.Core.csproj", "{72816760-BDE2-4CAF-AF41-F7BEE8E26158}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SchemaManager.Core.UnitTests", "test\SchemaManager.Core.UnitTests\SchemaManager.Core.UnitTests.csproj", "{0BEB2EE3-DBC3-4DC4-BB01-141DBFFED8D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Health.SqlServer.Tests.Integration", "test\Microsoft.Health.SqlServer.Tests.Integration\Microsoft.Health.SqlServer.Tests.Integration.csproj", "{5429C3A8-3429-43EA-BA05-E347CB4D1F61}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -159,6 +163,14 @@ Global
{72816760-BDE2-4CAF-AF41-F7BEE8E26158}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72816760-BDE2-4CAF-AF41-F7BEE8E26158}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72816760-BDE2-4CAF-AF41-F7BEE8E26158}.Release|Any CPU.Build.0 = Release|Any CPU
{0BEB2EE3-DBC3-4DC4-BB01-141DBFFED8D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0BEB2EE3-DBC3-4DC4-BB01-141DBFFED8D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0BEB2EE3-DBC3-4DC4-BB01-141DBFFED8D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0BEB2EE3-DBC3-4DC4-BB01-141DBFFED8D2}.Release|Any CPU.Build.0 = Release|Any CPU
{5429C3A8-3429-43EA-BA05-E347CB4D1F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5429C3A8-3429-43EA-BA05-E347CB4D1F61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5429C3A8-3429-43EA-BA05-E347CB4D1F61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5429C3A8-3429-43EA-BA05-E347CB4D1F61}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -187,9 +199,11 @@ Global
{0EAE32B6-97C9-43C8-ABA5-8C0BAD9ED864} = {8AD2A324-DAB5-4380-94A5-31F7D817C384}
{D4DD763B-5246-47F4-A618-211AA820B65E} = {8AD2A324-DAB5-4380-94A5-31F7D817C384}
{72816760-BDE2-4CAF-AF41-F7BEE8E26158} = {B70945F4-01A6-4351-955B-C4A2943B5E3B}
{0BEB2EE3-DBC3-4DC4-BB01-141DBFFED8D2} = {CCD9FF99-E177-446E-B9E5-9F570FD96A34}
{5429C3A8-3429-43EA-BA05-E347CB4D1F61} = {CCD9FF99-E177-446E-B9E5-9F570FD96A34}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {562bbd07-1817-4af5-ab66-8837cf849248}
RESX_SortFileContentOnSave = True
SolutionGuid = {562bbd07-1817-4af5-ab66-8837cf849248}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;

namespace Microsoft.Health.SqlServer.Extensions
{
public static class SqlConnectionExtensions
{
public static async Task TryOpenAsync(this SqlConnection connection, CancellationToken cancellationToken = default)
{
if (connection.State != System.Data.ConnectionState.Open)
{
await connection.OpenAsync(cancellationToken);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,17 @@ await Policy.Handle<SchemaManagerException>()

private async Task InstanceSchemaRecordCreatedAsync(CancellationToken cancellationToken)
{
if (!await _schemaManagerDataStore.InstanceSchemaRecordExistsAsync(cancellationToken))
try
{
throw new SchemaManagerException(Resources.InstanceSchemaRecordErrorMessage);
if (!await _schemaManagerDataStore.InstanceSchemaRecordExistsAsync(cancellationToken))
{
throw new SchemaManagerException(Resources.InstanceSchemaRecordErrorMessage);
}
}
catch (SqlException e) when (e.Message.Contains("Invalid object name", StringComparison.OrdinalIgnoreCase))
{
// Table doesn't exist
throw new SchemaManagerException(Resources.InstanceSchemaRecordTableNotFound, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,11 @@ public SchemaManagerException(string message)
{
Debug.Assert(!string.IsNullOrEmpty(message), "Exception message should not be empty");
}

public SchemaManagerException(string message, Exception innerException)
: base(message, innerException)
{
Debug.Assert(!string.IsNullOrEmpty(message), "Exception message should not be empty");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Health.SqlServer.Features.Schema.Manager
{
public interface IBaseSchemaRunner
{
public Task EnsureBaseSchemaExistsAsync(CancellationToken cancellationToken);

public Task EnsureInstanceSchemaRecordExistsAsync(CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading.Tasks;
using EnsureThat;
using Microsoft.Data.SqlClient;
using Microsoft.Health.SqlServer.Extensions;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;

Expand All @@ -33,7 +34,7 @@ public async Task ExecuteScriptAndCompleteSchemaVersionAsync(string script, int

using (var connection = await _sqlConnectionFactory.GetSqlConnectionAsync(cancellationToken: cancellationToken))
{
await connection.OpenAsync(cancellationToken);
await connection.TryOpenAsync(cancellationToken);
ServerConnection serverConnection = new ServerConnection(connection);

try
Expand Down Expand Up @@ -65,7 +66,7 @@ public async Task DeleteSchemaVersionAsync(int version, string status, Cancellat

using (var connection = await _sqlConnectionFactory.GetSqlConnectionAsync(cancellationToken: cancellationToken))
{
await connection.OpenAsync(cancellationToken);
await connection.TryOpenAsync(cancellationToken);

var deleteQuery = "DELETE FROM dbo.SchemaVersion WHERE Version = @version AND Status = @status";
using (var deleteCommand = new SqlCommand(deleteQuery, connection))
Expand All @@ -83,7 +84,7 @@ public async Task<int> GetCurrentSchemaVersionAsync(CancellationToken cancellati
{
using (var connection = await _sqlConnectionFactory.GetSqlConnectionAsync(cancellationToken: cancellationToken))
{
await connection.OpenAsync(cancellationToken);
await connection.TryOpenAsync(cancellationToken);

try
{
Expand Down Expand Up @@ -125,7 +126,7 @@ public async Task ExecuteScriptAsync(string script, CancellationToken cancellati

using (var connection = await _sqlConnectionFactory.GetSqlConnectionAsync(cancellationToken: cancellationToken))
{
await connection.OpenAsync(cancellationToken);
await connection.TryOpenAsync(cancellationToken);
var server = new Server(new ServerConnection(connection));

server.ConnectionContext.ExecuteNonQuery(script);
Expand All @@ -139,7 +140,7 @@ public async Task<bool> BaseSchemaExistsAsync(CancellationToken cancellationToke

using (var connection = await _sqlConnectionFactory.GetSqlConnectionAsync(cancellationToken: cancellationToken))
{
await connection.OpenAsync(cancellationToken);
await connection.TryOpenAsync(cancellationToken);

using (var command = new SqlCommand(procedureQuery, connection))
{
Expand All @@ -158,7 +159,7 @@ public async Task<bool> InstanceSchemaRecordExistsAsync(CancellationToken cancel

using (var connection = await _sqlConnectionFactory.GetSqlConnectionAsync(cancellationToken: cancellationToken))
{
await connection.OpenAsync(cancellationToken);
await connection.TryOpenAsync(cancellationToken);

using (var command = new SqlCommand(procedureQuery, connection))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using MediatR;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;
using Microsoft.Health.SqlServer.Extensions;
using Microsoft.Health.SqlServer.Features.Schema.Extensions;
using Microsoft.Health.SqlServer.Features.Schema.Manager;

Expand Down Expand Up @@ -60,7 +61,7 @@ public async Task ApplySchemaAsync(int version, bool applyFullSchemaSnapshot, Ca

await CompleteSchemaVersionAsync(version, cancellationToken);

_mediator.NotifySchemaUpgradedAsync(version, applyFullSchemaSnapshot).Wait();
await _mediator.NotifySchemaUpgradedAsync(version, applyFullSchemaSnapshot);
_logger.LogInformation("Completed applying schema {version}", version);
}

Expand Down Expand Up @@ -92,7 +93,7 @@ private async Task UpsertSchemaVersionAsync(int schemaVersion, string status, Ca
upsertCommand.Parameters.AddWithValue("@version", schemaVersion);
upsertCommand.Parameters.AddWithValue("@status", status);

await connection.OpenAsync(cancellationToken);
await connection.TryOpenAsync(cancellationToken);
await upsertCommand.ExecuteNonQueryAsync(cancellationToken);
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/Microsoft.Health.SqlServer/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.Health.SqlServer/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@
<data name="InstanceSchemaRecordErrorMessage" xml:space="preserve">
<value>The current version information could not be fetched from the service. Please try again.</value>
</data>
<data name="InstanceSchemaRecordTableNotFound" xml:space="preserve">
<value>InstanceSchema table does not exist.</value>
</data>
<data name="InsufficientDatabasePermissionsMessage" xml:space="preserve">
<value>Insufficient permissions to create the database.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Health.SqlServer.Features.Schema.Manager;
using Microsoft.Health.SqlServer.Features.Schema.Manager.Exceptions;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Health.SqlServer.Tests.Integration.Features.Schema.Manager
{
public class BaseSchemaRunnerTests : SqlIntegrationTestBase
{
private readonly BaseSchemaRunner _runner;
private readonly ISchemaManagerDataStore _dataStore;

public BaseSchemaRunnerTests(ITestOutputHelper output)
: base(output)
{
var sqlConnectionFactory = new DefaultSqlConnectionFactory(ConnectionStringProvider);
_dataStore = new SchemaManagerDataStore(sqlConnectionFactory);

_runner = new BaseSchemaRunner(sqlConnectionFactory, _dataStore, ConnectionStringProvider, NullLogger<BaseSchemaRunner>.Instance);
}

[Fact]
public async Task EnsureBaseSchemaExist_DoesNotExist_CreatesIt()
{
Assert.False(await _dataStore.BaseSchemaExistsAsync(CancellationToken.None));
await _runner.EnsureBaseSchemaExistsAsync(CancellationToken.None);
Assert.True(await _dataStore.BaseSchemaExistsAsync(CancellationToken.None));
}

[Fact]
public async Task EnsureBaseSchemaExist_Exists_DoesNothing()
{
Assert.False(await _dataStore.BaseSchemaExistsAsync(CancellationToken.None));
await _runner.EnsureBaseSchemaExistsAsync(CancellationToken.None);
Assert.True(await _dataStore.BaseSchemaExistsAsync(CancellationToken.None));
await _runner.EnsureBaseSchemaExistsAsync(CancellationToken.None);
Assert.True(await _dataStore.BaseSchemaExistsAsync(CancellationToken.None));
}

[Fact]
public async Task EnsureInstanceSchemaRecordExists_WhenNotExists_Throws()
{
await Assert.ThrowsAsync<SchemaManagerException>(() => _runner.EnsureInstanceSchemaRecordExistsAsync(CancellationToken.None));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Health.SqlServer.Features.Schema;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Health.SqlServer.Tests.Integration.Features.Schema
{
public class SchemaInitializerTests : SqlIntegrationTestBase
{
public SchemaInitializerTests(ITestOutputHelper outputHelper)
: base(outputHelper)
{
}

[Fact]
public async Task DatabaseDoesNotExist_DoesDatabaseExistAsync_ReturnsFalse()
{
Assert.False(await SchemaInitializer.DoesDatabaseExistAsync(Connection, "doesnotexist", CancellationToken.None));
}

[Fact]
public async Task DatabaseExists_DoesDatabaseExistAsync_ReturnsTrue()
{
const string dbName = "willexist";

try
{
Assert.False(await SchemaInitializer.DoesDatabaseExistAsync(Connection, dbName, CancellationToken.None));
Assert.True(await SchemaInitializer.CreateDatabaseAsync(Connection, dbName, CancellationToken.None));
Assert.True(await SchemaInitializer.DoesDatabaseExistAsync(Connection, dbName, CancellationToken.None));
}
finally
{
await DeleteDatabaseAsync(dbName);
}
}
}
}
Loading

0 comments on commit 76f116e

Please sign in to comment.