Skip to content

Commit

Permalink
fix migration 5->6 not run
Browse files Browse the repository at this point in the history
fixes if DatabaseVersion == 4, migration 5 -> 6 will not run resulting in invalid schema.

adds build.yml github action which builds and runs test on PR's and push'es to master branch.

also addresses  matteofabbri#81
  • Loading branch information
gottscj committed Nov 4, 2021
1 parent c685656 commit 0bf17b4
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 68 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Restore dependencies
run: dotnet restore ./src/AspNetCore.Identity.Mongo/
- name: Build
run: dotnet build ./src/AspNetCore.Identity.Mongo/ -c Release --no-restore
- name: Test
run: dotnet test ./Tests -c Release
7 changes: 7 additions & 0 deletions AspNetCore.Identity.Mongo.sln
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{767BB7BE
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7282BE10-249F-4308-BB55-EA2346584782}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{E3254B66-8E82-463F-9550-DFE8563341F8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -40,13 +42,18 @@ Global
{25627014-8963-42D8-B1EC-A56FBDE16937}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25627014-8963-42D8-B1EC-A56FBDE16937}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25627014-8963-42D8-B1EC-A56FBDE16937}.Release|Any CPU.Build.0 = Release|Any CPU
{E3254B66-8E82-463F-9550-DFE8563341F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3254B66-8E82-463F-9550-DFE8563341F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3254B66-8E82-463F-9550-DFE8563341F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3254B66-8E82-463F-9550-DFE8563341F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{39E2704C-B0DE-4BD2-849F-5B51332EE03F} = {7282BE10-249F-4308-BB55-EA2346584782}
{25627014-8963-42D8-B1EC-A56FBDE16937} = {238A25AE-691E-4A86-9B5E-727DFC186F33}
{E3254B66-8E82-463F-9550-DFE8563341F8} = {767BB7BE-35C8-424C-B873-FEBD69EE6C1A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3806160E-9B49-41B7-A532-95CD600CE2CF}
Expand Down
76 changes: 76 additions & 0 deletions Tests/MigrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AspNetCore.Identity.Mongo.Migrations;
using AspNetCore.Identity.Mongo.Model;
using MongoDB.Bson;
using MongoDB.Driver;
using NUnit.Framework;

namespace Tests
{
[TestFixture]
public class MigrationTests
{
private IDisposable _runner;
private IMongoClient _client;
private IMongoDatabase _db;

[OneTimeSetUp]
public void OneTimeSetup()
{
var runner = Mongo2Go.MongoDbRunner.Start();
_client = new MongoClient(runner.ConnectionString);
_db = _client.GetDatabase("migration-tests");
_runner = runner;
}

[OneTimeTearDown]
public void OneTimeTearDown()
{
_runner.Dispose();
}

[Test, Category("unit")]
public void Apply_Schema4_AllMigrationsApplied()
{
// ARRANGE
var history = _db.GetCollection<MigrationHistory>("migrations");
var users = _db.GetCollection<MigrationMongoUser>("users");
var roles = _db.GetCollection<MongoRole<ObjectId>>("roles");
var initialVersion = 4;
var existingHistory = new List<MigrationHistory>
{
new MigrationHistory
{
Id = ObjectId.GenerateNewId(),
DatabaseVersion = 3,
InstalledOn = DateTime.UtcNow.AddDays(-2)
},
new MigrationHistory
{
Id = ObjectId.GenerateNewId(),
DatabaseVersion = initialVersion,
InstalledOn = DateTime.UtcNow.AddDays(-1)
}
};
history.InsertMany(existingHistory);


// ACT
Migrator.Apply<MigrationMongoUser, MongoRole<ObjectId>, ObjectId>(history, users, roles);

// ASSERT
var historyAfter = history
.Find("{}")
.SortBy(h => h.DatabaseVersion)
.ToList();

var expectedHistoryObjectsAfter = Migrator.CurrentVersion - initialVersion + existingHistory.Count;
Assert.That(historyAfter.Count, Is.EqualTo(expectedHistoryObjectsAfter),
() => "Expected all migrations to run");
Assert.That(historyAfter.Last().DatabaseVersion, Is.EqualTo(Migrator.CurrentVersion));
}
}
}
21 changes: 21 additions & 0 deletions Tests/Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Mongo2Go" Version="3.1.3" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="coverlet.collector" Version="3.0.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\src\AspNetCore.Identity.Mongo\AspNetCore.Identity.Mongo.csproj" />
</ItemGroup>

</Project>
62 changes: 62 additions & 0 deletions src/AspNetCore.Identity.Mongo/Migrations/BaseMigration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AspNetCore.Identity.Mongo.Model;
using MongoDB.Bson;
using MongoDB.Driver;

namespace AspNetCore.Identity.Mongo.Migrations
{
internal abstract class BaseMigration
{
private static List<BaseMigration> _migrations;
public static List<BaseMigration> Migrations {
get
{
if (_migrations == null)
{
_migrations = typeof(BaseMigration)
.Assembly
.GetTypes()
.Where(t => typeof(BaseMigration).IsAssignableFrom(t))
.Select(t => t.GetConstructor(Type.EmptyTypes)?.Invoke(Array.Empty<object>()))
.Where(o => o != null)
.Cast<BaseMigration>()
.OrderBy(m => m.Version)
.ToList();
if (_migrations.Count != _migrations.Select(m => m.Version).Distinct().Count())
{
throw new InvalidOperationException("Migration versions must be unique, please check versions");
}
}

return _migrations;
}
}


public abstract int Version { get; }

public MigrationHistory Apply<TUser, TRole, TKey>(IMongoCollection<TUser> usersCollection,
IMongoCollection<TRole> rolesCollection)
where TKey : IEquatable<TKey>
where TUser : MigrationMongoUser<TKey>
where TRole : MongoRole<TKey>
{
DoApply<TUser, TRole, TKey>(usersCollection, rolesCollection);
return new MigrationHistory
{
Id = ObjectId.GenerateNewId(),
InstalledOn = DateTime.UtcNow,
DatabaseVersion = Version + 1
};
}

protected abstract void DoApply<TUser, TRole, TKey>(
IMongoCollection<TUser> usersCollection, IMongoCollection<TRole> rolesCollection)
where TKey : IEquatable<TKey>
where TUser : MigrationMongoUser<TKey>
where TRole : MongoRole<TKey>;
}
}
109 changes: 41 additions & 68 deletions src/AspNetCore.Identity.Mongo/Migrations/Migrator.cs
Original file line number Diff line number Diff line change
@@ -1,68 +1,41 @@
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using AspNetCore.Identity.Mongo.Model;
using AspNetCore.Identity.Mongo.Mongo;
using MongoDB.Driver;

namespace AspNetCore.Identity.Mongo.Migrations
{
internal static class Migrator
{
//Starting from 4 in case we want to implement migrations for previous versions
public static int CurrentVersion = 6;

public static void Apply<TUser, TRole, TKey>(IMongoCollection<MigrationHistory> migrationCollection, IMongoCollection<TUser> usersCollection, IMongoCollection<TRole> rolesCollection)
where TKey : IEquatable<TKey>
where TUser : MigrationMongoUser<TKey>
where TRole : MongoRole<TKey>
{
var history = migrationCollection.Find(_ => true).ToList();

if (history.Count > 0)
{
var lastHistory = history.OrderBy(x => x.DatabaseVersion).Last();

if (lastHistory.DatabaseVersion == CurrentVersion)
{
return;
}

// 4 -> 5
if (lastHistory.DatabaseVersion == 4)
{
var users = usersCollection.Find(x => !string.IsNullOrEmpty(x.AuthenticatorKey)).ToList();
foreach (var user in users)
{
var tokens = user.Tokens;
tokens.Add(new Microsoft.AspNetCore.Identity.IdentityUserToken<string>()
{
UserId = user.Id.ToString(),
Value = user.AuthenticatorKey,
LoginProvider = "[AspNetUserStore]",
Name = "AuthenticatorKey"
});
usersCollection.UpdateOne(x => x.Id.Equals(user.Id),
Builders<TUser>.Update.Set(x => x.Tokens, tokens)
.Set(x => x.AuthenticatorKey, null));
}
}

// 5 -> 6
if (lastHistory.DatabaseVersion == 5)
{
usersCollection.UpdateMany(x => true,
Builders<TUser>.Update.Unset(x => x.AuthenticatorKey)
.Unset(x => x.RecoveryCodes));
}
}

migrationCollection.InsertOne(new MigrationHistory
{
InstalledOn = DateTime.Now,
DatabaseVersion = CurrentVersion
});
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using AspNetCore.Identity.Mongo.Model;
using AspNetCore.Identity.Mongo.Mongo;
using MongoDB.Driver;

[assembly: InternalsVisibleTo("Tests")]

namespace AspNetCore.Identity.Mongo.Migrations
{
internal static class Migrator
{
//Starting from 4 in case we want to implement migrations for previous versions
public static int CurrentVersion = 6;

public static void Apply<TUser, TRole, TKey>(IMongoCollection<MigrationHistory> migrationCollection,
IMongoCollection<TUser> usersCollection, IMongoCollection<TRole> rolesCollection)
where TKey : IEquatable<TKey>
where TUser : MigrationMongoUser<TKey>
where TRole : MongoRole<TKey>
{
var version = migrationCollection
.Find(h => true)
.SortByDescending(h => h.DatabaseVersion)
.Project(h => h.DatabaseVersion)
.FirstOrDefault();

var appliedMigrations = BaseMigration.Migrations
.Where(m => m.Version >= version)
.Select(migration => migration.Apply<TUser, TRole, TKey>(usersCollection, rolesCollection))
.ToList();

migrationCollection.InsertMany(appliedMigrations);

}
}
}
31 changes: 31 additions & 0 deletions src/AspNetCore.Identity.Mongo/Migrations/Schema4Migration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using MongoDB.Driver;

namespace AspNetCore.Identity.Mongo.Migrations
{
internal class Schema4Migration: BaseMigration
{
public override int Version { get; } = 4;

protected override void DoApply<TUser, TRole, TKey>(
IMongoCollection<TUser> usersCollection,
IMongoCollection<TRole> rolesCollection)
{
var users = usersCollection.Find(x => !string.IsNullOrEmpty(x.AuthenticatorKey)).ToList();
foreach (var user in users)
{
var tokens = user.Tokens;
tokens.Add(new Microsoft.AspNetCore.Identity.IdentityUserToken<string>()
{
UserId = user.Id.ToString(),
Value = user.AuthenticatorKey,
LoginProvider = "[AspNetUserStore]",
Name = "AuthenticatorKey"
});
usersCollection.UpdateOne(x => x.Id.Equals(user.Id),
Builders<TUser>.Update.Set(x => x.Tokens, tokens)
.Set(x => x.AuthenticatorKey, null));

}
}
}
}
18 changes: 18 additions & 0 deletions src/AspNetCore.Identity.Mongo/Migrations/Schema5Migration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using MongoDB.Driver;

namespace AspNetCore.Identity.Mongo.Migrations
{
internal class Schema5Migration : BaseMigration
{
public override int Version { get; } = 5;

protected override void DoApply<TUser, TRole, TKey>(
IMongoCollection<TUser> usersCollection,
IMongoCollection<TRole> rolesCollection)
{
usersCollection.UpdateMany(x => true,
Builders<TUser>.Update.Unset(x => x.AuthenticatorKey)
.Unset(x => x.RecoveryCodes));
}
}
}

0 comments on commit 0bf17b4

Please sign in to comment.