diff --git a/.build/NuGet.config b/.build/NuGet.config deleted file mode 100644 index 21a6bf8..0000000 --- a/.build/NuGet.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 09faa15..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# Make the Turing team owner of the entire repo -* @sherweb/dothraki diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 0c2fc8d..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,24 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -registries: - nuget-org: - type: nuget-feed - url: https://api.nuget.org/v3/index.json - sherweb: - type: nuget-feed - url: https://nuget.pkg.github.com/Sherweb/index.json - username: op-github-svc - password: ${{secrets.OP_GITHUB_SVC_PAT}} -updates: - - package-ecosystem: "nuget" # See documentation for possible values - directory: "/" # Location of package manifests - registries: - - nuget-org - - sherweb - schedule: - interval: "daily" - time: "00:00" \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 81a831b..0000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,43 +0,0 @@ -## Summary of changes[^practices] - -````text -This section is where you explain the changes you made. - -Remove this block when done. -```` - -Related Work Item: AB#xxxxx - -## Dependencies - -````text -This section is where you list all the dependencies of this PR. - -Remove this block when done. -Remove this section if empty. -```` - -## Dependents[^deps] - -````text -This section is where you list all the changes to be done after this PR -(breaking changes in contracts, consumers to update, etc.). - -Remove this block when done. -Remove this section if empty. -```` - -### Checks - -- [ ] I have labeled the PR correctly -- [ ] I have set the PR title with the proper versioning[^versioning] prefix (will help for the merge commit message) -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] I have maximized the division of this story and could not make multiple smaller PRs -- [ ] I have used stable versions for every package in this PR (if not dependending on an unclosed PR) -- [ ] The changelog is written in the story - -![image](https://app.office-protect.com/assets/office-protect-logo-white.png) - -[^practices]: https://wiki.sherweb.com/display/TUR/Meilleures+pratiques+pour+la+revue+de+pull+requests -[^deps]: https://github.com/sherweb/OfficeProtect.Utils/blob/master/dependencies/README.md -[^versioning]: https://github.com/sherweb/OfficeProtect.Application/blob/master/docs/software/application-versioning.md diff --git a/.github/release.yml b/.github/release.yml deleted file mode 100644 index 08793e7..0000000 --- a/.github/release.yml +++ /dev/null @@ -1,14 +0,0 @@ -changelog: - categories: - - title: Breaking changes - labels: - - breaking change - - title: Features - labels: - - feature - - title: Changes - labels: - - change - - title: Fixes - labels: - - bug diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..4f08da3 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,23 @@ +name: Build and test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + DOTNET_NOLOGO: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-dotnet@v5 + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build -c Release --no-restore + - name: Test + run: dotnet test -c Release --no-build --framework net10.0 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..ebf65ac --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,33 @@ +name: Publish + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-[a-z]+[0-9]+" + +env: + DOTNET_NOLOGO: true + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-dotnet@v5 + - name: Set VERSION variable from tag + run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV + - name: Build + run: dotnet build -c Release /p:Version=${VERSION} + - name: Test + run: dotnet test -c Release /p:Version=${VERSION} --no-build --framework net10.0 + - name: Pack + run: dotnet pack -c Release /p:Version=${VERSION} --no-build --output . + - name: Push + env: + NUGET_SOURCE: ${{ vars.NUGET_SOURCE }} + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: dotnet nuget push Mongo.Migration.${VERSION}.nupkg --source $NUGET_SOURCE --api-key "$NUGET_API_KEY" diff --git a/.pipelines/azure-pipelines-pr.yaml b/.pipelines/azure-pipelines-pr.yaml deleted file mode 100644 index 0b9f09e..0000000 --- a/.pipelines/azure-pipelines-pr.yaml +++ /dev/null @@ -1,53 +0,0 @@ -parameters: - - name: arrayExecs - type: object - default: - - none - - name: arrayPackages - type: object - default: - - Mongo.Migration - - name: arrayTests - type: object - default: - - none - - name: dockerfilePath - type: string - default: "none" - - name: dotNetSdkVersion - type: string - default: '6.x' - - name: gitRepoName - type: string - default: "Mongo.Migration" - -variables: - - group: github-credentials - -pr: - - main - - master -trigger: none - -pool: - vmImage: 'ubuntu-latest' - -resources: - repositories: - - repository: tools - type: git - name: OfficeProtect/tools - ref: main - -stages: - - stage: PR_Analysis - displayName: Lint, Tests and Static Analysis - jobs: - - template: azdoTemplates/workflows/pr-dotnet.yaml@tools - parameters: - arrayExecs: ${{ parameters.arrayExecs }} - arrayPackages: ${{ parameters.arrayPackages }} - arrayTests: ${{ parameters.arrayTests }} - dockerfilePath: "${{ parameters.dockerfilePath }}" - dotNetSdkVersion: "${{ parameters.dotNetSdkVersion }}" - projectPath: "$(System.DefaultWorkingDirectory)/${{ parameters.gitRepoName }}" diff --git a/.pipelines/azure-pipelines.yaml b/.pipelines/azure-pipelines.yaml deleted file mode 100644 index 36cd355..0000000 --- a/.pipelines/azure-pipelines.yaml +++ /dev/null @@ -1,69 +0,0 @@ -parameters: - - name: acrRepoURI - type: string - default: 'acr0opp0dev0root0sw.azurecr.io' - - name: arrayExecs - type: object - default: - - none - - name: arrayPackages - type: object - default: - - Mongo.Migration - - name: arrayTests - type: object - default: - - none - - name: dockerfilePath - type: string - default: "none" - - name: dotNetSdkVersion - type: string - default: '6.x' - - name: gitRepoName - type: string - default: "Mongo.Migration" - -variables: - - group: DatadogCI - - group: github-credentials - -pr: none -trigger: - branches: - include: - - 'main' - - 'master' - -pool: - vmImage: 'ubuntu-latest' - -resources: - repositories: - - repository: tools - type: git - name: OfficeProtect/tools - ref: main - - repository: kube-services - type: github - endpoint: op-github-svc - name: sherweb/kube-services - ref: dev - -stages: - - stage: CI_CD - displayName: DotNet Continuous Integration & Deployment - jobs: - - template: azdoTemplates/workflows/ci-dotnet.yaml@tools - parameters: - acrRepoURI: "${{ parameters.acrRepoURI }}" - arrayExecs: ${{ parameters.arrayExecs }} - arrayPackages: ${{ parameters.arrayPackages }} - arrayTests: ${{ parameters.arrayTests }} - dockerfilePath: "${{ parameters.dockerfilePath }}" - dotNetSdkVersion: "${{ parameters.dotNetSdkVersion }}" - projectPath: "$(System.DefaultWorkingDirectory)/${{ parameters.gitRepoName }}" - - template: azdoTemplates/workflows/cd-docker.yaml@tools - parameters: - env: "dev" - imageNames: ${{ parameters.arrayExecs }} diff --git a/LICENSE.txt b/LICENSE.md similarity index 95% rename from LICENSE.txt rename to LICENSE.md index 6442aad..cfd64ed 100644 --- a/LICENSE.txt +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Sean Roddis +Copyright (c) 2026 Rafael Pallares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/Mongo.Migration.Test.Core/Mongo.Migration.Test.Core.csproj b/Mongo.Migration.Test.Core/Mongo.Migration.Test.Core.csproj deleted file mode 100644 index d4abe21..0000000 --- a/Mongo.Migration.Test.Core/Mongo.Migration.Test.Core.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - false - Library - net6.0 - x64 - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - \ No newline at end of file diff --git a/Mongo.Migration.Test/Documents/DocumentVersion_When_casting.cs b/Mongo.Migration.Test/Documents/DocumentVersion_When_casting.cs deleted file mode 100644 index dfb20ed..0000000 --- a/Mongo.Migration.Test/Documents/DocumentVersion_When_casting.cs +++ /dev/null @@ -1,30 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Documents -{ - [TestFixture] - public class DocumentVersion_When_casting - { - [Test] - public void If_implicit_string_to_version_Then_cast_should_work() - { - DocumentVersion version = "1.0.2"; - - version.ToString().Should().Be("1.0.2"); - } - - [Test] - public void If_implicit_version_to_string_Then_cast_should_work() - { - var version = new DocumentVersion("1.0.2"); - - string versionString = version; - - versionString.Should().Be("1.0.2"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Documents/DocumentVersion_When_compare.cs b/Mongo.Migration.Test/Documents/DocumentVersion_When_compare.cs deleted file mode 100644 index 3cd31c3..0000000 --- a/Mongo.Migration.Test/Documents/DocumentVersion_When_compare.cs +++ /dev/null @@ -1,66 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Documents -{ - [TestFixture] - public class DocumentVersion_When_compare - { - private readonly DocumentVersion equalLowerVersion = new DocumentVersion("0.0.1"); - - private readonly DocumentVersion higherVersion = new DocumentVersion("0.0.2"); - - private readonly DocumentVersion lowerVersion = new DocumentVersion("0.0.1"); - - [Test] - public void If_higherVersion_lte_equalLowerVersion_Then_false() - { - bool result = this.higherVersion <= this.lowerVersion; - - result.Should().BeFalse(); - } - - [Test] - public void If_lowerVersion_gt_higherVersion_Then_false() - { - bool result = this.lowerVersion > this.higherVersion; - - result.Should().BeFalse(); - } - - [Test] - public void If_lowerVersion_gte_equalLowerVersion_Then_true() - { - bool result = this.lowerVersion >= this.equalLowerVersion; - - result.Should().BeTrue(); - } - - [Test] - public void If_lowerVersion_gte_higherVersion_Then_false() - { - bool result = this.lowerVersion >= this.higherVersion; - - result.Should().BeFalse(); - } - - [Test] - public void If_lowerVersion_lt_higherVersion_Then_true() - { - bool result = this.lowerVersion < this.higherVersion; - - result.Should().BeTrue(); - } - - [Test] - public void If_lowerVersion_lte_equalLowerVersion_Then_true() - { - bool result = this.lowerVersion <= this.equalLowerVersion; - - result.Should().BeTrue(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Documents/DocumentVersion_When_creating.cs b/Mongo.Migration.Test/Documents/DocumentVersion_When_creating.cs deleted file mode 100644 index a3ff804..0000000 --- a/Mongo.Migration.Test/Documents/DocumentVersion_When_creating.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; - -using FluentAssertions; - -using Mongo.Migration.Documents; -using Mongo.Migration.Exceptions; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Documents -{ - [TestFixture] - public class DocumentVersion_When_creating - { - [Test] - public void If_Default_Then_version_is_default_value() - { - DocumentVersion version = DocumentVersion.Default(); - - version.ToString().Should().Be("0.0.0"); - } - - [Test] - public void If_first_part_contains_char_Then_exception_is_thrown() - { - Action act = () => new DocumentVersion("a.0.0"); - - act.Should().Throw(); - } - - [Test] - public void If_new_version_with_int_Then_version_string_should_be_same() - { - var version = new DocumentVersion(1, 0, 2); - - version.ToString().Should().Be("1.0.2"); - } - - [Test] - public void If_new_version_with_string_Then_version_string_should_be_same() - { - var version = new DocumentVersion("1.0.2"); - - version.ToString().Should().Be("1.0.2"); - } - - [Test] - public void If_second_part_contains_char_Then_exception_is_thrown() - { - Action act = () => new DocumentVersion("0.a.0"); - - act.Should().Throw(); - } - - [Test] - public void If_third_part_contains_char_Then_exception_is_thrown() - { - Action act = () => new DocumentVersion("0.0.a"); - - act.Should().Throw(); - } - - [Test] - public void If_version_string_is_to_long_Then_exception_is_thrown() - { - Action act = () => new DocumentVersion("0.0.0.0"); - - act.Should().Throw(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Documents/Document_when_creating.cs b/Mongo.Migration.Test/Documents/Document_when_creating.cs deleted file mode 100644 index 3fc4797..0000000 --- a/Mongo.Migration.Test/Documents/Document_when_creating.cs +++ /dev/null @@ -1,35 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Documents -{ - [TestFixture] - public class Document_when_creating - { - [Test] - public void Then_document_can_be_created() - { - // Arrange Act - IDocument document = new Document(); - - // Assert - document.Should().BeOfType(); - } - - [Test] - public void Then_document_has_a_version() - { - // Arrange - IDocument document = new Document(); - - // Act - var version = document.Version; - - // Assert - version.Should().Be("0.0.0"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Documents/Locators/AttributeMigrationLocator_when_locate.cs b/Mongo.Migration.Test/Documents/Locators/AttributeMigrationLocator_when_locate.cs deleted file mode 100644 index dd6cb8c..0000000 --- a/Mongo.Migration.Test/Documents/Locators/AttributeMigrationLocator_when_locate.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents.Locators; -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Documents.Locators -{ - [TestFixture] - internal class VersionLocator_when_locate - { - [Test] - public void Then_find_current_version_of_document() - { - // Arrange - var locator = new RuntimeVersionLocator(); - - // Act - var currentVersion = locator.GetLocateOrNull(typeof(TestDocumentWithOneMigration)); - - // Assert - currentVersion.ToString().Should().Be("0.0.1"); - } - - [Test] - public void When_document_has_no_attribute_Then_return_null() - { - // Arrange - var locator = new RuntimeVersionLocator(); - - // Act - var currentVersion = locator.GetLocateOrNull(typeof(TestDocumentWithoutAttribute)); - - // Assert - currentVersion.Should().BeNull(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Documents/Serializers/DocumentVersionSerializer_when_serialize_and_deserialize.cs b/Mongo.Migration.Test/Documents/Serializers/DocumentVersionSerializer_when_serialize_and_deserialize.cs deleted file mode 100644 index 0439e97..0000000 --- a/Mongo.Migration.Test/Documents/Serializers/DocumentVersionSerializer_when_serialize_and_deserialize.cs +++ /dev/null @@ -1,76 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents; -using Mongo.Migration.Documents.Serializers; - -using MongoDB.Bson; -using MongoDB.Bson.IO; -using MongoDB.Bson.Serialization; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Documents.Serializers -{ - [TestFixture] - public class DocumentVersionSerializer_when_serialize_and_deserialize - { - private DocumentVersionSerializer _serializer; - - [Test] - public void Then_version_is_deserialized_correct() - { - // Arrange - var document = new BsonDocument { { "version", "0.1.1" } }; - BsonDocumentReader reader = CreateVersionReader(document); - - BsonDeserializationContext context = BsonDeserializationContext.CreateRoot(reader); - var args = new BsonDeserializationArgs { NominalType = typeof(DocumentVersion) }; - - // Act - DocumentVersion result = this._serializer.Deserialize(context, args); - - // Assert - result.Should().BeOfType(); - result.Should().Be("0.1.1"); - } - - [Test] - public void Then_version_is_serialized_correct() - { - // Arrange - BsonDocumentWriter writer = CreateVersionWriter(); - BsonSerializationContext context = BsonSerializationContext.CreateRoot(writer); - var args = new BsonSerializationArgs { NominalType = typeof(DocumentVersion) }; - var version = new DocumentVersion("0.0.1"); - - // Act - this._serializer.Serialize(context, args, version); - - // Assert - BsonDocument document = writer.Document; - document.ToString().Should().Be("{ \"version\" : \"0.0.1\" }"); - } - - [SetUp] - public void SetUp() - { - this._serializer = new DocumentVersionSerializer(); - } - - private static BsonDocumentReader CreateVersionReader(BsonDocument document) - { - var reader = new BsonDocumentReader(document); - reader.ReadStartDocument(); - reader.ReadName(); - return reader; - } - - private static BsonDocumentWriter CreateVersionWriter() - { - var writer = new BsonDocumentWriter(new BsonDocument()); - writer.WriteStartDocument(); - writer.WriteName("version"); - return writer; - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/IntegrationTest.cs b/Mongo.Migration.Test/IntegrationTest.cs deleted file mode 100644 index 418d395..0000000 --- a/Mongo.Migration.Test/IntegrationTest.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -using Mongo.Migration.Startup; -using Mongo.Migration.Startup.Static; - -using Mongo2Go; - -using MongoDB.Driver; - -namespace Mongo.Migration.Test -{ - public class IntegrationTest : IDisposable - { - protected IMongoClient _client; - - protected IComponentRegistry _components; - - protected MongoDbRunner _mongoToGoRunner; - - public void Dispose() - { - this._mongoToGoRunner?.Dispose(); - } - - protected void OnSetUp() - { - this._mongoToGoRunner = MongoDbRunner.Start(); - this._client = new MongoClient(this._mongoToGoRunner.ConnectionString); - - this._client.GetDatabase("PerformanceTest").CreateCollection("Test"); - - this._components = new ComponentRegistry( - new MongoMigrationSettings - { ConnectionString = this._mongoToGoRunner.ConnectionString, Database = "PerformanceTest" }); - this._components.RegisterComponents(this._client); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Database/DatabaseIntegrationTest.cs b/Mongo.Migration.Test/Migrations/Database/DatabaseIntegrationTest.cs deleted file mode 100644 index 07f8933..0000000 --- a/Mongo.Migration.Test/Migrations/Database/DatabaseIntegrationTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using Mongo.Migration.Documents; -using Mongo.Migration.Migrations.Database; -using Mongo.Migration.Startup; -using Mongo.Migration.Startup.Static; - -using Mongo2Go; - -using MongoDB.Bson; -using MongoDB.Driver; - -namespace Mongo.Migration.Test.Migrations.Database -{ - internal class DatabaseIntegrationTest : IDisposable - { - private const string MigrationsCollectionName = "_migrations"; - - protected IMongoClient _client; - - protected IComponentRegistry _components; - - protected IMongoDatabase _db; - - protected MongoDbRunner _mongoToGoRunner; - - protected virtual string DatabaseName { get; set; } = "DatabaseMigration"; - - protected virtual string CollectionName { get; set; } = "Test"; - - public void Dispose() - { - this._mongoToGoRunner?.Dispose(); - } - - protected virtual void OnSetUp(DocumentVersion databaseMigrationVersion) - { - this._mongoToGoRunner = MongoDbRunner.Start(); - this._client = new MongoClient(this._mongoToGoRunner.ConnectionString); - this._db = this._client.GetDatabase(this.DatabaseName); - this._db.CreateCollection(this.CollectionName); - - this._components = new ComponentRegistry( - new MongoMigrationSettings - { - ConnectionString = this._mongoToGoRunner.ConnectionString, - Database = this.DatabaseName, - DatabaseMigrationVersion = databaseMigrationVersion - }); - this._components.RegisterComponents(this._client); - } - - protected void InsertMigrations(IEnumerable migrations) - { - var list = migrations.Select(m => new BsonDocument { { "MigrationId", m.GetType().ToString() }, { "Version", m.Version.ToString() } }); - this._db.GetCollection(MigrationsCollectionName).InsertManyAsync(list).Wait(); - } - - protected List GetMigrationHistory() - { - var migrationHistoryCollection = this._db.GetCollection(MigrationsCollectionName); - return migrationHistoryCollection.Find(m => true).ToList(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunnerSetup.cs b/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunnerSetup.cs deleted file mode 100644 index d35f768..0000000 --- a/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunnerSetup.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Mongo.Migration.Documents.Serializers; - -using MongoDB.Bson; -using MongoDB.Bson.Serialization; - -using NLog; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Database -{ - [SetUpFixture] - public class DatabaseMigrationRunnerSetup - { - private readonly Logger _logger = LogManager.GetCurrentClassLogger(); - - [OneTimeSetUp] - public void GlobalSetup() - { - try - { - var documentSerializaer = new DocumentVersionSerializer(); - BsonSerializer.RegisterSerializer(documentSerializaer.ValueType, documentSerializaer); - } - catch (BsonSerializationException ex) - { - this._logger.Warn(ex); - } - } - - [OneTimeTearDown] - public void GlobalTeardown() - { - // Do logout here - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunner_when_migrating_down.cs b/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunner_when_migrating_down.cs deleted file mode 100644 index 96e4f3e..0000000 --- a/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunner_when_migrating_down.cs +++ /dev/null @@ -1,74 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents; -using Mongo.Migration.Migrations.Database; -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Database -{ - [TestFixture] - internal class DatabaseMigrationRunner_when_migrating_down : DatabaseIntegrationTest - { - private IDatabaseMigrationRunner _runner; - - protected override void OnSetUp(DocumentVersion databaseMigrationVersion) - { - base.OnSetUp(databaseMigrationVersion); - - this._runner = this._components.Get(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void When_database_has_migrations_Then_down_all_migrations() - { - this.OnSetUp(DocumentVersion.Default()); - - // Arrange - this.InsertMigrations( - new DatabaseMigration[] - { - new TestDatabaseMigration_0_0_1(), - new TestDatabaseMigration_0_0_2(), - new TestDatabaseMigration_0_0_3() - }); - - // Act - this._runner.Run(this._db); - - // Assert - var migrations = this.GetMigrationHistory(); - migrations.Should().BeEmpty(); - } - - [Test] - public void When_database_has_migrations_Then_down_to_selected_migration() - { - this.OnSetUp(new DocumentVersion("0.0.1")); - - // Arrange - this.InsertMigrations( - new DatabaseMigration[] - { - new TestDatabaseMigration_0_0_1(), - new TestDatabaseMigration_0_0_2(), - new TestDatabaseMigration_0_0_3() - }); - - // Act - this._runner.Run(this._db); - - // Assert - var migrations = this.GetMigrationHistory(); - migrations.Should().NotBeEmpty(); - migrations.Should().OnlyContain(m => m.Version == "0.0.1"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunner_when_migrating_up.cs b/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunner_when_migrating_up.cs deleted file mode 100644 index 7a03cf0..0000000 --- a/Mongo.Migration.Test/Migrations/Database/DatabaseMigrationRunner_when_migrating_up.cs +++ /dev/null @@ -1,77 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents; -using Mongo.Migration.Migrations.Database; -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Database -{ - [TestFixture] - internal class DatabaseMigrationRunner_when_migrating_up : DatabaseIntegrationTest - { - private IDatabaseMigrationRunner _runner; - - [SetUp] - public void SetUp() - { - base.OnSetUp(DocumentVersion.Empty()); - - this._runner = this._components.Get(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void When_database_has_no_migrations_Then_all_migrations_are_used() - { - // Act - this._runner.Run(this._db); - - // Assert - var migrations = this.GetMigrationHistory(); - migrations.Should().NotBeEmpty(); - migrations[0].Version.ToString().Should().BeEquivalentTo("0.0.1"); - migrations[1].Version.ToString().Should().BeEquivalentTo("0.0.2"); - migrations[2].Version.ToString().Should().BeEquivalentTo("0.0.3"); - } - - [Test] - public void When_database_has_migrations_Then_latest_migrations_are_used() - { - // Arrange - this.InsertMigrations(new DatabaseMigration[] { new TestDatabaseMigration_0_0_1(), new TestDatabaseMigration_0_0_2() }); - - // Act - this._runner.Run(this._db); - - // Assert - var migrations = this.GetMigrationHistory(); - migrations.Should().NotBeEmpty(); - migrations[2].Version.ToString().Should().BeEquivalentTo("0.0.3"); - } - - [Test] - public void When_database_has_latest_version_Then_nothing_happens() - { - // Arrange - this.InsertMigrations( - new DatabaseMigration[] { new TestDatabaseMigration_0_0_1(), new TestDatabaseMigration_0_0_2(), new TestDatabaseMigration_0_0_3() }); - - // Act - this._runner.Run(this._db); - - // Assert - var migrations = this.GetMigrationHistory(); - migrations.Should().NotBeEmpty(); - migrations[0].Version.ToString().Should().BeEquivalentTo("0.0.1"); - migrations[1].Version.ToString().Should().BeEquivalentTo("0.0.2"); - migrations[2].Version.ToString().Should().BeEquivalentTo("0.0.3"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Database/DatabaseMigration_when_creating.cs b/Mongo.Migration.Test/Migrations/Database/DatabaseMigration_when_creating.cs deleted file mode 100644 index 1e3cc95..0000000 --- a/Mongo.Migration.Test/Migrations/Database/DatabaseMigration_when_creating.cs +++ /dev/null @@ -1,43 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Migrations.Database; -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Database -{ - [TestFixture] - public class DatabaseMigration_when_creating - { - [Test] - public void Then_migration_has_type_DatabaseMigration() - { - // Arrange Act - var migration = new TestDatabaseMigration_0_0_1(); - - // Assert - migration.Type.Should().Be(typeof(DatabaseMigration)); - } - - [Test] - public void Then_migration_have_version() - { - // Arrange Act - var migration = new TestDatabaseMigration_0_0_1(); - - // Assert - migration.Version.Should().Be("0.0.1"); - } - - [Test] - public void Then_migration_should_be_created() - { - // Arrange Act - var migration = new TestDatabaseMigration_0_0_1(); - - // Assert - migration.Should().BeOfType(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Document/DocumentMigrationRunner_when_migrating_down.cs b/Mongo.Migration.Test/Migrations/Document/DocumentMigrationRunner_when_migrating_down.cs deleted file mode 100644 index 8f6e382..0000000 --- a/Mongo.Migration.Test/Migrations/Document/DocumentMigrationRunner_when_migrating_down.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Linq; - -using FluentAssertions; - -using Mongo.Migration.Migrations.Document; -using Mongo.Migration.Test.TestDoubles; - -using MongoDB.Bson; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Document -{ - [TestFixture] - internal class DocumentMigrationRunner_when_migrating_down : IntegrationTest - { - private IDocumentMigrationRunner _runner; - - [SetUp] - public void SetUp() - { - this.OnSetUp(); - - this._runner = this._components.Get(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void When_migrating_down_Then_all_migrations_are_used() - { - // Arrange - BsonDocument document = new BsonDocument - { - { "Version", "0.0.2" }, - { "Door", 3 } - }; - - // Act - this._runner.Run(typeof(TestDocumentWithTwoMigration), document); - - // Assert - document.Names.ToList()[1].Should().Be("Dors"); - document.Values.ToList()[0].AsString.Should().Be("0.0.0"); - } - - [Test] - public void When_document_has_Then_all_migrations_are_used_to_that_version() - { - // Arrange - // Arrange - BsonDocument document = new BsonDocument - { - { "Version", "0.0.2" }, - { "Door", 3 } - }; - - // Act - this._runner.Run(typeof(TestDocumentWithTwoMigrationMiddleVersion), document); - - // Assert - document.Names.ToList()[1].Should().Be("Doors"); - document.Values.ToList()[0].AsString.Should().Be("0.0.1"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Document/DocumentMigrationRunner_when_migrating_up.cs b/Mongo.Migration.Test/Migrations/Document/DocumentMigrationRunner_when_migrating_up.cs deleted file mode 100644 index 7a1b466..0000000 --- a/Mongo.Migration.Test/Migrations/Document/DocumentMigrationRunner_when_migrating_up.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Linq; - -using FluentAssertions; - -using Mongo.Migration.Migrations.Document; -using Mongo.Migration.Test.TestDoubles; - -using MongoDB.Bson; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Document -{ - [TestFixture] - internal class DocumentMigrationRunner_when_migrating_up : IntegrationTest - { - private IDocumentMigrationRunner _runner; - - [SetUp] - public void SetUp() - { - this.OnSetUp(); - - this._runner = this._components.Get(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void When_migrate_up_the_lowest_version_Then_all_migrations_are_used() - { - // Arrange - BsonDocument document = new BsonDocument - { - { "Version", "0.0.0" }, - { "Dors", 3 } - }; - - // Act - this._runner.Run(typeof(TestDocumentWithTwoMigrationHighestVersion), document); - - // Assert - document.Names.ToList()[1].Should().Be("Door"); - document.Values.ToList()[0].AsString.Should().Be("0.0.2"); - } - - [Test] - public void When_document_has_no_version_Then_all_migrations_are_used() - { - // Arrange - BsonDocument document = new BsonDocument - { - { "Dors", 3 } - }; - - // Act - this._runner.Run(typeof(TestDocumentWithTwoMigrationHighestVersion), document); - - // Assert - document.Names.ToList()[1].Should().Be("Door"); - document.Values.ToList()[0].AsString.Should().Be("0.0.2"); - } - - [Test] - public void When_document_has_current_version_Then_nothing_happens() - { - // Arrange - BsonDocument document = new BsonDocument - { - { "Version", "0.0.2" }, - { "Door", 3 } - }; - - // Act - this._runner.Run(typeof(TestDocumentWithTwoMigrationHighestVersion), document); - - // Assert - document.Names.ToList()[1].Should().Be("Door"); - document.Values.ToList()[0].AsString.Should().Be("0.0.2"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Document/DocumentMigration_when_creating.cs b/Mongo.Migration.Test/Migrations/Document/DocumentMigration_when_creating.cs deleted file mode 100644 index 610f5fa..0000000 --- a/Mongo.Migration.Test/Migrations/Document/DocumentMigration_when_creating.cs +++ /dev/null @@ -1,42 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Document -{ - [TestFixture] - public class DocumentMigration_when_creating - { - [Test] - public void Then_migration_has_type_testClass() - { - // Arrange Act - var migration = new TestDocumentWithOneMigration_0_0_1(); - - // Assert - migration.Type.Should().Be(typeof(TestDocumentWithOneMigration)); - } - - [Test] - public void Then_migration_have_version() - { - // Arrange Act - var migration = new TestDocumentWithOneMigration_0_0_1(); - - // Assert - migration.Version.Should().Be("0.0.1"); - } - - [Test] - public void Then_migration_should_be_created() - { - // Arrange Act - var migration = new TestDocumentWithOneMigration_0_0_1(); - - // Assert - migration.Should().BeOfType(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Document/DocumentMigration_when_migrating.cs b/Mongo.Migration.Test/Migrations/Document/DocumentMigration_when_migrating.cs deleted file mode 100644 index 830dbb2..0000000 --- a/Mongo.Migration.Test/Migrations/Document/DocumentMigration_when_migrating.cs +++ /dev/null @@ -1,42 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Test.TestDoubles; - -using MongoDB.Bson; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Document -{ - [TestFixture] - public class DocumentMigration_when_migrating - { - [Test] - public void When_migrating_down_Then_document_changes() - { - // Arrange - var migration = new TestDocumentWithOneMigration_0_0_1(); - var document = new BsonDocument { { "Doors", 3 } }; - - // Act - migration.Down(document); - - // Assert - document.Should().BeEquivalentTo(new BsonDocument { { "Dors", 3 } }); - } - - [Test] - public void When_migrating_up_Then_document_changes() - { - // Arrange - var migration = new TestDocumentWithOneMigration_0_0_1(); - var document = new BsonDocument { { "Dors", 3 } }; - - // Act - migration.Up(document); - - // Assert - document.Should().BeEquivalentTo(new BsonDocument { { "Doors", 3 } }); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Migrations/Locators/TypeMigrationLocator_when_locate.cs b/Mongo.Migration.Test/Migrations/Locators/TypeMigrationLocator_when_locate.cs deleted file mode 100644 index 0bb8deb..0000000 --- a/Mongo.Migration.Test/Migrations/Locators/TypeMigrationLocator_when_locate.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Linq; - -using FluentAssertions; - -using Mongo.Migration.Migrations.Locators; -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Migrations.Locators -{ - [TestFixture] - public class TypeMigrationLocator_when_locate - { - private TypeMigrationLocator _locator; - - [OneTimeSetUp] - public void OneTimeSetUp() - { - // Arrange - this._locator = new TypeMigrationLocator(); - } - - [Test] - public void When_document_has_one_migration_Then_migrations_count_should_be_one() - { - // Act - var result = this._locator.GetMigrations(typeof(TestDocumentWithOneMigration)); - - // Assert - result.Count().Should().Be(1); - } - - [Test] - public void When_document_has_two_migration_Then_migrations_count_should_be_two() - { - // Act - var result = this._locator.GetMigrations(typeof(TestDocumentWithTwoMigration)); - - // Assert - result.Count().Should().Be(2); - } - - [Test] - public void When_get_latest_version_of_migrations() - { - // Act - var version = this._locator.GetLatestVersion(typeof(TestDocumentWithTwoMigration)); - - // Assert - version.Should().Be("0.0.2"); - } - - [Test] - public void When_get_migrations_gt_and_equal_version() - { - // Act - var result = this._locator.GetMigrationsGtEq(typeof(TestDocumentWithTwoMigration), "0.0.1").ToList(); - - // Assert - result[0].Should().BeOfType(); - result[1].Should().BeOfType(); - } - - [Test] - public void When_get_migrations_gt_version() - { - // Act - var result = this._locator.GetMigrationsGt(typeof(TestDocumentWithTwoMigration), "0.0.1").ToList(); - - // Assert - result[0].Should().BeOfType(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Mongo.Migration.Test.csproj b/Mongo.Migration.Test/Mongo.Migration.Test.csproj deleted file mode 100644 index cb0686d..0000000 --- a/Mongo.Migration.Test/Mongo.Migration.Test.csproj +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - Debug - AnyCPU - {8EDF4429-251A-416D-BB68-93F227191BCF} - Library - Properties - Mongo.Migration.Test - Mongo.Migration.Test - v4.8 - 512 - - default - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll - True - - - ..\packages\DnsClient.1.2.0\lib\net45\DnsClient.dll - True - - - ..\packages\FluentAssertions.5.6.0\lib\net47\FluentAssertions.dll - True - - - - ..\packages\Mongo2Go.2.2.11\lib\netstandard1.6\Mongo2Go.dll - True - - - ..\packages\MongoDB.Bson.2.8.0\lib\net452\MongoDB.Bson.dll - True - - - ..\packages\MongoDB.Driver.2.8.0\lib\net452\MongoDB.Driver.dll - True - - - ..\packages\MongoDB.Driver.Core.2.8.0\lib\net452\MongoDB.Driver.Core.dll - True - - - - ..\packages\NSubstitute.4.1.0\lib\net46\NSubstitute.dll - True - - - ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll - True - - - - ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll - True - - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - True - - - ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - True - - - ..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll - True - - - ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {8dffd615-1e1a-4bed-8a96-caf4c3637e81} - Mongo.Migration - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. - - - - - \ No newline at end of file diff --git a/Mongo.Migration.Test/MongoDB/MongoRegistrater_when_registrating.cs b/Mongo.Migration.Test/MongoDB/MongoRegistrater_when_registrating.cs deleted file mode 100644 index 370e559..0000000 --- a/Mongo.Migration.Test/MongoDB/MongoRegistrater_when_registrating.cs +++ /dev/null @@ -1,40 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents; -using Mongo.Migration.Services; - -using MongoDB.Bson.Serialization; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.MongoDB -{ - [TestFixture] - internal class MongoRegistrator_when_registrating : IntegrationTest - { - [SetUp] - public void SetUp() - { - this.OnSetUp(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void Then_serializer_is_registered() - { - // Arrange - var migrationService = this._components.Get(); - - // Act - migrationService.Migrate(); - - // Arrange - BsonSerializer.LookupSerializer().ValueType.Should().Be(typeof(DocumentVersion)); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Performance/PerformanceOnStartup.cs b/Mongo.Migration.Test/Performance/PerformanceOnStartup.cs deleted file mode 100644 index d675dd8..0000000 --- a/Mongo.Migration.Test/Performance/PerformanceOnStartup.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; - -using FluentAssertions; - -using Mongo.Migration.Startup.Static; -using Mongo.Migration.Test.TestDoubles; - -using Mongo2Go; - -using MongoDB.Bson; -using MongoDB.Driver; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Performance -{ - [TestFixture] - public class PerformanceTestOnStartup - { - private const int DOCUMENT_COUNT = 10000; - - private const string DATABASE_NAME = "PerformanceTest"; - - private const string COLLECTION_NAME = "Test"; - - private const int TOLERANCE_MS = 2800; - - private MongoClient _client; - - private MongoDbRunner _runner; - - [TearDown] - public void TearDown() - { - MongoMigrationClient.Reset(); - this._client = null; - this._runner.Dispose(); - } - - [SetUp] - public void SetUp() - { - this._runner = MongoDbRunner.Start(); - this._client = new MongoClient(this._runner.ConnectionString); - } - - [Test] - public void When_migrating_number_of_documents() - { - // Arrange - // Worm up MongoCache - this.ClearCollection(); - this.AddDocumentsToCache(); - this.ClearCollection(); - - // Act - // Measure time of MongoDb processing without Mongo.Migration - this.InsertMany(DOCUMENT_COUNT, false); - var sw = new Stopwatch(); - sw.Start(); - this.MigrateAll(false); - sw.Stop(); - - this.ClearCollection(); - - // Measure time of MongoDb processing without Mongo.Migration - this.InsertMany(DOCUMENT_COUNT, true); - var swWithMigration = new Stopwatch(); - swWithMigration.Start(); - MongoMigrationClient.Initialize(this._client); - swWithMigration.Stop(); - - this.ClearCollection(); - - var result = swWithMigration.ElapsedMilliseconds - sw.ElapsedMilliseconds; - - Console.WriteLine( - $"MongoDB: {sw.ElapsedMilliseconds}ms, Mongo.Migration: {swWithMigration.ElapsedMilliseconds}ms, Diff: {result}ms (Tolerance: {TOLERANCE_MS}ms), Documents: {DOCUMENT_COUNT}, Migrations per Document: 2"); - - // Assert - result.Should().BeLessThan(TOLERANCE_MS); - } - - private void InsertMany(int number, bool withVersion) - { - var documents = new List(); - for (var n = 0; n < number; n++) - { - var document = new BsonDocument - { - { "Dors", 3 } - }; - if (withVersion) - { - document.Add("Version", "0.0.0"); - } - - documents.Add(document); - } - - this._client.GetDatabase(DATABASE_NAME).GetCollection(COLLECTION_NAME).InsertManyAsync(documents) - .Wait(); - } - - private void MigrateAll(bool withVersion) - { - if (withVersion) - { - var versionedCollectin = this._client.GetDatabase(DATABASE_NAME) - .GetCollection(COLLECTION_NAME); - var versionedResult = versionedCollectin.FindAsync(_ => true).Result.ToListAsync().Result; - return; - } - - var collection = this._client.GetDatabase(DATABASE_NAME) - .GetCollection(COLLECTION_NAME); - var result = collection.FindAsync(_ => true).Result.ToListAsync().Result; - } - - private void AddDocumentsToCache() - { - this.InsertMany(DOCUMENT_COUNT, false); - this.MigrateAll(false); - } - - private void ClearCollection() - { - this._client.GetDatabase(DATABASE_NAME).DropCollection(COLLECTION_NAME); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Properties/AssemblyInfo.cs b/Mongo.Migration.Test/Properties/AssemblyInfo.cs deleted file mode 100644 index 5edd146..0000000 --- a/Mongo.Migration.Test/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Mongo.Migration.Test")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("nunit.tests")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Mongo.Migration.Test/Services/DatabaseVersionService_when_determine_version.cs b/Mongo.Migration.Test/Services/DatabaseVersionService_when_determine_version.cs deleted file mode 100644 index 77d6a81..0000000 --- a/Mongo.Migration.Test/Services/DatabaseVersionService_when_determine_version.cs +++ /dev/null @@ -1,55 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Documents; -using Mongo.Migration.Services; -using Mongo.Migration.Test.Migrations.Database; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Services -{ - [TestFixture] - internal class DatabaseVersionService_when_determine_version : DatabaseIntegrationTest - { - private IDatabaseVersionService _service; - - protected override void OnSetUp(DocumentVersion version) - { - base.OnSetUp(version); - - this._service = this._components.Get(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void When_project_has_migrations_Then_get_latest_version() - { - // Arrange - this.OnSetUp(DocumentVersion.Empty()); - - // Act - var migrationVersion = this._service.GetCurrentOrLatestMigrationVersion(); - - // Assert - migrationVersion.ToString().Should().Be("0.0.3"); - } - - [Test] - public void When_version_set_on_startup_Then_use_startup_version() - { - // Arrange - this.OnSetUp(new DocumentVersion(0, 0, 2)); - - // Act - var migrationVersion = this._service.GetCurrentOrLatestMigrationVersion(); - - // Assert - migrationVersion.ToString().Should().Be("0.0.2"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Services/DocumentVersionService_when_determine_version.cs b/Mongo.Migration.Test/Services/DocumentVersionService_when_determine_version.cs deleted file mode 100644 index 219ce0b..0000000 --- a/Mongo.Migration.Test/Services/DocumentVersionService_when_determine_version.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; - -using FluentAssertions; - -using Mongo.Migration.Exceptions; -using Mongo.Migration.Services; -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Services -{ - [TestFixture] - internal class DocumentVersionService_when_determine_version : IntegrationTest - { - private IDocumentVersionService _service; - - [SetUp] - public void SetUp() - { - this.OnSetUp(); - - this._service = this._components.Get(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void When_document_has_current_version_Then_current_version_is_set() - { - // Arrange - var document = new TestDocumentWithTwoMigrationMiddleVersion(); - - // Act - this._service.DetermineVersion(document); - - // Assert - document.Version.Should().Be("0.0.1"); - } - - [Test] - public void When_document_has_highest_version_Then_highest_version_is_set() - { - // Arrange - var document = new TestDocumentWithTwoMigrationHighestVersion(); - - // Act - this._service.DetermineVersion(document); - - // Assert - document.Version.Should().Be("0.0.2"); - } - - [Test] - public void When_document_has_version_that_should_not_be_Then_throw_exception() - { - // Arrange - var document = new TestDocumentWithTwoMigrationHighestVersion { Version = "0.0.1" }; - - // Act// Act - Action checkAction = () => { this._service.DetermineVersion(document); }; - - // Assert - checkAction.Should().Throw(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Services/Initializers/MongoMigration_when_initialize.cs b/Mongo.Migration.Test/Services/Initializers/MongoMigration_when_initialize.cs deleted file mode 100644 index 8a125b5..0000000 --- a/Mongo.Migration.Test/Services/Initializers/MongoMigration_when_initialize.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -using FluentAssertions; - -using Mongo.Migration.Exceptions; -using Mongo.Migration.Startup.Static; - -using NSubstitute; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Services.Initializers -{ - [TestFixture] - public class MongoMigration_when_initialize - { - [TearDown] - public void TearDown() - { - MongoMigrationClient.Reset(); - } - - [Test] - public void When_inizialize_twice_Then_throw_exception() - { - // Arrange - var registry = Substitute.For(); - var mongoMigration = Substitute.For(); - - registry.Get().Returns(mongoMigration); - - // Act - MongoMigrationClient.Initialize(registry); - - Action comparison = () => MongoMigrationClient.Initialize(registry); - - // Assert - comparison.Should().Throw(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Services/Interceptors/MigrationInterceptorFactory_when_creating.cs b/Mongo.Migration.Test/Services/Interceptors/MigrationInterceptorFactory_when_creating.cs deleted file mode 100644 index a94c12b..0000000 --- a/Mongo.Migration.Test/Services/Interceptors/MigrationInterceptorFactory_when_creating.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; - -using FluentAssertions; - -using Mongo.Migration.Services.Interceptors; -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Services.Interceptors -{ - [TestFixture] - internal class MigrationInterceptorFactory_when_creating : IntegrationTest - { - [SetUp] - public void SetUp() - { - this.OnSetUp(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void If_type_is_assignable_to_document_Then_interceptor_is_created() - { - // Arrange - var factory = this._components.Get(); - - // Act - var interceptor = factory.Create(typeof(TestDocumentWithOneMigration)); - - // Assert - interceptor.ValueType.Should().Be(); - } - - [Test] - public void If_type_is_not_assignable_to_document_Then_exception_is_thrown() - { - // Arrange - var factory = this._components.Get(); - - // Act - Action act = () => factory.Create(typeof(TestClass)); - - // Assert - act.Should().ThrowExactly(); - } - - [Test] - public void If_type_is_null_Then_exception_is_thrown() - { - // Arrange - var factory = this._components.Get(); - - // Act - Action act = () => factory.Create(null); - - // Assert - act.Should().ThrowExactly(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/Services/Interceptors/MigrationInterceptorProvider_when_get_serializer.cs b/Mongo.Migration.Test/Services/Interceptors/MigrationInterceptorProvider_when_get_serializer.cs deleted file mode 100644 index 7e48b69..0000000 --- a/Mongo.Migration.Test/Services/Interceptors/MigrationInterceptorProvider_when_get_serializer.cs +++ /dev/null @@ -1,51 +0,0 @@ -using FluentAssertions; - -using Mongo.Migration.Services.Interceptors; -using Mongo.Migration.Test.TestDoubles; - -using NUnit.Framework; - -namespace Mongo.Migration.Test.Services.Interceptors -{ - [TestFixture] - internal class MigrationInterceptorProvider_when_get_serializer : IntegrationTest - { - [SetUp] - public void SetUp() - { - this.OnSetUp(); - } - - [TearDown] - public void TearDown() - { - this.Dispose(); - } - - [Test] - public void When_entity_is_document_Then_provide_serializer() - { - // Arrange - var provider = this._components.Get(); - - // Act - var serializer = provider.GetSerializer(typeof(TestDocumentWithOneMigration)); - - // Assert - serializer.ValueType.Should().Be(typeof(TestDocumentWithOneMigration)); - } - - [Test] - public void When_entity_is_not_document_Then_provide_null() - { - // Arrange - var provider = this._components.Get(); - - // Act - var serializer = provider.GetSerializer(typeof(TestClass)); - - // Assert - serializer.Should().BeNull(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_1.cs b/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_1.cs deleted file mode 100644 index 64ecd6e..0000000 --- a/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_1.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Mongo.Migration.Migrations.Database; - -using MongoDB.Driver; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDatabaseMigration_0_0_1 : DatabaseMigration - { - public TestDatabaseMigration_0_0_1() - : base("0.0.1") - { - } - - public override void Up(IMongoDatabase db) - { - } - - public override void Down(IMongoDatabase db) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_2.cs b/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_2.cs deleted file mode 100644 index 5e260a7..0000000 --- a/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_2.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Mongo.Migration.Migrations.Database; - -using MongoDB.Driver; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDatabaseMigration_0_0_2 : DatabaseMigration - { - public TestDatabaseMigration_0_0_2() - : base("0.0.2") - { - } - - public override void Up(IMongoDatabase db) - { - } - - public override void Down(IMongoDatabase db) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_3.cs b/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_3.cs deleted file mode 100644 index 7658858..0000000 --- a/Mongo.Migration.Test/TestDoubles/Database/TestDatabaseMigration_0_0_3.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Mongo.Migration.Migrations.Database; - -using MongoDB.Driver; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDatabaseMigration_0_0_3 : DatabaseMigration - { - public TestDatabaseMigration_0_0_3() - : base("0.0.3") - { - } - - public override void Up(IMongoDatabase db) - { - } - - public override void Down(IMongoDatabase db) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestClass.cs b/Mongo.Migration.Test/TestDoubles/TestClass.cs deleted file mode 100644 index a2cd389..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestClass.cs +++ /dev/null @@ -1,11 +0,0 @@ -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - public class TestClass - { - public ObjectId Id { get; set; } - - public int Dors { get; set; } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithOneMigration.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithOneMigration.cs deleted file mode 100644 index 58ddf83..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithOneMigration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Mongo.Migration.Documents; -using Mongo.Migration.Documents.Attributes; - -namespace Mongo.Migration.Test.TestDoubles -{ - [RuntimeVersion("0.0.1")] - internal class TestDocumentWithOneMigration : Document - { - public int Doors { get; set; } - } - - internal class TestDocumentWithoutAttribute : Document - { - public int Doors { get; set; } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithOneMigration_0_0_1.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithOneMigration_0_0_1.cs deleted file mode 100644 index d5ef2d3..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithOneMigration_0_0_1.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDocumentWithOneMigration_0_0_1 : DocumentMigration - { - public TestDocumentWithOneMigration_0_0_1() - : base("0.0.1") - { - } - - public override void Up(BsonDocument document) - { - var doors = document["Dors"].ToInt32(); - document.Add("Doors", doors); - document.Remove("Dors"); - } - - public override void Down(BsonDocument document) - { - var doors = document["Doors"].ToInt32(); - document.Add("Dors", doors); - document.Remove("Doors"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration.cs deleted file mode 100644 index 20dc81b..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Mongo.Migration.Documents; -using Mongo.Migration.Documents.Attributes; - -namespace Mongo.Migration.Test.TestDoubles -{ - [RuntimeVersion("0.0.0")] - internal class TestDocumentWithTwoMigration : Document - { - public int Dors { get; set; } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion.cs deleted file mode 100644 index 71c355d..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Mongo.Migration.Documents; -using Mongo.Migration.Documents.Attributes; - -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - [RuntimeVersion("0.0.2")] - [CollectionLocation("Test", "PerformanceTest")] - internal class TestDocumentWithTwoMigrationHighestVersion : Document - { - public ObjectId Id { get; set; } - - public int Door { get; set; } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_1.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_1.cs deleted file mode 100644 index b5ebed9..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_1.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDocumentWithTwoMigrationHighestVersion_0_0_1 : DocumentMigration - { - public TestDocumentWithTwoMigrationHighestVersion_0_0_1() - : base("0.0.1") - { - } - - public override void Up(BsonDocument document) - { - var doors = document["Dors"].ToInt32(); - document.Add("Doors", doors); - document.Remove("Dors"); - } - - public override void Down(BsonDocument document) - { - var doors = document["Doors"].ToInt32(); - document.Add("Dors", doors); - document.Remove("Doors"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_2.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_2.cs deleted file mode 100644 index 4226663..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_2.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDocumentWithTwoMigrationHighestVersion_0_0_2 : DocumentMigration - { - public TestDocumentWithTwoMigrationHighestVersion_0_0_2() - : base("0.0.2") - { - } - - public override void Up(BsonDocument document) - { - var doors = document["Doors"].ToInt32(); - document.Add("Door", doors); - document.Remove("Doors"); - } - - public override void Down(BsonDocument document) - { - var doors = document["Door"].ToInt32(); - document.Add("Doors", doors); - document.Remove("Door"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion.cs deleted file mode 100644 index 8c567b4..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Mongo.Migration.Documents; -using Mongo.Migration.Documents.Attributes; - -namespace Mongo.Migration.Test.TestDoubles -{ - [RuntimeVersion("0.0.1")] - internal class TestDocumentWithTwoMigrationMiddleVersion : Document - { - public int Door { get; set; } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_1.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_1.cs deleted file mode 100644 index 77bb0cb..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_1.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDocumentWithTwoMigrationMiddleVersion_0_0_1 : DocumentMigration - { - public TestDocumentWithTwoMigrationMiddleVersion_0_0_1() - : base("0.0.1") - { - } - - public override void Up(BsonDocument document) - { - var doors = document["Dors"].ToInt32(); - document.Add("Doors", doors); - document.Remove("Dors"); - } - - public override void Down(BsonDocument document) - { - var doors = document["Doors"].ToInt32(); - document.Add("Dors", doors); - document.Remove("Doors"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_2.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_2.cs deleted file mode 100644 index 413cd36..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_2.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDocumentWithTwoMigrationMiddleVersion_0_0_2 : DocumentMigration - { - public TestDocumentWithTwoMigrationMiddleVersion_0_0_2() - : base("0.0.2") - { - } - - public override void Up(BsonDocument document) - { - var doors = document["Doors"].ToInt32(); - document.Add("Door", doors); - document.Remove("Doors"); - } - - public override void Down(BsonDocument document) - { - var doors = document["Door"].ToInt32(); - document.Add("Doors", doors); - document.Remove("Door"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration_0_0_1.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration_0_0_1.cs deleted file mode 100644 index 813fe9d..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration_0_0_1.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDocumentWithTwoMigration_0_0_1 : DocumentMigration - { - public TestDocumentWithTwoMigration_0_0_1() - : base("0.0.1") - { - } - - public override void Up(BsonDocument document) - { - var doors = document["Dors"].ToInt32(); - document.Add("Doors", doors); - document.Remove("Dors"); - } - - public override void Down(BsonDocument document) - { - var doors = document["Doors"].ToInt32(); - document.Add("Dors", doors); - document.Remove("Doors"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration_0_0_2.cs b/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration_0_0_2.cs deleted file mode 100644 index 6531a2c..0000000 --- a/Mongo.Migration.Test/TestDoubles/TestDocumentWithTwoMigration_0_0_2.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson; - -namespace Mongo.Migration.Test.TestDoubles -{ - internal class TestDocumentWithTwoMigration_0_0_2 : DocumentMigration - { - public TestDocumentWithTwoMigration_0_0_2() - : base("0.0.2") - { - } - - public override void Up(BsonDocument document) - { - var doors = document["Doors"].ToInt32(); - document.Add("Door", doors); - document.Remove("Doors"); - } - - public override void Down(BsonDocument document) - { - var doors = document["Door"].ToInt32(); - document.Add("Doors", doors); - document.Remove("Door"); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration.Test/packages.config b/Mongo.Migration.Test/packages.config deleted file mode 100644 index 25ca16e..0000000 --- a/Mongo.Migration.Test/packages.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Mongo.Migration.Tests/Documents/DocumentVersion_When_casting.cs b/Mongo.Migration.Tests/Documents/DocumentVersion_When_casting.cs new file mode 100644 index 0000000..4710144 --- /dev/null +++ b/Mongo.Migration.Tests/Documents/DocumentVersion_When_casting.cs @@ -0,0 +1,24 @@ +using Mongo.Migration.Documents; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Documents; + +[TestFixture] +public class DocumentVersionWhenCasting +{ + [Test] + public void If_implicit_string_to_version_Then_cast_should_work() + { + DocumentVersion version = new(1,0,2); + Assert.That(version.ToString(), Is.EqualTo("1.0.2")); + } + + [Test] + public void If_implicit_version_to_string_Then_cast_should_work() + { + var version = new DocumentVersion(1,0,2); + + string versionString = version.ToString(); + Assert.That(versionString, Is.EqualTo("1.0.2")); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Documents/DocumentVersion_When_compare.cs b/Mongo.Migration.Tests/Documents/DocumentVersion_When_compare.cs new file mode 100644 index 0000000..ae98c3c --- /dev/null +++ b/Mongo.Migration.Tests/Documents/DocumentVersion_When_compare.cs @@ -0,0 +1,62 @@ +using Mongo.Migration.Documents; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Documents; + +[TestFixture] +public class DocumentVersionWhenCompare +{ + private readonly DocumentVersion _equalLowerVersion = new(0,0,1); + + private readonly DocumentVersion _higherVersion = new(0, 0, 2); + + private readonly DocumentVersion _lowerVersion = new(0, 0, 1); + + [Test] + public void If_higherVersion_lte_equalLowerVersion_Then_false() + { + bool result = _higherVersion <= _lowerVersion; + + Assert.That(result, Is.False); + } + + [Test] + public void If_lowerVersion_gt_higherVersion_Then_false() + { + bool result = _lowerVersion > _higherVersion; + + Assert.That(result, Is.False); + } + + [Test] + public void If_lowerVersion_gte_equalLowerVersion_Then_true() + { + bool result = _lowerVersion >= _equalLowerVersion; + + Assert.That(result, Is.True); + } + + [Test] + public void If_lowerVersion_gte_higherVersion_Then_false() + { + bool result = _lowerVersion >= _higherVersion; + + Assert.That(result, Is.False); + } + + [Test] + public void If_lowerVersion_lt_higherVersion_Then_true() + { + bool result = _lowerVersion < _higherVersion; + + Assert.That(result, Is.True); + } + + [Test] + public void If_lowerVersion_lte_equalLowerVersion_Then_true() + { + bool result = _lowerVersion <= _equalLowerVersion; + + Assert.That(result, Is.True); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Documents/DocumentVersion_When_creating.cs b/Mongo.Migration.Tests/Documents/DocumentVersion_When_creating.cs new file mode 100644 index 0000000..b347bdc --- /dev/null +++ b/Mongo.Migration.Tests/Documents/DocumentVersion_When_creating.cs @@ -0,0 +1,75 @@ +using Mongo.Migration.Documents; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Documents; + +[TestFixture] +public class DocumentVersionWhenCreating +{ + [Test] + public void If_Empty_Then_version_is_empty_value() + { + DocumentVersion version = DocumentVersion.Empty; + Assert.That(version.ToString(), Is.EqualTo("-1.0.0")); + } + + [Test] + public void If_Default_Then_version_is_default_value() + { + DocumentVersion version = DocumentVersion.Default; + + Assert.That(version.ToString(), Is.EqualTo("0.0.0")); + } + + [Test] + public void If_first_part_contains_char_Then_exception_is_thrown() + { + Assert.Throws(() => DocumentVersion.Parse("a.0.0")); + } + + [Test] + public void If_new_version_with_int_Then_version_string_should_be_same() + { + var version = new DocumentVersion(1, 0, 2); + Assert.That(version.ToString(), Is.EqualTo("1.0.2")); + } + + [Test] + public void If_new_version_with_string_Then_version_string_should_be_same() + { + var version = DocumentVersion.Parse("1.0.2"); + Assert.That(version.ToString(), Is.EqualTo("1.0.2")); + } + + [Test] + public void If_second_part_contains_char_Then_exception_is_thrown() + { + Assert.Throws(() => DocumentVersion.Parse("0.a.0")); + } + + [Test] + public void If_third_part_contains_char_Then_exception_is_thrown() + { + Assert.Throws(() => DocumentVersion.Parse("0.0.a")); + } + + [Test] + public void If_version_string_is_too_long_Then_exception_is_thrown() + { + Assert.Throws(() => DocumentVersion.Parse("0.0.0.0")); + } + + [Test] + public void If_version_string_is_too_short1_then_default() + { + var version = DocumentVersion.Parse("33"); + Assert.That(version.ToString(), Is.EqualTo("33.0.0")); + } + + [Test] + public void If_version_string_is_too_short2_then_default() + { + var version = DocumentVersion.Parse("42.27"); + Assert.That(version.ToString(), Is.EqualTo("42.27.0")); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Documents/Document_when_creating.cs b/Mongo.Migration.Tests/Documents/Document_when_creating.cs new file mode 100644 index 0000000..69f588d --- /dev/null +++ b/Mongo.Migration.Tests/Documents/Document_when_creating.cs @@ -0,0 +1,31 @@ +using Mongo.Migration.Documents; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Documents; + +[TestFixture] +public class DocumentWhenCreating +{ + [Test] + public void Then_document_can_be_created() + { + // Arrange Act + Document document = new(); + + // Assert + Assert.That(document, Is.TypeOf()); + } + + [Test] + public void Then_document_has_a_version() + { + // Arrange + Document document = new(); + + // Act + var version = document.Version; + + // Assert + Assert.That(version.ToString(), Is.EqualTo("0.0.0")); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Documents/Locators/AttributeMigrationLocator_when_locate.cs b/Mongo.Migration.Tests/Documents/Locators/AttributeMigrationLocator_when_locate.cs new file mode 100644 index 0000000..be8d6dd --- /dev/null +++ b/Mongo.Migration.Tests/Documents/Locators/AttributeMigrationLocator_when_locate.cs @@ -0,0 +1,35 @@ +using Mongo.Migration.Documents.Locators; +using Mongo.Migration.Tests.TestDoubles; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Documents.Locators; + +[TestFixture] +internal class VersionLocatorWhenLocate +{ + [Test] + public void Then_find_current_version_of_document() + { + // Arrange + var locator = new RuntimeVersionLocator([]); + + // Act + var currentVersion = locator.GetLocateOrNull(typeof(TestDocumentWithOneMigration)); + + // Assert + Assert.That(currentVersion.ToString(), Is.EqualTo("0.0.1")); + } + + [Test] + public void When_document_has_no_attribute_Then_return_null() + { + // Arrange + var locator = new RuntimeVersionLocator([]); + + // Act + var currentVersion = locator.GetLocateOrNull(typeof(TestDocumentWithoutAttribute)); + + // Assert + Assert.That(currentVersion, Is.Null); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Documents/Serializers/DocumentVersionSerializer_when_serialize_and_deserialize.cs b/Mongo.Migration.Tests/Documents/Serializers/DocumentVersionSerializer_when_serialize_and_deserialize.cs new file mode 100644 index 0000000..5a89afe --- /dev/null +++ b/Mongo.Migration.Tests/Documents/Serializers/DocumentVersionSerializer_when_serialize_and_deserialize.cs @@ -0,0 +1,71 @@ +using Mongo.Migration.Documents; +using Mongo.Migration.Documents.Serializers; +using MongoDB.Bson; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Documents.Serializers; + +[TestFixture] +public class DocumentVersionSerializerWhenSerializeAndDeserialize +{ + private DocumentVersionSerializer _serializer; + + [Test] + public void Then_version_is_deserialized_correct() + { + // Arrange + var document = new BsonDocument { { "version", "0.1.1" } }; + BsonDocumentReader reader = CreateVersionReader(document); + + BsonDeserializationContext context = BsonDeserializationContext.CreateRoot(reader); + var args = new BsonDeserializationArgs { NominalType = typeof(DocumentVersion) }; + + // Act + DocumentVersion result = _serializer.Deserialize(context, args); + + // Assert + Assert.That(result, Is.TypeOf()); + Assert.That(result.ToString(), Is.EqualTo("0.1.1")); + } + + [Test] + public void Then_version_is_serialized_correct() + { + // Arrange + BsonDocumentWriter writer = CreateVersionWriter(); + BsonSerializationContext context = BsonSerializationContext.CreateRoot(writer); + var args = new BsonSerializationArgs { NominalType = typeof(DocumentVersion) }; + var version = new DocumentVersion(0,0,1); + + // Act + _serializer.Serialize(context, args, version); + + // Assert + BsonDocument document = writer.Document; + Assert.That(document.ToString(), Is.EqualTo("{ \"version\" : \"0.0.1\" }")); + } + + [SetUp] + public void SetUp() + { + _serializer = new(); + } + + private static BsonDocumentReader CreateVersionReader(BsonDocument document) + { + var reader = new BsonDocumentReader(document); + reader.ReadStartDocument(); + reader.ReadName(); + return reader; + } + + private static BsonDocumentWriter CreateVersionWriter() + { + var writer = new BsonDocumentWriter([]); + writer.WriteStartDocument(); + writer.WriteName("version"); + return writer; + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/IntegrationTest.cs b/Mongo.Migration.Tests/IntegrationTest.cs new file mode 100644 index 0000000..00ce512 --- /dev/null +++ b/Mongo.Migration.Tests/IntegrationTest.cs @@ -0,0 +1,27 @@ +using MongoDB.Bson; +using MongoDB.Driver; +using NUnit.Framework; + +namespace Mongo.Migration.Tests; + +[TestFixture] +public abstract class IntegrationTest +{ + private const string DatabaseName = "IntegrationTest"; + private const string CollectionName = "Test"; + + [SetUp] + protected void SetUp() + { + IMongoClient client = TestcontainersContext.MongoClient; + client.GetDatabase(DatabaseName).GetCollection(CollectionName); + } + + [TearDown] + protected async Task TearDownAsync() + { + IMongoClient client = TestcontainersContext.MongoClient; + await client.GetDatabase(DatabaseName) + .DropCollectionAsync(CollectionName); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Database/DatabaseIntegrationTest.cs b/Mongo.Migration.Tests/Migrations/Database/DatabaseIntegrationTest.cs new file mode 100644 index 0000000..f1d6172 --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Database/DatabaseIntegrationTest.cs @@ -0,0 +1,48 @@ +using Mongo.Migration.Migrations.Database; +using MongoDB.Bson; +using MongoDB.Driver; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Database; + +[TestFixture] +internal abstract class DatabaseIntegrationTest +{ + private const string MigrationsCollectionName = "_migrations"; + + private IMongoDatabase? _db; + protected IMongoDatabase Db => _db ?? throw new InvalidOperationException("Must be setup"); + + protected virtual string DatabaseName { get; set; } = "DatabaseMigration"; + + protected virtual string CollectionName { get; set; } = "Test"; + + protected async Task OnSetUpAsync() + { + IMongoClient client = TestcontainersContext.MongoClient; + _db = client.GetDatabase(DatabaseName); + await _db.CreateCollectionAsync(CollectionName); + } + + [TearDown] + public async Task TearDownAsync() + { + IMongoClient client = TestcontainersContext.MongoClient; + IMongoDatabase database = client.GetDatabase(DatabaseName); + await database.DropCollectionAsync(CollectionName); + await database.DropCollectionAsync(MigrationsCollectionName); + } + + protected void InsertMigrations(IEnumerable migrations) + { + var list = migrations.Select(m => new BsonDocument { { "MigrationId", m.GetType().ToString() }, { "Version", m.Version.ToString() } }); + Db.GetCollection(MigrationsCollectionName) + .InsertManyAsync(list).Wait(); + } + + protected List GetMigrationHistory() + { + var migrationHistoryCollection = Db.GetCollection(MigrationsCollectionName); + return migrationHistoryCollection.Find(m => true).ToList(); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Database/DatabaseMigrationRunner_when_migrating_down.cs b/Mongo.Migration.Tests/Migrations/Database/DatabaseMigrationRunner_when_migrating_down.cs new file mode 100644 index 0000000..0eef9c1 --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Database/DatabaseMigrationRunner_when_migrating_down.cs @@ -0,0 +1,56 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Documents; +using Mongo.Migration.Migrations.Database; +using Mongo.Migration.Tests.TestDoubles.Database; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Database; + +[TestFixture] +internal class DatabaseMigrationRunnerWhenMigratingDown : DatabaseIntegrationTest +{ + [Test] + public async Task When_database_has_migrations_Then_down_all_migrations() + { + await OnSetUpAsync(); + IDatabaseMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + + // Arrange + InsertMigrations( + [ + new TestDatabaseMigration001(), + new TestDatabaseMigration002(), + new TestDatabaseMigration003() + ]); + + // Act + await runner.RunAsync(Db, DocumentVersion.Default); + + // Assert + var migrations = GetMigrationHistory(); + Assert.That(migrations, Is.Empty); + } + + [Test] + public async Task When_database_has_migrations_Then_down_to_selected_migration() + { + await OnSetUpAsync(); + IDatabaseMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + + // Arrange + InsertMigrations( + [ + new TestDatabaseMigration001(), + new TestDatabaseMigration002(), + new TestDatabaseMigration003() + ]); + + // Act + await runner.RunAsync(Db, new DocumentVersion(0, 0, 1)); + + // Assert + List migrations = GetMigrationHistory(); + Assert.That(migrations, Is.Not.Empty); + Assert.That(migrations, Has.One.Matches(m => m.Version == new DocumentVersion(0, 0, 1))); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Database/DatabaseMigrationRunner_when_migrating_up.cs b/Mongo.Migration.Tests/Migrations/Database/DatabaseMigrationRunner_when_migrating_up.cs new file mode 100644 index 0000000..bea4bd8 --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Database/DatabaseMigrationRunner_when_migrating_up.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Documents; +using Mongo.Migration.Migrations.Database; +using Mongo.Migration.Tests.TestDoubles.Database; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Database; + +[TestFixture] +internal class DatabaseMigrationRunnerWhenMigratingUp : DatabaseIntegrationTest +{ + [Test] + public async Task When_database_has_no_migrations_Then_all_migrations_are_used() + { + // Arrange + await OnSetUpAsync(); + IDatabaseMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + + // Act + await runner.RunAsync(Db, DocumentVersion.Empty); + + // Assert + var migrations = GetMigrationHistory(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(migrations, Is.Not.Empty); + Assert.That(migrations[0].Version.ToString(), Is.EqualTo("0.0.1")); + Assert.That(migrations[1].Version.ToString(), Is.EqualTo("0.0.2")); + Assert.That(migrations[2].Version.ToString(), Is.EqualTo("0.0.3")); + } + } + + [Test] + public async Task When_database_has_migrations_Then_latest_migrations_are_used() + { + // Arrange + await OnSetUpAsync(); + IDatabaseMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + InsertMigrations([new TestDatabaseMigration001(), new TestDatabaseMigration002()]); + + // Act + await runner.RunAsync(Db, DocumentVersion.Empty); + + // Assert + var migrations = GetMigrationHistory(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(migrations, Is.Not.Empty); + Assert.That(migrations[2].Version.ToString(), Is.EqualTo("0.0.3")); + } + } + + [Test] + public async Task When_database_has_latest_version_Then_nothing_happens() + { + // Arrange + await OnSetUpAsync(); + IDatabaseMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + InsertMigrations( + [ + new TestDatabaseMigration001(), + new TestDatabaseMigration002(), + new TestDatabaseMigration003() + ]); + + // Act + await runner.RunAsync(Db, DocumentVersion.Empty, CancellationToken.None); + + // Assert + var migrations = GetMigrationHistory(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(migrations, Is.Not.Empty); + Assert.That(migrations[0].Version.ToString(), Is.EqualTo("0.0.1")); + Assert.That(migrations[1].Version.ToString(), Is.EqualTo("0.0.2")); + Assert.That(migrations[2].Version.ToString(), Is.EqualTo("0.0.3")); + } + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Database/DatabaseMigration_when_creating.cs b/Mongo.Migration.Tests/Migrations/Database/DatabaseMigration_when_creating.cs new file mode 100644 index 0000000..25e9402 --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Database/DatabaseMigration_when_creating.cs @@ -0,0 +1,39 @@ +using Mongo.Migration.Migrations.Database; +using Mongo.Migration.Tests.TestDoubles.Database; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Database; + +[TestFixture] +public class DatabaseMigrationWhenCreating +{ + [Test] + public void Then_migration_has_type_DatabaseMigration() + { + // Arrange Act + var migration = new TestDatabaseMigration001(); + + // Assert + Assert.That(migration.Type, Is.EqualTo(typeof(DatabaseMigration))); + } + + [Test] + public void Then_migration_have_version() + { + // Arrange Act + var migration = new TestDatabaseMigration001(); + + // Assert + Assert.That(migration.Version.ToString(), Is.EqualTo("0.0.1")); + } + + [Test] + public void Then_migration_should_be_created() + { + // Arrange Act + var migration = new TestDatabaseMigration001(); + + // Assert + Assert.That(migration, Is.TypeOf()); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Document/DocumentMigrationRunner_when_migrating_down.cs b/Mongo.Migration.Tests/Migrations/Document/DocumentMigrationRunner_when_migrating_down.cs new file mode 100644 index 0000000..ce7ed76 --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Document/DocumentMigrationRunner_when_migrating_down.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Migrations.Document; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Bson; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Document; + +[TestFixture] +internal class DocumentMigrationRunnerWhenMigratingDown : IntegrationTest +{ + [Test] + public void When_migrating_down_Then_all_migrations_are_used() + { + // Arrange + IDocumentMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + BsonDocument document = new() + { + { "Version", "0.0.2" }, + { "Door", 3 } + }; + + // Act + runner.Run(typeof(TestDocumentWithTwoMigration), document); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(document.Names.ToList()[1], Is.EqualTo("Dors")); + Assert.That(document.Values.ToList()[0].AsString, Is.EqualTo("0.0.0")); + } + } + + [Test] + public void When_document_has_Then_all_migrations_are_used_to_that_version() + { + // Arrange + IDocumentMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + BsonDocument document = new() + { + { "Version", "0.0.2" }, + { "Doors2", 3 } + }; + + // Act + runner.Run(typeof(TestDocumentWithTwoMigrationMiddleVersion), document); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(document.Names.ToList()[1], Is.EqualTo("Doors1")); + Assert.That(document.Values.ToList()[0].AsString, Is.EqualTo("0.0.1")); + } + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Document/DocumentMigrationRunner_when_migrating_up.cs b/Mongo.Migration.Tests/Migrations/Document/DocumentMigrationRunner_when_migrating_up.cs new file mode 100644 index 0000000..1d775b0 --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Document/DocumentMigrationRunner_when_migrating_up.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Migrations.Document; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Bson; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Document; + +[TestFixture] +internal class DocumentMigrationRunnerWhenMigratingUp : IntegrationTest +{ + [Test] + public void When_migrate_up_the_lowest_version_Then_all_migrations_are_used() + { + // Arrange + IDocumentMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + BsonDocument document = new() + { + { "Version", "0.0.0" }, + { "Dors", 3 } + }; + + // Act + runner.Run(typeof(TestDocumentWithTwoMigrationHighestVersion), document); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(document.Names.ToList()[1], Is.EqualTo("Door")); + Assert.That(document.Values.ToList()[0].AsString, Is.EqualTo("0.0.2")); + } + } + + [Test] + public void When_document_has_no_version_Then_all_migrations_are_used() + { + // Arrange + IDocumentMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + BsonDocument document = new() + { + { "Dors", 3 } + }; + + // Act + runner.Run(typeof(TestDocumentWithTwoMigrationHighestVersion), document); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(document["Door"].AsInt32, Is.EqualTo(3)); + Assert.That(document["Version"].AsString, Is.EqualTo("0.0.2")); + } + } + + [Test] + public void When_document_has_current_version_Then_nothing_happens() + { + // Arrange + IDocumentMigrationRunner runner = TestcontainersContext.Provider.GetRequiredService(); + BsonDocument document = new() + { + { "Version", "0.0.2" }, + { "Door", 3 } + }; + + // Act + runner.Run(typeof(TestDocumentWithTwoMigrationHighestVersion), document); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(document.Names.ToList()[1], Is.EqualTo("Door")); + Assert.That(document.Values.ToList()[0].AsString, Is.EqualTo("0.0.2")); + } + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Document/DocumentMigration_when_creating.cs b/Mongo.Migration.Tests/Migrations/Document/DocumentMigration_when_creating.cs new file mode 100644 index 0000000..d32c488 --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Document/DocumentMigration_when_creating.cs @@ -0,0 +1,38 @@ +using Mongo.Migration.Tests.TestDoubles; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Document; + +[TestFixture] +public class DocumentMigrationWhenCreating +{ + [Test] + public void Then_migration_has_type_testClass() + { + // Arrange Act + var migration = new TestDocumentWithOneMigration001(); + + // Assert + Assert.That(migration.Type, Is.EqualTo(typeof(TestDocumentWithOneMigration))); + } + + [Test] + public void Then_migration_have_version() + { + // Arrange Act + var migration = new TestDocumentWithOneMigration001(); + + // Assert + Assert.That(migration.Version.ToString(), Is.EqualTo("0.0.1")); + } + + [Test] + public void Then_migration_should_be_created() + { + // Arrange Act + var migration = new TestDocumentWithOneMigration001(); + + // Assert + Assert.That(migration, Is.TypeOf()); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Document/DocumentMigration_when_migrating.cs b/Mongo.Migration.Tests/Migrations/Document/DocumentMigration_when_migrating.cs new file mode 100644 index 0000000..f15b66d --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Document/DocumentMigration_when_migrating.cs @@ -0,0 +1,65 @@ +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Bson; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Document; + +[TestFixture] +public class DocumentMigrationWhenMigrating +{ + [Test] + public void When_migrating_down_Then_document_changes() + { + // Arrange + var migration = new TestDocumentWithOneMigration001(); + var document = new BsonDocument { { "Doors", 3 } }; + + // Act + migration.Down(document); + + // Assert + Assert.That(document, Is.EquivalentTo(new BsonDocument { { "Dors", 3 } })); + } + + [Test] + public void When_migrating_up_Then_document_changes() + { + // Arrange + var migration = new TestDocumentWithOneMigration001(); + var document = new BsonDocument { { "Dors", 3 } }; + + // Act + migration.Up(document); + + // Assert + Assert.That(document, Is.EquivalentTo(new BsonDocument { { "Doors", 3 } })); + } + + [Test] + public void When_version_as_string_migrating_down_Then_document_changes() + { + // Arrange + var migration = new TestClassWithTwoMigrationMiddleVersion001(); + var document = new BsonDocument { { "Doors1", 3 } }; + + // Act + migration.Down(document); + + // Assert + Assert.That(document, Is.EquivalentTo(new BsonDocument { { "Doors0", 3 } })); + } + + [Test] + public void When_version_as_string_migrating_up_Then_document_changes() + { + // Arrange + var migration = new TestClassWithTwoMigrationMiddleVersion001(); + var document = new BsonDocument { { "Doors0", 3 } }; + + // Act + migration.Up(document); + + // Assert + Assert.That(document, Is.EquivalentTo(new BsonDocument { { "Doors1", 3 } })); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Interceptor/InterceptorTests.cs b/Mongo.Migration.Tests/Migrations/Interceptor/InterceptorTests.cs new file mode 100644 index 0000000..36442e1 --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Interceptor/InterceptorTests.cs @@ -0,0 +1,162 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Bson; +using MongoDB.Driver; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Interceptor; + +public class InterceptorMigrationWhenCreating : IntegrationTest +{ + private const string DatabaseName = "TestDatabase"; + private const string CollectionName = "TestCollection"; + + [Test] + public async Task TestSerializationIntercepted() + { + IMongoCollection untypedCollection = GetCollection(); + IMongoCollection testDocumentCollection = + GetCollection(); + + await testDocumentCollection.InsertOneAsync(new TestDocumentWithTwoMigrationMiddleVersion + { + Doors1 = 42 + }); + + BsonDocument? documentInserted = await untypedCollection + .Find(Builders.Filter.Eq("Doors1", 42)) + .FirstOrDefaultAsync(); + + Assert.That(documentInserted, Is.Not.Null); + Assert.That(documentInserted["Version"].AsString, Is.EqualTo("0.0.1")); + } + + [Test] + public async Task TestDeserializationIntercepted() + { + IMongoCollection untypedCollection = GetCollection(); + IMongoCollection testDocumentCollection = + GetCollection(); + + await untypedCollection.InsertManyAsync( + [ + [ + new("Doors0", new BsonInt32(0)) + ], + [ + new("Doors0", new BsonInt32(1)), + new("Version", new BsonString("0.0.0")) + ], + [ + new("Doors1", new BsonInt32(2)), + new("Version", new BsonString("0.0.1")) + ], + [ + new("Doors2", new BsonInt32(3)), + new("Version", new BsonString("0.0.2")) + ] + ]); + + var asyncCursor = await testDocumentCollection + .FindAsync(FilterDefinition.Empty); + List documents = await asyncCursor.ToListAsync(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(documents, Has.Count.EqualTo(4)); + Assert.That( + documents.Select(d => d.Version.ToString()), + Is.All.EqualTo("0.0.1")); + + Assert.That(documents[0].Doors1, Is.Zero); + Assert.That(documents[1].Doors1, Is.EqualTo(1)); + Assert.That(documents[2].Doors1, Is.EqualTo(2)); + Assert.That(documents[3].Doors1, Is.EqualTo(3)); + } + } + + [Test] + public async Task TestSerializationVersionAsStringIntercepted() + { + IMongoCollection untypedCollection = GetCollection(); + IMongoCollection testDocumentCollection = + GetCollection(); + + await testDocumentCollection.InsertOneAsync(new TestClassWithTwoMigrationMiddleVersion + { + Doors1 = 42 + }); + + BsonDocument? documentInserted = await untypedCollection + .Find(Builders.Filter.Eq("Doors1", 42)) + .FirstOrDefaultAsync(); + + Assert.That(documentInserted, Is.Not.Null); + Assert.That(documentInserted["Version"].AsString, Is.EqualTo("0.0.1")); + } + + [Test] + public async Task TestDeserializationVersionAsStringIntercepted() + { + IMongoCollection untypedCollection = GetCollection(); + IMongoCollection testDocumentCollection = + GetCollection(); + + await untypedCollection.InsertManyAsync( + [ + [ + new("Doors0", new BsonInt32(0)) + ], + [ + new("Doors0", new BsonInt32(1)), + new("Version", new BsonString("0.0.0")) + ], + [ + new("Doors1", new BsonInt32(2)), + new("Version", new BsonString("0.0.1")) + ], + [ + new("Doors2", new BsonInt32(3)), + new("Version", new BsonString("0.0.2")) + ] + ]); + + var asyncCursor = await testDocumentCollection + .FindAsync(FilterDefinition.Empty); + List documents = await asyncCursor.ToListAsync(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(documents, Has.Count.EqualTo(4)); + Assert.That( + documents.Select(d => d.Version.ToString()), + Is.All.EqualTo("0.0.1")); + + Assert.That(documents[0].Doors1, Is.Zero); + Assert.That(documents[1].Doors1, Is.EqualTo(1)); + Assert.That(documents[2].Doors1, Is.EqualTo(2)); + Assert.That(documents[3].Doors1, Is.EqualTo(3)); + } + } + + + [SetUp] + public void SetUpLocal() + { + IMongoCollection _ = GetCollection(); + } + + [TearDown] + public async Task TearDownLocalAsync() + { + await GetCollection() + .DeleteManyAsync(d => true); + } + + private static IMongoCollection GetCollection() + { + return TestcontainersContext.Provider.GetRequiredService() + .GetDatabase(DatabaseName) + .GetCollection(CollectionName); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Migrations/Locators/TypeMigrationLocator_when_locate.cs b/Mongo.Migration.Tests/Migrations/Locators/TypeMigrationLocator_when_locate.cs new file mode 100644 index 0000000..3c5669f --- /dev/null +++ b/Mongo.Migration.Tests/Migrations/Locators/TypeMigrationLocator_when_locate.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Mongo.Migration.Documents; +using Mongo.Migration.Migrations.Locators; +using Mongo.Migration.Tests.TestDoubles; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Migrations.Locators; + +[TestFixture] +public class TypeMigrationLocatorWhenLocate : IDisposable +{ + private readonly TypeMigrationLocator _locator; + private readonly ServiceProvider _serviceProvider; + + public TypeMigrationLocatorWhenLocate() + { + _serviceProvider = new ServiceCollection().BuildServiceProvider(); + _locator = new TypeMigrationLocator(NullLogger.Instance, _serviceProvider); + } + + [Test] + public void When_document_has_one_migration_Then_migrations_count_should_be_one() + { + // Act + var result = _locator.GetMigrations(typeof(TestDocumentWithOneMigration)); + + // Assert + Assert.That(result, Has.Count.EqualTo(1)); + } + + [Test] + public void When_document_has_two_migration_Then_migrations_count_should_be_two() + { + // Act + var result = _locator.GetMigrations(typeof(TestDocumentWithTwoMigration)); + + // Assert + Assert.That(result, Has.Count.EqualTo(2)); + } + + [Test] + public void When_get_latest_version_of_migrations() + { + // Act + var version = _locator.GetLatestVersion(typeof(TestDocumentWithTwoMigration)); + + // Assert + Assert.That(version.ToString(), Is.EqualTo("0.0.2")); + } + + [Test] + public void When_get_migrations_from_to() + { + // Act + var result = _locator + .GetMigrationsFromTo(typeof(TestDocumentWithTwoMigration), DocumentVersion.Default, DocumentVersion.Parse("0.0.1")) + .ToList(); + // Assert + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.TypeOf()); + } + + [Test] + public void When_get_migrations_from_to_down() + { + // Act + var result = _locator + .GetMigrationsFromToDown(typeof(TestDocumentWithTwoMigration), DocumentVersion.Parse("0.0.2"), DocumentVersion.Parse("0.0.1")) + .ToList(); + // Assert + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.TypeOf()); + } + + public void Dispose() + { + _serviceProvider.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Mongo.Migration.Tests.csproj b/Mongo.Migration.Tests/Mongo.Migration.Tests.csproj new file mode 100644 index 0000000..69bc721 --- /dev/null +++ b/Mongo.Migration.Tests/Mongo.Migration.Tests.csproj @@ -0,0 +1,35 @@ + + + + net10.0;net8.0 + 14.0 + enable + enable + false + true + true + Exe + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Mongo.Migration.Tests/MongoDB/MongoRegistryTests.cs b/Mongo.Migration.Tests/MongoDB/MongoRegistryTests.cs new file mode 100644 index 0000000..d93ca03 --- /dev/null +++ b/Mongo.Migration.Tests/MongoDB/MongoRegistryTests.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Bson; +using Mongo.Migration.Documents; +using Mongo.Migration.Documents.Serializers; +using Mongo.Migration.Services; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.MongoDB; + +/// +/// Test that static serializers are registered (called from ) +/// +[TestFixture] +internal class MongoRegistryTests : IntegrationTest +{ + [Test] + public void DocumentVersionSerializerIsRegistered() + { + Assert.That( + BsonSerializer.SerializerRegistry.GetSerializer(), + Is.TypeOf()); + } + + [Test] + public void MongoSerializerProviderIsRegistered() + { + Assert.That( + BsonSerializer.SerializerRegistry.GetSerializer(), + Is.TypeOf>()); + } + + [Test] + public void WhenInitializeTwiceThrows() + { + var migrationService = TestcontainersContext.Provider.GetRequiredService(); + Assert.Throws(migrationService.RegisterBsonStatics); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/MongoDB/VariousCrudOperations.cs b/Mongo.Migration.Tests/MongoDB/VariousCrudOperations.cs new file mode 100644 index 0000000..9e1a044 --- /dev/null +++ b/Mongo.Migration.Tests/MongoDB/VariousCrudOperations.cs @@ -0,0 +1,136 @@ +using MongoDB.Bson; +using MongoDB.Driver; +using NUnit.Framework; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Driver.Linq; + +namespace Mongo.Migration.Tests.MongoDB; + +[TestFixture] +public class VariousCrudOperations +{ + private const string DatabaseName = "CrudTest"; + private const string CollectionName = "TestDocumentWithOneMigration"; + private IMongoCollection? _collection; + + private IMongoCollection Collection => _collection ?? throw new InvalidOperationException(); + + [SetUp] + public void SetUp() + { + IMongoClient client = TestcontainersContext.MongoClient; + _collection = client.GetDatabase(DatabaseName) + .GetCollection(CollectionName); + } + + [Test] + public async Task TestCreate() + { + var id = ObjectId.GenerateNewId(); + var document = new TestDocumentWithOneMigration + { + Id = id, + Doors = 1 + }; + + await Collection.InsertOneAsync(document); + + var documentCreated = Collection.AsQueryable() + .Where(d => d.Id == id) + .Single(); + + Assert.That(documentCreated.Id, Is.EqualTo(id)); + } + + [Test] + public async Task TestRead() + { + var id = ObjectId.GenerateNewId(); + var document = new TestDocumentWithOneMigration + { + Id = id, + Doors = 99 + }; + + await Collection.InsertOneAsync(document); + + var documentById = Collection.AsQueryable() + .Where(d => d.Id == id) + .Single(); + + var documentByExpression = Collection.AsQueryable() + .Where(d => d.Doors >= 98) + .Single(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(documentById.Id, Is.EqualTo(id)); + Assert.That(documentByExpression.Id, Is.EqualTo(id)); + } + } + + [Test] + public async Task TestUpdate() + { + var id = ObjectId.GenerateNewId(); + var document = new TestDocumentWithOneMigration + { + Id = id, + Doors = 7 + }; + + await Collection.InsertOneAsync(document); + + var documentCreated = Collection.AsQueryable() + .Where(d => d.Id == id) + .Single(); + + Assert.That(documentCreated.Id, Is.EqualTo(id)); + + var updateResult = await Collection.UpdateOneAsync( + Builders.Filter.Where(d => d.Id == id && d.Doors == 7), + Builders.Update + .Set(d => d.Doors, 8)); + + Assert.That(updateResult.MatchedCount, Is.EqualTo(1)); + + var documentUpdated = Collection.AsQueryable() + .Where(d => d.Id == id) + .Single(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(documentUpdated.Id, Is.EqualTo(id)); + Assert.That(documentUpdated.Doors, Is.EqualTo(8)); + } + } + + [Test] + public async Task TestDelete() + { + var id = ObjectId.GenerateNewId(); + var document = new TestDocumentWithOneMigration + { + Id = id, + Doors = 9 + }; + + await Collection.InsertOneAsync(document); + + var documentCreated = Collection.AsQueryable() + .Where(d => d.Id == id) + .Single(); + + Assert.That(documentCreated.Id, Is.EqualTo(id)); + + var deleteResult = await Collection.DeleteOneAsync(d => d.Id == id); + + Assert.That(deleteResult.DeletedCount, Is.EqualTo(1)); + + var searchResult = await Collection.AsQueryable() + .Where(d => d.Id == id) + .ToListAsync(); + + Assert.That(searchResult, Is.Empty); + } +} diff --git a/Mongo.Migration.Tests/Performance/PerformanceOnStartup.cs b/Mongo.Migration.Tests/Performance/PerformanceOnStartup.cs new file mode 100644 index 0000000..7581fbc --- /dev/null +++ b/Mongo.Migration.Tests/Performance/PerformanceOnStartup.cs @@ -0,0 +1,109 @@ +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Migrations.Document; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Bson; +using MongoDB.Driver; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Performance; + +[TestFixture] +public class PerformanceTestOnStartup +{ + private const int DocumentCount = 5000; + + private const string DatabaseName = "PerformanceTest"; + + private const string CollectionName = "Test"; + + private const int ToleranceMs = 2800; + + [Test] + public async Task When_migrating_number_of_documents() + { + // Arrange + // Worm up MongoCache + ClearCollection(); + await AddDocumentsToCacheAsync(); + ClearCollection(); + + // Act + // Measure time of MongoDb processing without Mongo.Migration + await InsertDocumentsAsync(DocumentCount); + var sw = new Stopwatch(); + sw.Start(); + var _ = await QueryAllAsync(false); + sw.Stop(); + + ClearCollection(); + + // Measure time of MongoDb processing with Mongo.Migration + IMongoClient client = TestcontainersContext.MongoClient; + await InsertDocumentsAsync(DocumentCount); + var swWithMigration = new Stopwatch(); + swWithMigration.Start(); + + IStartUpDocumentMigrationRunner documentMigrationRunner = + TestcontainersContext.Provider.GetRequiredService(); + await documentMigrationRunner.RunAllAsync(client.GetDatabase(DatabaseName), CancellationToken.None); + swWithMigration.Stop(); + + ClearCollection(); + + var result = swWithMigration.ElapsedMilliseconds - sw.ElapsedMilliseconds; + + await TestContext.Out.WriteLineAsync($"MongoDB: {sw.ElapsedMilliseconds}ms, Mongo.Migration: {swWithMigration.ElapsedMilliseconds}ms, Diff: {result}ms (Tolerance: {ToleranceMs}ms), Documents: {DocumentCount}, Migrations per Document: 2"); + + // Assert + Assert.That(result, Is.LessThan(ToleranceMs)); + } + + private static Task InsertDocumentsAsync(int documentCount) + { + var documents = Enumerable + .Range(0, documentCount) + .Select(i => new BsonDocument + { + { "_id", new BsonObjectId(ObjectId.GenerateNewId())}, + { "Dors", new BsonInt32(i) }, + { "Version", BsonString.Create("0.0.0") } + }); + + return TestcontainersContext.MongoClient + .GetDatabase(DatabaseName) + .GetCollection(CollectionName) + .InsertManyAsync(documents); + } + + private static async Task> QueryAllAsync(bool withVersion) + { + IMongoClient client = TestcontainersContext.MongoClient; + + if (withVersion) + { + var versionedCollection = client.GetDatabase(DatabaseName) + .GetCollection(CollectionName); + var versionedResult = await (await versionedCollection.FindAsync(_ => true)).ToListAsync(); + return [.. versionedResult.Cast()]; + } + + var collection = client.GetDatabase(DatabaseName) + .GetCollection(CollectionName); + var result = await (await collection.FindAsync(_ => true)).ToListAsync(); + return [.. result.Cast()]; + } + + private static async Task AddDocumentsToCacheAsync() + { + await InsertDocumentsAsync(DocumentCount); + await QueryAllAsync(false); + } + + private static void ClearCollection() + { + TestcontainersContext.MongoClient + .GetDatabase(DatabaseName) + .DropCollection(CollectionName); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Performance/PerformanceRuntimeMigration.cs b/Mongo.Migration.Tests/Performance/PerformanceRuntimeMigration.cs new file mode 100644 index 0000000..c128b4d --- /dev/null +++ b/Mongo.Migration.Tests/Performance/PerformanceRuntimeMigration.cs @@ -0,0 +1,106 @@ + +using System.Diagnostics; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Bson; +using MongoDB.Driver; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Performance; + +public class PerformanceRuntimeMigration +{ + private const string DatabaseName = "PerformanceTest"; + + private const string CollectionName = "TestRuntime"; + + [TearDown] + public Task TearDownAsync() + { + return GetCollection() + .DeleteManyAsync(FilterDefinition.Empty); + } + + private static IMongoCollection GetCollection() + { + return TestcontainersContext.MongoClient + .GetDatabase(DatabaseName) + .GetCollection(CollectionName); + } + + private static Task InsertDocumentsAsync(int documentCount, Func documentFactory) + { + var documents = Enumerable + .Range(0, documentCount) + .Select(documentFactory); + + return GetCollection() + .InsertManyAsync(documents); + } + + [TestCase(1_000, 100L)] + [TestCase(10_000, 200L)] + [TestCase(50_000, 500L)] + public async Task MeasureVanillaRead(int documentCount, long msDurationThreshold) + { + await InsertDocumentsAsync(documentCount, i => new BsonDocument + { + { "_id", new BsonObjectId(ObjectId.GenerateNewId())}, + { "Dors", new BsonInt32(i) }, + { "Version", BsonString.Create("0.0.0") } + }); + + IMongoCollection collection = GetCollection(); + Stopwatch sw = Stopwatch.StartNew(); + List results = await (await collection.FindAsync(FilterDefinition.Empty)).ToListAsync(); + sw.Stop(); + + await TestContext.Out.WriteLineAsync($"Elapsed {sw.ElapsedMilliseconds} ms to read {results.Count} documents"); + Assert.That(sw.ElapsedMilliseconds, Is.LessThan(msDurationThreshold)); + } + + [TestCase(1_000, 200L)] + [TestCase(10_000, 400L)] + [TestCase(50_000, 1000L)] + public async Task MeasureRead2Migrations(int documentCount, long msDurationThreshold) + { + await InsertDocumentsAsync(documentCount, i => new BsonDocument + { + { "_id", new BsonObjectId(ObjectId.GenerateNewId())}, + { "Dors", new BsonInt32(i) }, + { "Version", BsonString.Create("0.0.0") } + }); + + IMongoCollection collection = GetCollection(); + TestDocumentWithTwoMigrationHighestVersion? _ = collection.AsQueryable().FirstOrDefault(); + + Stopwatch sw = Stopwatch.StartNew(); + List results = await (await collection.FindAsync(FilterDefinition.Empty)).ToListAsync(); + sw.Stop(); + + await TestContext.Out.WriteLineAsync($"Elapsed {sw.ElapsedMilliseconds} ms to read {results.Count} documents"); + Assert.That(sw.ElapsedMilliseconds, Is.LessThan(msDurationThreshold)); + } + + [TestCase(1_000, 200L)] + [TestCase(10_000, 400L)] + [TestCase(50_000, 1000L)] + public async Task MeasureReadNoMigration(int documentCount, long msDurationThreshold) + { + await InsertDocumentsAsync(documentCount, i => new BsonDocument + { + { "_id", new BsonObjectId(ObjectId.GenerateNewId())}, + { "Door", new BsonInt32(i) }, + { "Version", BsonString.Create("0.0.2") } + }); + + IMongoCollection collection = GetCollection(); + TestDocumentWithTwoMigrationHighestVersion? _ = collection.AsQueryable().FirstOrDefault(); + + Stopwatch sw = Stopwatch.StartNew(); + List results = await (await collection.FindAsync(FilterDefinition.Empty)).ToListAsync(); + sw.Stop(); + + await TestContext.Out.WriteLineAsync($"Elapsed {sw.ElapsedMilliseconds} ms to read {results.Count} documents"); + Assert.That(sw.ElapsedMilliseconds, Is.LessThan(msDurationThreshold)); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Services/DatabaseVersionService_when_determine_version.cs b/Mongo.Migration.Tests/Services/DatabaseVersionService_when_determine_version.cs new file mode 100644 index 0000000..25a8f24 --- /dev/null +++ b/Mongo.Migration.Tests/Services/DatabaseVersionService_when_determine_version.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Services; +using Mongo.Migration.Tests.Migrations.Database; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Services; + +[TestFixture] +internal class DatabaseVersionServiceWhenDetermineVersion : DatabaseIntegrationTest +{ + [Test] + public async Task WhenProjectHasMigrationsThenGetLatestVersion() + { + // Arrange + await OnSetUpAsync(); + IDatabaseVersionService service = TestcontainersContext.Provider.GetRequiredService(); + + // Act + var migrationVersion = service.GetLatestMigrationVersion(); + + // Assert + Assert.That(migrationVersion.ToString(), Is.EqualTo("0.0.3")); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Services/DocumentVersionService_when_determine_version.cs b/Mongo.Migration.Tests/Services/DocumentVersionService_when_determine_version.cs new file mode 100644 index 0000000..c66d07e --- /dev/null +++ b/Mongo.Migration.Tests/Services/DocumentVersionService_when_determine_version.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Documents; +using Mongo.Migration.Exceptions; +using Mongo.Migration.Services; +using Mongo.Migration.Tests.TestDoubles; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Services; + +[TestFixture] +internal class DocumentVersionServiceWhenDetermineVersion : IntegrationTest +{ + [Test] + public void When_document_has_current_version_Then_current_version_is_set() + { + // Arrange + IDocumentVersionService service = TestcontainersContext.Provider.GetRequiredService(); + var document = new TestDocumentWithTwoMigrationMiddleVersion(); + + // Act + service.DetermineVersion(document); + + // Assert + Assert.That(document.Version.ToString(), Is.EqualTo("0.0.1")); + } + + [Test] + public void When_document_has_highest_version_Then_highest_version_is_set() + { + // Arrange + IDocumentVersionService service = TestcontainersContext.Provider.GetRequiredService(); + var document = new TestDocumentWithTwoMigrationHighestVersion(); + + // Act + service.DetermineVersion(document); + + // Assert + Assert.That(document.Version.ToString(), Is.EqualTo("0.0.2")); + } + + [Test] + public void When_document_has_version_that_should_not_be_Then_throw_exception() + { + // Arrange + IDocumentVersionService service = TestcontainersContext.Provider.GetRequiredService(); + var document = new TestDocumentWithTwoMigrationHighestVersion { Version = new DocumentVersion(0,0,1) }; + + // Assert + Assert.Throws(() => service.DetermineVersion(document)); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Services/Interceptors/MigrationInterceptorFactory_when_creating.cs b/Mongo.Migration.Tests/Services/Interceptors/MigrationInterceptorFactory_when_creating.cs new file mode 100644 index 0000000..1bf0cc3 --- /dev/null +++ b/Mongo.Migration.Tests/Services/Interceptors/MigrationInterceptorFactory_when_creating.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Bson; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Bson.Serialization; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Services.Interceptors; + +[TestFixture] +internal class MigrationInterceptorFactoryWhenCreating : IntegrationTest +{ + [Test] + public void If_type_is_assignable_to_document_Then_interceptor_is_created() + { + // Arrange + var serializerProvider = TestcontainersContext.Provider.GetRequiredService(); + + // Act + IBsonSerializer serializer = serializerProvider.GetSerializer(typeof(TestDocumentWithOneMigration)); + + // Assert + Assert.That(serializer, Is.TypeOf>()); + } + + [Test] + public void If_type_is_not_assignable_to_document_Then_null_returned() + { + // Arrange + var serializerProvider = TestcontainersContext.Provider.GetRequiredService(); + + // Act + IBsonSerializer serializer = serializerProvider.GetSerializer(typeof(TestClassNoMigration)); + + // Assert + Assert.That(serializer, Is.Null); + } + + [Test] + public void If_type_is_not_assignable_to_document_but_manually_added_Then_interceptor_created() + { + // Arrange + var serializerProvider = TestcontainersContext.Provider.GetRequiredService(); + + // Act + IBsonSerializer serializer = serializerProvider.GetSerializer(typeof(TestClassWithTwoMigrationMiddleVersion)); + + // Assert + Assert.That(serializer, Is.TypeOf>()); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Services/Interceptors/MigrationInterceptorProvider_when_get_serializer.cs b/Mongo.Migration.Tests/Services/Interceptors/MigrationInterceptorProvider_when_get_serializer.cs new file mode 100644 index 0000000..64533de --- /dev/null +++ b/Mongo.Migration.Tests/Services/Interceptors/MigrationInterceptorProvider_when_get_serializer.cs @@ -0,0 +1,49 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Bson; +using Mongo.Migration.Tests.TestDoubles; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Services.Interceptors; + +[TestFixture] +internal class MigrationInterceptorProviderWhenGetSerializer : IntegrationTest +{ + [Test] + public void When_entity_is_document_Then_provide_serializer() + { + // Arrange + var serializationProvider = TestcontainersContext.Provider.GetRequiredService(); + + // Act + var serializer = serializationProvider.GetSerializer(typeof(TestDocumentWithOneMigration)); + + // Assert + Assert.That(serializer.ValueType, Is.EqualTo(typeof(TestDocumentWithOneMigration))); + } + + [Test] + public void When_entity_is_not_document_Then_provide_null() + { + // Arrange + var serializationProvider = TestcontainersContext.Provider.GetRequiredService(); + + // Act + var serializer = serializationProvider.GetSerializer(typeof(TestClassNoMigration)); + + // Assert + Assert.That(serializer, Is.Null); + } + + [Test] + public void When_entity_is_not_document_but_manually_added_Then_provide_serializer() + { + // Arrange + var serializationProvider = TestcontainersContext.Provider.GetRequiredService(); + + // Act + var serializer = serializationProvider.GetSerializer(typeof(TestClassWithTwoMigrationMiddleVersion)); + + // Assert + Assert.That(serializer.ValueType, Is.EqualTo(typeof(TestClassWithTwoMigrationMiddleVersion))); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/Startup/MigrationBuilderTests.cs b/Mongo.Migration.Tests/Startup/MigrationBuilderTests.cs new file mode 100644 index 0000000..ae22427 --- /dev/null +++ b/Mongo.Migration.Tests/Startup/MigrationBuilderTests.cs @@ -0,0 +1,128 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Mongo.Migration.Bson; +using Mongo.Migration.Documents.Locators; +using Mongo.Migration.Migrations.Database; +using Mongo.Migration.Migrations.Document; +using Mongo.Migration.Services; +using Mongo.Migration.Startup; +using MongoDB.Driver; +using NUnit.Framework; + +namespace Mongo.Migration.Tests.Startup; + +[TestFixture] +internal class MigrationBuilderTests +{ + [Test] + public void AllMigrationsEnabledWhenNotSet() + { + IServiceCollection services = CreateEmptyServiceCollection(); + + services.AddMigration(); + + using ServiceProvider provider = services.BuildServiceProvider(); + + MongoMigrationStartupSettings startupSettings = provider.GetRequiredService(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(startupSettings.RuntimeMigrationEnabled, Is.True); + Assert.That(startupSettings.DatabaseMigrationEnabled, Is.True); + Assert.That(startupSettings.StartupDocumentMigrationEnabled, Is.True); + } + } + + [Test] + public void RuntimeMigrationOnlyRequiredRegistered() + { + IServiceCollection services = CreateEmptyServiceCollection(); + + services.AddMigration(builder => builder.AddRuntimeDocumentMigration()); + + using ServiceProvider provider = services.BuildServiceProvider(); + + MongoMigrationStartupSettings startupSettings = provider.GetRequiredService(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(startupSettings.RuntimeMigrationEnabled, Is.True); + Assert.That(startupSettings.DatabaseMigrationEnabled, Is.False); + Assert.That(startupSettings.StartupDocumentMigrationEnabled, Is.False); + + Assert.That(provider.GetRequiredService(), Is.TypeOf()); + Assert.DoesNotThrow(() => provider.GetRequiredService()); + + Assert.Throws(() => provider.GetRequiredService()); + Assert.Throws(() => provider.GetRequiredService()); + Assert.Throws(() => provider.GetRequiredService()); + } + } + + [Test] + public void DatabaseMigrationOnlyRequiredRegistered() + { + IServiceCollection services = CreateEmptyServiceCollection(); + + services.AddMigration(builder => builder.AddDatabaseMigration()); + + using ServiceProvider provider = services.BuildServiceProvider(); + + MongoMigrationStartupSettings startupSettings = provider.GetRequiredService(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(startupSettings.RuntimeMigrationEnabled, Is.False); + Assert.That(startupSettings.DatabaseMigrationEnabled, Is.True); + Assert.That(startupSettings.StartupDocumentMigrationEnabled, Is.False); + + Assert.That( + provider.GetRequiredService(), + Is.TypeOf()); + Assert.That( + provider.GetRequiredService(), + Is.TypeOf()); + + Assert.Throws(() => provider.GetRequiredService()); + Assert.Throws(() => provider.GetRequiredService()); + Assert.Throws(() => provider.GetRequiredService()); + } + } + + [Test] + public void DocumentStartupMigrationOnlyRequiredRegistered() + { + IServiceCollection services = CreateEmptyServiceCollection(); + + services.AddMigration(builder => builder.AddStartupDocumentMigration()); + + using ServiceProvider provider = services.BuildServiceProvider(); + + MongoMigrationStartupSettings startupSettings = provider.GetRequiredService(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(startupSettings.RuntimeMigrationEnabled, Is.False); + Assert.That(startupSettings.DatabaseMigrationEnabled, Is.False); + Assert.That(startupSettings.StartupDocumentMigrationEnabled, Is.True); + + Assert.That( + provider.GetRequiredService(), + Is.TypeOf()); + Assert.That( + provider.GetRequiredService(), + Is.TypeOf()); + + Assert.Throws(() => provider.GetRequiredService()); + Assert.Throws(() => provider.GetRequiredService()); + } + } + + private static IServiceCollection CreateEmptyServiceCollection() + { + return new ServiceCollection() + .AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance)) + .AddSingleton(new MongoClient()); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_1.cs b/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_1.cs new file mode 100644 index 0000000..efe02d9 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_1.cs @@ -0,0 +1,13 @@ +using Mongo.Migration.Migrations.Database; +using MongoDB.Driver; + +namespace Mongo.Migration.Tests.TestDoubles.Database; + +public class TestDatabaseMigration001 : DatabaseMigration +{ + public TestDatabaseMigration001() : base("0.0.1") { } + + public override Task UpAsync(IMongoDatabase db, CancellationToken cancellationToken) => Task.CompletedTask; + + public override Task DownAsync(IMongoDatabase db, CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_2.cs b/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_2.cs new file mode 100644 index 0000000..7471e05 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_2.cs @@ -0,0 +1,13 @@ +using Mongo.Migration.Migrations.Database; +using MongoDB.Driver; + +namespace Mongo.Migration.Tests.TestDoubles.Database; + +public class TestDatabaseMigration002 : DatabaseMigration +{ + public TestDatabaseMigration002() : base("0.0.2") { } + + public override Task UpAsync(IMongoDatabase db, CancellationToken cancellationToken) => Task.CompletedTask; + + public override Task DownAsync(IMongoDatabase db, CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_3.cs b/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_3.cs new file mode 100644 index 0000000..441b2e1 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/Database/TestDatabaseMigration_0_0_3.cs @@ -0,0 +1,13 @@ +using Mongo.Migration.Migrations.Database; +using MongoDB.Driver; + +namespace Mongo.Migration.Tests.TestDoubles.Database; + +public class TestDatabaseMigration003 : DatabaseMigration +{ + public TestDatabaseMigration003() : base("0.0.3") { } + + public override Task UpAsync(IMongoDatabase db, CancellationToken cancellationToken) => Task.CompletedTask; + + public override Task DownAsync(IMongoDatabase db, CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestClassNoMigration.cs b/Mongo.Migration.Tests/TestDoubles/TestClassNoMigration.cs new file mode 100644 index 0000000..37fe744 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestClassNoMigration.cs @@ -0,0 +1,12 @@ +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +public class TestClassNoMigration +{ + public ObjectId Id { get; set; } + + public int Dors { get; set; } + + public required string Version { get; set; } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestClassWithTwoMigrationMiddleVersion.cs b/Mongo.Migration.Tests/TestDoubles/TestClassWithTwoMigrationMiddleVersion.cs new file mode 100644 index 0000000..4bd6e38 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestClassWithTwoMigrationMiddleVersion.cs @@ -0,0 +1,48 @@ +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; +public class TestClassWithTwoMigrationMiddleVersion +{ + public ObjectId Id { get; set; } + public int Doors1 { get; set; } + public string Version { get; set; } = "0.0.0"; +} + +public class TestClassWithTwoMigrationMiddleVersion001 : DocumentMigration +{ + public TestClassWithTwoMigrationMiddleVersion001() : base("0.0.1") { } + + public override void Up(BsonDocument document) + { + var doors = document["Doors0"].ToInt32(); + document.Add("Doors1", doors); + document.Remove("Doors0"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Doors1"].ToInt32(); + document.Add("Doors0", doors); + document.Remove("Doors1"); + } +} + +public class TestClassWithTwoMigrationMiddleVersion002 : DocumentMigration +{ + public TestClassWithTwoMigrationMiddleVersion002() : base("0.0.2") { } + + public override void Up(BsonDocument document) + { + var doors = document["Doors1"].ToInt32(); + document.Add("Doors2", doors); + document.Remove("Doors1"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Doors2"].ToInt32(); + document.Add("Doors1", doors); + document.Remove("Doors2"); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithOneMigration.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithOneMigration.cs new file mode 100644 index 0000000..5d3afb3 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithOneMigration.cs @@ -0,0 +1,18 @@ +using Mongo.Migration.Documents; +using Mongo.Migration.Documents.Attributes; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +[RuntimeVersion("0.0.1")] +public class TestDocumentWithOneMigration : Document +{ + public ObjectId Id { get; set; } + public int Doors { get; set; } +} + +public class TestDocumentWithoutAttribute : Document +{ + public ObjectId Id { get; set; } + public int Doors { get; set; } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithOneMigration_0_0_1.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithOneMigration_0_0_1.cs new file mode 100644 index 0000000..9f9f6d1 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithOneMigration_0_0_1.cs @@ -0,0 +1,26 @@ +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +public class TestDocumentWithOneMigration001 : DocumentMigration +{ + public TestDocumentWithOneMigration001() + : base("0.0.1") + { + } + + public override void Up(BsonDocument document) + { + var doors = document["Dors"].ToInt32(); + document.Add("Doors", doors); + document.Remove("Dors"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Doors"].ToInt32(); + document.Add("Dors", doors); + document.Remove("Doors"); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration.cs new file mode 100644 index 0000000..53a0696 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration.cs @@ -0,0 +1,12 @@ +using Mongo.Migration.Documents; +using Mongo.Migration.Documents.Attributes; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +[RuntimeVersion("0.0.0")] +public class TestDocumentWithTwoMigration : Document +{ + public ObjectId Id { get; set; } + public int Dors { get; set; } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion.cs new file mode 100644 index 0000000..9beff38 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion.cs @@ -0,0 +1,14 @@ +using Mongo.Migration.Documents; +using Mongo.Migration.Documents.Attributes; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +[RuntimeVersion("0.0.2")] +[CollectionLocation("Test")] +public class TestDocumentWithTwoMigrationHighestVersion : Document +{ + public ObjectId Id { get; set; } + + public int Door { get; set; } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_1.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_1.cs new file mode 100644 index 0000000..17435ab --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_1.cs @@ -0,0 +1,26 @@ +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +public class TestDocumentWithTwoMigrationHighestVersion001 : DocumentMigration +{ + public TestDocumentWithTwoMigrationHighestVersion001() + : base("0.0.1") + { + } + + public override void Up(BsonDocument document) + { + var doors = document["Dors"].ToInt32(); + document.Add("Doors", doors); + document.Remove("Dors"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Doors"].ToInt32(); + document.Add("Dors", doors); + document.Remove("Doors"); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_2.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_2.cs new file mode 100644 index 0000000..360c613 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationHighestVersion_0_0_2.cs @@ -0,0 +1,26 @@ +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +public class TestDocumentWithTwoMigrationHighestVersion002 : DocumentMigration +{ + public TestDocumentWithTwoMigrationHighestVersion002() + : base("0.0.2") + { + } + + public override void Up(BsonDocument document) + { + var doors = document["Doors"].ToInt32(); + document.Add("Door", doors); + document.Remove("Doors"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Door"].ToInt32(); + document.Add("Doors", doors); + document.Remove("Door"); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion.cs new file mode 100644 index 0000000..f43f1c4 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion.cs @@ -0,0 +1,12 @@ +using Mongo.Migration.Documents; +using Mongo.Migration.Documents.Attributes; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +[RuntimeVersion("0.0.1")] +public class TestDocumentWithTwoMigrationMiddleVersion : Document +{ + public ObjectId Id { get; set; } + public int Doors1 { get; set; } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_1.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_1.cs new file mode 100644 index 0000000..7b92906 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_1.cs @@ -0,0 +1,26 @@ +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +public class TestDocumentWithTwoMigrationMiddleVersion001 : DocumentMigration +{ + public TestDocumentWithTwoMigrationMiddleVersion001() + : base("0.0.1") + { + } + + public override void Up(BsonDocument document) + { + var doors = document["Doors0"].ToInt32(); + document.Add("Doors1", doors); + document.Remove("Doors0"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Doors1"].ToInt32(); + document.Add("Doors0", doors); + document.Remove("Doors1"); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_2.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_2.cs new file mode 100644 index 0000000..8ca4714 --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigrationMiddleVersion_0_0_2.cs @@ -0,0 +1,26 @@ +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +public class TestDocumentWithTwoMigrationMiddleVersion002 : DocumentMigration +{ + public TestDocumentWithTwoMigrationMiddleVersion002() + : base("0.0.2") + { + } + + public override void Up(BsonDocument document) + { + var doors = document["Doors1"].ToInt32(); + document.Add("Doors2", doors); + document.Remove("Doors1"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Doors2"].ToInt32(); + document.Add("Doors1", doors); + document.Remove("Doors2"); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration_0_0_1.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration_0_0_1.cs new file mode 100644 index 0000000..789c8fb --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration_0_0_1.cs @@ -0,0 +1,26 @@ +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +public class TestDocumentWithTwoMigration001 : DocumentMigration +{ + public TestDocumentWithTwoMigration001() + : base("0.0.1") + { + } + + public override void Up(BsonDocument document) + { + var doors = document["Dors"].ToInt32(); + document.Add("Doors", doors); + document.Remove("Dors"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Doors"].ToInt32(); + document.Add("Dors", doors); + document.Remove("Doors"); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration_0_0_2.cs b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration_0_0_2.cs new file mode 100644 index 0000000..2f81e3b --- /dev/null +++ b/Mongo.Migration.Tests/TestDoubles/TestDocumentWithTwoMigration_0_0_2.cs @@ -0,0 +1,26 @@ +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson; + +namespace Mongo.Migration.Tests.TestDoubles; + +public class TestDocumentWithTwoMigration002 : DocumentMigration +{ + public TestDocumentWithTwoMigration002() + : base("0.0.2") + { + } + + public override void Up(BsonDocument document) + { + var doors = document["Doors"].ToInt32(); + document.Add("Door", doors); + document.Remove("Doors"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Door"].ToInt32(); + document.Add("Doors", doors); + document.Remove("Door"); + } +} \ No newline at end of file diff --git a/Mongo.Migration.Tests/TestcontainersContext.cs b/Mongo.Migration.Tests/TestcontainersContext.cs new file mode 100644 index 0000000..249684a --- /dev/null +++ b/Mongo.Migration.Tests/TestcontainersContext.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Mongo.Migration.Services; +using Mongo.Migration.Startup; +using Mongo.Migration.Tests.TestDoubles; +using MongoDB.Driver; +using NUnit.Framework; +using Testcontainers.MongoDb; + +namespace Mongo.Migration.Tests; + +[SetUpFixture] +public sealed class TestcontainersContext +{ + private static readonly Lazy s_lazyMongoDbContainer = new(() => new MongoDbBuilder("mongo:8").Build()); + + private static ServiceProvider? s_provider; + + public static IServiceProvider Provider => s_provider ?? throw new InvalidOperationException("Must be setup"); + + public static IMongoClient MongoClient => Provider.GetRequiredService(); + + + [OneTimeSetUp] + public async Task OneTimeSetup() + { + await s_lazyMongoDbContainer.Value.StartAsync(); + + IServiceCollection services = new ServiceCollection(); + services + .AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance)) + .AddSingleton(new MongoClient(s_lazyMongoDbContainer.Value.GetConnectionString())) + .AddMigration(cfg => + { + cfg + .AddDocumentMigratedType("0.0.1"); + + cfg.AddRuntimeDocumentMigration() + .AddStartupDocumentMigration() + .AddDatabaseMigration(); + }); + + s_provider = services.BuildServiceProvider(); + + IMigrationService migrationService = s_provider.GetRequiredService(); + migrationService.RegisterBsonStatics(); + } + + [OneTimeTearDown] + public async Task OneTimeTearDown() + { + if (s_provider is not null) + { + await s_provider.DisposeAsync(); + } + + await s_lazyMongoDbContainer.Value.StopAsync(); + await s_lazyMongoDbContainer.Value.DisposeAsync(); + } +} \ No newline at end of file diff --git a/Mongo.Migration.sln b/Mongo.Migration.sln deleted file mode 100644 index 5f1345f..0000000 --- a/Mongo.Migration.sln +++ /dev/null @@ -1,48 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.4.33122.133 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Git", "Git", "{CB26356F-6A6A-4B13-BD95-672A8AF24518}" - ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore - LICENSE.txt = LICENSE.txt - Readme.md = Readme.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mongo.Migration", "Mongo.Migration\Mongo.Migration.csproj", "{8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mongo.Migration.Test.Core", "Mongo.Migration.Test.Core\Mongo.Migration.Test.Core.csproj", "{1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}.Debug|Any CPU.ActiveCfg = Debug|x64 - {8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}.Debug|Any CPU.Build.0 = Debug|x64 - {8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}.Debug|x64.ActiveCfg = Debug|x64 - {8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}.Debug|x64.Build.0 = Debug|x64 - {8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}.Release|Any CPU.Build.0 = Release|Any CPU - {8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}.Release|x64.ActiveCfg = Release|x64 - {8DFFD615-1E1A-4BED-8A96-CAF4C3637E81}.Release|x64.Build.0 = Release|x64 - {1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}.Debug|x64.ActiveCfg = Debug|x64 - {1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}.Debug|x64.Build.0 = Debug|x64 - {1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}.Release|Any CPU.Build.0 = Release|Any CPU - {1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}.Release|x64.ActiveCfg = Release|x64 - {1EEC8464-61D1-4FA3-97D4-21A35A45F3FE}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {141CC212-9A27-4082-94E7-A80340DFDF4C} - EndGlobalSection -EndGlobal diff --git a/Mongo.Migration.slnx b/Mongo.Migration.slnx new file mode 100644 index 0000000..13f16be --- /dev/null +++ b/Mongo.Migration.slnx @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Mongo.Migration/Bson/BaseMigrationSerializer.cs b/Mongo.Migration/Bson/BaseMigrationSerializer.cs new file mode 100644 index 0000000..40f70a5 --- /dev/null +++ b/Mongo.Migration/Bson/BaseMigrationSerializer.cs @@ -0,0 +1,68 @@ +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Serializers; +using Mongo.Migration.Migrations.Document; +using Mongo.Migration.Services; +using Mongo.Migration.Documents; +using MongoDB.Bson.Serialization.Conventions; + +namespace Mongo.Migration.Bson; +internal abstract class BaseMigrationSerializer : SerializerBase, IBsonIdProvider, IBsonDocumentSerializer, IBsonPolymorphicSerializer, IHasDiscriminatorConvention +{ + private static readonly Type s_tDocumentType = typeof(TDocument); + + private readonly IDocumentMigrationRunner _migrationRunner; + + protected string VersionFieldName { get; } + + protected DocumentVersion RuntimeVersion { get; } + + protected string RuntimeVersionString { get; } + + protected BsonClassMapSerializer InnerSerializer { get; } + + protected BaseMigrationSerializer(IDocumentMigrationRunner migrationRunner, IDocumentVersionService documentVersionService) + { + _migrationRunner = migrationRunner; + VersionFieldName = documentVersionService.GetVersionFieldName(); + RuntimeVersion = documentVersionService.GetCurrentOrLatestMigrationVersion(s_tDocumentType); + RuntimeVersionString = RuntimeVersion.ToString(); + BsonClassMap classMap = BsonClassMap.LookupClassMap(s_tDocumentType); + InnerSerializer = new BsonClassMapSerializer(classMap); + } + + public override TDocument Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + BsonReaderBookmark bookmark = context.Reader.GetBookmark(); + + context.Reader.ReadStartDocument(); + string currentVersion = context.Reader.FindStringElement(VersionFieldName); + context.Reader.ReturnToBookmark(bookmark); + + if (RuntimeVersionString == currentVersion) + { + return InnerSerializer.Deserialize(context, args); + } + + BsonDocument document = BsonDocumentSerializer.Instance.Deserialize(context); + _migrationRunner.Run(s_tDocumentType, document, RuntimeVersion); + var migratedContext = BsonDeserializationContext.CreateRoot(new BsonDocumentReader(document)); + return InnerSerializer.Deserialize(migratedContext, args); + } + + public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo) + => InnerSerializer.TryGetMemberSerializationInfo(memberName, out serializationInfo); + + public bool GetDocumentId(object document, out object id, out Type idNominalType, out IIdGenerator idGenerator) + => InnerSerializer.GetDocumentId(document, out id, out idNominalType, out idGenerator); + + public void SetDocumentId(object document, object id) + => InnerSerializer.SetDocumentId(document, id); + + public bool IsDiscriminatorCompatibleWithObjectSerializer + => InnerSerializer.IsDiscriminatorCompatibleWithObjectSerializer; + + public IDiscriminatorConvention DiscriminatorConvention + => InnerSerializer.DiscriminatorConvention; +} diff --git a/Mongo.Migration/Bson/MigrationBsonSerializerProvider.cs b/Mongo.Migration/Bson/MigrationBsonSerializerProvider.cs new file mode 100644 index 0000000..804407a --- /dev/null +++ b/Mongo.Migration/Bson/MigrationBsonSerializerProvider.cs @@ -0,0 +1,47 @@ +using Mongo.Migration.Documents; +using Mongo.Migration.Documents.Locators; +using Mongo.Migration.Migrations.Document; +using Mongo.Migration.Services; +using MongoDB.Bson.Serialization; + +namespace Mongo.Migration.Bson; + +internal sealed class MigrationBsonSerializerProvider : IRegistryAwareBsonSerializationProvider +{ + private static readonly Type s_migrationDocumentSerializerGenericType = typeof(MigrationDocumentSerializer<>); + private static readonly Type s_iDocumentType = typeof(IDocument); + private static readonly Type s_migrationReflexionSerializerGenericType = typeof(MigrationReflexionSerializer<>); + + private readonly IRuntimeVersionLocator _runtimeVersionLocator; + private readonly object[] _constructorParameters; + + public MigrationBsonSerializerProvider(IDocumentMigrationRunner migrationRunner, + IDocumentVersionService documentVersionService, + IRuntimeVersionLocator runtimeVersionLocator) + { + _runtimeVersionLocator = runtimeVersionLocator; + _constructorParameters = [migrationRunner, documentVersionService]; + } + + public IBsonSerializer GetSerializer(Type type) + { + return GetSerializer(type, BsonSerializer.SerializerRegistry); + } + + public IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry) + { + if (type.GetInterfaces().Contains(s_iDocumentType)) + { + var genericType = s_migrationDocumentSerializerGenericType.MakeGenericType(type); + return (IBsonSerializer)Activator.CreateInstance(genericType, _constructorParameters)!; + } + + if (_runtimeVersionLocator.GetLocateOrNull(type) is not null) + { + var genericType = s_migrationReflexionSerializerGenericType.MakeGenericType(type); + return (IBsonSerializer)Activator.CreateInstance(genericType, _constructorParameters)!; + } + + return null!; + } +} \ No newline at end of file diff --git a/Mongo.Migration/Bson/MigrationDocumentSerializer.cs b/Mongo.Migration/Bson/MigrationDocumentSerializer.cs new file mode 100644 index 0000000..aef62f3 --- /dev/null +++ b/Mongo.Migration/Bson/MigrationDocumentSerializer.cs @@ -0,0 +1,18 @@ +using Mongo.Migration.Documents; +using Mongo.Migration.Migrations.Document; +using MongoDB.Bson.Serialization; +using Mongo.Migration.Services; + +namespace Mongo.Migration.Bson; +internal sealed class MigrationDocumentSerializer : BaseMigrationSerializer + where TDocument : IDocument +{ + public MigrationDocumentSerializer(IDocumentMigrationRunner migrationRunner, IDocumentVersionService documentVersionService) + : base(migrationRunner, documentVersionService) { } + + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TDocument value) + { + value.Version = RuntimeVersion; + InnerSerializer.Serialize(context, args, value); + } +} diff --git a/Mongo.Migration/Bson/MigrationReflexionSerializer.cs b/Mongo.Migration/Bson/MigrationReflexionSerializer.cs new file mode 100644 index 0000000..09620b5 --- /dev/null +++ b/Mongo.Migration/Bson/MigrationReflexionSerializer.cs @@ -0,0 +1,33 @@ +using Mongo.Migration.Migrations.Document; +using Mongo.Migration.Services; +using MongoDB.Bson.Serialization; +using System.Linq.Expressions; + +namespace Mongo.Migration.Bson; +internal sealed class MigrationReflexionSerializer : BaseMigrationSerializer +{ + private readonly Action _versionSetter; + + public MigrationReflexionSerializer(IDocumentMigrationRunner migrationRunner, + IDocumentVersionService documentVersionService) + : base(migrationRunner, documentVersionService) + { + _versionSetter = CreateSetter(VersionFieldName); + } + + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TDocument value) + { + _versionSetter.Invoke(value, RuntimeVersion); + InnerSerializer.Serialize(context, args, value); + } + + private static Action CreateSetter(string propertyName) + { + var objParam = Expression.Parameter(typeof(T), "x"); + var valueParam = Expression.Parameter(typeof(TProperty), "value"); + var property = Expression.Property(objParam, propertyName); + var assign = Expression.Assign(property, valueParam); + var lambda = Expression.Lambda>(assign, objParam, valueParam); + return lambda.Compile(); + } +} diff --git a/Mongo.Migration/Documents/Attributes/CollectionLocation.cs b/Mongo.Migration/Documents/Attributes/CollectionLocation.cs deleted file mode 100644 index 752c6aa..0000000 --- a/Mongo.Migration/Documents/Attributes/CollectionLocation.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Mongo.Migration.Documents.Attributes -{ - [AttributeUsage(AttributeTargets.Class)] - public class CollectionLocation : Attribute - { - public CollectionLocationInformation CollectionInformation { get; } - - public CollectionLocation(string collectionName, string databaseName = null) - { - this.CollectionInformation = new CollectionLocationInformation(databaseName, collectionName); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Documents/Attributes/CollectionLocationAttribute.cs b/Mongo.Migration/Documents/Attributes/CollectionLocationAttribute.cs new file mode 100644 index 0000000..28efb9b --- /dev/null +++ b/Mongo.Migration/Documents/Attributes/CollectionLocationAttribute.cs @@ -0,0 +1,12 @@ +namespace Mongo.Migration.Documents.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class CollectionLocationAttribute : Attribute +{ + public CollectionLocationInformation CollectionInformation { get; } + + public CollectionLocationAttribute(string collectionName) + { + CollectionInformation = new CollectionLocationInformation(collectionName); + } +} \ No newline at end of file diff --git a/Mongo.Migration/Documents/Attributes/CollectionLocationInformation.cs b/Mongo.Migration/Documents/Attributes/CollectionLocationInformation.cs index b0c00ed..422ae4f 100644 --- a/Mongo.Migration/Documents/Attributes/CollectionLocationInformation.cs +++ b/Mongo.Migration/Documents/Attributes/CollectionLocationInformation.cs @@ -1,15 +1,11 @@ -namespace Mongo.Migration.Documents.Attributes +namespace Mongo.Migration.Documents.Attributes; + +public readonly struct CollectionLocationInformation { - public struct CollectionLocationInformation + public CollectionLocationInformation(string collection) { - public CollectionLocationInformation(string database, string collection) - { - this.Database = database; - this.Collection = collection; - } - - public string Database { get; } - - public string Collection { get; } + Collection = collection; } + + public string Collection { get; } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Attributes/RuntimeVersion.cs b/Mongo.Migration/Documents/Attributes/RuntimeVersion.cs deleted file mode 100644 index cd1ffbe..0000000 --- a/Mongo.Migration/Documents/Attributes/RuntimeVersion.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Mongo.Migration.Documents.Attributes -{ - [AttributeUsage(AttributeTargets.Class)] - public class RuntimeVersion : Attribute - { - public DocumentVersion Version { get; } - - public RuntimeVersion(string version) - { - this.Version = version; - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Documents/Attributes/RuntimeVersionAttribute.cs b/Mongo.Migration/Documents/Attributes/RuntimeVersionAttribute.cs new file mode 100644 index 0000000..3c58e75 --- /dev/null +++ b/Mongo.Migration/Documents/Attributes/RuntimeVersionAttribute.cs @@ -0,0 +1,12 @@ +namespace Mongo.Migration.Documents.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class RuntimeVersionAttribute : Attribute +{ + public DocumentVersion Version { get; } + + public RuntimeVersionAttribute(string version) + { + Version = DocumentVersion.Parse(version.AsSpan()); + } +} \ No newline at end of file diff --git a/Mongo.Migration/Documents/Attributes/StartUpVersion.cs b/Mongo.Migration/Documents/Attributes/StartUpVersion.cs deleted file mode 100644 index 85b2ca5..0000000 --- a/Mongo.Migration/Documents/Attributes/StartUpVersion.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Mongo.Migration.Documents.Attributes -{ - [AttributeUsage(AttributeTargets.Class)] - public class StartUpVersion : Attribute - { - public DocumentVersion Version { get; } - - public StartUpVersion(string version) - { - this.Version = version; - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Documents/Attributes/StartUpVersionAttribute.cs b/Mongo.Migration/Documents/Attributes/StartUpVersionAttribute.cs new file mode 100644 index 0000000..0958949 --- /dev/null +++ b/Mongo.Migration/Documents/Attributes/StartUpVersionAttribute.cs @@ -0,0 +1,12 @@ +namespace Mongo.Migration.Documents.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class StartUpVersionAttribute : Attribute +{ + public DocumentVersion Version { get; } + + public StartUpVersionAttribute(string version) + { + Version = DocumentVersion.Parse(version.AsSpan()); + } +} \ No newline at end of file diff --git a/Mongo.Migration/Documents/Document.cs b/Mongo.Migration/Documents/Document.cs index bc4f575..4740e45 100644 --- a/Mongo.Migration/Documents/Document.cs +++ b/Mongo.Migration/Documents/Document.cs @@ -1,7 +1,6 @@ -namespace Mongo.Migration.Documents +namespace Mongo.Migration.Documents; + +public class Document : IDocument { - public class Document : IDocument - { - public DocumentVersion Version { get; set; } - } + public DocumentVersion Version { get; set; } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/DocumentVersion.cs b/Mongo.Migration/Documents/DocumentVersion.cs index a9e8dfb..b1e6a16 100644 --- a/Mongo.Migration/Documents/DocumentVersion.cs +++ b/Mongo.Migration/Documents/DocumentVersion.cs @@ -1,165 +1,76 @@ -using System; +namespace Mongo.Migration.Documents; -using Mongo.Migration.Documents.Serializers; -using Mongo.Migration.Exceptions; - -using MongoDB.Bson.Serialization; - -namespace Mongo.Migration.Documents +public readonly record struct DocumentVersion : IComparable { - public struct DocumentVersion : IComparable - { - private const char VERSION_SPLIT_CHAR = '.'; - - private const int MAX_LENGTH = 3; - - public int Major { get; init; } - - public int Minor { get; init; } - - public int Revision { get; init; } - - static DocumentVersion() - { - try - { - BsonSerializer.RegisterSerializer(typeof(DocumentVersion), new DocumentVersionSerializer()); - } - catch (Exception) - { - } - } - - public DocumentVersion(string version) - { - string[] versionParts = version.Split(VERSION_SPLIT_CHAR); + private const char VersionSplitChar = '.'; - if (versionParts.Length != MAX_LENGTH) - { - throw new VersionStringToLongException(version); - } - - this.Major = ParseVersionPart(versionParts[0]); - - this.Minor = ParseVersionPart(versionParts[1]); - - this.Revision = ParseVersionPart(versionParts[2]); - } - - public DocumentVersion(int major, int minor, int revision) - { - this.Major = major; - this.Minor = minor; - this.Revision = revision; - } + public int Major { get; } - public static DocumentVersion Default() - { - return default(DocumentVersion); - } + public int Minor { get; } - public static DocumentVersion Empty() - { - return new DocumentVersion(-1, 0, 0); - } + public int Revision { get; } - public static implicit operator DocumentVersion(string version) - { - return new DocumentVersion(version); - } + public DocumentVersion(int major, int minor, int revision) + { + Major = major; + Minor = minor; + Revision = revision; + } - public static implicit operator string(DocumentVersion documentVersion) - { - return documentVersion.ToString(); - } + public static readonly DocumentVersion Default = new(0,0,0); - public override string ToString() - { - return $"{this.Major}.{this.Minor}.{this.Revision}"; - } + public static readonly DocumentVersion Empty = new(-1, 0, 0); - public int CompareTo(DocumentVersion other) - { - if (this.Equals(other)) - { - return 0; - } + public static implicit operator DocumentVersion(string version) => Parse(version.AsSpan()); - return this > other ? 1 : -1; - } + public static implicit operator DocumentVersion(ReadOnlySpan versionSpan) => Parse(versionSpan); - public static bool operator ==(DocumentVersion a, DocumentVersion b) - { - return a.Equals(b); - } + public static implicit operator string(DocumentVersion documentVersion) => documentVersion.ToString(); - public static bool operator !=(DocumentVersion a, DocumentVersion b) - { - return !(a == b); - } + public override string ToString() => $"{Major}.{Minor}.{Revision}"; - public static bool operator >(DocumentVersion a, DocumentVersion b) + public int CompareTo(DocumentVersion other) + { + int compare = Major.CompareTo(other.Major); + if (compare != 0) { - return a.Major > b.Major - || (a.Major == b.Major && a.Minor > b.Minor) - || (a.Major == b.Major && a.Minor == b.Minor && a.Revision > b.Revision); + return compare; } - public static bool operator <(DocumentVersion a, DocumentVersion b) + compare = Minor.CompareTo(other.Minor); + if (compare != 0) { - return a != b && !(a > b); + return compare; } - public static bool operator <=(DocumentVersion a, DocumentVersion b) - { - return a == b || a < b; - } + return Revision.CompareTo(other.Revision); + } - public static bool operator >=(DocumentVersion a, DocumentVersion b) - { - return a == b || a > b; - } + public static bool operator >(DocumentVersion a, DocumentVersion b) => a.CompareTo(b) > 0; - public bool Equals(DocumentVersion other) - { - return other.Major == this.Major && other.Minor == this.Minor && other.Revision == this.Revision; - } + public static bool operator <(DocumentVersion a, DocumentVersion b) => a.CompareTo(b) < 0; - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } + public static bool operator >=(DocumentVersion a, DocumentVersion b) => a.CompareTo(b) >= 0; - if (obj.GetType() != typeof(DocumentVersion)) - { - return false; - } + public static bool operator <=(DocumentVersion a, DocumentVersion b) => a.CompareTo(b) <= 0; - return this.Equals((DocumentVersion)obj); - } + public static DocumentVersion Parse(ReadOnlySpan versionSpan) + { + int[] versionParts = new int[3]; + int versionPartCount = 0; + int startIndex = 0; - public override int GetHashCode() + for (int i = 0; i < versionSpan.Length; i++) { - unchecked + if (versionSpan[i] == VersionSplitChar) { - int result = this.Major; - result = (result * 397) ^ this.Minor; - result = (result * 397) ^ this.Revision; - return result; + versionParts[versionPartCount++] = int.Parse(versionSpan[startIndex..i]); + startIndex = i + 1; } } - private static int ParseVersionPart(string value) - { - string revisionString = value; - if (!int.TryParse(revisionString, out var target)) - { - throw new InvalidVersionValueException(revisionString); - } + versionParts[versionPartCount] = int.Parse(versionSpan[startIndex..]); - return target; - } + return new DocumentVersion(versionParts[0], versionParts[1], versionParts[2]); } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/IDocument.cs b/Mongo.Migration/Documents/IDocument.cs index 5188393..b803619 100644 --- a/Mongo.Migration/Documents/IDocument.cs +++ b/Mongo.Migration/Documents/IDocument.cs @@ -1,7 +1,6 @@ -namespace Mongo.Migration.Documents +namespace Mongo.Migration.Documents; + +public interface IDocument { - public interface IDocument - { - DocumentVersion Version { get; set; } - } + DocumentVersion Version { get; set; } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Locators/AbstractLocator.cs b/Mongo.Migration/Documents/Locators/AbstractLocator.cs index 8cbbcf0..b00d04c 100644 --- a/Mongo.Migration/Documents/Locators/AbstractLocator.cs +++ b/Mongo.Migration/Documents/Locators/AbstractLocator.cs @@ -1,30 +1,43 @@ -using System.Collections.Generic; +using System.Collections.Frozen; +using System.Reflection; -namespace Mongo.Migration.Documents.Locators +namespace Mongo.Migration.Documents.Locators; + +public abstract class AbstractLocator : ILocator + where TReturnType : struct + where TAttributeType : Attribute { - public abstract class AbstractLocator : ILocator - where TReturnType : struct - where TTypeIdentifier : class + private readonly Lazy> _lazyLocateDictionary; + + protected AbstractLocator() { - private IDictionary _locatesDictionary; + _lazyLocateDictionary = new Lazy>( + LoadLocateDictionary, + LazyThreadSafetyMode.PublicationOnly); + } - protected IDictionary LocatesDictionary - { - get - { - if (this._locatesDictionary == null) - { - this.Locate(); - } + protected abstract TReturnType GetAttributeValue(TAttributeType attribute); - return this._locatesDictionary; - } + protected IDictionary LocatesDictionary => _lazyLocateDictionary.Value; - set => this._locatesDictionary = value; + public virtual TReturnType? GetLocateOrNull(Type identifier) + { + if (LocatesDictionary.TryGetValue(identifier, out TReturnType returnType)) + { + return returnType; } - public abstract TReturnType? GetLocateOrNull(TTypeIdentifier identifier); + return null; + } - public abstract void Locate(); + private FrozenDictionary LoadLocateDictionary() + { + return AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetExportedTypes()) + .Select(t => (t, t.GetCustomAttribute())) + .Where(t => t.Item2 is not null) + .ToFrozenDictionary( + pair => pair.t, + pair => GetAttributeValue(pair.Item2!)); } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Locators/CollectionLocator.cs b/Mongo.Migration/Documents/Locators/CollectionLocator.cs index a25694e..52c3b47 100644 --- a/Mongo.Migration/Documents/Locators/CollectionLocator.cs +++ b/Mongo.Migration/Documents/Locators/CollectionLocator.cs @@ -1,47 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using Mongo.Migration.Documents.Attributes; -namespace Mongo.Migration.Documents.Locators +namespace Mongo.Migration.Documents.Locators; + +public class CollectionLocator : AbstractLocator, ICollectionLocator { - public class CollectionLocator : AbstractLocator, ICollectionLocator + protected override CollectionLocationInformation GetAttributeValue(CollectionLocationAttribute attribute) { - public override CollectionLocationInformation? GetLocateOrNull(Type identifier) - { - if (!this.LocatesDictionary.ContainsKey(identifier)) - { - return null; - } - - this.LocatesDictionary.TryGetValue(identifier, out var value); - return value; - } + return attribute.CollectionInformation; + } - public override void Locate() + public override CollectionLocationInformation? GetLocateOrNull(Type identifier) + { + if (!LocatesDictionary.ContainsKey(identifier)) { - var types = - from a in AppDomain.CurrentDomain.GetAssemblies() - from t in a.GetTypes() - let attributes = t.GetCustomAttributes(typeof(CollectionLocation), true) - where attributes != null && attributes.Length > 0 - select new { Type = t, Attributes = attributes.Cast() }; - - var versions = new Dictionary(); - - foreach (var type in types) - { - var version = type.Attributes.First().CollectionInformation; - versions.Add(type.Type, version); - } - - this.LocatesDictionary = versions; + return null; } - public IDictionary GetLocatesOrEmpty() - { - return this.LocatesDictionary; - } + LocatesDictionary.TryGetValue(identifier, out var value); + return value; + } + + public IDictionary GetLocatesOrEmpty() + { + return LocatesDictionary; } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Locators/ICollectionLocator.cs b/Mongo.Migration/Documents/Locators/ICollectionLocator.cs index 19f1dde..d348d90 100644 --- a/Mongo.Migration/Documents/Locators/ICollectionLocator.cs +++ b/Mongo.Migration/Documents/Locators/ICollectionLocator.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; - using Mongo.Migration.Documents.Attributes; -namespace Mongo.Migration.Documents.Locators +namespace Mongo.Migration.Documents.Locators; + +public interface ICollectionLocator : ILocator { - public interface ICollectionLocator : ILocator - { - IDictionary GetLocatesOrEmpty(); - } + IDictionary GetLocatesOrEmpty(); } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Locators/ILocator.cs b/Mongo.Migration/Documents/Locators/ILocator.cs index bd7d7ac..c79677a 100644 --- a/Mongo.Migration/Documents/Locators/ILocator.cs +++ b/Mongo.Migration/Documents/Locators/ILocator.cs @@ -1,11 +1,8 @@ -namespace Mongo.Migration.Documents.Locators -{ - public interface ILocator - where TReturnType : struct - where TTypeIdentifier : class - { - TReturnType? GetLocateOrNull(TTypeIdentifier identifier); +namespace Mongo.Migration.Documents.Locators; - void Locate(); - } +public interface ILocator + where TReturnType : struct + where TTypeIdentifier : class +{ + TReturnType? GetLocateOrNull(TTypeIdentifier identifier); } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Locators/IRuntimeVersionLocator.cs b/Mongo.Migration/Documents/Locators/IRuntimeVersionLocator.cs index 837e65d..ef54d48 100644 --- a/Mongo.Migration/Documents/Locators/IRuntimeVersionLocator.cs +++ b/Mongo.Migration/Documents/Locators/IRuntimeVersionLocator.cs @@ -1,8 +1,5 @@ -using System; +namespace Mongo.Migration.Documents.Locators; -namespace Mongo.Migration.Documents.Locators +internal interface IRuntimeVersionLocator : ILocator { - internal interface IRuntimeVersionLocator : ILocator - { - } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Locators/IStartUpVersionLocator.cs b/Mongo.Migration/Documents/Locators/IStartUpVersionLocator.cs index fe444a9..2e74882 100644 --- a/Mongo.Migration/Documents/Locators/IStartUpVersionLocator.cs +++ b/Mongo.Migration/Documents/Locators/IStartUpVersionLocator.cs @@ -1,8 +1,5 @@ -using System; +namespace Mongo.Migration.Documents.Locators; -namespace Mongo.Migration.Documents.Locators +internal interface IStartUpVersionLocator : ILocator { - internal interface IStartUpVersionLocator : ILocator - { - } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Locators/RuntimeVersionLocator.cs b/Mongo.Migration/Documents/Locators/RuntimeVersionLocator.cs index a77a51f..47785e0 100644 --- a/Mongo.Migration/Documents/Locators/RuntimeVersionLocator.cs +++ b/Mongo.Migration/Documents/Locators/RuntimeVersionLocator.cs @@ -1,42 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; - +using System.Collections.Frozen; using Mongo.Migration.Documents.Attributes; -namespace Mongo.Migration.Documents.Locators +namespace Mongo.Migration.Documents.Locators; + +internal class RuntimeVersionLocator : AbstractLocator, IRuntimeVersionLocator { - internal class RuntimeVersionLocator : AbstractLocator, IRuntimeVersionLocator + private readonly FrozenDictionary _codeDefinedDictionary; + + internal RuntimeVersionLocator(IEnumerable> alreadyDefinedRuntimeVersions) { - public override DocumentVersion? GetLocateOrNull(Type identifier) - { - if (!this.LocatesDictionary.ContainsKey(identifier)) - { - return null; - } + _codeDefinedDictionary = alreadyDefinedRuntimeVersions.ToFrozenDictionary(); + } - this.LocatesDictionary.TryGetValue(identifier, out var value); - return value; - } + protected override DocumentVersion GetAttributeValue(RuntimeVersionAttribute attribute) + { + return attribute.Version; + } - public override void Locate() + public override DocumentVersion? GetLocateOrNull(Type identifier) + { + if (_codeDefinedDictionary.TryGetValue(identifier, out DocumentVersion version)) { - var types = - from a in AppDomain.CurrentDomain.GetAssemblies() - from t in a.GetTypes() - let attributes = t.GetCustomAttributes(typeof(RuntimeVersion), true) - where attributes != null && attributes.Length > 0 - select new { Type = t, Attributes = attributes.Cast() }; - - var versions = new Dictionary(); - - foreach (var type in types) - { - var version = type.Attributes.First().Version; - versions.Add(type.Type, version); - } - - this.LocatesDictionary = versions; + return version; } + + return base.GetLocateOrNull(identifier); } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Locators/StartUpVersionLocator.cs b/Mongo.Migration/Documents/Locators/StartUpVersionLocator.cs index 24abc04..5ef96c4 100644 --- a/Mongo.Migration/Documents/Locators/StartUpVersionLocator.cs +++ b/Mongo.Migration/Documents/Locators/StartUpVersionLocator.cs @@ -1,42 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using Mongo.Migration.Documents.Attributes; -using Mongo.Migration.Documents.Attributes; +namespace Mongo.Migration.Documents.Locators; -namespace Mongo.Migration.Documents.Locators +internal class StartUpVersionLocator : AbstractLocator, IStartUpVersionLocator { - internal class StartUpVersionLocator : AbstractLocator, IStartUpVersionLocator + protected override DocumentVersion GetAttributeValue(StartUpVersionAttribute attribute) { - public override DocumentVersion? GetLocateOrNull(Type identifier) - { - if (!this.LocatesDictionary.ContainsKey(identifier)) - { - return null; - } - - this.LocatesDictionary.TryGetValue(identifier, out var value); - return value; - } - - public override void Locate() - { - var types = - from a in AppDomain.CurrentDomain.GetAssemblies() - from t in a.GetTypes() - let attributes = t.GetCustomAttributes(typeof(StartUpVersion), true) - where attributes != null && attributes.Length > 0 - select new { Type = t, Attributes = attributes.Cast() }; - - var versions = new Dictionary(); - - foreach (var type in types) - { - var version = type.Attributes.First().Version; - versions.Add(type.Type, version); - } - - this.LocatesDictionary = versions; - } + return attribute.Version; } } \ No newline at end of file diff --git a/Mongo.Migration/Documents/Serializers/DocumentVersionSerializer.cs b/Mongo.Migration/Documents/Serializers/DocumentVersionSerializer.cs index 31b4d60..dda29b0 100644 --- a/Mongo.Migration/Documents/Serializers/DocumentVersionSerializer.cs +++ b/Mongo.Migration/Documents/Serializers/DocumentVersionSerializer.cs @@ -1,23 +1,21 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; -namespace Mongo.Migration.Documents.Serializers +namespace Mongo.Migration.Documents.Serializers; + +public sealed class DocumentVersionSerializer : SerializerBase { - public class DocumentVersionSerializer : SerializerBase + public override void Serialize( + BsonSerializationContext context, + BsonSerializationArgs _, + DocumentVersion value) { - public override void Serialize( - BsonSerializationContext context, - BsonSerializationArgs args, - DocumentVersion value) - { - var versionString = $"{value.Major}.{value.Minor}.{value.Revision}"; - context.Writer.WriteString(versionString); - } + context.Writer.WriteString(value.ToString()); + } - public override DocumentVersion Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) - { - var versionString = context.Reader.ReadString(); - return new DocumentVersion(versionString); - } + public override DocumentVersion Deserialize(BsonDeserializationContext context, BsonDeserializationArgs _) + { + var versionString = context.Reader.ReadString(); + return DocumentVersion.Parse(versionString.AsSpan()); } } \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/AlreadyInitializedException.cs b/Mongo.Migration/Exceptions/AlreadyInitializedException.cs deleted file mode 100644 index a13cc1a..0000000 --- a/Mongo.Migration/Exceptions/AlreadyInitializedException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Mongo.Migration.Exceptions -{ - public class AlreadyInitializedException : Exception - { - public AlreadyInitializedException() - : base(string.Format(ErrorTexts.AlreadyInitialized)) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/DuplicateVersionException.cs b/Mongo.Migration/Exceptions/DuplicateVersionException.cs index 6c0c43d..5cad71f 100644 --- a/Mongo.Migration/Exceptions/DuplicateVersionException.cs +++ b/Mongo.Migration/Exceptions/DuplicateVersionException.cs @@ -1,12 +1,9 @@ -using System; +namespace Mongo.Migration.Exceptions; -namespace Mongo.Migration.Exceptions +public class DuplicateVersionException : Exception { - internal class DuplicateVersionException : Exception + public DuplicateVersionException(string typeName, string version) + : base($"Migration '{typeName}' contains duplicate version: {version}") { - public DuplicateVersionException(string typeName, string version) - : base(string.Format(ErrorTexts.DuplicateVersion, typeName, version)) - { - } } } \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/ErrorTexts.Designer.cs b/Mongo.Migration/Exceptions/ErrorTexts.Designer.cs deleted file mode 100644 index a8b34e7..0000000 --- a/Mongo.Migration/Exceptions/ErrorTexts.Designer.cs +++ /dev/null @@ -1,135 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Mongo.Migration.Exceptions { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class ErrorTexts { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ErrorTexts() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Mongo.Migration.Exceptions.ErrorTexts", typeof(ErrorTexts).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to MongoMigration was already initialized. - /// - public static string AlreadyInitialized { - get { - return ResourceManager.GetString("AlreadyInitialized", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Application directory could not be found. - /// - public static string AppDirNotFound { - get { - return ResourceManager.GetString("AppDirNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Database could not be found for: '{0}', database: '{1}'. - /// - public static string ConnectionCheckError { - get { - return ResourceManager.GetString("ConnectionCheckError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Migration '{0}' contains duplicate version: {1}. - /// - public static string DuplicateVersion { - get { - return ResourceManager.GetString("DuplicateVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid value: {0}. - /// - public static string InvalidVersionValue { - get { - return ResourceManager.GetString("InvalidVersionValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No MongoClient, you need to register a MongoClient to the DI-Container or define connection settings with MongoMigrationSettings. - /// - public static string NoMongoClient { - get { - return ResourceManager.GetString("NoMongoClient", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Versions must have format: major.minor.revision, this doesn't match: {0}. - /// - public static string VersionStringToLong { - get { - return ResourceManager.GetString("VersionStringToLong", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CurrentVersion: '{0}', DocumentVersion: '{1}', LatestMigrationVersion: '{2}'. - /// - public static string VersionViolated { - get { - return ResourceManager.GetString("VersionViolated", resourceCulture); - } - } - } -} diff --git a/Mongo.Migration/Exceptions/ErrorTexts.resx b/Mongo.Migration/Exceptions/ErrorTexts.resx deleted file mode 100644 index 2320653..0000000 --- a/Mongo.Migration/Exceptions/ErrorTexts.resx +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - MongoMigration was already initialized - - - Migration '{0}' contains duplicate version: {1} - - - Invalid value: {0} - - - Versions must have format: major.minor.revision, this doesn't match: {0} - - - No MongoClient, you need to register a MongoClient to the DI-Container or define connection settings with MongoMigrationSettings - - - CurrentVersion: '{0}', DocumentVersion: '{1}', LatestMigrationVersion: '{2}' - - - Application directory could not be found - - - Database could not be found for: '{0}', database: '{1}' - - \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/InvalidVersionValueException.cs b/Mongo.Migration/Exceptions/InvalidVersionValueException.cs deleted file mode 100644 index 216a3c8..0000000 --- a/Mongo.Migration/Exceptions/InvalidVersionValueException.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Mongo.Migration.Exceptions -{ - public class InvalidVersionValueException : Exception - { - public InvalidVersionValueException(string value) - : - base(string.Format(ErrorTexts.InvalidVersionValue, value)) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/MongoMigrationDatabaseNotFound.cs b/Mongo.Migration/Exceptions/MongoMigrationDatabaseNotFound.cs deleted file mode 100644 index 29a93e6..0000000 --- a/Mongo.Migration/Exceptions/MongoMigrationDatabaseNotFound.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Mongo.Migration.Exceptions -{ - internal class MongoMigrationDatabaseNotFound - : Exception - { - public MongoMigrationDatabaseNotFound(string databaseName, string valueConnectionString) - : base(string.Format(ErrorTexts.ConnectionCheckError, databaseName, valueConnectionString)) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/MongoMigrationNoMongoClientException.cs b/Mongo.Migration/Exceptions/MongoMigrationNoMongoClientException.cs deleted file mode 100644 index e8398b2..0000000 --- a/Mongo.Migration/Exceptions/MongoMigrationNoMongoClientException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Mongo.Migration.Exceptions -{ - public class MongoMigrationNoMongoClientException : Exception - { - public MongoMigrationNoMongoClientException() - : base(string.Format(ErrorTexts.NoMongoClient)) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/NoDatabaseNameFoundException.cs b/Mongo.Migration/Exceptions/NoDatabaseNameFoundException.cs deleted file mode 100644 index 70fe214..0000000 --- a/Mongo.Migration/Exceptions/NoDatabaseNameFoundException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Mongo.Migration.Exceptions -{ - internal class NoDatabaseNameFoundException : Exception - { - } -} \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/NoMigrationsFoundException.cs b/Mongo.Migration/Exceptions/NoMigrationsFoundException.cs deleted file mode 100644 index 3fa2a72..0000000 --- a/Mongo.Migration/Exceptions/NoMigrationsFoundException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Mongo.Migration.Exceptions -{ - public class NoMigrationsFoundException : Exception - { - } -} \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/RuntimeVersionDefinitionException.cs b/Mongo.Migration/Exceptions/RuntimeVersionDefinitionException.cs new file mode 100644 index 0000000..d07199d --- /dev/null +++ b/Mongo.Migration/Exceptions/RuntimeVersionDefinitionException.cs @@ -0,0 +1,14 @@ +using Mongo.Migration.Documents; + +namespace Mongo.Migration.Exceptions; +public class RuntimeVersionDefinitionException : Exception +{ + internal RuntimeVersionDefinitionException( + Type type, + DocumentVersion addMigrationRuntimeVersionDefinition, + DocumentVersion attributeRuntimeVersionDefinition) + : base($"Migrated type {type.Name} runtime version definition conflict: AddMigration<{type.Name}>@{addMigrationRuntimeVersionDefinition} != RuntimeVersionAttribute({attributeRuntimeVersionDefinition})") + { + + } +} diff --git a/Mongo.Migration/Exceptions/VersionStringToLongException.cs b/Mongo.Migration/Exceptions/VersionStringToLongException.cs deleted file mode 100644 index eef22c4..0000000 --- a/Mongo.Migration/Exceptions/VersionStringToLongException.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Mongo.Migration.Exceptions -{ - public class VersionStringToLongException : Exception - { - public VersionStringToLongException(string version) - : - base(string.Format(ErrorTexts.VersionStringToLong, version)) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Exceptions/VersionViolationException.cs b/Mongo.Migration/Exceptions/VersionViolationException.cs index 235e51a..9018929 100644 --- a/Mongo.Migration/Exceptions/VersionViolationException.cs +++ b/Mongo.Migration/Exceptions/VersionViolationException.cs @@ -1,17 +1,14 @@ -using System; +using Mongo.Migration.Documents; -using Mongo.Migration.Documents; +namespace Mongo.Migration.Exceptions; -namespace Mongo.Migration.Exceptions +public class VersionViolationException : Exception { - public class VersionViolationException : Exception + public VersionViolationException( + DocumentVersion currentVersion, + DocumentVersion documentVersion, + DocumentVersion latestVersion) + : base($"CurrentVersion: '{currentVersion}', DocumentVersion: '{documentVersion}', LatestMigrationVersion: '{latestVersion}'") { - public VersionViolationException( - DocumentVersion currentVersion, - DocumentVersion documentVersion, - DocumentVersion latestVersion) - : base(string.Format(ErrorTexts.DuplicateVersion, currentVersion, documentVersion, latestVersion)) - { - } } } \ No newline at end of file diff --git a/Mongo.Migration/Extensions/EnumerableExtensions.cs b/Mongo.Migration/Extensions/EnumerableExtensions.cs index e48483d..ea936f1 100644 --- a/Mongo.Migration/Extensions/EnumerableExtensions.cs +++ b/Mongo.Migration/Extensions/EnumerableExtensions.cs @@ -1,59 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; - +using System.Collections.Frozen; +using System.Collections.ObjectModel; +using Mongo.Migration.Documents; using Mongo.Migration.Exceptions; using Mongo.Migration.Migrations; -namespace Mongo.Migration.Extensions +namespace Mongo.Migration.Extensions; + +internal static class EnumerableExtensions { - internal static class EnumerableExtensions + private static IEnumerable CheckForDuplicates(this IEnumerable list) + where TMigrationType : IMigration { - internal static bool NullOrEmpty(this IEnumerable list) - { - return list == null || !list.Any(); - } - - internal static IEnumerable CheckForDuplicates(this IEnumerable list) - where TMigrationType : class, IMigration + var uniqueHashes = new HashSet(); + foreach (var element in list) { - var uniqueHashes = new HashSet(); - foreach (var element in list) + if (uniqueHashes.Add(element.Version)) { - var version = element.Version.ToString(); - if (uniqueHashes.Add(version)) - { - continue; - } - - var typeName = element.GetType().Name; - throw new DuplicateVersionException(typeName, element.Version); + yield return element; } - - return list; - } - - internal static IDictionary> ToMigrationDictionary( - this IEnumerable migrations) - where TMigrationType : class, IMigration - { - var dictonary = new Dictionary>(); - var list = migrations.ToList(); - var types = (from m in list select m.Type).Distinct(); - - foreach (var type in types) + else { - if (dictonary.ContainsKey(type)) - { - continue; - } - - var uniqueMigrations = - list.Where(m => m.Type == type).CheckForDuplicates().OrderBy(m => m.Version).ToList(); - dictonary.Add(type, uniqueMigrations); + throw new DuplicateVersionException(element.GetType().Name, element.Version.ToString()); } - - return dictonary; } } + + internal static IDictionary> ToMigrationDictionary( + this IEnumerable migrations) + where TMigrationType : IMigration + { + return migrations + .GroupBy(m => m.Type) + .ToFrozenDictionary( + g => g.Key, + g => g + .CheckForDuplicates() + .OrderBy(m => m.Version).ToList().AsReadOnly()); + } } \ No newline at end of file diff --git a/Mongo.Migration/IMongoMigration.cs b/Mongo.Migration/IMongoMigration.cs deleted file mode 100644 index 1e610d7..0000000 --- a/Mongo.Migration/IMongoMigration.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Mongo.Migration -{ - public interface IMongoMigration - { - void Run(); - } -} \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Adapters/IContainerAdapter.cs b/Mongo.Migration/Migrations/Adapters/IContainerAdapter.cs deleted file mode 100644 index 2a2db60..0000000 --- a/Mongo.Migration/Migrations/Adapters/IContainerAdapter.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Mongo.Migration.Migrations.Adapters -{ - public interface IContainerAdapter : IContainerCollection, IContainerProvider - { - } -} \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Adapters/IContainerCollection.cs b/Mongo.Migration/Migrations/Adapters/IContainerCollection.cs deleted file mode 100644 index 757be3a..0000000 --- a/Mongo.Migration/Migrations/Adapters/IContainerCollection.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Mongo.Migration.Migrations.Adapters -{ - public interface IContainerCollection - { - void Register() - where TInterface : class - where TImplementation : class, TInterface; - - void RegisterInstance(object instance); - - void RegisterSingleton() - where TInterface : class - where TImplementation : class, TInterface; - - void Register(Type serviceType, Type implementingType); - } -} \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Adapters/IContainerProvider.cs b/Mongo.Migration/Migrations/Adapters/IContainerProvider.cs deleted file mode 100644 index 55d3f59..0000000 --- a/Mongo.Migration/Migrations/Adapters/IContainerProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Mongo.Migration.Migrations.Adapters -{ - public interface IContainerProvider - { - object GetInstance(Type type); - } -} \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Adapters/LightInjectAdapter.cs b/Mongo.Migration/Migrations/Adapters/LightInjectAdapter.cs deleted file mode 100644 index 29e4155..0000000 --- a/Mongo.Migration/Migrations/Adapters/LightInjectAdapter.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; - -using LightInject; - -namespace Mongo.Migration.Migrations.Adapters -{ - public class LightInjectAdapter : IContainerAdapter - { - private readonly IServiceContainer _container; - - public LightInjectAdapter(IServiceContainer container) - { - this._container = container; - } - - public object GetInstance(Type type) - { - return this._container.GetInstance(type); - } - - public void Register() - where TInterface : class - where TImplementation : class, TInterface - { - this._container.Register(); - } - - public void Register(Type serviceType, Type implementingType) - { - this._container.Register(serviceType, implementingType); - } - - public void RegisterInstance(object instance) - { - this._container.RegisterInstance(typeof(TInterface), instance); - } - - public void RegisterSingleton() - where TInterface : class - where TImplementation : class, TInterface - { - this._container.Register(new PerContainerLifetime()); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Adapters/ServiceProvider.cs b/Mongo.Migration/Migrations/Adapters/ServiceProvider.cs deleted file mode 100644 index ecdeae3..0000000 --- a/Mongo.Migration/Migrations/Adapters/ServiceProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Mongo.Migration.Migrations.Adapters -{ - public class ServiceProvider : IContainerProvider - { - private readonly IServiceProvider _serviceProvider; - - public ServiceProvider(IServiceProvider serviceProvider) - { - this._serviceProvider = serviceProvider; - } - - public object GetInstance(Type type) - { - return this._serviceProvider.GetService(type); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Database/DatabaseMigration.cs b/Mongo.Migration/Migrations/Database/DatabaseMigration.cs index 6073606..b7a26a4 100644 --- a/Mongo.Migration/Migrations/Database/DatabaseMigration.cs +++ b/Mongo.Migration/Migrations/Database/DatabaseMigration.cs @@ -1,24 +1,21 @@ -using System; - -using Mongo.Migration.Documents; +using Mongo.Migration.Documents; using MongoDB.Driver; -namespace Mongo.Migration.Migrations.Database +namespace Mongo.Migration.Migrations.Database; + +public abstract class DatabaseMigration : IDatabaseMigration { - public abstract class DatabaseMigration : IDatabaseMigration + protected DatabaseMigration(string version) { - protected DatabaseMigration(string version) - { - this.Version = version; - } + Version = DocumentVersion.Parse(version.AsSpan()); + } - public DocumentVersion Version { get; } + public DocumentVersion Version { get; } - public Type Type => typeof(DatabaseMigration); + public Type Type => typeof(DatabaseMigration); - public abstract void Up(IMongoDatabase db); + public abstract Task UpAsync(IMongoDatabase db, CancellationToken cancellationToken); - public abstract void Down(IMongoDatabase db); - } + public abstract Task DownAsync(IMongoDatabase db, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Database/DatabaseMigrationRunner.cs b/Mongo.Migration/Migrations/Database/DatabaseMigrationRunner.cs index 378accf..a0dfeb1 100644 --- a/Mongo.Migration/Migrations/Database/DatabaseMigrationRunner.cs +++ b/Mongo.Migration/Migrations/Database/DatabaseMigrationRunner.cs @@ -1,115 +1,91 @@ -using System; -using System.Linq; - -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - +using Microsoft.Extensions.Logging; using Mongo.Migration.Documents; using Mongo.Migration.Migrations.Locators; using Mongo.Migration.Services; - using MongoDB.Driver; -namespace Mongo.Migration.Migrations.Database +namespace Mongo.Migration.Migrations.Database; + +internal class DatabaseMigrationRunner : IDatabaseMigrationRunner { - internal class DatabaseMigrationRunner : IDatabaseMigrationRunner - { - private readonly IDatabaseVersionService _databaseVersionService; + private readonly IDatabaseVersionService _databaseVersionService; - private readonly ILogger _logger; + private readonly ILogger _logger; - private readonly Type DatabaseMigrationType = typeof(DatabaseMigration); + private readonly Type _databaseMigrationType = typeof(DatabaseMigration); - private IDatabaseTypeMigrationDependencyLocator _migrationLocator { get; } + private readonly IDatabaseTypeMigrationDependencyLocator _migrationLocator; - public DatabaseMigrationRunner( - IDatabaseTypeMigrationDependencyLocator migrationLocator, - IDatabaseVersionService databaseVersionService) - : this(migrationLocator, databaseVersionService, NullLoggerFactory.Instance) - { - } + public DatabaseMigrationRunner( + IDatabaseTypeMigrationDependencyLocator migrationLocator, + IDatabaseVersionService databaseVersionService, + ILogger logger) + { + _migrationLocator = migrationLocator; + _databaseVersionService = databaseVersionService; + _logger = logger; + } + + public async Task RunAsync(IMongoDatabase db, DocumentVersion? targetVersion = null, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Database migration started."); + DocumentVersion databaseVersion = await _databaseVersionService.GetLatestDatabaseVersionAsync(db, cancellationToken); + DocumentVersion currentOrLatest = targetVersion is not null && targetVersion > DocumentVersion.Empty + ? targetVersion.Value + : _databaseVersionService.GetLatestMigrationVersion(); - private DatabaseMigrationRunner( - IDatabaseTypeMigrationDependencyLocator migrationLocator, - IDatabaseVersionService databaseVersionService, - ILoggerFactory loggerFactory) + if (databaseVersion != currentOrLatest) { - this._migrationLocator = migrationLocator; - this._databaseVersionService = databaseVersionService; - this._logger = loggerFactory.CreateLogger(); + await MigrateUpOrDownAsync(db, databaseVersion, currentOrLatest, cancellationToken); } + + _logger.LogInformation("Database migration finished."); + } - public void Run(IMongoDatabase db) + private async Task MigrateUpOrDownAsync( + IMongoDatabase db, + DocumentVersion databaseVersion, + DocumentVersion to, + CancellationToken cancellationToken) + { + if (databaseVersion > to) { - this._logger.LogInformation("Database migration started."); - var databaseVersion = this._databaseVersionService.GetLatestDatabaseVersion(db); - var currentOrLatest = this._databaseVersionService.GetCurrentOrLatestMigrationVersion(); - - if (databaseVersion == currentOrLatest) - { - return; - } - - this.MigrateUpOrDown(db, databaseVersion, currentOrLatest); - this._logger.LogInformation("Database migration finished."); + await MigrateDownAsync(db, databaseVersion, to, cancellationToken); + return; } - private void MigrateUpOrDown( - IMongoDatabase db, - DocumentVersion databaseVersion, - DocumentVersion to) - { - if (databaseVersion > to) - { - this.MigrateDown(db, databaseVersion, to); - return; - } + await MigrateUpAsync(db, databaseVersion, to, cancellationToken); + } - this.MigrateUp(db, databaseVersion, to); - } + private async Task MigrateUpAsync(IMongoDatabase db, DocumentVersion currentVersion, DocumentVersion toVersion, CancellationToken cancellationToken) + { + var migrations = _migrationLocator + .GetMigrationsFromTo(_databaseMigrationType, currentVersion, toVersion); - private void MigrateUp(IMongoDatabase db, DocumentVersion currentVersion, DocumentVersion toVersion) + foreach (var migration in migrations) { - var migrations = this._migrationLocator.GetMigrationsFromTo(this.DatabaseMigrationType, currentVersion, toVersion).ToList(); + _logger.LogInformation("Database Migration Up: {Type}:{Version} ", currentVersion.GetType(), migration.Version); - foreach (var migration in migrations) - { - this._logger.LogInformation("Database Migration Up: {0}:{1} ", currentVersion.GetType().ToString(), migration.Version); + await migration.UpAsync(db, cancellationToken); + await _databaseVersionService.SaveAsync(db, migration, cancellationToken); - migration.Up(db); - this._databaseVersionService.Save(db, migration); - - this._logger.LogInformation("Database Migration Up finished successful: {0}:{1} ", migration.GetType().ToString(), migration.Version); - } + _logger.LogInformation("Database Migration Up finished successful: {Type}:{Version} ", migration.GetType(), migration.Version); } + } + + private async Task MigrateDownAsync(IMongoDatabase db, DocumentVersion currentVersion, DocumentVersion toVersion, CancellationToken cancellationToken) + { + var migrations = _migrationLocator + .GetMigrationsFromToDown(_databaseMigrationType, currentVersion, toVersion); - private void MigrateDown(IMongoDatabase db, DocumentVersion currentVersion, DocumentVersion toVersion) + foreach (var migration in migrations) { - var migrations = this._migrationLocator - .GetMigrationsGtEq(this.DatabaseMigrationType, toVersion) - .OrderByDescending(m => m.Version) - .ToList(); - - for (var m = 0; m < migrations.Count; m++) - { - var migration = migrations[m]; - if (migration.Version == toVersion) - { - break; - } - else if (migration.Version > currentVersion) // a migration that was never applied - { - this._logger.LogInformation("Skipping Migration Down: {0}:{1} because current version is {2} ", migration.GetType().ToString(), migration.Version, currentVersion); - continue; - } - - this._logger.LogInformation("Database Migration Down: {0}:{1} ", migration.GetType().ToString(), migration.Version); - - migration.Down(db); - this._databaseVersionService.Remove(db, migration); - - this._logger.LogInformation("Database Migration Down finished successful: {0}:{1} ", migration.GetType().ToString(), migration.Version); - } + _logger.LogInformation("Database Migration Down: {Type}:{Version} ", migration.GetType(), migration.Version); + + await migration.DownAsync(db, cancellationToken); + await _databaseVersionService.RemoveAsync(db, migration, cancellationToken); + + _logger.LogInformation("Database Migration Down finished successful: {Type}:{Version} ", migration.GetType(), migration.Version); } } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Database/IDatabaseMigration.cs b/Mongo.Migration/Migrations/Database/IDatabaseMigration.cs index 79e87b5..b6a79ad 100644 --- a/Mongo.Migration/Migrations/Database/IDatabaseMigration.cs +++ b/Mongo.Migration/Migrations/Database/IDatabaseMigration.cs @@ -1,11 +1,10 @@ using MongoDB.Driver; -namespace Mongo.Migration.Migrations.Database +namespace Mongo.Migration.Migrations.Database; + +public interface IDatabaseMigration : IMigration { - public interface IDatabaseMigration : IMigration - { - void Up(IMongoDatabase db); + Task UpAsync(IMongoDatabase db, CancellationToken cancellationToken); - void Down(IMongoDatabase db); - } + Task DownAsync(IMongoDatabase db, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Database/IDatabaseMigrationRunner.cs b/Mongo.Migration/Migrations/Database/IDatabaseMigrationRunner.cs index d0a6086..1d87b8e 100644 --- a/Mongo.Migration/Migrations/Database/IDatabaseMigrationRunner.cs +++ b/Mongo.Migration/Migrations/Database/IDatabaseMigrationRunner.cs @@ -1,9 +1,9 @@ -using MongoDB.Driver; +using Mongo.Migration.Documents; +using MongoDB.Driver; -namespace Mongo.Migration.Migrations.Database +namespace Mongo.Migration.Migrations.Database; + +public interface IDatabaseMigrationRunner { - internal interface IDatabaseMigrationRunner - { - void Run(IMongoDatabase db); - } + Task RunAsync(IMongoDatabase db, DocumentVersion? targetVersion = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Database/IStartUpDatabaseMigrationRunner.cs b/Mongo.Migration/Migrations/Database/IStartUpDatabaseMigrationRunner.cs deleted file mode 100644 index 94411da..0000000 --- a/Mongo.Migration/Migrations/Database/IStartUpDatabaseMigrationRunner.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Mongo.Migration.Migrations.Database -{ - internal interface IStartUpDatabaseMigrationRunner - { - void RunAll(); - } -} \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Database/MigrationHistory.cs b/Mongo.Migration/Migrations/Database/MigrationHistory.cs index b214a26..a003511 100644 --- a/Mongo.Migration/Migrations/Database/MigrationHistory.cs +++ b/Mongo.Migration/Migrations/Database/MigrationHistory.cs @@ -2,14 +2,13 @@ using MongoDB.Bson; -namespace Mongo.Migration.Migrations.Database +namespace Mongo.Migration.Migrations.Database; + +public record MigrationHistory { - public class MigrationHistory - { - public ObjectId Id { get; set; } + public ObjectId Id { get; init; } - public string MigrationId { get; set; } + public required string MigrationId { get; init; } - public DocumentVersion Version { get; set; } - } + public DocumentVersion Version { get; init; } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Database/StartUpDatabaseMigrationRunner.cs b/Mongo.Migration/Migrations/Database/StartUpDatabaseMigrationRunner.cs deleted file mode 100644 index e50b82e..0000000 --- a/Mongo.Migration/Migrations/Database/StartUpDatabaseMigrationRunner.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Linq; - -using Mongo.Migration.Documents.Attributes; -using Mongo.Migration.Documents.Locators; -using Mongo.Migration.Exceptions; -using Mongo.Migration.Startup; - -using MongoDB.Driver; - -namespace Mongo.Migration.Migrations.Database -{ - internal class StartUpDatabaseMigrationRunner : IStartUpDatabaseMigrationRunner - { - private readonly IMongoClient _client; - - private readonly ICollectionLocator _collectionLocator; - - private readonly string _databaseName; - - private readonly IDatabaseMigrationRunner _migrationRunner; - - public StartUpDatabaseMigrationRunner( - IMongoMigrationSettings settings, - ICollectionLocator collectionLocator, - IDatabaseMigrationRunner migrationRunner) - : this( - collectionLocator, - migrationRunner) - { - if ((settings.ConnectionString == null || settings.ClientSettings == null) && settings.Database == null) - { - throw new MongoMigrationNoMongoClientException(); - } - - if (settings.ClientSettings != null) - { - this._client = new MongoClient(settings.ClientSettings); - } - else - { - this._client = new MongoClient(settings.ConnectionString); - } - - this._databaseName = settings.Database; - } - - public StartUpDatabaseMigrationRunner( - IMongoClient client, - IMongoMigrationSettings settings, - ICollectionLocator collectionLocator, - IDatabaseMigrationRunner migrationRunner) - : this( - collectionLocator, - migrationRunner) - { - this._client = client; - if (settings.ConnectionString == null && settings.Database == null) - { - return; - } - - this._client = new MongoClient(settings.ConnectionString); - this._databaseName = settings.Database; - } - - private StartUpDatabaseMigrationRunner( - ICollectionLocator collectionLocator, - IDatabaseMigrationRunner migrationRunner) - { - this._collectionLocator = collectionLocator; - this._migrationRunner = migrationRunner; - } - - public void RunAll() - { - var locations = this._collectionLocator.GetLocatesOrEmpty().ToList(); - var information = locations.FirstOrDefault().Value; - var databaseName = this.GetDatabaseOrDefault(information); - - this._migrationRunner.Run(this._client.GetDatabase(databaseName)); - } - - private string GetDatabaseOrDefault(CollectionLocationInformation information) - { - if (string.IsNullOrEmpty(this._databaseName) && string.IsNullOrEmpty(information.Database)) - { - throw new NoDatabaseNameFoundException(); - } - - return string.IsNullOrEmpty(information.Database) ? this._databaseName : information.Database; - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Document/DocumentMigration.cs b/Mongo.Migration/Migrations/Document/DocumentMigration.cs index c4fa196..31f8392 100644 --- a/Mongo.Migration/Migrations/Document/DocumentMigration.cs +++ b/Mongo.Migration/Migrations/Document/DocumentMigration.cs @@ -1,25 +1,23 @@ -using System; - -using Mongo.Migration.Documents; - +using Mongo.Migration.Documents; using MongoDB.Bson; -namespace Mongo.Migration.Migrations.Document +namespace Mongo.Migration.Migrations.Document; + +public abstract class DocumentMigration : IDocumentMigration { - public abstract class DocumentMigration : IDocumentMigration - where TClass : class, IDocument + protected DocumentMigration(DocumentVersion version) { - protected DocumentMigration(string version) - { - this.Version = version; - } + Version = version; + } - public DocumentVersion Version { get; } + protected DocumentMigration(ReadOnlySpan span) + : this(DocumentVersion.Parse(span)) { } - public Type Type => typeof(TClass); + public DocumentVersion Version { get; } - public abstract void Up(BsonDocument document); + public Type Type { get; } = typeof(TClass); - public abstract void Down(BsonDocument document); - } + public abstract void Up(BsonDocument document); + + public abstract void Down(BsonDocument document); } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Document/DocumentMigrationRunner.cs b/Mongo.Migration/Migrations/Document/DocumentMigrationRunner.cs index 44b2440..d89a432 100644 --- a/Mongo.Migration/Migrations/Document/DocumentMigrationRunner.cs +++ b/Mongo.Migration/Migrations/Document/DocumentMigrationRunner.cs @@ -1,97 +1,78 @@ -using System; -using System.Linq; - -using Mongo.Migration.Documents; +using Mongo.Migration.Documents; using Mongo.Migration.Migrations.Locators; using Mongo.Migration.Services; using MongoDB.Bson; -namespace Mongo.Migration.Migrations.Document -{ - internal class DocumentMigrationRunner : IDocumentMigrationRunner - { - private readonly IDocumentVersionService _documentVersionService; +namespace Mongo.Migration.Migrations.Document; - private readonly IMigrationLocator _migrationLocator; +internal class DocumentMigrationRunner : IDocumentMigrationRunner +{ + private readonly IDocumentVersionService _documentVersionService; - public DocumentMigrationRunner(IMigrationLocator migrationLocator, IDocumentVersionService documentVersionService) - { - this._migrationLocator = migrationLocator; - this._documentVersionService = documentVersionService; - } + private readonly IMigrationLocator _migrationLocator; - public void Run(Type type, BsonDocument document) - { - var documentVersion = this._documentVersionService.GetVersionOrDefault(document); - var currentOrLatest = this._documentVersionService.GetCurrentOrLatestMigrationVersion(type); + public DocumentMigrationRunner(IMigrationLocator migrationLocator, IDocumentVersionService documentVersionService) + { + _migrationLocator = migrationLocator; + _documentVersionService = documentVersionService; + } - if (documentVersion == currentOrLatest) - { - return; - } + public void Run(Type type, BsonDocument document) + { + var currentOrLatest = _documentVersionService.GetCurrentOrLatestMigrationVersion(type); + Run(type, document, currentOrLatest); + } - this.MigrateUpOrDown(type, document, documentVersion, currentOrLatest); - } + public void Run(Type type, BsonDocument document, in DocumentVersion to) + { + var documentVersion = _documentVersionService.GetVersionOrDefault(document); - public void Run(Type type, BsonDocument document, DocumentVersion to) + if (documentVersion == to) { - var documentVersion = this._documentVersionService.GetVersionOrDefault(document); - var currentOrLatest = this._documentVersionService.GetCurrentOrLatestMigrationVersion(type); - - if (documentVersion == to || documentVersion == currentOrLatest) - { - return; - } - - this.MigrateUpOrDown(type, document, documentVersion, to); + return; } - private void MigrateUpOrDown( - Type type, - BsonDocument document, - DocumentVersion documentVersion, - DocumentVersion to) - { - if (documentVersion > to) - { - this.MigrateDown(type, document, to); - return; - } + MigrateUpOrDown(type, document, documentVersion, to); + } - this.MigrateUp(type, document, documentVersion, to); + private void MigrateUpOrDown( + Type type, + BsonDocument document, + in DocumentVersion documentVersion, + in DocumentVersion to) + { + if (documentVersion > to) + { + MigrateDown(type, document, documentVersion, to); } - - private void MigrateUp(Type type, BsonDocument document, DocumentVersion version, DocumentVersion toVersion) + else { - var migrations = this._migrationLocator.GetMigrationsFromTo(type, version, toVersion).ToList(); - - foreach (var migration in migrations) - { - migration.Up(document); - this._documentVersionService.SetVersion(document, migration.Version); - } + MigrateUp(type, document, documentVersion, to); } + + _documentVersionService.SetVersion(document, to); + } - private void MigrateDown(Type type, BsonDocument document, DocumentVersion version) - { - var migrations = this._migrationLocator - .GetMigrationsGtEq(type, version) - .OrderByDescending(m => m.Version) - .ToList(); + private void MigrateUp(Type type, BsonDocument document, in DocumentVersion version, in DocumentVersion toVersion) + { + var migrations = _migrationLocator + .GetMigrationsFromTo(type, version, toVersion); - for (var m = 0; m < migrations.Count; m++) - { - if (version == migrations[m].Version) - { - break; - } + foreach (var migration in migrations) + { + migration.Up(document); + } + } - migrations[m].Down(document); + private void MigrateDown(Type type, BsonDocument document, in DocumentVersion version, in DocumentVersion toVersion) + { + var migrations = _migrationLocator + .GetMigrationsFromToDown(type, version, toVersion); - var docVersion = this._documentVersionService.DetermineLastVersion(version, migrations, m); - this._documentVersionService.SetVersion(document, docVersion); - } + foreach (var migration in migrations) + { + migration.Down(document); } } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Document/IDocumentMigration.cs b/Mongo.Migration/Migrations/Document/IDocumentMigration.cs index f0fff23..931ed7a 100644 --- a/Mongo.Migration/Migrations/Document/IDocumentMigration.cs +++ b/Mongo.Migration/Migrations/Document/IDocumentMigration.cs @@ -1,11 +1,10 @@ using MongoDB.Bson; -namespace Mongo.Migration.Migrations.Document +namespace Mongo.Migration.Migrations.Document; + +public interface IDocumentMigration : IMigration { - public interface IDocumentMigration : IMigration - { - void Up(BsonDocument document); + void Up(BsonDocument document); - void Down(BsonDocument document); - } + void Down(BsonDocument document); } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Document/IDocumentMigrationRunner.cs b/Mongo.Migration/Migrations/Document/IDocumentMigrationRunner.cs index dde4076..51ef928 100644 --- a/Mongo.Migration/Migrations/Document/IDocumentMigrationRunner.cs +++ b/Mongo.Migration/Migrations/Document/IDocumentMigrationRunner.cs @@ -1,15 +1,12 @@ -using System; - -using Mongo.Migration.Documents; +using Mongo.Migration.Documents; using MongoDB.Bson; -namespace Mongo.Migration.Migrations.Document +namespace Mongo.Migration.Migrations.Document; + +public interface IDocumentMigrationRunner { - internal interface IDocumentMigrationRunner - { - void Run(Type type, BsonDocument document, DocumentVersion to); + void Run(Type type, BsonDocument document, in DocumentVersion to); - void Run(Type type, BsonDocument document); - } + void Run(Type type, BsonDocument document); } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Document/IStartUpDocumentMigrationRunner.cs b/Mongo.Migration/Migrations/Document/IStartUpDocumentMigrationRunner.cs index f308d93..cdd7cb8 100644 --- a/Mongo.Migration/Migrations/Document/IStartUpDocumentMigrationRunner.cs +++ b/Mongo.Migration/Migrations/Document/IStartUpDocumentMigrationRunner.cs @@ -1,7 +1,8 @@ -namespace Mongo.Migration.Migrations.Document +using MongoDB.Driver; + +namespace Mongo.Migration.Migrations.Document; + +internal interface IStartUpDocumentMigrationRunner { - internal interface IStartUpDocumentMigrationRunner - { - void RunAll(); - } + Task RunAllAsync(IMongoDatabase database, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Document/StartUpDocumentMigrationRunner.cs b/Mongo.Migration/Migrations/Document/StartUpDocumentMigrationRunner.cs index 9e67194..86b07a3 100644 --- a/Mongo.Migration/Migrations/Document/StartUpDocumentMigrationRunner.cs +++ b/Mongo.Migration/Migrations/Document/StartUpDocumentMigrationRunner.cs @@ -1,153 +1,79 @@ -using System; -using System.Collections.Generic; - -using Mongo.Migration.Documents.Attributes; using Mongo.Migration.Documents.Locators; -using Mongo.Migration.Exceptions; using Mongo.Migration.Services; -using Mongo.Migration.Startup; - using MongoDB.Bson; using MongoDB.Driver; -namespace Mongo.Migration.Migrations.Document +namespace Mongo.Migration.Migrations.Document; + +internal class StartUpDocumentMigrationRunner : IStartUpDocumentMigrationRunner { - internal class StartUpDocumentMigrationRunner : IStartUpDocumentMigrationRunner - { - private readonly IMongoClient _client; + private readonly ICollectionLocator _collectionLocator; - private readonly ICollectionLocator _collectionLocator; + private readonly IDocumentVersionService _documentVersionService; - private readonly string _databaseName; + private readonly IDocumentMigrationRunner _migrationRunner; - private readonly IDocumentVersionService _documentVersionService; + public StartUpDocumentMigrationRunner( + ICollectionLocator collectionLocator, + IDocumentVersionService documentVersionService, + IDocumentMigrationRunner migrationRunner) + { + _collectionLocator = collectionLocator; + _documentVersionService = documentVersionService; + _migrationRunner = migrationRunner; + } - private readonly IDocumentMigrationRunner _migrationRunner; + public async Task RunAllAsync(IMongoDatabase database, CancellationToken cancellationToken) + { + var locations = _collectionLocator.GetLocatesOrEmpty(); - public StartUpDocumentMigrationRunner( - IMongoMigrationSettings settings, - ICollectionLocator collectionLocator, - IDocumentVersionService documentVersionService, - IDocumentMigrationRunner migrationRunner) - : this( - collectionLocator, - documentVersionService, - migrationRunner) + foreach (var locate in locations) { - if ((settings.ConnectionString == null || settings.ClientSettings == null) && settings.Database == null) - { - throw new MongoMigrationNoMongoClientException(); - } + var information = locate.Value; + var type = locate.Key; + var collectionVersion = _documentVersionService.GetCollectionVersion(type); - if (settings.ClientSettings != null) - { - this._client = new MongoClient(settings.ClientSettings); - } - else - { - this._client = new MongoClient(settings.ConnectionString); - } + var collection = database.GetCollection(information.Collection); - this._databaseName = settings.Database; - } + var bulk = new List>(); - public StartUpDocumentMigrationRunner( - IMongoClient client, - IMongoMigrationSettings settings, - ICollectionLocator collectionLocator, - IDocumentVersionService documentVersionService, - IDocumentMigrationRunner migrationRunner) - : this( - collectionLocator, - documentVersionService, - migrationRunner) - { - this._client = client; + var query = CreateQueryForRelevantDocuments(type); - if (settings.ConnectionString == null && settings.Database == null) + using (var cursor = await collection.FindAsync(query, cancellationToken: cancellationToken)) { - return; - } - - this._client = new MongoClient(settings.ConnectionString); - this._databaseName = settings.Database; - } - - private StartUpDocumentMigrationRunner( - ICollectionLocator collectionLocator, - IDocumentVersionService documentVersionService, - IDocumentMigrationRunner migrationRunner) - { - this._collectionLocator = collectionLocator; - this._documentVersionService = documentVersionService; - this._migrationRunner = migrationRunner; - } - - public void RunAll() - { - var locations = this._collectionLocator.GetLocatesOrEmpty(); - - foreach (var locate in locations) - { - var information = locate.Value; - var type = locate.Key; - var databaseName = this.GetDatabaseOrDefault(information); - var collectionVersion = this._documentVersionService.GetCollectionVersion(type); - - var collection = this._client.GetDatabase(databaseName) - .GetCollection(information.Collection); - - var bulk = new List>(); - - var query = this.CreateQueryForRelevantDocuments(type); - - using (var cursor = collection.FindSync(query)) + while (await cursor.MoveNextAsync(cancellationToken)) { - while (cursor.MoveNext()) + var batch = cursor.Current; + foreach (var document in batch) { - var batch = cursor.Current; - foreach (var document in batch) - { - this._migrationRunner.Run(type, document, collectionVersion); - - var update = new ReplaceOneModel( - new BsonDocument { { "_id", document["_id"] } }, - document - ); - - bulk.Add(update); - } - } - } + _migrationRunner.Run(type, document, collectionVersion); - if (bulk.Count > 0) - { - collection.BulkWrite(bulk); + var update = new ReplaceOneModel( + new BsonDocument { { "_id", document["_id"] } }, + document + ); + + bulk.Add(update); + } } } - } - private string GetDatabaseOrDefault(CollectionLocationInformation information) - { - if (string.IsNullOrEmpty(this._databaseName) && string.IsNullOrEmpty(information.Database)) + if (bulk.Count > 0) { - throw new NoDatabaseNameFoundException(); + await collection.BulkWriteAsync(bulk, cancellationToken: cancellationToken); } - - return string.IsNullOrEmpty(information.Database) ? this._databaseName : information.Database; } + } - private FilterDefinition CreateQueryForRelevantDocuments( - Type type) - { - var currentVersion = this._documentVersionService.GetCurrentOrLatestMigrationVersion(type); + private FilterDefinition CreateQueryForRelevantDocuments(Type type) + { + var currentVersion = _documentVersionService.GetCurrentOrLatestMigrationVersion(type); - var existFilter = Builders.Filter.Exists(this._documentVersionService.GetVersionFieldName(), false); - var notEqualFilter = Builders.Filter.Ne( - this._documentVersionService.GetVersionFieldName(), - currentVersion); + var existFilter = Builders.Filter.Exists(_documentVersionService.GetVersionFieldName(), false); + var notEqualFilter = Builders.Filter.Ne( + _documentVersionService.GetVersionFieldName(), + currentVersion); - return Builders.Filter.Or(existFilter, notEqualFilter); - } + return Builders.Filter.Or(existFilter, notEqualFilter); } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/IMigration.cs b/Mongo.Migration/Migrations/IMigration.cs index e40690d..c9b44f4 100644 --- a/Mongo.Migration/Migrations/IMigration.cs +++ b/Mongo.Migration/Migrations/IMigration.cs @@ -1,13 +1,10 @@ -using System; +using Mongo.Migration.Documents; -using Mongo.Migration.Documents; +namespace Mongo.Migration.Migrations; -namespace Mongo.Migration.Migrations +public interface IMigration { - public interface IMigration - { - DocumentVersion Version { get; } + DocumentVersion Version { get; } - Type Type { get; } - } + Type Type { get; } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Locators/DatabaseTypeMigrationDependencyLocator.cs b/Mongo.Migration/Migrations/Locators/DatabaseTypeMigrationDependencyLocator.cs index 6d1d34b..e9a8c01 100644 --- a/Mongo.Migration/Migrations/Locators/DatabaseTypeMigrationDependencyLocator.cs +++ b/Mongo.Migration/Migrations/Locators/DatabaseTypeMigrationDependencyLocator.cs @@ -1,35 +1,10 @@ -using System; -using System.Collections.Generic; - -using Mongo.Migration.Migrations.Adapters; +using Microsoft.Extensions.Logging; using Mongo.Migration.Migrations.Database; -namespace Mongo.Migration.Migrations.Locators -{ - internal class DatabaseTypeMigrationDependencyLocator : TypeMigrationDependencyLocator, IDatabaseTypeMigrationDependencyLocator - { - private IDictionary> _migrations; - - protected override IDictionary> Migrations - { - get - { - if (this._migrations == null) - { - this.Locate(); - } +namespace Mongo.Migration.Migrations.Locators; - return this._migrations; - } - set - { - this._migrations = value; - } - } - - public DatabaseTypeMigrationDependencyLocator(IContainerProvider containerProvider) - : base(containerProvider) - { - } - } +internal class DatabaseTypeMigrationDependencyLocator : TypeMigrationDependencyLocator, IDatabaseTypeMigrationDependencyLocator +{ + public DatabaseTypeMigrationDependencyLocator(ILogger logger, IServiceProvider serviceProvider) + : base(logger, serviceProvider) { } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Locators/IDatabaseTypeMigrationDependencyLocator.cs b/Mongo.Migration/Migrations/Locators/IDatabaseTypeMigrationDependencyLocator.cs index f99fea4..3b9f22d 100644 --- a/Mongo.Migration/Migrations/Locators/IDatabaseTypeMigrationDependencyLocator.cs +++ b/Mongo.Migration/Migrations/Locators/IDatabaseTypeMigrationDependencyLocator.cs @@ -1,8 +1,5 @@ using Mongo.Migration.Migrations.Database; -namespace Mongo.Migration.Migrations.Locators -{ - internal interface IDatabaseTypeMigrationDependencyLocator : IMigrationLocator - { - } -} \ No newline at end of file +namespace Mongo.Migration.Migrations.Locators; + +internal interface IDatabaseTypeMigrationDependencyLocator : IMigrationLocator; \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Locators/IMigrationLocator.cs b/Mongo.Migration/Migrations/Locators/IMigrationLocator.cs index 55c6e2a..6b45799 100644 --- a/Mongo.Migration/Migrations/Locators/IMigrationLocator.cs +++ b/Mongo.Migration/Migrations/Locators/IMigrationLocator.cs @@ -1,23 +1,17 @@ -using System; -using System.Collections.Generic; - using Mongo.Migration.Documents; -namespace Mongo.Migration.Migrations.Locators -{ - public interface IMigrationLocator - where TMigrationType : class, IMigration - { - IEnumerable GetMigrations(Type type); +namespace Mongo.Migration.Migrations.Locators; - IEnumerable GetMigrationsGt(Type type, DocumentVersion version); +public interface IMigrationLocator + where TMigrationType : class, IMigration +{ + IReadOnlyCollection GetMigrations(Type type); - IEnumerable GetMigrationsGtEq(Type type, DocumentVersion version); + IEnumerable GetMigrationsFromTo(Type type, DocumentVersion version, DocumentVersion otherVersion); - IEnumerable GetMigrationsFromTo(Type type, DocumentVersion version, DocumentVersion otherVersion); + IEnumerable GetMigrationsFromToDown(Type type, DocumentVersion version, DocumentVersion otherVersion); - DocumentVersion GetLatestVersion(Type type); + DocumentVersion GetLatestVersion(Type type); - void Locate(); - } + void Initialize(); } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Locators/MigrationLocator.cs b/Mongo.Migration/Migrations/Locators/MigrationLocator.cs index e3aeef2..8895cd4 100644 --- a/Mongo.Migration/Migrations/Locators/MigrationLocator.cs +++ b/Mongo.Migration/Migrations/Locators/MigrationLocator.cs @@ -1,116 +1,113 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.Collections.ObjectModel; using System.Reflection; - +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Mongo.Migration.Documents; -using Mongo.Migration.Exceptions; using Mongo.Migration.Extensions; -using NLog; +namespace Mongo.Migration.Migrations.Locators; -namespace Mongo.Migration.Migrations.Locators +public abstract class MigrationLocator : IMigrationLocator + where TMigrationType : class, IMigration { - public abstract class MigrationLocator : IMigrationLocator - where TMigrationType : class, IMigration - { - private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly ILogger> _logger; - private IEnumerable _assemblies; + private readonly IServiceProvider _serviceProvider; - private IDictionary> _migrations; + private readonly Lazy>> _lazyMigrations; + + protected MigrationLocator(ILogger> logger, IServiceProvider serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + _lazyMigrations = new Lazy>>( + LoadMigrations, + LazyThreadSafetyMode.PublicationOnly); + } - protected IEnumerable Assemblies => this._assemblies ??= GetAssemblies(); + protected virtual IDictionary> Migrations + => _lazyMigrations.Value; - protected virtual IDictionary> Migrations + public IReadOnlyCollection GetMigrations(Type type) + { + if(Migrations.TryGetValue(type, out var migrations)) { - get - { - if (this._migrations == null) - { - this.Locate(); - } - - if (this._migrations.NullOrEmpty()) - { - this._logger.Info(new NoMigrationsFoundException()); - } - - return this._migrations; - } - set => this._migrations = value; + return migrations; } - public IEnumerable GetMigrations(Type type) - { - IReadOnlyCollection migrations; - this.Migrations.TryGetValue(type, out migrations); + return []; + } - return migrations ?? Enumerable.Empty(); - } + public IEnumerable GetMigrationsFromTo(Type type, DocumentVersion version, DocumentVersion otherVersion) + { + return GetMigrations(type) + .Where(m => m.Version > version && m.Version <= otherVersion); + } - public IEnumerable GetMigrationsFromTo(Type type, DocumentVersion version, DocumentVersion otherVersion) - { - var migrations = this.GetMigrations(type); + public IEnumerable GetMigrationsFromToDown(Type type, DocumentVersion version, DocumentVersion otherVersion) + { + return GetMigrations(type) + .Where(m => m.Version <= version && m.Version > otherVersion) + .Reverse(); + } - return - migrations - .Where(m => m.Version > version) - .Where(m => m.Version <= otherVersion) - .ToList(); - } + public DocumentVersion GetLatestVersion(Type type) + { + var migrations = GetMigrations(type); - public IEnumerable GetMigrationsGt(Type type, DocumentVersion version) - { - var migrations = this.GetMigrations(type); + return migrations.Count > 0 + ? migrations.Max(m => m.Version) + : DocumentVersion.Default; + } - return - migrations - .Where(m => m.Version > version) - .ToList(); + public void Initialize() + { + if (!_lazyMigrations.IsValueCreated) + { + var migrations = _lazyMigrations.Value; + _logger.LogDebug("Migration locator initialized and loaded {Count} migrations", migrations.Count); } + } - public IEnumerable GetMigrationsGtEq(Type type, DocumentVersion version) - { - var migrations = this.GetMigrations(type); + private IDictionary> LoadMigrations() + { + Type migrationType = typeof(TMigrationType); - return - migrations - .Where(m => m.Version >= version) - .ToList(); - } + List assemblies = GetAssemblies(); - public DocumentVersion GetLatestVersion(Type type) - { - var migrations = this.GetMigrations(type); + var migrationTypes = assemblies + .SelectMany(a => a.GetExportedTypes()) + .Where(type => !type.IsAbstract && migrationType.IsAssignableFrom(type)) + .DistinctBy(t => t.AssemblyQualifiedName) + .Select(GetMigrationInstance) + .ToMigrationDictionary(); - if (migrations == null || !migrations.Any()) - { - return DocumentVersion.Default(); - } + _logger.LogDebug("{Count} {MigrationType} migrations found", migrationTypes.Count, migrationType.Name); - return migrations.Max(m => m.Version); - } + return migrationTypes; + } - public abstract void Locate(); + private static List GetAssemblies() + { + var location = AppDomain.CurrentDomain.BaseDirectory; + var path = Path.GetDirectoryName(location); - private static IEnumerable GetAssemblies() + if (string.IsNullOrWhiteSpace(path)) { - var location = AppDomain.CurrentDomain.BaseDirectory; - var path = Path.GetDirectoryName(location); + throw new DirectoryNotFoundException(location); + } - if (string.IsNullOrWhiteSpace(path)) - { - throw new DirectoryNotFoundException(ErrorTexts.AppDirNotFound); - } + var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList(); + var migrationAssemblies = Directory.GetFiles(path, "*.MongoMigrations*.dll").Select(Assembly.LoadFile); - var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList(); - var migrationAssemblies = Directory.GetFiles(path, "*.MongoMigrations*.dll").Select(Assembly.LoadFile); + assemblies.AddRange(migrationAssemblies); - assemblies.AddRange(migrationAssemblies); + return assemblies; + } - return assemblies; - } + private TMigrationType GetMigrationInstance(Type type) + { + return ActivatorUtilities.CreateInstance(_serviceProvider, type) as TMigrationType + ?? throw new InvalidOperationException($"Cannot create {type} migration"); } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Locators/TypeMigrationDependencyLocator.cs b/Mongo.Migration/Migrations/Locators/TypeMigrationDependencyLocator.cs index 335b6a0..967ea5c 100644 --- a/Mongo.Migration/Migrations/Locators/TypeMigrationDependencyLocator.cs +++ b/Mongo.Migration/Migrations/Locators/TypeMigrationDependencyLocator.cs @@ -1,63 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using Microsoft.Extensions.Logging; -using Mongo.Migration.Extensions; -using Mongo.Migration.Migrations.Adapters; +namespace Mongo.Migration.Migrations.Locators; -namespace Mongo.Migration.Migrations.Locators +internal class TypeMigrationDependencyLocator : MigrationLocator + where TMigrationType : class, IMigration { - internal class TypeMigrationDependencyLocator : MigrationLocator - where TMigrationType : class, IMigration - { - private readonly IContainerProvider _containerProvider; - - public TypeMigrationDependencyLocator(IContainerProvider containerProvider) - { - this._containerProvider = containerProvider; - } - - public override void Locate() - { - var migrationTypes = - (from assembly in this.Assemblies - from type in assembly.GetTypes() - where typeof(TMigrationType).IsAssignableFrom(type) && !type.IsAbstract - select type).Distinct(new TypeComparer()); - - this.Migrations = migrationTypes.Select(this.GetMigrationInstance).ToMigrationDictionary(); - } - - private TMigrationType GetMigrationInstance(Type type) - { - ConstructorInfo constructor = type.GetConstructors()[0]; - - if (constructor != null) - { - object[] args = constructor - .GetParameters() - .Select(o => o.ParameterType) - .Select(o => this._containerProvider.GetInstance(o)) - .ToArray(); - - return Activator.CreateInstance(type, args) as TMigrationType; - } - - return Activator.CreateInstance(type) as TMigrationType; - } - - private class TypeComparer : IEqualityComparer - { - public bool Equals(Type x, Type y) - { - return x.AssemblyQualifiedName == y.AssemblyQualifiedName; - } - - public int GetHashCode(Type obj) - { - return obj.AssemblyQualifiedName.GetHashCode(); - } - } - } + public TypeMigrationDependencyLocator(ILogger> logger, IServiceProvider serviceProvider) + : base(logger, serviceProvider) { } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Locators/TypeMigrationLocator.cs b/Mongo.Migration/Migrations/Locators/TypeMigrationLocator.cs index e990b9f..4caf514 100644 --- a/Mongo.Migration/Migrations/Locators/TypeMigrationLocator.cs +++ b/Mongo.Migration/Migrations/Locators/TypeMigrationLocator.cs @@ -1,22 +1,9 @@ -using System; -using System.Linq; - -using Mongo.Migration.Extensions; +using Microsoft.Extensions.Logging; using Mongo.Migration.Migrations.Document; -namespace Mongo.Migration.Migrations.Locators -{ - internal class TypeMigrationLocator : MigrationLocator - { - public override void Locate() - { - var migrationTypes = - (from assembly in this.Assemblies - from type in assembly.GetTypes() - where typeof(IDocumentMigration).IsAssignableFrom(type) && !type.IsAbstract - select type).Distinct(); +namespace Mongo.Migration.Migrations.Locators; - this.Migrations = migrationTypes.Select(t => (IDocumentMigration)Activator.CreateInstance(t)).ToMigrationDictionary(); - } - } +internal class TypeMigrationLocator : MigrationLocator +{ + public TypeMigrationLocator(ILogger logger, IServiceProvider serviceProvider) : base(logger, serviceProvider) { } } \ No newline at end of file diff --git a/Mongo.Migration/Migrations/Migration.cs b/Mongo.Migration/Migrations/Migration.cs deleted file mode 100644 index 15c42f9..0000000 --- a/Mongo.Migration/Migrations/Migration.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -using Mongo.Migration.Documents; -using Mongo.Migration.Migrations.Document; - -namespace Mongo.Migration.Migrations -{ - [Obsolete] - public abstract class Migration : DocumentMigration - where TClass : class, IDocument - { - protected Migration(string version) - : base(version) - { - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Mongo.Migration.csproj b/Mongo.Migration/Mongo.Migration.csproj index fca899c..d3f56a7 100644 --- a/Mongo.Migration/Mongo.Migration.csproj +++ b/Mongo.Migration/Mongo.Migration.csproj @@ -1,39 +1,37 @@  - - net6.0 - x64 - Sherweb - Sherweb - Office Protect - OfficeProtect.Mongo.Migration - Forked from Mongo.Migration which is designed for MongoDB C# Driver to migrate your documents easily on-the-fly. - https://github.com/sherweb/Mongo.Migration.git - git - op-icon.png - true - true - snupkg - false - true - true - true - v - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - \ No newline at end of file + + + net10.0;net8.0 + 14.0 + enable + enable + embedded + true + true + True + + $(NoWarn);CS1591 + + + + Mongo.Migration + git + https://github.com/sroddis/Mongo.Migration/ + sroddis,rpallares + Mongo.Migration is designed for the MongoDB.Driver to migrate documents easily and on-the-fly + mongo, library, migration + MIT + README.md + + + + + + + + + + + + + diff --git a/Mongo.Migration/MongoMigration.cs b/Mongo.Migration/MongoMigration.cs deleted file mode 100644 index 2adb03e..0000000 --- a/Mongo.Migration/MongoMigration.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Mongo.Migration.Documents.Locators; -using Mongo.Migration.Migrations.Document; -using Mongo.Migration.Migrations.Locators; -using Mongo.Migration.Services; - -namespace Mongo.Migration -{ - internal class MongoMigration : IMongoMigration - { - private readonly ICollectionLocator _collectionLocator; - - private readonly IDatabaseTypeMigrationDependencyLocator _databaseMigrationLocator; - - private readonly IMigrationLocator _documentMigrationLocator; - - private readonly IMigrationService _migrationService; - - private readonly IRuntimeVersionLocator _runtimeVersionLocator; - - private readonly IStartUpVersionLocator _startUpVersionLocator; - - public MongoMigration( - IMigrationLocator documentMigrationLocator, - IDatabaseTypeMigrationDependencyLocator databaseMigrationLocator, - IRuntimeVersionLocator runtimeVersionLocator, - ICollectionLocator collectionLocator, - IStartUpVersionLocator startUpVersionLocator, - IMigrationService migrationService) - { - this._documentMigrationLocator = documentMigrationLocator; - this._databaseMigrationLocator = databaseMigrationLocator; - this._runtimeVersionLocator = runtimeVersionLocator; - this._collectionLocator = collectionLocator; - this._startUpVersionLocator = startUpVersionLocator; - this._migrationService = migrationService; - } - - public void Run() - { - this._documentMigrationLocator.Locate(); - this._databaseMigrationLocator.Locate(); - this._runtimeVersionLocator.Locate(); - this._collectionLocator.Locate(); - this._startUpVersionLocator.Locate(); - - this._migrationService.Migrate(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Properties/AssemblyInfo.cs b/Mongo.Migration/Properties/AssemblyInfo.cs deleted file mode 100644 index 9070160..0000000 --- a/Mongo.Migration/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Mongo.Migration.Test")] -[assembly: InternalsVisibleTo("Mongo.Migration.Test.Core")] \ No newline at end of file diff --git a/Mongo.Migration/Services/DatabaseVersionService.cs b/Mongo.Migration/Services/DatabaseVersionService.cs index 5a77d85..393419e 100644 --- a/Mongo.Migration/Services/DatabaseVersionService.cs +++ b/Mongo.Migration/Services/DatabaseVersionService.cs @@ -1,66 +1,62 @@ -using System.Linq; - using Mongo.Migration.Documents; using Mongo.Migration.Migrations.Database; using Mongo.Migration.Migrations.Locators; -using Mongo.Migration.Startup; - using MongoDB.Driver; -namespace Mongo.Migration.Services +namespace Mongo.Migration.Services; + +internal class DatabaseVersionService : IDatabaseVersionService { - internal class DatabaseVersionService : IDatabaseVersionService - { - private const string MigrationsCollectionName = "_migrations"; + private const string MigrationsCollectionName = "_migrations"; - private readonly IDatabaseTypeMigrationDependencyLocator _migrationLocator; + private readonly IDatabaseTypeMigrationDependencyLocator _migrationLocator; - private readonly IMongoMigrationSettings _mongoMigrationSettings; + public DatabaseVersionService(IDatabaseTypeMigrationDependencyLocator migrationLocator) + { + _migrationLocator = migrationLocator; + } - public DatabaseVersionService( - IDatabaseTypeMigrationDependencyLocator migrationLocator, - IMongoMigrationSettings mongoMigrationSettings) - { - this._migrationLocator = migrationLocator; - this._mongoMigrationSettings = mongoMigrationSettings; - } + public DocumentVersion GetLatestMigrationVersion() + { + return _migrationLocator.GetLatestVersion(typeof(DatabaseMigration)); + } - public DocumentVersion GetCurrentOrLatestMigrationVersion() - { - return this._mongoMigrationSettings.DatabaseMigrationVersion > DocumentVersion.Empty() - ? this._mongoMigrationSettings.DatabaseMigrationVersion - : this._migrationLocator.GetLatestVersion(typeof(DatabaseMigration)); - } + public async Task GetLatestDatabaseVersionAsync(IMongoDatabase db, CancellationToken cancellationToken) + { + var cursor = await GetMigrationsCollection(db) + .FindAsync(m => true, cancellationToken: cancellationToken); + var migrations = await cursor.ToListAsync(cancellationToken); - public DocumentVersion GetLatestDatabaseVersion(IMongoDatabase db) + if (migrations is { Count: 0 }) { - var migrations = this.GetMigrationsCollection(db).Find(m => true).ToList(); - if (migrations == null || !migrations.Any()) - { - return DocumentVersion.Default(); - } - - return migrations.Max(m => m.Version); + return DocumentVersion.Default; } - public void Save(IMongoDatabase db, IDatabaseMigration migration) - { - this.GetMigrationsCollection(db).InsertOne( - new MigrationHistory + return migrations.Max(m => m.Version); + } + + public async Task SaveAsync(IMongoDatabase db, IDatabaseMigration migration, CancellationToken cancellationToken) + { + await GetMigrationsCollection(db) + .InsertOneAsync( + new() { MigrationId = migration.GetType().ToString(), Version = migration.Version - }); - } + }, + cancellationToken: cancellationToken); + } - public void Remove(IMongoDatabase db, IDatabaseMigration migration) - { - this.GetMigrationsCollection(db).DeleteOne(Builders.Filter.Eq(mh => mh.MigrationId, migration.GetType().ToString())); - } + public async Task RemoveAsync(IMongoDatabase db, IDatabaseMigration migration, CancellationToken cancellationToken) + { + await GetMigrationsCollection(db) + .DeleteOneAsync( + Builders.Filter.Eq(mh => mh.MigrationId, migration.GetType().ToString()), + cancellationToken: cancellationToken); + } - private IMongoCollection GetMigrationsCollection(IMongoDatabase db) - { - return db.GetCollection(MigrationsCollectionName); - } + private static IMongoCollection GetMigrationsCollection(IMongoDatabase db) + { + return db.GetCollection(MigrationsCollectionName); } } \ No newline at end of file diff --git a/Mongo.Migration/Services/DocumentVersionService.cs b/Mongo.Migration/Services/DocumentVersionService.cs index 1e70178..6e63cfe 100644 --- a/Mongo.Migration/Services/DocumentVersionService.cs +++ b/Mongo.Migration/Services/DocumentVersionService.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using Mongo.Migration.Documents; using Mongo.Migration.Documents.Locators; using Mongo.Migration.Exceptions; @@ -11,127 +7,111 @@ using MongoDB.Bson; -namespace Mongo.Migration.Services +namespace Mongo.Migration.Services; + +internal sealed class DocumentVersionService : IDocumentVersionService { - internal class DocumentVersionService : IDocumentVersionService - { - private static readonly string VERSION_FIELD_NAME = "Version"; + private const string DefaultVersionFieldName = "Version"; + - private readonly IMigrationLocator _migrationLocator; + private readonly IMigrationLocator _migrationLocator; - private readonly IRuntimeVersionLocator _runtimeVersionLocator; + private readonly IRuntimeVersionLocator _runtimeVersionLocator; - private readonly IStartUpVersionLocator _startUpVersionLocator; + private readonly IStartUpVersionLocator _startUpVersionLocator; - private readonly string _versionFieldName; + private readonly string _versionFieldName; - public DocumentVersionService( - IMigrationLocator migrationLocator, - IRuntimeVersionLocator runtimeVersionLocator, - IStartUpVersionLocator startUpVersionLocator, - IMongoMigrationSettings mongoMigrationSettings) - { - this._migrationLocator = migrationLocator; - this._runtimeVersionLocator = runtimeVersionLocator; - this._startUpVersionLocator = startUpVersionLocator; - this._versionFieldName = string.IsNullOrWhiteSpace(mongoMigrationSettings.VersionFieldName) - ? VERSION_FIELD_NAME - : mongoMigrationSettings.VersionFieldName; - } + public DocumentVersionService( + IMigrationLocator migrationLocator, + IRuntimeVersionLocator runtimeVersionLocator, + IStartUpVersionLocator startUpVersionLocator, + MongoMigrationSettings mongoMigrationSettings) + { + _migrationLocator = migrationLocator; + _runtimeVersionLocator = runtimeVersionLocator; + _startUpVersionLocator = startUpVersionLocator; + _versionFieldName = string.IsNullOrWhiteSpace(mongoMigrationSettings.VersionFieldName) + ? DefaultVersionFieldName + : mongoMigrationSettings.VersionFieldName; + } - public string GetVersionFieldName() - { - return this._versionFieldName; - } + public string GetVersionFieldName() + { + return _versionFieldName; + } - public DocumentVersion GetCurrentOrLatestMigrationVersion(Type type) - { - var latestVersion = this._migrationLocator.GetLatestVersion(type); - return this.GetCurrentVersion(type) ?? latestVersion; - } + public DocumentVersion GetCurrentOrLatestMigrationVersion(Type type) + { + var latestVersion = _migrationLocator.GetLatestVersion(type); + return GetCurrentVersion(type) ?? latestVersion; + } + + public DocumentVersion GetCollectionVersion(Type type) + { + var version = GetCurrentOrLatestMigrationVersion(type); + return _startUpVersionLocator.GetLocateOrNull(type) ?? version; + } - public DocumentVersion GetCollectionVersion(Type type) + public DocumentVersion GetVersionOrDefault(BsonDocument document) + { + if (document.TryGetValue(GetVersionFieldName(), out BsonValue value) && !value.IsBsonNull) { - var version = this.GetCurrentOrLatestMigrationVersion(type); - return this._startUpVersionLocator.GetLocateOrNull(type) ?? version; + return DocumentVersion.Parse(value.AsString.AsSpan()); } - public DocumentVersion GetVersionOrDefault(BsonDocument document) - { - BsonValue value; - document.TryGetValue(this.GetVersionFieldName(), out value); + return DocumentVersion.Default; + } - if (value != null && !value.IsBsonNull) - { - return value.AsString; - } + public void SetVersion(BsonDocument document, in DocumentVersion version) + { + document[GetVersionFieldName()] = new BsonString(version.ToString()); + } - return DocumentVersion.Default(); - } + public void DetermineVersion(TClass instance) + where TClass : IDocument + { + var type = typeof(TClass); + var documentVersion = instance.Version; + var latestVersion = _migrationLocator.GetLatestVersion(type); + var currentVersion = GetCurrentVersion(type) ?? latestVersion; - public void SetVersion(BsonDocument document, DocumentVersion version) + if (documentVersion == currentVersion) { - document[this.GetVersionFieldName()] = version.ToString(); + return; } - public void DetermineVersion(TClass instance) - where TClass : class, IDocument + if (documentVersion == latestVersion) { - var type = typeof(TClass); - var documentVersion = instance.Version.ToString(); - var latestVersion = this._migrationLocator.GetLatestVersion(type); - var currentVersion = this._runtimeVersionLocator.GetLocateOrNull(type) ?? latestVersion; - - if (documentVersion == currentVersion) - { - return; - } - - if (documentVersion == latestVersion) - { - return; - } - - if (DocumentVersion.Default() == documentVersion) - { - SetVersion(instance, currentVersion, latestVersion); - return; - } - - throw new VersionViolationException(currentVersion.ToString(), documentVersion, latestVersion); + return; } - public DocumentVersion DetermineLastVersion( - DocumentVersion version, - List migrations, - int currentMigration) + if (DocumentVersion.Default == documentVersion || DocumentVersion.Empty == documentVersion) { - if (migrations.Last() != migrations[currentMigration]) - { - return migrations[currentMigration + 1].Version; - } - - return version; + SetVersion(instance, currentVersion, latestVersion); + return; } - private DocumentVersion? GetCurrentVersion(Type type) - { - return this._runtimeVersionLocator.GetLocateOrNull(type); - } + throw new VersionViolationException(currentVersion, documentVersion, latestVersion); + } - private static void SetVersion( - TClass instance, - DocumentVersion? currentVersion, - DocumentVersion latestVersion) - where TClass : class, IDocument - { - if (currentVersion < latestVersion) - { - instance.Version = currentVersion.ToString(); - return; - } + private DocumentVersion? GetCurrentVersion(Type type) + { + return _runtimeVersionLocator.GetLocateOrNull(type); + } - instance.Version = latestVersion; + private static void SetVersion( + TClass instance, + in DocumentVersion currentVersion, + in DocumentVersion latestVersion) + where TClass : IDocument + { + if (currentVersion < latestVersion) + { + instance.Version = currentVersion; + return; } + + instance.Version = latestVersion; } } \ No newline at end of file diff --git a/Mongo.Migration/Services/IDatabaseVersionService.cs b/Mongo.Migration/Services/IDatabaseVersionService.cs index 853daaf..f3e68cf 100644 --- a/Mongo.Migration/Services/IDatabaseVersionService.cs +++ b/Mongo.Migration/Services/IDatabaseVersionService.cs @@ -3,16 +3,15 @@ using MongoDB.Driver; -namespace Mongo.Migration.Services +namespace Mongo.Migration.Services; + +public interface IDatabaseVersionService { - public interface IDatabaseVersionService - { - DocumentVersion GetCurrentOrLatestMigrationVersion(); + DocumentVersion GetLatestMigrationVersion(); - DocumentVersion GetLatestDatabaseVersion(IMongoDatabase db); + Task GetLatestDatabaseVersionAsync(IMongoDatabase db, CancellationToken cancellationToken); - void Save(IMongoDatabase db, IDatabaseMigration migration); + Task SaveAsync(IMongoDatabase db, IDatabaseMigration migration, CancellationToken cancellationToken); - void Remove(IMongoDatabase db, IDatabaseMigration migration); - } + Task RemoveAsync(IMongoDatabase db, IDatabaseMigration migration, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Mongo.Migration/Services/IDocumentVersionService.cs b/Mongo.Migration/Services/IDocumentVersionService.cs index 9c75b1f..27afc4e 100644 --- a/Mongo.Migration/Services/IDocumentVersionService.cs +++ b/Mongo.Migration/Services/IDocumentVersionService.cs @@ -1,31 +1,20 @@ -using System; -using System.Collections.Generic; - using Mongo.Migration.Documents; -using Mongo.Migration.Migrations.Document; - using MongoDB.Bson; -namespace Mongo.Migration.Services -{ - public interface IDocumentVersionService - { - string GetVersionFieldName(); +namespace Mongo.Migration.Services; - DocumentVersion GetCurrentOrLatestMigrationVersion(Type type); +public interface IDocumentVersionService +{ + string GetVersionFieldName(); - DocumentVersion GetCollectionVersion(Type type); + DocumentVersion GetCurrentOrLatestMigrationVersion(Type type); - DocumentVersion GetVersionOrDefault(BsonDocument document); + DocumentVersion GetCollectionVersion(Type type); - void SetVersion(BsonDocument document, DocumentVersion version); + DocumentVersion GetVersionOrDefault(BsonDocument document); - void DetermineVersion(TClass instance) - where TClass : class, IDocument; + void SetVersion(BsonDocument document, in DocumentVersion version); - DocumentVersion DetermineLastVersion( - DocumentVersion version, - List migrations, - int currentMigration); - } + void DetermineVersion(TClass instance) + where TClass : IDocument; } \ No newline at end of file diff --git a/Mongo.Migration/Services/IMigrationService.cs b/Mongo.Migration/Services/IMigrationService.cs index 5b63f8c..2608b3c 100644 --- a/Mongo.Migration/Services/IMigrationService.cs +++ b/Mongo.Migration/Services/IMigrationService.cs @@ -1,7 +1,38 @@ -namespace Mongo.Migration.Services +namespace Mongo.Migration.Services; + +public interface IMigrationService { - internal interface IMigrationService - { - void Migrate(); - } + /// + /// Register all serializer and other settings to MongoDB.Driver. + /// This enables the DocumentRuntime migration if it has been setup + /// + /// Must be called once, before any mongo interaction + void RegisterBsonStatics(); + + /// + /// Immediately execute all migration setup + /// Don't do anything if only document runtime migration added + /// + /// The target database + /// The expected database version (latest of null) + /// + /// + Task ExecuteMigrationsAsync(string databaseName, string? targetDatabaseVersion, CancellationToken cancellationToken); + + /// + /// Immediately execute the database migrations + /// + /// The target database + /// The expected database version (latest of null) + /// + /// + Task ExecuteDatabaseMigrationAsync(string databaseName, string? targetVersion, CancellationToken cancellationToken); + + /// + /// Immediately execute the document migrations + /// + /// The target database + /// + /// + Task ExecuteDocumentMigrationAsync(string databaseName, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Mongo.Migration/Services/Interceptors/IMigrationInterceptorFactory.cs b/Mongo.Migration/Services/Interceptors/IMigrationInterceptorFactory.cs deleted file mode 100644 index 30dd4dc..0000000 --- a/Mongo.Migration/Services/Interceptors/IMigrationInterceptorFactory.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -using MongoDB.Bson.Serialization; - -namespace Mongo.Migration.Services.Interceptors -{ - internal interface IMigrationInterceptorFactory - { - IBsonSerializer Create(Type type); - } -} \ No newline at end of file diff --git a/Mongo.Migration/Services/Interceptors/IMigrationInterceptorProvider.cs b/Mongo.Migration/Services/Interceptors/IMigrationInterceptorProvider.cs deleted file mode 100644 index 8b6e7f0..0000000 --- a/Mongo.Migration/Services/Interceptors/IMigrationInterceptorProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using MongoDB.Bson.Serialization; - -namespace Mongo.Migration.Services.Interceptors -{ - public interface IMigrationInterceptorProvider : IBsonSerializationProvider - { - } -} \ No newline at end of file diff --git a/Mongo.Migration/Services/Interceptors/MigrationInterceptor.cs b/Mongo.Migration/Services/Interceptors/MigrationInterceptor.cs deleted file mode 100644 index 319f503..0000000 --- a/Mongo.Migration/Services/Interceptors/MigrationInterceptor.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Mongo.Migration.Documents; -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson.IO; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Serializers; - -namespace Mongo.Migration.Services.Interceptors -{ - internal class MigrationInterceptor : BsonClassMapSerializer - where TDocument : class, IDocument - { - private readonly IDocumentVersionService _documentVersionService; - - private readonly IDocumentMigrationRunner _migrationRunner; - - public MigrationInterceptor(IDocumentMigrationRunner migrationRunner, IDocumentVersionService documentVersionService) - : base(BsonClassMap.LookupClassMap(typeof(TDocument))) - { - this._migrationRunner = migrationRunner; - this._documentVersionService = documentVersionService; - } - - public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TDocument value) - { - this._documentVersionService.DetermineVersion(value); - - base.Serialize(context, args, value); - } - - public override TDocument Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) - { - // TODO: Performance? LatestVersion, dont do anything - var document = BsonDocumentSerializer.Instance.Deserialize(context); - - this._migrationRunner.Run(typeof(TDocument), document); - - var migratedContext = - BsonDeserializationContext.CreateRoot(new BsonDocumentReader(document)); - - return base.Deserialize(migratedContext, args); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Services/Interceptors/MigrationInterceptorFactory.cs b/Mongo.Migration/Services/Interceptors/MigrationInterceptorFactory.cs deleted file mode 100644 index d830859..0000000 --- a/Mongo.Migration/Services/Interceptors/MigrationInterceptorFactory.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -using Mongo.Migration.Migrations.Document; - -using MongoDB.Bson.Serialization; - -namespace Mongo.Migration.Services.Interceptors -{ - internal class MigrationInterceptorFactory : IMigrationInterceptorFactory - { - private readonly IDocumentVersionService _documentVersionService; - - private readonly IDocumentMigrationRunner _migrationRunner; - - public MigrationInterceptorFactory(IDocumentMigrationRunner migrationRunner, IDocumentVersionService documentVersionService) - { - this._migrationRunner = migrationRunner; - this._documentVersionService = documentVersionService; - } - - public IBsonSerializer Create(Type type) - { - var genericType = typeof(MigrationInterceptor<>).MakeGenericType(type); - var interceptor = Activator.CreateInstance(genericType, this._migrationRunner, this._documentVersionService); - return interceptor as IBsonSerializer; - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Services/Interceptors/MigrationInterceptorProvider.cs b/Mongo.Migration/Services/Interceptors/MigrationInterceptorProvider.cs deleted file mode 100644 index c1b8ed8..0000000 --- a/Mongo.Migration/Services/Interceptors/MigrationInterceptorProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Linq; - -using Mongo.Migration.Documents; - -using MongoDB.Bson; -using MongoDB.Bson.Serialization; - -namespace Mongo.Migration.Services.Interceptors -{ - internal class MigrationInterceptorProvider : IMigrationInterceptorProvider - { - private readonly IMigrationInterceptorFactory _migrationInterceptorFactory; - - public MigrationInterceptorProvider(IMigrationInterceptorFactory migrationInterceptorFactory) - { - this._migrationInterceptorFactory = migrationInterceptorFactory; - } - - public IBsonSerializer GetSerializer(Type type) - { - if (ShouldBeMigrated(type)) - { - return this._migrationInterceptorFactory.Create(type); - } - - return null; - } - - private static bool ShouldBeMigrated(Type type) - { - return type.GetInterfaces().Contains(typeof(IDocument)) && type != typeof(BsonDocument); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Services/MigrationService.cs b/Mongo.Migration/Services/MigrationService.cs index 6253dec..104d717 100644 --- a/Mongo.Migration/Services/MigrationService.cs +++ b/Mongo.Migration/Services/MigrationService.cs @@ -1,77 +1,99 @@ +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - +using Mongo.Migration.Bson; +using Mongo.Migration.Documents; using Mongo.Migration.Documents.Serializers; using Mongo.Migration.Migrations.Database; using Mongo.Migration.Migrations.Document; -using Mongo.Migration.Services.Interceptors; - -using MongoDB.Bson; +using Mongo.Migration.Migrations.Locators; +using Mongo.Migration.Startup; using MongoDB.Bson.Serialization; +using MongoDB.Driver; + +namespace Mongo.Migration.Services; -namespace Mongo.Migration.Services +internal class MigrationService : IMigrationService { - internal class MigrationService : IMigrationService - { - private readonly ILogger _logger; + private readonly ILogger _logger; + private readonly IMongoClient _client; + private readonly MongoMigrationStartupSettings _startupSettings; + private readonly IServiceProvider _provider; - private readonly IMigrationInterceptorProvider _provider; + public MigrationService( + ILogger logger, + IMongoClient client, + MongoMigrationStartupSettings startupSettings, + IServiceProvider provider) + { + _logger = logger; + _client = client; + _startupSettings = startupSettings; + _provider = provider; + } - private readonly DocumentVersionSerializer _serializer; + /// + public void RegisterBsonStatics() + { + BsonSerializer.RegisterSerializer(new DocumentVersionSerializer()); - private readonly IStartUpDatabaseMigrationRunner _startUpDatabaseMigrationRunner; + if (_startupSettings.RuntimeMigrationEnabled) + { + MigrationBsonSerializerProvider migrationSerializerProvider = _provider.GetRequiredService(); + BsonSerializer.RegisterSerializationProvider(migrationSerializerProvider); - private readonly IStartUpDocumentMigrationRunner _startUpDocumentMigrationRunner; + var documentMigrationLocator = _provider.GetRequiredService>(); + documentMigrationLocator.Initialize(); + } + } - public MigrationService( - DocumentVersionSerializer serializer, - IMigrationInterceptorProvider provider, - IStartUpDocumentMigrationRunner startUpDocumentMigrationRunner, - IStartUpDatabaseMigrationRunner startUpDatabaseMigrationRunner) - : this(serializer, provider, NullLoggerFactory.Instance) + /// + public async Task ExecuteMigrationsAsync(string databaseName, string? targetDatabaseVersion, CancellationToken cancellationToken) + { + if (_startupSettings.DatabaseMigrationEnabled) { - this._startUpDocumentMigrationRunner = startUpDocumentMigrationRunner; - this._startUpDatabaseMigrationRunner = startUpDatabaseMigrationRunner; + await ExecuteDatabaseMigrationAsync(databaseName, targetDatabaseVersion, cancellationToken); } - private MigrationService( - DocumentVersionSerializer serializer, - IMigrationInterceptorProvider provider, - ILoggerFactory loggerFactory) + if (_startupSettings.StartupDocumentMigrationEnabled) { - this._serializer = serializer; - this._provider = provider; - this._logger = loggerFactory.CreateLogger(); + await ExecuteDocumentMigrationAsync(databaseName, cancellationToken); } + } - public void Migrate() - { - BsonSerializer.RegisterSerializationProvider(this._provider); - this.RegisterSerializer(); + /// + public async Task ExecuteDatabaseMigrationAsync(string databaseName, string? targetVersion, CancellationToken cancellationToken) + { + Stopwatch sw = Stopwatch.StartNew(); - this.OnStartup(); - } + DocumentVersion? typedTargetVersion = null; - private void OnStartup() + if (targetVersion is not null) { - this._startUpDatabaseMigrationRunner.RunAll(); - this._startUpDocumentMigrationRunner.RunAll(); + typedTargetVersion = DocumentVersion.Parse(targetVersion.AsSpan()); } - private void RegisterSerializer() - { - try - { - BsonSerializer.RegisterSerializer(this._serializer.ValueType, this._serializer); - } - catch (BsonSerializationException ex) - { - // Catch if Serializer was registered already ... not great, I know. - // We have to do this, because there is always a default DocumentVersionSerialzer. - // BsonSerializer.LookupSerializer(), does not work. - - this._logger.LogError(ex, ex.GetType().ToString()); - } - } + _logger.LogInformation("Executing database migration..."); + await using var scope = _provider.CreateAsyncScope(); + + IMongoDatabase database = _client.GetDatabase(databaseName); + IDatabaseMigrationRunner runner = scope.ServiceProvider.GetRequiredService(); + await runner.RunAsync(database, typedTargetVersion, cancellationToken); + + _logger.LogInformation("Database migration done in {ElapsedMs} ms", sw.ElapsedMilliseconds); + } + + /// + public async Task ExecuteDocumentMigrationAsync(string databaseName, CancellationToken cancellationToken) + { + Stopwatch sw = Stopwatch.StartNew(); + _logger.LogInformation("Executing document migration..."); + await using var scope = _provider.CreateAsyncScope(); + + IMongoDatabase database = _client.GetDatabase(databaseName); + IStartUpDocumentMigrationRunner runner = scope.ServiceProvider.GetRequiredService(); + await runner.RunAllAsync(database, cancellationToken); + + _logger.LogInformation("Database migration done in {ElapsedMs} ms", sw.ElapsedMilliseconds); } } \ No newline at end of file diff --git a/Mongo.Migration/Startup/DotNetCore/MongoMigrationExtensions.cs b/Mongo.Migration/Startup/DotNetCore/MongoMigrationExtensions.cs deleted file mode 100644 index 96a41ec..0000000 --- a/Mongo.Migration/Startup/DotNetCore/MongoMigrationExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -using Mongo.Migration.Documents.Locators; -using Mongo.Migration.Documents.Serializers; -using Mongo.Migration.Migrations.Adapters; -using Mongo.Migration.Migrations.Database; -using Mongo.Migration.Migrations.Document; -using Mongo.Migration.Migrations.Locators; -using Mongo.Migration.Services; -using Mongo.Migration.Services.Interceptors; - -namespace Mongo.Migration.Startup.DotNetCore -{ - public static class MongoMigrationExtensions - { - public static void AddMigration(this IServiceCollection services, IMongoMigrationSettings settings = null) - { - RegisterDefaults(services, settings ?? new MongoMigrationSettings()); - - services.AddScoped(); - } - - private static void RegisterDefaults(IServiceCollection services, IMongoMigrationSettings settings) - { - services.AddSingleton(settings); - - services.AddSingleton(); - services.AddSingleton(typeof(IMigrationLocator<>), typeof(TypeMigrationDependencyLocator<>)); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - services.AddTransient(); - services.AddTransient(); - - services.AddTransient(); - services.AddTransient(); - - services.AddTransient(); - - services.AddTransient(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Startup/IMongoMigrationSettings.cs b/Mongo.Migration/Startup/IMongoMigrationSettings.cs deleted file mode 100644 index 9ed34cb..0000000 --- a/Mongo.Migration/Startup/IMongoMigrationSettings.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Mongo.Migration.Documents; - -using MongoDB.Driver; - -namespace Mongo.Migration.Startup -{ - public interface IMongoMigrationSettings - { - string ConnectionString { get; set; } - - string Database { get; set; } - - DocumentVersion DatabaseMigrationVersion { get; set; } - - string VersionFieldName { get; set; } - - MongoClientSettings ClientSettings { get; set; } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Startup/MongoMigrationConfigurator.cs b/Mongo.Migration/Startup/MongoMigrationConfigurator.cs new file mode 100644 index 0000000..1be0f20 --- /dev/null +++ b/Mongo.Migration/Startup/MongoMigrationConfigurator.cs @@ -0,0 +1,77 @@ +using Mongo.Migration.Documents.Attributes; +using Mongo.Migration.Documents; +using Mongo.Migration.Exceptions; +using System.Reflection; + +namespace Mongo.Migration.Startup; + +public class MongoMigrationConfigurator +{ + internal MongoMigrationSettings MongoMigrationSettings { get; } + internal MongoMigrationStartupSettings MongoMigrationStartupSettings { get; } + internal Dictionary RuntimeMigrationDictionary { get; } + + internal MongoMigrationConfigurator() + { + MongoMigrationSettings = new MongoMigrationSettings(); + MongoMigrationStartupSettings = new MongoMigrationStartupSettings(); + RuntimeMigrationDictionary = []; + } + + public MongoMigrationConfigurator SetVersionFieldName(string fieldName) + { + MongoMigrationSettings.VersionFieldName = fieldName; + return this; + } + + public MongoMigrationConfigurator AddRuntimeDocumentMigration() + { + MongoMigrationStartupSettings.RuntimeMigrationEnabled = true; + return this; + } + + public MongoMigrationConfigurator AddStartupDocumentMigration() + { + MongoMigrationStartupSettings.StartupDocumentMigrationEnabled = true; + return this; + } + + public MongoMigrationConfigurator AddDocumentMigratedType(DocumentVersion runtimeVersion) + { + Type t = typeof(T); + RuntimeVersionAttribute? runtimeVersionAttribute = t.GetCustomAttributes(true) + .FirstOrDefault(); + + if (runtimeVersionAttribute is null || runtimeVersionAttribute.Version == runtimeVersion) + { + RuntimeMigrationDictionary.Add(t, runtimeVersion); + } + else + { + throw new RuntimeVersionDefinitionException(t, runtimeVersion, runtimeVersionAttribute.Version); + } + + return this; + } + + public MongoMigrationConfigurator AddDocumentMigratedType(string runtimeVersion) + { + return AddDocumentMigratedType(DocumentVersion.Parse(runtimeVersion.AsSpan())); + } + + public MongoMigrationConfigurator AddDatabaseMigration() + { + MongoMigrationStartupSettings.DatabaseMigrationEnabled = true; + return this; + } + + internal void AddAllMigrationsIfNothingWasAdded() + { + if (MongoMigrationStartupSettings is { RuntimeMigrationEnabled: false, StartupDocumentMigrationEnabled: false, DatabaseMigrationEnabled: false }) + { + AddRuntimeDocumentMigration(); + AddDatabaseMigration(); + AddStartupDocumentMigration(); + } + } +} \ No newline at end of file diff --git a/Mongo.Migration/Startup/MongoMigrationExtensions.cs b/Mongo.Migration/Startup/MongoMigrationExtensions.cs new file mode 100644 index 0000000..1538193 --- /dev/null +++ b/Mongo.Migration/Startup/MongoMigrationExtensions.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo.Migration.Bson; +using Mongo.Migration.Documents.Locators; +using Mongo.Migration.Migrations.Database; +using Mongo.Migration.Migrations.Document; +using Mongo.Migration.Migrations.Locators; +using Mongo.Migration.Services; +namespace Mongo.Migration.Startup; + +public static class MongoMigrationExtensions +{ + public static IServiceCollection AddMigration( + this IServiceCollection services, + Action? configure = null) + { + MongoMigrationConfigurator configurator = new(); + + configure?.Invoke(configurator); + + configurator.AddAllMigrationsIfNothingWasAdded(); + + BuildDocumentMigration(services, configurator); + + BuildDatabaseMigration(services, configurator); + + services + .AddSingleton(configurator.MongoMigrationSettings) + .AddSingleton(configurator.MongoMigrationStartupSettings) + .AddTransient(); + + return services; + } + + private static void BuildDocumentMigration(IServiceCollection services, MongoMigrationConfigurator migrationConfigurator) + { + if (migrationConfigurator.MongoMigrationStartupSettings.RuntimeMigrationEnabled || + migrationConfigurator.MongoMigrationStartupSettings.StartupDocumentMigrationEnabled) + { + services + .AddSingleton, TypeMigrationLocator>() + .AddSingleton(new RuntimeVersionLocator(migrationConfigurator.RuntimeMigrationDictionary)) + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + if (migrationConfigurator.MongoMigrationStartupSettings.RuntimeMigrationEnabled) + { + services + .AddSingleton(); + } + + if (migrationConfigurator.MongoMigrationStartupSettings.StartupDocumentMigrationEnabled) + { + services + .AddTransient() + .AddTransient(); + } + } + } + + private static void BuildDatabaseMigration(IServiceCollection services, MongoMigrationConfigurator migrationConfigurator) + { + if (migrationConfigurator.MongoMigrationStartupSettings.DatabaseMigrationEnabled) + { + services + .AddTransient() + .AddTransient() + .AddTransient(); + } + } +} \ No newline at end of file diff --git a/Mongo.Migration/Startup/MongoMigrationSettings.cs b/Mongo.Migration/Startup/MongoMigrationSettings.cs index 9349347..73c9886 100644 --- a/Mongo.Migration/Startup/MongoMigrationSettings.cs +++ b/Mongo.Migration/Startup/MongoMigrationSettings.cs @@ -1,19 +1,6 @@ -using Mongo.Migration.Documents; +namespace Mongo.Migration.Startup; -using MongoDB.Driver; - -namespace Mongo.Migration.Startup +public class MongoMigrationSettings { - public class MongoMigrationSettings : IMongoMigrationSettings - { - public string ConnectionString { get; set; } - - public string Database { get; set; } - - public DocumentVersion DatabaseMigrationVersion { get; set; } = DocumentVersion.Empty(); - - public string VersionFieldName { get; set; } - - public MongoClientSettings ClientSettings { get; set; } - } + public string VersionFieldName { get; set; } = "Version"; } \ No newline at end of file diff --git a/Mongo.Migration/Startup/MongoMigrationStartupSettings.cs b/Mongo.Migration/Startup/MongoMigrationStartupSettings.cs new file mode 100644 index 0000000..13f63c3 --- /dev/null +++ b/Mongo.Migration/Startup/MongoMigrationStartupSettings.cs @@ -0,0 +1,8 @@ +namespace Mongo.Migration.Startup; + +internal class MongoMigrationStartupSettings +{ + public bool RuntimeMigrationEnabled { get; set; } + public bool StartupDocumentMigrationEnabled { get; set; } + public bool DatabaseMigrationEnabled { get; set; } +} \ No newline at end of file diff --git a/Mongo.Migration/Startup/Static/ComponentRegistry.cs b/Mongo.Migration/Startup/Static/ComponentRegistry.cs deleted file mode 100644 index 7b219cb..0000000 --- a/Mongo.Migration/Startup/Static/ComponentRegistry.cs +++ /dev/null @@ -1,77 +0,0 @@ -using LightInject; - -using Mongo.Migration.Documents.Locators; -using Mongo.Migration.Documents.Serializers; -using Mongo.Migration.Migrations.Adapters; -using Mongo.Migration.Migrations.Database; -using Mongo.Migration.Migrations.Document; -using Mongo.Migration.Migrations.Locators; -using Mongo.Migration.Services; -using Mongo.Migration.Services.Interceptors; - -using MongoDB.Driver; - -namespace Mongo.Migration.Startup.Static -{ - internal class ComponentRegistry : IComponentRegistry - { - private readonly IContainerAdapter _containerAdapter; - - private readonly IMongoMigrationSettings _settings; - - public ComponentRegistry(IMongoMigrationSettings settings, IContainerAdapter containerAdapter = null) - { - this._settings = settings; - - if (containerAdapter == null) - { - containerAdapter = new LightInjectAdapter(new ServiceContainer()); - } - - this._containerAdapter = containerAdapter; - } - - public void RegisterComponents(IMongoClient client) - { - this.RegisterDefaults(); - - this._containerAdapter.RegisterInstance(client); - - this._containerAdapter.Register(); - } - - public TComponent Get() - where TComponent : class - { - return (TComponent)this._containerAdapter.GetInstance(typeof(TComponent)); - } - - private void RegisterDefaults() - { - this._containerAdapter.RegisterInstance(this._containerAdapter); - - this._containerAdapter.Register(typeof(IMigrationLocator<>), typeof(TypeMigrationDependencyLocator<>)); - - this._containerAdapter.RegisterInstance(this._settings); - - this._containerAdapter.RegisterSingleton(); - this._containerAdapter.RegisterSingleton(); - this._containerAdapter.RegisterSingleton(); - this._containerAdapter.RegisterSingleton(); - - this._containerAdapter.Register(); - this._containerAdapter.Register(); - this._containerAdapter.Register(); - this._containerAdapter.Register(); - - this._containerAdapter.Register(); - this._containerAdapter.Register(); - this._containerAdapter.Register(); - - this._containerAdapter.Register(); - this._containerAdapter.Register(); - - this._containerAdapter.Register(); - } - } -} \ No newline at end of file diff --git a/Mongo.Migration/Startup/Static/IComponentRegistry.cs b/Mongo.Migration/Startup/Static/IComponentRegistry.cs deleted file mode 100644 index 8dbd83a..0000000 --- a/Mongo.Migration/Startup/Static/IComponentRegistry.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MongoDB.Driver; - -namespace Mongo.Migration.Startup.Static -{ - public interface IComponentRegistry - { - void RegisterComponents(IMongoClient client); - - TComponent Get() - where TComponent : class; - } -} \ No newline at end of file diff --git a/Mongo.Migration/Startup/Static/MongoMigrationClient.cs b/Mongo.Migration/Startup/Static/MongoMigrationClient.cs deleted file mode 100644 index 2163b19..0000000 --- a/Mongo.Migration/Startup/Static/MongoMigrationClient.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Mongo.Migration.Exceptions; -using Mongo.Migration.Migrations.Adapters; - -using MongoDB.Driver; - -namespace Mongo.Migration.Startup.Static -{ - public static class MongoMigrationClient - { - private static bool _isRunning; - - public static void Initialize(IComponentRegistry componentRegistry) - { - if (_isRunning) - { - throw new AlreadyInitializedException(); - } - - var app = componentRegistry.Get(); - app.Run(); - - _isRunning = true; - } - - public static void Initialize(IMongoClient client, IContainerAdapter containerAdapter) - { - var componentRegistry = new ComponentRegistry(new MongoMigrationSettings(), containerAdapter); - componentRegistry.RegisterComponents(client); - - Initialize(componentRegistry); - } - - public static void Initialize(IMongoClient client, IMongoMigrationSettings settings = null, IContainerAdapter containerAdapter = null) - { - var componentRegistry = new ComponentRegistry(settings ?? new MongoMigrationSettings(), containerAdapter); - componentRegistry.RegisterComponents(client); - - Initialize(componentRegistry); - } - - public static void Reset() - { - _isRunning = false; - } - } -} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..630b12d --- /dev/null +++ b/README.md @@ -0,0 +1,245 @@ +[![GitHub License](https://img.shields.io/github/license/sroddis/Mongo.Migration)](https://github.com/sroddis/Mongo.Migration/tree/master?tab=MIT-1-ov-file) +[![NuGet Downloads](https://img.shields.io/nuget/dt/Mongo.Migration)](https://www.nuget.org/packages/Mongo.Migration/) +[![NuGet](https://img.shields.io/nuget/v/Mongo.Migration)](https://www.nuget.org/packages/Mongo.Migration/) +[![GitHub last commit](https://img.shields.io/github/last-commit/sroddis/Mongo.Migration)](https://github.com/sroddis/Mongo.Migration/commits/master/) + +# Mongo.Migration + +![](https://media.giphy.com/media/10tLOFXDFDjgQM/giphy.gif) + +Mongo.Migration is designed for the [MongoDB C# Driver](https://github.com/mongodb/mongo-csharp-driver) to migrate your documents easily and on-the-fly. +No more downtime for schema-migrations. Just write small and simple `migrations`. + +The library is still based on the official [MongoDB.Driver](https://www.mongodb.com/docs/drivers/csharp/) (3.5.2+) and supports the following 3 types of migration: + +- **Runtime document migration:** + This kind of migration is executed at serialization/deserialization process. It allows your application to consume data serialized at another version +- **Startup document migration:** + This kind of migration can be executed on demand (generally at startup) and execute the same document migrations using bulk writes. + This migration is slow and memory consuming, but it could be enough for small data sets +- **Database migration:** + This kind of migration give access to the entire database and allows to write much faster migrations. They also allow to manage indexes, rights, or any other maintenance of the database. + +**Notes:** A robust application will probably use two kind of migrations. + +- The database migration executed by the continuous integration +- The runtime document migration executed by the runtime to prevent any error during the database migration + +# Installation + +Install via nuget [Mongo.Migration](https://www.nuget.org/packages/Mongo.Migration): + +```shell +dotnet add package Mongo.Migration +``` + +# Register migration services + +```c# +string myConnectionString = "..."; +IServiceCollection services = new ServiceCollection(); +services + .AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance)) // Require logging + .AddSingleton(new MongoClient(myConnectionString) // Require IMongoClient + .AddMigration(builder => // Configure the migration (null => all migration types enabled) + { + builder + .AddDocumentMigratedType("0.0.1") // Declare POCO outside assembly for runtime migration (requires an existing string Version property) + .AddRuntimeDocumentMigration() + .AddStartupDocumentMigration() + .AddDatabaseMigration() + }); +``` + +# Execute migrations + +```c# +IServiceProvider provider = services.BuildServiceProvider(); + +IMigrationService migrationService = provider + .GetRequiredService(); + +// Register serializers in MongoDb.Driver and enable runtime migration if setup +// Must be called once before any mongo call +migrationService.RegisterBsonStatics(); + +// Execute the migrations +await migrationService + .ExecuteDatabaseMigrationAsync("my-database", "1.0.0"); + +await migrationService + .ExecuteDocumentMigrationAsync("my-database"); +``` + +# Document migrations quick Start +Document migrations first usage is to be executed at runtime on POCO serialize/deserialize. +You can still execute them at startup to migrate your database but this **is not efficient** and should only be done for **very small volumes of data**. + +1. Implement `IDocument` or add `Document` to your entities to provide the `DocumentVersion`. (Optional) Add the `RuntimeVersion` attribute to mark the current version of the document. So you have the possibility to downgrade in case of a rollback. + ```csharp + [RuntimeVersion("0.0.1")] + public class Car : IDocument + { + public ObjectId Id { get; set; } + + public string Type { get; set; } + + public int Doors { get; set; } + + public DocumentVersion Version { get; set; } + } + ``` +2. Create a migration by extending the abstract class `DocumentMigration`. Best practice for the version is to use [Semantic Versioning,](http://semver.org/) but ultimately it is up to you. You could simply use the patch version to count the number of migrations. If there is a duplicate for a specific type an exception is thrown on initialization. + ```csharp + public class M001_RenameDorsToDoors : DocumentMigration + { + public M001_RenameDorsToDoors() + : base("0.0.1") + { + } + + public override void Up(BsonDocument document) + { + var doors = document["Dors"].ToInt32(); + document.Add("Doors", doors); + document.Remove("Dors"); + } + + public override void Down(BsonDocument document) + { + var doors = document["Doors"].ToInt32(); + document.Add("Dors", doors); + document.Remove("Doors"); + } + } + ``` +3. `(Optional)` If you choose to put your migrations into an extra project, +add the suffix `".MongoMigrations"` to the name and make sure it is referenced in the main project. By convention Mongo.Migration collects all .dlls named like that in your bin folder. + +Compile, run and enjoy! + +# Database migrations quick start + +Database migrations are very efficient to migrate large volume of data, and it allows to manage indexes or whatever you need. +Here are some tips to implement your migrations: +- Do not create transactions +- Try to write atomic updates as most as possible +- Create many small migrations +- Also migrate your documents and update their runtime version +- Write the most robust migrations possible (specially atomicity isn't guaranteed) +- Do not depend on your data model (it will change, you migrations shouldn't) + +1. Create a migration by extending the abstract class `DatabaseMigration`. Best practice for the version is to use [Semantic Versioning,](http://semver.org/) but ultimately it is up to you. You could simply use the patch version to count the number of migrations. All database migrations you add for a database will be executed at StartUp. + ```csharp + public class M110AddNewWheels : DatabaseMigration + { + private const string PreviousCarVersion = "3.5.0"; + private const string NewCarVersion = "3.6.0"; + + public M110AddNewWheels() : base("1.1.0") { } + + public override async Task UpAsync(IMongoDatabase db, CancellationToken cancellationToken) + { + var collection = db.GetCollection("Car"); + await collection.UpdateManyAsync( + Builders.Filter.Eq("Version", PreviousCarVersion), // update only previous documents + Builders.Update + .Set("Wheels", 4) + .Set(nameof(IDocument.Version), NewCarVersion), // updates in atomic operation + null, + cancellationToken + ); + } + + public override async Task DownAsync(IMongoDatabase db, CancellationToken cancellationToken) + { + var collection = db.GetCollection("Car"); + await collection.UpdateManyAsync( + Builders.Filter.Eq("Version", NewCarVersion), // update only new documents + Builders.Update + .Unset("Wheels") + .Set(nameof(IDocument.Version), PreviousCarVersion), + null, + cancellationToken + ); + } + } + ``` + +# Attributes + +## RuntimeVersion + +Add `RuntimeVersion` attribute to mark the current version of the document. So you have the possibility to downgrade in case of a rollback. +If you do not set the `RuntimeVersion`, all migrations will be applied. + +```csharp +[RuntimeVersion("0.0.1")] +public class Car : IDocument { } +``` + +## CollectionLocation + +Add `CollectionLocation` attribute if you want to migrate your collections at startup with document migration. +This attribute tells Mongo.Migration where to find your Collections. + +```csharp +[CollectionLocation("Car")] +public class Car : IDocument { } +``` +## StartUpVersion +Add `StartUpVersion` attribute to set the version you want to migrate to at startup with document migration. +This attribute limits the migrations to be performed on startup. + +```csharp +[StartUpVersion("0.0.1")] +public class Car : IDocument { } +``` + +# Suggestions + +Deploy the migrations in a separate artifact. Otherwise, you lose the ability to downgrade in case of a rollback. + +# Release notes + +## v5.1.0 +This version mostly introduce .Net 10 support. + +### Updates +- .Net version update (.net10.0, .net9.0, .net8.0) +- MongoDB.Driver@3.5.2 (required for .Net 10) + +## v5.0.0 +This could be not 100% exhaustive but v5.0.0 did a lot of changes comparing to older versions. +Consider also there was a lot of changes between the last 3.1.4 officially published version and the source code. + +### Updates +- .Net version update (.net8_0, .net9_0) +- MongDB.Driver@3.0.0+ +- Dependency updates +- Remove Mongo2Go in favor of Testcontainers +- Refactoring initialisation + - Can migrate multiple database + - Remove CollectionLocationAttribute `Database` property + - Can enable separately all migration types + - Add extension method to initialize before app startup +- Use span for DocumentVersion parsing +- Use mongo bookmark when no migration needed +- A lot of cleanup and optimization +- Documentation rewriting +- More tests + +### Breaking changes +- Remove .Net framework support +- MongoDB.Driver@3.0.0 +- DatabaseMigration now use async methods for UpAsync and DownAsync +- CollectionLocationAttribute Database property removed +- Refactoring initialisation + +# Next Feature/Todo + +1. Create startup setting to limit database migrations ran at startup on fresh database +2. Add real benchmark for runtime migrations + +# License +Mongo.Migration is licensed under [MIT](http://www.opensource.org/licenses/mit-license.php "Read more about the MIT license form"). Refer to [LICENSE.md](LICENSE.md) for more information. diff --git a/Readme.md b/Readme.md deleted file mode 100644 index 5898e93..0000000 --- a/Readme.md +++ /dev/null @@ -1,470 +0,0 @@ -[![.NET](https://github.com/SRoddis/Mongo.Migration/actions/workflows/dotnet.yml/badge.svg)](https://github.com/SRoddis/Mongo.Migration/actions/workflows/dotnet.yml) -[![Coverage Status](https://coveralls.io/repos/github/SRoddis/Mongo.Migration/badge.svg)](https://coveralls.io/github/SRoddis/Mongo.Migration) -[![NuGet](https://img.shields.io/nuget/dt/Mongo.Migration.svg)](https://www.nuget.org/packages/Mongo.Migration/) -[![NuGet](https://img.shields.io/nuget/v/Mongo.Migration.svg)](https://www.nuget.org/packages/Mongo.Migration/) -![GitHub last commit](https://img.shields.io/github/last-commit/sroddis/Mongo.Migration.svg) - -# Mongo.Migration - - -![](https://media.giphy.com/media/10tLOFXDFDjgQM/giphy.gif) - - -Mongo.Migration is designed for the [MongoDB C# Driver](https://github.com/mongodb/mongo-csharp-driver) to migrate your documents easily and on-the-fly. -No more downtime for schema-migrations. Just write small and simple `migrations`. - -`**Edit**` - -With version 3.0.0 of Mongo.Migration I added the possibility to run migrations on StartUp. In order to keep the core of Mongo.Migration in focus, it is still possible to run migrations at runtime (on-the-fly). In addition, there is now the option of executing migrations at the start of the application. - -`**PLEASE NOTE**` If you use on-the-fly migration updates, aggregation pipeline and projections are not handled, because they don’t use serialization. You have to handle them yourself. - -With version 4.0.0 of Mongo.Migration was added the possibility to run database migrations on StartUp. -Now exist additional option of executing migrations which can manipulate the mongo database insert documents, rename collections and other operations that you need. It's more generic operations than only manipulate document when exists. - -# Installation - -Install via nuget https://www.nuget.org/packages/Mongo.Migration - -``` -PM> Install-Package Mongo.Migration -``` - -# Document migrations quick Start - -#### .Net Framework -1. Initialize `MongoMigration` behind the `MongoClient`. ([Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)) - -```csharp -// Init MongoDB -var runner = MongoDbRunner.Start(); // Mongo2Go -var client = new MongoClient(runner.ConnectionString); - -// Init MongoMigration -MongoMigrationClient.Initialize(client); -``` - -#### .Net Core - -1.1 Add `MongoMigration` with the StartupFilter (`IMongoClient` has to be registered at the DI-container before) - -```csharp -public void ConfigureServices(IServiceCollection services) -{ - services.AddMvc(); - - _client = new MongoClient( _configuration.GetSection("MongoDb:ConnectionString").Value); - - services.AddSingleton(_client); - - services.AddMigration(); -} -``` -1.2 Add `MongoMigration` with the StartupFilter add connection setting to use separate client -```csharp -public void ConfigureServices(IServiceCollection services) -{ - services.AddMvc(); - - _client = new MongoClient( _configuration.GetSection("MongoDb:ConnectionString").Value); - - services.AddSingleton(_client); - - services.AddMigration(new MongoMigrationSettings - { - ConnectionString = _configuration.GetSection("MongoDb:ConnectionString").Value, - Database = _configuration.GetSection("MongoDb:Database").Value, - VersionFieldName = "TestVersionName" // Optional - }); -} -``` - - -2. Implement `IDocument` or add `Document` to your entities to provide the `DocumentVersion`. (Optional) Add the `RuntimeVersion` attribute to mark the current version of the document. So you have the possibility to downgrade in case of a rollback. - -```csharp -[RuntimeVersion("0.0.1")] -public class Car : IDocument -{ - public ObjectId Id { get; set; } - - public string Type { get; set; } - - public int Doors { get; set; } - - public DocumentVersion Version { get; set; } -} -``` -3. Create a migration by extending the abstract class `DocumentMigration`. Best practice for the version is to use [Semantic Versioning](http://semver.org/) but ultimately it is up to you. You could simply use the patch version to count the number of migrations. If there is a duplicate for a specific type an exception is thrown on initialization. - -```csharp -public class M001_RenameDorsToDoors : DocumentMigration -{ - public M001_RenameDorsToDoors() - : base("0.0.1") - { - } - - public override void Up(BsonDocument document) - { - var doors = document["Dors"].ToInt32(); - document.Add("Doors", doors); - document.Remove("Dors"); - } - - public override void Down(BsonDocument document) - { - var doors = document["Doors"].ToInt32(); - document.Add("Dors", doors); - document.Remove("Doors"); - } -} -``` -4. `(Optional)` If you choose to put your migrations into an extra project, -add the suffix `".MongoMigrations"` to the name and make sure it is referenced in the main project. By convention Mongo.Migration collects all .dlls named like that in your bin folder. - -Compile, run and enjoy! - -## How to use document migration - -With version 3.0.0 of Mongo.Migration I added the possibility to run migrations on StartUp. -In order to keep the core of Mongo.Migration in focus, it is still possible to run migrations at runtime (on-the-fly). -In addition, there is now the option of executing migrations at the start of the application. - -#### Document migration at runtime - -See `Quick Start ` - -#### Document migration on startup - -If you want to run migrations on StartUp, the only thing you have to do is add the attribute `CollectionLocation`. -Now all migrations you add for a `IDocument` will be executed at StartUp. - -```csharp - [CollectionLocation("Car", "TestCars")] - public class Car : IDocument - { - public ObjectId Id { get; set; } - - public string Type { get; set; } - - public int Doors { get; set; } - - public DocumentVersion Version { get; set; } - } -``` - -Additionally you can fix the version of the document with `StartUpVersion` - -```csharp - [StartUpVersion("0.1.1")] - [CollectionLocation("Car", "TestCars")] - public class Car : IDocument - { - public ObjectId Id { get; set; } - - public string Type { get; set; } - - public int Doors { get; set; } - - public DocumentVersion Version { get; set; } - } -``` - -`**PLEASE NOTE**` -Mongo.Migration uses the IStartUpFilter for .net core. -Maybe you want to read this [article](https://andrewlock.net/running-async-tasks-on-app-startup-in-asp-net-core-part-1/), to check if there is a better option to migrate with Mongo.Migration at StartUp. - -#### Document migration on startup and at runtime - -This is an example how you can use both. -At startup the version will be 0.0.1 and at runtime, when a document will be deserialized the version will be migrated to 0.1.1 - -```csharp - [RuntimeVersion("0.1.1")] - [StartUpVersion("0.0.1")] - [CollectionLocation("Car", "TestCars")] - public class Car : IDocument - { - public ObjectId Id { get; set; } - - public string Type { get; set; } - - public int Doors { get; set; } - - public DocumentVersion Version { get; set; } - } -``` - - -### Annotations - -#### RuntimeVersion -Add `RuntimeVersion` attribute to mark the current version of the document. So you have the possibility to downgrade in case of a rollback. -If you do not set the `RuntimeVersion`, all migrations will be applied. - -```csharp -[RuntimeVersion("0.0.1")] -public class Car : IDocument -... -``` -#### CollectionLocation -Add `CollectionLocation` attribute if you want to migrate your collections at startup. This attribute tells Mongo.Migration where to find your Collections. - -```csharp -[CollectionLocation("Car", "TestCars")] -public class Car : IDocument -... -``` -#### StartUpVersion -Add `StartUpVersion` attribute to set the version you want to migrate to at startup. This attribute limits the migrations to be performed on startup - -```csharp -[StartUpVersion("0.0.1")] -public class Car : IDocument -... -``` - -# Database migrations quick start - -#### .Net Framework -1. Initialize `MongoMigration` behind the `MongoClient`. ([Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)) - -```csharp -// Init MongoDB -var runner = MongoDbRunner.Start(); // Mongo2Go -var client = new MongoClient(runner.ConnectionString); - -// Init MongoMigration - MongoMigrationClient.Initialize( - client, - new MongoMigrationSettings() - { - ConnectionString = runner.ConnectionString, - Database = "TestCars" - }, - new LightInjectAdapter(new LightInject.ServiceContainer())) -``` - -#### .Net Core - -1.1 Add `MongoMigration` with the StartupFilter (`IMongoClient` has to be registered at the DI-container before) - -```csharp -public void ConfigureServices(IServiceCollection services) -{ - services.AddMvc(); - - _client = new MongoClient( _configuration.GetSection("MongoDb:ConnectionString").Value); - - services.AddSingleton(_client); - - services.AddMigration(new MongoMigrationSettings() - { - ConnectionString = runner.ConnectionString, - Database = "TestCars" - }); -} - -``` -1.2 Add `MongoMigration` with the StartupFilter add connection setting to use separate client -```csharp -public void ConfigureServices(IServiceCollection services) -{ - services.AddMvc(); - - services.AddMigration(new MongoMigrationSettings - { - ConnectionString = _configuration.GetSection("MongoDb:ConnectionString").Value, - Database = _configuration.GetSection("MongoDb:Database").Value, - DatabaseMigrationVersion = new DocumentVersion(2,0,0) // Optional - }); -} -``` - - -2. Create a migration by extending the abstract class `DatabaseMigration`. Best practice for the version is to use [Semantic Versioning](http://semver.org/) but ultimately it is up to you. You could simply use the patch version to count the number of migrations. All database migrations you add for a database will be executed at StartUp. - -```csharp - public class M100_AddNewCar : DatabaseMigration - { - public M100_AddNewCar() - : base("1.0.0") - { - } - - public override void Up(IMongoDatabase db) - { - var collection = db.GetCollection("Car"); - collection.InsertOne(new Car - { - Doors = 123, - Type = "AddedInMigration" - }); - } - - public override void Down(IMongoDatabase db) - { - var collection = db.GetCollection("Car"); - collection.DeleteOne(Builders.Filter.Eq(c => c.Type, "AddedInMigration")); - } - } -``` - -### Annotations -#### CollectionLocation -`CollectionLocation` attribute if you not specify database in MongoMigrationSettings then database from attribure will be use at startup. - -#### Setup current database version -Database will downgrade to current version using database migrations -```csharp -new MongoMigrationSettings() -{ - ... - DatabaseMigrationVersion = new DocumentVersion(2,0,0) -} -``` - -## Dependency injection -With the latest update (3.0.94) I added a requested feature to `Mongo.Migration`. `Migration` can be injected with dependencies from now on. - -#### .NetCore -It is pitty simple with .NetCore. `Mongo.Migration` uses the `IServiceProvider` to resolve all used dependencies. So you have access to all registered dependencies. - - -#### .Net Framework -When you initialize `Mongo.Migration` you can now add a `IContainerAdapter`. At the moment following Containers can be used out of the box: -- LightInject -- .NetCore ServiceProvider -- ... more planned in the future. - -If you use an other Container, you have to implement the interface yourself. As an example, see `LightInjectAdapter`. - -When that is done, you can pass the Adapter as a parameter to initialize `Mongo.Migration`. - -```csharp - // Your Container - var conatiner = ServiceContainer() - ontainer.Register(); - - // Init MongoDB - var runner = MongoDbRunner.Start(); // Mongo2Go - var client = new MongoClient(runner.ConnectionString); - - // Your Adapter implementation to abstract the container - var adapter = new LightInjectAdapter(container) - - // Init MongoMigration - MongoMigrationClient.Initialize(client, adapter); -``` - -```csharp - public class M001_RenameDorsToDoors : DocumentMigration - { - private readonly IYourDependency _service; - - public M001_RenameDorsToDoors(IYourDependency service) - : base("0.0.1") - { - _service = service; - } - - ... -``` - -## Document migrations demo - -Inside of the repository you can find a [Mongo.Migration.Demo]( https://github.com/SRoddis/Mongo.Migration/tree/master/Mongo.Migration.Demo) which is a simple demo to show how to use Mongo.Migration. - -1. Compile and run the demo application. -2. Now you should see the following output in the console. - -```bash - Migrate from: - { "_id" : ObjectId("59624d5beb5bb330386cd859"), "Dors" : 3, "Type" : "Cabrio", "UnnecessaryField" : "" } - - { "_id" : ObjectId("59624d5beb5bb330386cd85a"), "Dors" : 5, "Type" : "Combi", "UnnecessaryField" : "" } - - { "_id" : ObjectId("59624d5beb5bb330386cd85b"), "Doors" : 3, "Type" : "Truck", "UnnecessaryField" : "", "Version" : "0.0.1" } - - { "_id" : ObjectId("59624d5beb5bb330386cd85c"), "Doors" : 5, "Type" : "Van", "Version" : "0.1.1" } - - To: - { "_id" : ObjectId("59624d5beb5bb330386cd859"), "Type" : "Cabrio", "Doors" : 3, "Version" : "0.1.1" } - - { "_id" : ObjectId("59624d5beb5bb330386cd85a"), "Type" : "Combi", "Doors" : 5, "Version" : "0.1.1" } - - { "_id" : ObjectId("59624d5beb5bb330386cd85b"), "Type" : "Truck", "Doors" : 3, "Version" : "0.1.1" } - - { "_id" : ObjectId("59624d5beb5bb330386cd85c"), "Type" : "Van", "Doors" : 5, "Version" : "0.1.1" } - - New Car was created with version: 0.1.1 - - Press any Key to exit... -``` - -3. `(Optional)` Run [Mongo.Migration.Demo.Performance.Console]( https://github.com/SRoddis/Mongo.Migration/tree/master/Mongo.Migration.Demo.Performance.Console) - - -## Database migrations demo - -Inside of the repository you can find a [Mongo.Migration.Database.Demo]( https://github.com/SRoddis/Mongo.Migration/tree/master/Mongo.Migration.Database.Demo) which is a simple demo to show how to use database Mongo.Migration. - -1. Compile and run the demo application. -2. Now you should see the following output in the console. - -```bash -Apply database migrations: - - -{ "_id" : ObjectId("5ead80b68f9c4c402c35ebb9"), "MigrationId" : "Mongo.Migration.Database.Demo.Migrations.M100_AddNewCar", "Version" : "1.0.0" } - -{ "_id" : ObjectId("5ead80b68f9c4c402c35ebba"), "MigrationId" : "Mongo.Migration.Database.Demo.Migrations.M200_UpdateNewCar", "Version" : "2.0.0" } - -{ "_id" : ObjectId("5ead80b78f9c4c402c35ebbb"), "MigrationId" : "Mongo.Migration.Database.Demo.Migrations.M300_AddSparePartsCollection", "Version" : "3.0.0" } - -New Car was added and updated in database migrations: -{ "_id" : ObjectId("5ead80b68f9c4c402c35ebb8"), "Type" : "AddedInMigration", "Doors" : 222 } - -New collection was added in database: -SparePart - - - -Press any Key to exit... -``` - - -## Suggestions - -Deploy the migrations in a separate artifact. Otherwise you lose the ability to downgrade in case of a rollback. - -## Performance - -The performance is measured on every push to the repository with a small performance-test. It measures the time MongoDB needs to insert and read `n documents` (5000) with and without Mongo.Migration. The difference is asserted and should be not higher than a given tolerance (150ms). - -Example output of the automated test: -```bash -MongoDB: 73ms, Mongo.Migration: 168ms, Diff: 95ms (Tolerance: 150ms), Documents: 5000, Migrations per Document: 2 - -MongoDB: 88ms, Mongo.Migration: 109ms, Diff: 21ms (Tolerance: 70ms), Documents: 1500, Migrations per Document: 2 - -MongoDB: 62ms, Mongo.Migration: 63ms, Diff: 1ms (Tolerance: 40ms), Documents: 100, Migrations per Document: 2 - -MongoDB: 48ms, Mongo.Migration: 50ms, Diff: 2ms (Tolerance: 10ms), Documents: 10, Migrations per Document: 2 - -``` - -After bigger changes the code is analyzed with profiling tools to check for performance or memory problems. - -## Next Feature/Todo - - 1. Intercept updates, aggregation pipeline and projections. - -## Copyright - -Copyright © 2018 Sean Roddis - -## License - -Mongo.Migration is licensed under [MIT](http://www.opensource.org/licenses/mit-license.php "Read more about the MIT license form"). Refer to license.txt for more information. diff --git a/global.json b/global.json new file mode 100644 index 0000000..11fd6aa --- /dev/null +++ b/global.json @@ -0,0 +1,9 @@ +{ + "sdk": { + "version": "10.0.101", + "rollForward": "latestMajor" + }, + "test": { + "runner": "Microsoft.Testing.Platform" + } +} \ No newline at end of file