Skip to content

Commit

Permalink
Server side changes for Schema migration (#19)
Browse files Browse the repository at this point in the history
* Initial commit

* Minor cleanup

* Fixes background job

* Adds TestFixture

* Skips e2e test

* Addresses feedback

* Fixes status consistency

* Addresses feedback.

* Adds index on timeout

* Turned on automatic updates

* Minor refactoring

* Minor code refactor

* Addresses feedback

* Updates test and adds 2.diff.sql

* Fixes fixture configuration

* Adds a validation

* Adds enum for SchemaVersion status

* Fixes build
  • Loading branch information
rbans96 authored May 11, 2020
1 parent a4fd3b0 commit 220693c
Show file tree
Hide file tree
Showing 45 changed files with 1,949 additions and 49 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ ipch/
*.vspx
*.sap

# Visual Studio Trace Files
*.e2e

# TFS 2012 Local Workspace
$tf/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// -------------------------------------------------------------------------------------------------

using System;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Logging.Abstractions;
Expand All @@ -19,10 +20,12 @@ public class SchemaControllerTests
{
private readonly SchemaController _schemaController;
private readonly SchemaInformation _schemaInformation;
private readonly IMediator _mediator;

public SchemaControllerTests()
{
_schemaInformation = new SchemaInformation((int)TestSchemaVersion.Version1, (int)TestSchemaVersion.Version3);
_mediator = Substitute.For<IMediator>();

var urlHelperFactory = Substitute.For<IUrlHelperFactory>();
var urlHelper = Substitute.For<IUrlHelper>();
Expand All @@ -31,7 +34,7 @@ public SchemaControllerTests()

var scriptProvider = Substitute.For<IScriptProvider>();

_schemaController = new SchemaController(_schemaInformation, scriptProvider, urlHelperFactory, NullLogger<SchemaController>.Instance);
_schemaController = new SchemaController(_schemaInformation, scriptProvider, urlHelperFactory, _mediator, NullLogger<SchemaController>.Instance);
}

[Fact]
Expand All @@ -56,6 +59,15 @@ public void GivenAnAvailableVersionsRequest_WhenCurrentVersionIsNull_ThenAllVers
JToken firstResult = jArrayResult.First;
Assert.Equal(1, firstResult["id"]);
Assert.Equal("https://localhost/script", firstResult["script"]);

// Ensure available versions are in the ascending order
jArrayResult.RemoveAt(0);
var previousId = (int)firstResult["id"];
foreach (JToken item in jArrayResult)
{
var currentId = (int)item["id"];
Assert.True(previousId < currentId, "The available versions are not in the ascending order");
}
}

[Fact]
Expand All @@ -74,17 +86,5 @@ public void GivenAnAvailableVersionsRequest_WhenCurrentVersionNotNull_ThenCorrec
Assert.Equal(2, firstResult["id"]);
Assert.Equal("https://localhost/script", firstResult["script"]);
}

[Fact]
public void GivenACurrentVersionRequest_WhenNotImplemented_ThenNotImplementedShouldBeThrown()
{
Assert.Throws<NotImplementedException>(() => _schemaController.CurrentVersion());
}

