Skip to content

Commit

Permalink
refactor cosmos emulator class and add xunit fixtures
Browse files Browse the repository at this point in the history
  • Loading branch information
Confusingboat committed Feb 15, 2024
1 parent 14a5950 commit dd95641
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 61 deletions.
53 changes: 53 additions & 0 deletions src/Ephemerally.Azure.Cosmos.Xunit/CosmosClientFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Microsoft.Azure.Cosmos;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
using Xunit;

namespace Ephemerally.Azure.Cosmos.Xunit;

[SuppressMessage("ReSharper", "UseConfigureAwaitFalse")]
public class CosmosClientFixture :
IAsyncDisposable,
IAsyncLifetime
{
private readonly Lazy<Task<CosmosClient>> _client;

public CosmosClient Client => _client.Value.Result;

protected Task<CosmosClient> GetClient() => _client.Value;

public CosmosClientFixture()
{
_client = new(CreateClientAsync);
}

protected virtual Task<CosmosClient> CreateClientAsync() => Task.FromResult(CosmosEmulator.GetClient());

public virtual Task InitializeAsync() => _client.Value;

public virtual async Task DisposeAsync()
{
if (!_client.IsValueCreated) return;

await IgnoreSocketException(async () =>
{
var client = await GetClient();
client.Dispose();
});
}

async ValueTask IAsyncDisposable.DisposeAsync() =>
await ((IAsyncLifetime)this).DisposeAsync();

protected static async Task IgnoreSocketException(Func<Task> action)
{
try
{
await action();
}
catch (HttpRequestException ex) when (ex.InnerException is SocketException { SocketErrorCode: SocketError.ConnectionRefused })
{
// If the emulator or instance is not accessible, we don't need to do anything
}
}
}
27 changes: 27 additions & 0 deletions src/Ephemerally.Azure.Cosmos.Xunit/CosmosEmulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.Azure.Cosmos;

namespace Ephemerally.Azure.Cosmos.Xunit;

