File: MongoDBReplCommandTests.cs
Web Access
Project: src\tests\Aspire.Hosting.MongoDB.Tests\Aspire.Hosting.MongoDB.Tests.csproj (Aspire.Hosting.MongoDB.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.ApplicationModel;
using Aspire.Hosting.Tests;
using Aspire.Hosting.Utils;
using Aspire.TestUtilities;
 
#pragma warning disable ASPIRECERTIFICATES001
#pragma warning disable ASPIREMONGODB001
#pragma warning disable ASPIRETERMINAL001
 
namespace Aspire.Hosting.MongoDB.Tests;
 
public class MongoDBReplCommandTests(ITestOutputHelper outputHelper) : ContainerReplCommandTestBase
{
    protected override IResourceBuilder<ContainerResource> AddContainer(IDistributedApplicationBuilder builder, bool enableRepl)
    {
        var resource = builder.AddMongoDB("mongo");
        if (enableRepl)
        {
            Assert.Same(resource, resource.WithRepl());
        }
 
        return resource;
    }
 
    [Fact]
    public void WithReplRejectsNullBuilder()
    {
        var exception = Assert.Throws<ArgumentNullException>(() => MongoDBBuilderExtensions.WithRepl(null!));
 
        Assert.Equal("builder", exception.ParamName);
    }
 
    [Theory]
    [InlineData(null, "admin", false, false)]
    [InlineData("user with spaces", "user%20with%20spaces", true, false)]
    [InlineData("user@:/", "user%40%3A%2F", false, true)]
    public async Task ReplUsesConfiguredCredentialsAndDirectContainerEndpoint(
        string? username, string encodedUsername, bool tlsEnabled, bool replicaSet)
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var user = username is null ? null : builder.AddParameter("username", username);
        var password = builder.AddParameter("password", "quotes'\" $; spaces", secret: true);
        var mongo = builder.AddMongoDB("mongo", port: 37017, userName: user, password: password)
            .WithEndpoint("tcp", endpoint => endpoint.TlsEnabled = tlsEnabled);
        if (replicaSet)
        {
            mongo.WithReplicaSet();
        }
 
        var options = await MongoDBBuilderExtensions.CreateReplOptionsAsync(mongo.Resource, TestContext.Current.CancellationToken);
        var execOptions = ContainerReplCommand.CreateExecOptions(options, "docker", "container-id");
 
        Assert.Equal("mongosh (mongo)", execOptions.Title);
        Assert.Equal(["exec", "-it", "--env", "ASPIRE_MONGODB_REPL_CONNECTION_STRING", "container-id",
            "mongosh", "--quiet", "--nodb", "--shell", "--eval", """
            const uri = process.env.ASPIRE_MONGODB_REPL_CONNECTION_STRING;
            const ca = process.env.ASPIRE_MONGODB_REPL_CA_FILE;
            db = new Mongo(ca ? `${uri}&tlsCAFile=${encodeURIComponent(ca)}` : uri).getDB("admin");
            """], execOptions.Arguments);
        Assert.Collection(execOptions.EnvironmentVariables, variable =>
        {
            Assert.Equal("ASPIRE_MONGODB_REPL_CONNECTION_STRING", variable.Key);
            Assert.Equal($"mongodb://{encodedUsername}:quotes%27%22%20%24%3B%20spaces@localhost:27017/admin?directConnection=true&authSource=admin&authMechanism=SCRAM-SHA-256{(tlsEnabled ? "&tls=true" : "")}",
                variable.Value);
        });
    }
 
    [Fact]
    public async Task ReplSupportsPasswordlessMongoDB()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var mongo = builder.AddResource(new MongoDBServerResource("mongo"))
            .WithEndpoint(targetPort: 27018, name: "tcp");
 
        var options = await MongoDBBuilderExtensions.CreateReplOptionsAsync(mongo.Resource, TestContext.Current.CancellationToken);
 
        Assert.Collection(options.EnvironmentVariables, variable =>
        {
            Assert.Equal("ASPIRE_MONGODB_REPL_CONNECTION_STRING", variable.Key);
            Assert.Equal("mongodb://localhost:27018/admin?directConnection=true", variable.Value);
        });
    }
 
    [Theory]
    [InlineData("", "password")]
    [InlineData("username", "")]
    public async Task ReplRejectsUnavailableCredentials(string username, string password)
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var mongo = builder.AddMongoDB("mongo",
            userName: builder.AddParameter("username", username),
            password: builder.AddParameter("password", password, secret: true));
 
        var exception = await Assert.ThrowsAsync<DistributedApplicationException>(() =>
            MongoDBBuilderExtensions.CreateReplOptionsAsync(mongo.Resource, TestContext.Current.CancellationToken));
 
        Assert.Equal("The MongoDB REPL credentials are not available.", exception.Message);
    }
 
    [Theory]
    [InlineData(false, "/usr/lib/ssl/aspire/cert.pem", DistributedApplicationOperation.Run)]
    [InlineData(true, "/usr/lib/ssl/aspire/cert.pem", DistributedApplicationOperation.Run)]
    [InlineData(true, "/custom certificates/cert.pem", DistributedApplicationOperation.Run)]
    [InlineData(true, "/custom certificates/cert.pem", DistributedApplicationOperation.Publish)]
    public async Task ReplInheritsConfiguredCertificateTrustBundle(bool tlsEnabled, string bundlePath, DistributedApplicationOperation operation)
    {
        using var builder = TestDistributedApplicationBuilder.Create(operation);
        var mongo = builder.AddMongoDB("mongo")
            .WithEndpoint("tcp", endpoint => endpoint.TlsEnabled = tlsEnabled);
        var context = new CertificateTrustConfigurationCallbackAnnotationContext
        {
            ExecutionContext = builder.ExecutionContext,
            Resource = mongo.Resource,
            Arguments = [],
            EnvironmentVariables = [],
            CertificateBundlePath = ReferenceExpression.Create($"{bundlePath}"),
            CertificateDirectoriesPath = ReferenceExpression.Create($"{"/certificates"}"),
            Scope = CertificateTrustScope.Append,
            CancellationToken = TestContext.Current.CancellationToken
        };
 
        var callback = Assert.Single(mongo.Resource.Annotations.OfType<CertificateTrustConfigurationCallbackAnnotation>());
        await callback.Callback(context);
 
        if (tlsEnabled)
        {
            Assert.Equal(["--tlsCAFile", context.CertificateBundlePath], context.Arguments);
            Assert.Equal(bundlePath, await context.CertificateBundlePath.GetValueAsync(TestContext.Current.CancellationToken));
        }
        else
        {
            Assert.Empty(context.Arguments);
        }
 
        if (tlsEnabled && operation == DistributedApplicationOperation.Run)
        {
            Assert.Collection(context.EnvironmentVariables, variable =>
            {
                Assert.Equal("ASPIRE_MONGODB_REPL_CA_FILE", variable.Key);
                Assert.Same(context.CertificateBundlePath, variable.Value);
            });
        }
        else
        {
            Assert.Empty(context.EnvironmentVariables);
        }
    }
 
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    [RequiresFeature(TestFeature.ContainerRuntime)]
    public async Task ReplExecutesAuthenticatedQuery(bool replicaSet)
    {
        using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper);
        var username = builder.AddParameter("username", "repl-user");
        var password = builder.AddParameter("password", "repl-p@ss$word", secret: true);
        var mongo = builder.AddMongoDB("mongo", userName: username, password: password).WithRepl().WithoutHttpsCertificate();
        if (replicaSet)
        {
            mongo.WithReplicaSet();
        }
 
        await using var app = builder.Build();
 
        await VerifyReplAsync(app, mongo.Resource, "mongosh (mongo)",
            "admin>", """print('authenticated-' + db.runCommand({ connectionStatus: 1 }).authInfo.authenticatedUsers[0].user)""" + "\r",
            "authenticated-repl-user");
    }
 
    [Theory]
    [InlineData(null)]
    [InlineData("/usr/local/share/aspire-repl-ca")]
    [RequiresFeature(TestFeature.Docker)]
    [RequiresFeature(TestFeature.DevCert)]
    public async Task ReplExecutesAuthenticatedQueryWithTls(string? certificateDirectory)
    {
        using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper);
        var mongo = builder.AddMongoDB("mongo").WithRepl().WithHttpsDeveloperCertificate();
        if (certificateDirectory is not null)
        {
            mongo.WithContainerCertificatePaths(customCertificatesDestination: certificateDirectory);
        }
 
        await using var app = builder.Build();
 
        await VerifyReplAsync(app, mongo.Resource, "mongosh (mongo)",
            "admin>", """print('authenticated-' + db.runCommand({ connectionStatus: 1 }).authInfo.authenticatedUsers[0].user)""" + "\r",
            "authenticated-admin");
        Assert.True(mongo.Resource.TlsEnabled);
    }
}