File: EFMigrationConfigurationTests.cs
Web Access
Project: src\tests\Aspire.Hosting.EntityFrameworkCore.Tests\Aspire.Hosting.EntityFrameworkCore.Tests.csproj (Aspire.Hosting.EntityFrameworkCore.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.Utils;
using Aspire.Hosting.ApplicationModel;
 
namespace Aspire.Hosting.EntityFrameworkCore.Tests;
 
public class EFMigrationConfigurationTests
{
    [Fact]
    public void RunDatabaseUpdateOnStartRegistersEventSubscription()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .RunDatabaseUpdateOnStart();
 
        // The resource should have the migrations applied at startup
        // Event subscription is internal, so we just verify the call doesn't throw
        Assert.NotNull(migrations);
    }
 
    [Fact]
    public void PublishAsMigrationScriptSetsOption()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .PublishAsMigrationScript();
 
        Assert.True(migrations.Resource.PublishAsMigrationScript);
    }
 
    [Fact]
    public void PublishAsMigrationBundleSetsOption()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .PublishAsMigrationBundle();
 
        Assert.True(migrations.Resource.PublishAsMigrationBundle);
    }
 
    [Fact]
    public void WithMigrationOutputDirectorySetsOption()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .WithMigrationOutputDirectory("Data/Migrations");
 
        Assert.Equal("Data/Migrations", migrations.Resource.MigrationOutputDirectory);
    }
 
    [Fact]
    public void WithMigrationNamespaceSetsOption()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .WithMigrationNamespace("MyApp.Data.Migrations");
 
        Assert.Equal("MyApp.Data.Migrations", migrations.Resource.MigrationNamespace);
    }
 
    [Fact]
    public void WithMigrationOutputDirectoryThrowsForEmptyString()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!);
 
        Assert.Throws<ArgumentException>(() => migrations.WithMigrationOutputDirectory(""));
    }
 
    [Fact]
    public void WithMigrationNamespaceThrowsForEmptyString()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!);
 
        Assert.Throws<ArgumentException>(() => migrations.WithMigrationNamespace(""));
    }
 
    [Fact]
    public void MultipleConfigurationOptionsCanBeChained()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .RunDatabaseUpdateOnStart()
            .PublishAsMigrationScript();
 
        Assert.True(migrations.Resource.PublishAsMigrationScript);
    }
 
    [Fact]
    public void AllConfigurationOptionsCanBeChained()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .RunDatabaseUpdateOnStart()
            .PublishAsMigrationScript()
            .PublishAsMigrationBundle()
            .WithMigrationOutputDirectory("CustomDir")
            .WithMigrationNamespace("MyApp.Migrations");
 
        Assert.True(migrations.Resource.PublishAsMigrationScript);
        Assert.True(migrations.Resource.PublishAsMigrationBundle);
        Assert.Equal("CustomDir", migrations.Resource.MigrationOutputDirectory);
        Assert.Equal("MyApp.Migrations", migrations.Resource.MigrationNamespace);
    }
 
    [Fact]
    public void ConfigurationMethodsPreserveContextTypeName()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .RunDatabaseUpdateOnStart()
            .PublishAsMigrationScript()
            .PublishAsMigrationBundle();
 
        // The context type name should be preserved through chaining
        Assert.Equal(typeof(TestDbContext).FullName, migrations.Resource.DbContextTypeName);
    }
 
    [Fact]
    public void ConfigurationMethodsReturnSameBuilderType()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!);
        
        var afterUpdate = migrations.RunDatabaseUpdateOnStart();
        var afterScript = afterUpdate.PublishAsMigrationScript();
        var afterBundle = afterScript.PublishAsMigrationBundle();
        var afterOutputDir = afterBundle.WithMigrationOutputDirectory("Migrations");
        var afterNamespace = afterOutputDir.WithMigrationNamespace("MyApp.Migrations");
 
        // All methods should return IResourceBuilder<EFMigrationResource> for proper chaining
        Assert.IsAssignableFrom<IResourceBuilder<EFMigrationResource>>(afterUpdate);
        Assert.IsAssignableFrom<IResourceBuilder<EFMigrationResource>>(afterScript);
        Assert.IsAssignableFrom<IResourceBuilder<EFMigrationResource>>(afterBundle);
        Assert.IsAssignableFrom<IResourceBuilder<EFMigrationResource>>(afterOutputDir);
        Assert.IsAssignableFrom<IResourceBuilder<EFMigrationResource>>(afterNamespace);
    }
 
    [Fact]
    public void OptionsInitiallyFalseOrNull()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!);
 
        // Options should all be false/null initially
        Assert.False(migrations.Resource.PublishAsMigrationScript);
        Assert.False(migrations.Resource.PublishAsMigrationBundle);
        Assert.Null(migrations.Resource.MigrationOutputDirectory);
        Assert.Null(migrations.Resource.MigrationNamespace);
        Assert.Null(migrations.Resource.MigrationsProjectPath);
    }
 
    [Fact]
    public void WithMigrationsProjectWithPathSetsOption()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var startupProject = builder.AddProject<Projects.ServiceA>("startup");
        var migrations = startupProject.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .WithMigrationsProject("path/to/Target.csproj");
 
        Assert.NotNull(migrations.Resource.MigrationsProjectPath);
        // Path gets combined with AppHostDirectory and normalized
        Assert.EndsWith("Target.csproj", migrations.Resource.MigrationsProjectPath);
    }
 
    [Fact]
    public void WithMigrationsProjectThrowsForNull()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!);
 
        Assert.Throws<ArgumentNullException>(() => migrations.WithMigrationsProject(null!));
    }
 
    [Fact]
    public void WithMigrationsProjectThrowsForEmpty()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!);
 
        Assert.Throws<ArgumentException>(() => migrations.WithMigrationsProject(""));
    }
 
    [Fact]
    public void PublishAsMigrationScriptDefaultsIdempotentToTrue()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .PublishAsMigrationScript();
 
        Assert.True(migrations.Resource.PublishAsMigrationScript);
        Assert.True(migrations.Resource.ScriptIdempotent);
    }
 
    [Fact]
    public void PublishAsMigrationScriptCanOptOutOfIdempotentDefault()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .PublishAsMigrationScript(idempotent: false);
 
        Assert.True(migrations.Resource.PublishAsMigrationScript);
        Assert.False(migrations.Resource.ScriptIdempotent);
    }
 
    [Fact]
    public void PublishAsMigrationScriptSetsNoTransactionsProperty()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .PublishAsMigrationScript(noTransactions: true);
 
        Assert.True(migrations.Resource.PublishAsMigrationScript);
        Assert.True(migrations.Resource.ScriptNoTransactions);
    }
 
    [Fact]
    public void PublishAsMigrationBundleSetsTargetRuntimeProperty()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .PublishAsMigrationBundle(targetRuntime: "linux-x64");
 
        Assert.True(migrations.Resource.PublishAsMigrationBundle);
        Assert.Equal("linux-x64", migrations.Resource.BundleTargetRuntime);
    }
 
    [Fact]
    public void PublishAsMigrationBundleSetsSelfContainedProperty()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .PublishAsMigrationBundle(selfContained: true);
 
        Assert.True(migrations.Resource.PublishAsMigrationBundle);
        Assert.True(migrations.Resource.BundleSelfContained);
    }
 
    [Fact]
    public void PublishAsMigrationScriptAndBundleWithAllOptions()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddProject<Projects.ServiceA>("myproject");
        var migrations = project.AddEFMigrations("mymigrations", typeof(TestDbContext).FullName!)
            .PublishAsMigrationScript(idempotent: true, noTransactions: true)
            .PublishAsMigrationBundle(targetRuntime: "win-x64", selfContained: true);
 
        Assert.True(migrations.Resource.PublishAsMigrationScript);
        Assert.True(migrations.Resource.ScriptIdempotent);
        Assert.True(migrations.Resource.ScriptNoTransactions);
        Assert.True(migrations.Resource.PublishAsMigrationBundle);
        Assert.Equal("win-x64", migrations.Resource.BundleTargetRuntime);
        Assert.True(migrations.Resource.BundleSelfContained);
    }
 
    // Test classes for DbContext types
    private sealed class TestDbContext { }
}