public static class CosmosEmulator
{
public static CosmosClient GetClient(Action<CosmosClientOptions> configureOptions = null)
{
var options = new CosmosClientOptions
{
RequestTimeout = TimeSpan.FromSeconds(30),
ServerCertificateCustomValidationCallback = (_, _, _) => true,
ConnectionMode = ConnectionMode.Gateway,
LimitToEndpoint = true
};
configureOptions?.Invoke(options);
return new(
AccountEndpoint,
AuthKey,
options);
}

public const string
AccountEndpoint = "https://localhost:8081",
AuthKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
ConnectionString = $"AccountEndpoint={AccountEndpoint};AccountKey={AuthKey};";
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
using Microsoft.Azure.Cosmos;
using Xunit;
using System.Diagnostics.CodeAnalysis;

namespace Ephemerally.Azure.Cosmos.Xunit;

internal abstract class EphemeralCosmosContainerFixture :
IAsyncDisposable,
IAsyncLifetime
[SuppressMessage("ReSharper", "UseConfigureAwaitFalse")]
public class EphemeralCosmosContainerFixture : EphemeralCosmosDatabaseFixture
{
private Lazy<ValueTask<EphemeralCosmosContainer>> _container;
private readonly Lazy<Task<EphemeralCosmosContainer>> _container;

protected EphemeralCosmosContainerFixture(
Database database,
EphemeralCreationOptions options)
public EphemeralCosmosContainer Container => _container.Value.Result;

protected Task<EphemeralCosmosContainer> GetContainer() => _container.Value;

public EphemeralCosmosContainerFixture()
{
_container = new(CreateContainerAsync);
}

protected virtual async Task<EphemeralCosmosContainer> CreateContainerAsync()
{
_container = new(async () => await database.CreateEphemeralContainerAsync(options).ConfigureAwait(false));
var db = await GetDatabase();
return await db.CreateEphemeralContainerAsync();
}

async Task IAsyncLifetime.InitializeAsync() =>
await _container.Value.ConfigureAwait(false);
public override async Task InitializeAsync()
{
await base.InitializeAsync();
await _container.Value;
}

async Task IAsyncLifetime.DisposeAsync() =>
await ((IAsyncDisposable)this).DisposeAsync().ConfigureAwait(false);
public override async Task DisposeAsync()
{
if (!_container.IsValueCreated) return;

ValueTask IAsyncDisposable.DisposeAsync() =>
_container.Value.Result.DisposeAsync();
}
await IgnoreSocketException(async () =>
{
var container = await GetContainer();
await container.DisposeAsync();
});

await base.DisposeAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Diagnostics.CodeAnalysis;

namespace Ephemerally.Azure.Cosmos.Xunit;

[SuppressMessage("ReSharper", "UseConfigureAwaitFalse")]
public class EphemeralCosmosDatabaseFixture : CosmosClientFixture
{
private readonly Lazy<Task<EphemeralCosmosDatabase>> _database;

public EphemeralCosmosDatabase Database => _database.Value.Result;

protected Task<EphemeralCosmosDatabase> GetDatabase() => _database.Value;

public EphemeralCosmosDatabaseFixture()
{
_database = new(CreateDatabaseAsync);
}

protected virtual async Task<EphemeralCosmosDatabase> CreateDatabaseAsync()
{
var client = await GetClient();
return await client.CreateEphemeralDatabaseAsync();
}

public override async Task InitializeAsync()
{
await base.InitializeAsync();
await _database.Value;
}

public override async Task DisposeAsync()
{
if (!_database.IsValueCreated) return;

await IgnoreSocketException(async () =>
{
var db = await GetDatabase();
await db.DisposeAsync();
});

await base.DisposeAsync();
}
}
15 changes: 0 additions & 15 deletions tests/Ephemerally.Azure.Cosmos.Tests/CosmosEmulator.cs

This file was deleted.

10 changes: 5 additions & 5 deletions tests/Ephemerally.Azure.Cosmos.Tests/CosmosEmulatorFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Ephemerally.Azure.Cosmos.Tests;
using Ephemerally.Azure.Cosmos.Xunit;

namespace Ephemerally.Azure.Cosmos.Tests;

[SetUpFixture]
[FixtureLifeCycle(LifeCycle.SingleInstance)]
Expand All @@ -7,9 +9,7 @@ public class CosmosEmulatorFixture
[OneTimeSetUp]
public static async Task OneTimeSetUp()
{
await CosmosEmulator.Client.ConnectOrThrowAsync();
using var client = CosmosEmulator.GetClient();
await client.ConnectOrThrowAsync();
}

[OneTimeTearDown]
public static void OneTimeTearDown() => CosmosEmulator.Client.Dispose();
}
23 changes: 12 additions & 11 deletions tests/Ephemerally.Azure.Cosmos.Tests/EphemeralContainerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Azure.Cosmos;
using Ephemerally.Azure.Cosmos.Xunit;
using Microsoft.Azure.Cosmos;

namespace Ephemerally.Azure.Cosmos.Tests;

Expand All @@ -7,7 +8,7 @@ public class EphemeralContainerTests
[Test]
public async Task Should_create_container_and_tear_it_down()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();
var sut = await db.CreateEphemeralContainerAsync();

Expand All @@ -21,7 +22,7 @@ public async Task Should_create_container_and_tear_it_down()
[Test]
public async Task User_supplied_container_should_be_torn_down()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();

var userSuppliedContainer = (await db.CreateContainerAsync("user-supplied-container", "/id")).Container;
Expand All @@ -38,7 +39,7 @@ public async Task User_supplied_container_should_be_torn_down()
[Test]
public async Task CleanupBehavior_SelfAndExpired_should_remove_self_and_expired_orphaned_container()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();

var orphanContainer = await db.CreateEphemeralContainerAsync(new EphemeralCreationOptions(DateTimeOffset.MinValue));
Expand All @@ -58,7 +59,7 @@ public async Task CleanupBehavior_SelfAndExpired_should_remove_self_and_expired_
[Test]
public async Task CleanupBehavior_SelfOnly_should_remove_self_and_not_remove_unexpired_orphaned_container()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();
var orphanContainer = await db.CreateEphemeralContainerAsync(new EphemeralCreationOptions(DateTimeOffset.MaxValue));

Expand All @@ -77,7 +78,7 @@ public async Task CleanupBehavior_SelfOnly_should_remove_self_and_not_remove_une
[Test]
public async Task CleanupBehavior_SelfOnly_should_remove_self_and_not_remove_expired_orphaned_container()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();
var orphanContainer = await db.CreateEphemeralContainerAsync(new EphemeralCreationOptions(DateTimeOffset.MinValue));

Expand All @@ -96,7 +97,7 @@ public async Task CleanupBehavior_SelfOnly_should_remove_self_and_not_remove_exp
[Test]
public async Task CleanupBehavior_NoCleanup_should_not_remove_anything()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();
var orphanContainer = await db.CreateEphemeralContainerAsync(new EphemeralCreationOptions(DateTimeOffset.MinValue));

Expand All @@ -117,7 +118,7 @@ public async Task Should_create_container_when_container_properties_Id_is_provid
{
const string userSuppliedId = "user-supplied-id";

var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();

await using var sut = await db.CreateEphemeralContainerAsync(containerProperties: new ContainerProperties { Id = userSuppliedId });
Expand All @@ -129,7 +130,7 @@ public async Task Should_create_container_when_container_properties_Id_is_provid
[Test]
public async Task Should_create_container_when_container_properties_Id_is_not_provided()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();

await using var sut = await db.CreateEphemeralContainerAsync(containerProperties: new());
Expand All @@ -142,7 +143,7 @@ public async Task Should_create_container_when_container_properties_PartitionKey
{
const string userSuppliedKey = "/userSuppliedKey";

var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();

await using var sut = await db.CreateEphemeralContainerAsync(containerProperties: new ContainerProperties { PartitionKeyPath = userSuppliedKey });
Expand All @@ -153,7 +154,7 @@ public async Task Should_create_container_when_container_properties_PartitionKey
[Test]
public async Task Should_create_container_when_container_properties_PartitionKeyPath_is_not_provided()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var db = await client.CreateEphemeralDatabaseAsync();

await using var sut = await db.CreateEphemeralContainerAsync(containerProperties: new());
Expand Down
14 changes: 8 additions & 6 deletions tests/Ephemerally.Azure.Cosmos.Tests/EphemeralDatabaseTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Ephemerally.Azure.Cosmos.Xunit;

namespace Ephemerally.Azure.Cosmos.Tests;

public class EphemeralDatabaseTests
{
[Test]
public async Task Should_create_database_and_tear_it_down()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
var sut = await client.CreateEphemeralDatabaseAsync();

Assert.That(await sut.ExistsAsync(), Is.True);
Expand All @@ -18,7 +20,7 @@ public async Task Should_create_database_and_tear_it_down()
[Test]
public async Task User_supplied_database_should_be_torn_down()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();

var userSuppliedDatabase = (await client.CreateDatabaseAsync("user-supplied-database")).Database;

Expand All @@ -34,7 +36,7 @@ public async Task User_supplied_database_should_be_torn_down()
[Test]
public async Task CleanupBehavior_SelfAndExpired_should_remove_self_and_expired_orphaned_database()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
var orphanDb = await client.CreateEphemeralDatabaseAsync(new EphemeralCreationOptions(TimeSpan.Zero));

Assert.That(await orphanDb.ExistsAsync(), Is.True);
Expand All @@ -52,7 +54,7 @@ public async Task CleanupBehavior_SelfAndExpired_should_remove_self_and_expired_
[Test]
public async Task CleanupBehavior_SelfAndExpired_should_remove_self_and_not_remove_unexpired_orphaned_database()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var orphanDb = await client.CreateEphemeralDatabaseAsync();

Assert.That(await orphanDb.ExistsAsync(), Is.True);
Expand All @@ -70,7 +72,7 @@ public async Task CleanupBehavior_SelfAndExpired_should_remove_self_and_not_remo
[Test]
public async Task CleanupBehavior_SelfOnly_should_remove_self_only_and_not_remove_expired_orphaned_database()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var orphanDb = await client.CreateEphemeralDatabaseAsync(new EphemeralCreationOptions(TimeSpan.Zero));

Assert.That(await orphanDb.ExistsAsync(), Is.True);
Expand All @@ -88,7 +90,7 @@ public async Task CleanupBehavior_SelfOnly_should_remove_self_only_and_not_remov
[Test]
public async Task CleanupBehavior_NoCleanup_should_not_remove_anything()
{
var client = CosmosEmulator.Client;
using var client = CosmosEmulator.GetClient();
await using var orphanDb = await client.CreateEphemeralDatabaseAsync(new EphemeralCreationOptions(TimeSpan.Zero));

Assert.That(await orphanDb.ExistsAsync(), Is.True);
Expand Down
Loading

0 comments on commit dd95641

Please sign in to comment.