[Fact]
public void GivenACompatibilityRequest_WhenNotImplemented_ThenNotImplementedShouldBeThrown()
{
Assert.Throws<NotImplementedException>(() => _schemaController.Compatibility());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// -------------------------------------------------------------------------------------------------
// 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 MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Health.Extensions.DependencyInjection;
using Microsoft.Health.SqlServer.Api.Features;
using Microsoft.Health.SqlServer.Features.Schema;
using Microsoft.Health.SqlServer.Features.Schema.Extensions;
using Microsoft.Health.SqlServer.Features.Schema.Messages.Get;
using Microsoft.Health.SqlServer.Features.Schema.Model;
using NSubstitute;
using Xunit;

namespace Microsoft.Health.SqlServer.Api.UnitTests.Features
{
public class CompatibilityVersionHandlerTests
{
private readonly ISchemaDataStore _schemaMigrationDataStore;
private readonly IMediator _mediator;
private readonly CancellationToken _cancellationToken;

public CompatibilityVersionHandlerTests()
{
_schemaMigrationDataStore = Substitute.For<ISchemaDataStore>();
var collection = new ServiceCollection();
collection.Add(sp => new CompatibilityVersionHandler(_schemaMigrationDataStore)).Singleton().AsSelf().AsImplementedInterfaces();

ServiceProvider provider = collection.BuildServiceProvider();
_mediator = new Mediator(type => provider.GetService(type));
_cancellationToken = new CancellationTokenSource().Token;
}

[Fact]
public async Task GivenAMediator_WhenCompatibleRequest_ThenReturnsCompatibleVersions()
{
_schemaMigrationDataStore.GetLatestCompatibleVersionsAsync(Arg.Any<CancellationToken>())
.Returns(new CompatibleVersions(1, 3));
GetCompatibilityVersionResponse response = await _mediator.GetCompatibleVersionAsync(_cancellationToken);

Assert.Equal(1, response.CompatibleVersions.Min);
Assert.Equal(3, response.CompatibleVersions.Max);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Health.Extensions.DependencyInjection;
using Microsoft.Health.SqlServer.Api.Features;
using Microsoft.Health.SqlServer.Features.Schema;
using Microsoft.Health.SqlServer.Features.Schema.Extensions;
using Microsoft.Health.SqlServer.Features.Schema.Messages.Get;
using Microsoft.Health.SqlServer.Features.Schema.Model;
using NSubstitute;
using Xunit;

namespace Microsoft.Health.SqlServer.Api.UnitTests.Features
{
public class CurrentVersionHandlerTests
{
private readonly ISchemaDataStore _schemaDataStore;
private readonly IMediator _mediator;
private readonly CancellationToken _cancellationToken;

public CurrentVersionHandlerTests()
{
_schemaDataStore = Substitute.For<ISchemaDataStore>();
var collection = new ServiceCollection();
collection.Add(sp => new CurrentVersionHandler(_schemaDataStore)).Singleton().AsSelf().AsImplementedInterfaces();

ServiceProvider provider = collection.BuildServiceProvider();
_mediator = new Mediator(type => provider.GetService(type));
_cancellationToken = new CancellationTokenSource().Token;
}

[Fact]
public async Task GivenACurrentMediator_WhenCurrentRequest_ThenReturnsCurrentVersionInformation()
{
string status = "completed";

var mockCurrentVersions = new List<CurrentVersionInformation>()
{
new CurrentVersionInformation(1, (SchemaVersionStatus)Enum.Parse(typeof(SchemaVersionStatus), status, true), new List<string>() { "server1", "server2" }),
new CurrentVersionInformation(2, (SchemaVersionStatus)Enum.Parse(typeof(SchemaVersionStatus), status, true), new List<string>()),
};

_schemaDataStore.GetCurrentVersionAsync(Arg.Any<CancellationToken>())
.Returns(mockCurrentVersions);
GetCurrentVersionResponse response = await _mediator.GetCurrentVersionAsync(_cancellationToken);
var currentVersionsResponse = response.CurrentVersions;

Assert.Equal(mockCurrentVersions.Count, currentVersionsResponse.Count);
Assert.Equal(1, currentVersionsResponse[0].Id);
Assert.Equal(SchemaVersionStatus.Completed, currentVersionsResponse[0].Status);
Assert.Equal(2, currentVersionsResponse[0].Servers.Count);
}

[Fact]
public async Task GivenACurrentMediator_WhenCurrentRequestAndEmptySchemaVersionTable_ThenReturnsEmptyArray()
{
var mockCurrentVersions = new List<CurrentVersionInformation>();

_schemaDataStore.GetCurrentVersionAsync(Arg.Any<CancellationToken>())
.Returns(mockCurrentVersions);

GetCurrentVersionResponse response = await _mediator.GetCurrentVersionAsync(_cancellationToken);

Assert.Equal(0, response.CurrentVersions.Count);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
Expand All @@ -17,6 +18,7 @@
using Microsoft.Health.SqlServer.Api.Controllers;
using Microsoft.Health.SqlServer.Api.Features.Filters;
using Microsoft.Health.SqlServer.Api.UnitTests.Controllers;
using Microsoft.Health.SqlServer.Features.Exceptions;
using Microsoft.Health.SqlServer.Features.Schema;
using NSubstitute;
using Xunit;
Expand All @@ -36,6 +38,7 @@ public HttpExceptionFilterTests()
new SchemaInformation((int)TestSchemaVersion.Version1, (int)TestSchemaVersion.Version3),
Substitute.For<IScriptProvider>(),
Substitute.For<IUrlHelperFactory>(),
Substitute.For<IMediator>(),
NullLogger<SchemaController>.Instance));
}

Expand Down Expand Up @@ -68,5 +71,35 @@ public void GivenANotFoundException_WhenExecutingAnAction_ThenTheResponseShouldB
Assert.NotNull(result);
Assert.Equal((int)HttpStatusCode.NotFound, result.StatusCode);
}

[Fact]
public void GivenASqlRecordNotFoundException_WhenExecutingAnAction_ThenTheResponseShouldBeAJsonResultWithNotFoundStatusCode()
{
var filter = new HttpExceptionFilterAttribute();

_context.Exception = Substitute.For<SqlRecordNotFoundException>("SQL record not found");

filter.OnActionExecuted(_context);

var result = _context.Result as JsonResult;

Assert.NotNull(result);
Assert.Equal((int)HttpStatusCode.NotFound, result.StatusCode);
}

[Fact]
public void GivenASqlOperationFailedException_WhenExecutingAnAction_ThenTheResponseShouldBeAJsonResultWithInternalServerErrorAsStatusCode()
{
var filter = new HttpExceptionFilterAttribute();

_context.Exception = Substitute.For<SqlOperationFailedException>("SQL operation failed");

filter.OnActionExecuted(_context);

var result = _context.Result as JsonResult;

Assert.NotNull(result);
Assert.Equal((int)HttpStatusCode.InternalServerError, result.StatusCode);
}
}
}
21 changes: 13 additions & 8 deletions src/Microsoft.Health.SqlServer.Api/Controllers/SchemaController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EnsureThat;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Health.SqlServer.Api.Features.Filters;
using Microsoft.Health.SqlServer.Api.Features.Routing;
using Microsoft.Health.SqlServer.Features.Schema;
using Microsoft.Health.SqlServer.Features.Schema.Extensions;

namespace Microsoft.Health.SqlServer.Api.Controllers
{
Expand All @@ -24,17 +26,20 @@ public class SchemaController : Controller
private readonly IScriptProvider _scriptProvider;
private readonly IUrlHelperFactory _urlHelperFactory;
private readonly ILogger<SchemaController> _logger;
private readonly IMediator _mediator;

public SchemaController(SchemaInformation schemaInformation, IScriptProvider scriptProvider, IUrlHelperFactory urlHelperFactoryFactory, ILogger<SchemaController> logger)
public SchemaController(SchemaInformation schemaInformation, IScriptProvider scriptProvider, IUrlHelperFactory urlHelperFactoryFactory, IMediator mediator, ILogger<SchemaController> logger)
{
EnsureArg.IsNotNull(schemaInformation, nameof(schemaInformation));
EnsureArg.IsNotNull(scriptProvider, nameof(scriptProvider));
EnsureArg.IsNotNull(urlHelperFactoryFactory, nameof(urlHelperFactoryFactory));
EnsureArg.IsNotNull(mediator, nameof(mediator));
EnsureArg.IsNotNull(logger, nameof(logger));

_schemaInformation = schemaInformation;
_scriptProvider = scriptProvider;
_urlHelperFactory = urlHelperFactoryFactory;
_mediator = mediator;
_logger = logger;
}

Expand All @@ -61,11 +66,11 @@ public ActionResult AvailableVersions()
[HttpGet]
[AllowAnonymous]
[Route(KnownRoutes.Current)]
public ActionResult CurrentVersion()
public async Task<ActionResult> CurrentVersionAsync()
{
_logger.LogInformation("Attempting to get current schemas");

throw new NotImplementedException(Resources.CurrentVersionNotImplemented);
var currentVersionResponse = await _mediator.GetCurrentVersionAsync(HttpContext.RequestAborted);
return new JsonResult(currentVersionResponse.CurrentVersions);
}

[HttpGet]
Expand All @@ -81,11 +86,11 @@ public FileContentResult SqlScript(int id)
[HttpGet]
[AllowAnonymous]
[Route(KnownRoutes.Compatibility)]
public ActionResult Compatibility()
public async Task<ActionResult> CompatibilityAsync()
{
_logger.LogInformation("Attempting to get compatibility");

throw new NotImplementedException(Resources.CompatibilityNotImplemented);
var compatibleResponse = await _mediator.GetCompatibleVersionAsync(HttpContext.RequestAborted);
return new JsonResult(compatibleResponse.CompatibleVersions);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// -------------------------------------------------------------------------------------------------
// 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 EnsureThat;
using MediatR;
using Microsoft.Health.SqlServer.Features.Schema;
using Microsoft.Health.SqlServer.Features.Schema.Messages.Get;
using Microsoft.Health.SqlServer.Features.Schema.Model;

namespace Microsoft.Health.SqlServer.Api.Features
{
public class CompatibilityVersionHandler : IRequestHandler<GetCompatibilityVersionRequest, GetCompatibilityVersionResponse>
{
private readonly ISchemaDataStore _schemaDataStore;

public CompatibilityVersionHandler(ISchemaDataStore schemaDataStore)
{
EnsureArg.IsNotNull(schemaDataStore, nameof(schemaDataStore));
_schemaDataStore = schemaDataStore;
}

public async Task<GetCompatibilityVersionResponse> Handle(GetCompatibilityVersionRequest request, CancellationToken cancellationToken)
{
EnsureArg.IsNotNull(request, nameof(request));

CompatibleVersions compatibleVersions = await _schemaDataStore.GetLatestCompatibleVersionsAsync(cancellationToken);

return new GetCompatibilityVersionResponse(compatibleVersions);
}
}
}
Loading

0 comments on commit 220693c

Please sign in to comment.