Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(cosmos): deprecate unnecessary az cosmos nosql functionality #262

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions docs/preview/03-Features/04-Azure/04-Storage/02-cosmos.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,8 @@ await TemporaryNoSqlContainer.CreateIfNotExistsAsync(..., options =>

// Delete all NoSql items that matches any of the configured filters,
// upon the test fixture creation, when there was already a NoSql container available.
options.OnSetup.CleanMatchingItems(
NoSqlItemFilter.Where(ship => ship["BoatName"] == "The Alice"),
NoSqlItemFilter.ItemIdEqual("123"));
options.OnSetup.CleanMatchingItems((NoSqlItem item) => item.Id == "123");
options.OnSetup.CleanMatchingItems((Shipment item) => item.BoatName == "The Alice");

// Options related to when the test fixture is teared down.
// --------------------------------------------------------
Expand All @@ -207,9 +206,8 @@ await TemporaryNoSqlContainer.CreateIfNotExistsAsync(..., options =>
// Delete all NoSql items that matches any of the configured filters,
// upon the test fixture disposal, even if the test fixture didn't inserted them.
// ⚠️ NoSql items inserted by the test fixture will always be deleted, regardless of the configured filters.
options.OnTeardown.CleanMatchingItems(
NoSqlItemFilter.Where((Shipment s) => s.BoatName == "The Alice"),
NoSqlItemFilter.IdEqual("123"));
options.OnTeardown.CleanMatchingItems((NoSqlItem item) => item.Id == "123");
options.OnTeardown.CleanMatchingItems((Shipment item) => item.BoatName == "The Alice");
});

// `OnTeardown` is also still available after the temporary container is created:
Expand Down
182 changes: 167 additions & 15 deletions src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,55 @@
using Newtonsoft.Json.Linq;
using static Arcus.Testing.NoSqlExtraction;

#pragma warning disable CS0618, S1133 // Ignore obsolete warnings that we added ourselves, should be removed upon releasing v2.0.

namespace Arcus.Testing
{
/// <summary>
/// Represents the available options when the <see cref="TemporaryNoSqlContainer"/> is created.
/// </summary>
internal enum OnSetupNoSqlContainer { LeaveExisted = 0, CleanIfExisted, CleanIfMatched }

/// <summary>
/// Represents the available options when the <see cref="TemporaryNoSqlContainer"/> is deleted.
/// </summary>
internal enum OnTeardownNoSqlContainer { CleanIfCreated = 0, CleanAll, CleanIfMatched }

/// <summary>
/// Represents a generic dictionary-like type which defines an arbitrary set of properties on a NoSql item as key-value pairs.
/// </summary>
public class NoSqlItem : JObject
{
/// <summary>
/// Initializes a new instance of the <see cref="NoSqlItem" /> class.
/// </summary>
internal NoSqlItem(
string id,
PartitionKey partitionKey,
JObject properties)
: base(properties)
{
ArgumentNullException.ThrowIfNull(id);

Id = id;
PartitionKey = partitionKey;
}

/// <summary>
/// Gets the unique identifier to distinguish items.
/// </summary>
public string Id { get; }

/// <summary>
/// Gets the key to group items together in partitions.
/// </summary>
public PartitionKey PartitionKey { get; }
}

/// <summary>
/// Represents a filter to match against a stored NoSql item in the <see cref="TemporaryNoSqlContainer"/> upon setup or teardown.
/// </summary>
[Obsolete("Additional layer of abstraction will be removed in favor of using a predicate on a '" + nameof(NoSqlItem) + "' function directly")]
public class NoSqlItemFilter
{
private readonly Func<string, PartitionKey, JObject, CosmosClient, bool> _filter;
Expand Down Expand Up @@ -104,7 +138,7 @@ public static NoSqlItemFilter Where<TItem>(Func<TItem, bool> itemFilter)
}

using var body = new MemoryStream(Encoding.UTF8.GetBytes(json.ToString()));

var item = client.ClientOptions.Serializer.FromStream<TItem>(body);
if (item is null)
{
Expand Down Expand Up @@ -147,7 +181,8 @@ internal bool IsMatch(string itemId, PartitionKey partitionKey, JObject item, Co
/// </summary>
public class OnSetupNoSqlContainerOptions
{
private readonly List<NoSqlItemFilter> _filters = new();
private readonly List<NoSqlItemFilter> _typedFilters = new();
private readonly List<Func<NoSqlItem, bool>> _genericFilters = new();

/// <summary>
/// Gets the configurable setup option on what to do with existing NoSql items in the Azure NoSql container upon the test fixture creation.
Expand Down Expand Up @@ -183,6 +218,8 @@ public OnSetupNoSqlContainerOptions CleanAllItems()
/// <param name="filters">The filters to match NoSql items that should be removed.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="filters"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when one or more <paramref name="filters"/> is <c>null</c>.</exception>
[Obsolete("Use the other overload that takes in the predicate function on a '" + nameof(NoSqlItem) + "' directly, " +
"this overload will be removed in v2.0")]
public OnSetupNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter[] filters)
{
ArgumentNullException.ThrowIfNull(filters);
Expand All @@ -193,7 +230,58 @@ public OnSetupNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter[]
}

Items = OnSetupNoSqlContainer.CleanIfMatched;
_filters.AddRange(filters);
_typedFilters.AddRange(filters);

return this;
}

/// <summary>
/// Configures the <see cref="TemporaryNoSqlContainer"/> to delete the NoSql items
/// upon the test fixture creation that match any of the configured <paramref name="filters"/>.
/// </summary>
/// <remarks>
/// Multiple calls will be aggregated together in an OR expression.
/// </remarks>
/// <param name="filters">The filters to match NoSql items that should be removed.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="filters"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when one or more <paramref name="filters"/> is <c>null</c>.</exception>
public OnSetupNoSqlContainerOptions CleanMatchingItems(params Func<NoSqlItem, bool>[] filters)
{
ArgumentNullException.ThrowIfNull(filters);

if (Array.Exists(filters, f => f is null))
{
throw new ArgumentException("Requires all filters to be non-null", nameof(filters));
}

Items = OnSetupNoSqlContainer.CleanIfMatched;
_genericFilters.AddRange(filters);

return this;
}

/// <summary>
/// Configures the <see cref="TemporaryNoSqlContainer"/> to delete the NoSql items
/// upon the test fixture creation that match any of the configured <paramref name="filters"/>.
/// </summary>
/// <remarks>
/// Multiple calls will be aggregated together in an OR expression.
/// </remarks>
/// <typeparam name="TItem">The custom type of the NoSql item.</typeparam>
/// <param name="filters">The filters to match NoSql items that should be removed.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="filters"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when one or more <paramref name="filters"/> is <c>null</c>.</exception>
public OnSetupNoSqlContainerOptions CleanMatchingItems<TItem>(params Func<TItem, bool>[] filters)
{
ArgumentNullException.ThrowIfNull(filters);

if (Array.Exists(filters, f => f is null))
{
throw new ArgumentException("Requires all filters to be non-null", nameof(filters));
}

Items = OnSetupNoSqlContainer.CleanIfMatched;
_typedFilters.AddRange(filters.Select(NoSqlItemFilter.Where).ToArray());

return this;
}
Expand All @@ -203,7 +291,10 @@ public OnSetupNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter[]
/// </summary>
internal bool IsMatched(string itemId, PartitionKey partitionKey, JObject itemStream, CosmosClient client)
{
return _filters.Exists(filter => filter.IsMatch(itemId, partitionKey, itemStream, client));
var item = new NoSqlItem(itemId, partitionKey, itemStream);

return _typedFilters.Exists(filter => filter.IsMatch(itemId, partitionKey, itemStream, client))
|| _genericFilters.Exists(filter => filter(item));
}
}

Expand All @@ -212,7 +303,8 @@ internal bool IsMatched(string itemId, PartitionKey partitionKey, JObject itemSt
/// </summary>
public class OnTeardownNoSqlContainerOptions
{
private readonly List<NoSqlItemFilter> _filters = new();
private readonly List<NoSqlItemFilter> _typedFilters = new();
private readonly List<Func<NoSqlItem, bool>> _genericFilters = new();

/// <summary>
/// Gets the configurable setup option on what to do with existing NoSql items in the Azure NoSql container upon the test fixture deletion.
Expand Down Expand Up @@ -250,6 +342,8 @@ public OnTeardownNoSqlContainerOptions CleanAllItems()
/// <param name="filters">The filters to match NoSql items that should be removed.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="filters"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="filters"/> contains <c>null</c>.</exception>
[Obsolete("Use the other overload that takes in the predicate function on a '" + nameof(NoSqlItem) + "' directly, " +
"this overload will be removed in v2.0")]
public OnTeardownNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter[] filters)
{
ArgumentNullException.ThrowIfNull(filters);
Expand All @@ -260,7 +354,62 @@ public OnTeardownNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter
}

Items = OnTeardownNoSqlContainer.CleanIfMatched;
_filters.AddRange(filters);
_typedFilters.AddRange(filters);

return this;
}

/// <summary>
/// Configures the <see cref="TemporaryNoSqlContainer"/> to delete the NoSql items
/// upon disposal that match any of the configured <paramref name="filters"/>.
/// </summary>
/// <remarks>
/// The matching of items only happens on NoSql items that were created outside the scope of the test fixture.
/// All items created by the test fixture will be deleted upon disposal, regardless of the filters.
/// This follows the 'clean environment' principle where the test fixture should clean up after itself and not linger around any state it created.
/// </remarks>
/// <param name="filters">The filters to match NoSql items that should be removed.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="filters"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="filters"/> contains <c>null</c>.</exception>
public OnTeardownNoSqlContainerOptions CleanMatchingItems(params Func<NoSqlItem, bool>[] filters)
{
ArgumentNullException.ThrowIfNull(filters);

if (Array.Exists(filters, f => f is null))
{
throw new ArgumentException("Requires all filters to be non-null", nameof(filters));
}

Items = OnTeardownNoSqlContainer.CleanIfMatched;
_genericFilters.AddRange(filters);

return this;
}

/// <summary>
/// Configures the <see cref="TemporaryNoSqlContainer"/> to delete the NoSql items
/// upon disposal that match any of the configured <paramref name="filters"/>.
/// </summary>
/// <remarks>
/// The matching of items only happens on NoSql items that were created outside the scope of the test fixture.
/// All items created by the test fixture will be deleted upon disposal, regardless of the filters.
/// This follows the 'clean environment' principle where the test fixture should clean up after itself and not linger around any state it created.
/// </remarks>
/// <typeparam name="TItem">The custom type of the NoSql item.</typeparam>
/// <param name="filters">The filters to match NoSql items that should be removed.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="filters"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="filters"/> contains <c>null</c>.</exception>
public OnTeardownNoSqlContainerOptions CleanMatchingItems<TItem>(params Func<TItem, bool>[] filters)
{
ArgumentNullException.ThrowIfNull(filters);

if (Array.Exists(filters, f => f is null))
{
throw new ArgumentException("Requires all filters to be non-null", nameof(filters));
}

Items = OnTeardownNoSqlContainer.CleanIfMatched;
_typedFilters.AddRange(filters.Select(NoSqlItemFilter.Where).ToArray());

return this;
}
Expand All @@ -270,7 +419,10 @@ public OnTeardownNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter
/// </summary>
internal bool IsMatched(string itemId, PartitionKey partitionKey, JObject itemStream, CosmosClient client)
{
return _filters.Exists(filter => filter.IsMatch(itemId, partitionKey, itemStream, client));
var item = new NoSqlItem(itemId, partitionKey, itemStream);

return _typedFilters.Exists(filter => filter.IsMatch(itemId, partitionKey, itemStream, client))
|| _genericFilters.Exists(filter => filter(item));
}
}

Expand Down Expand Up @@ -501,7 +653,7 @@ public static async Task<TemporaryNoSqlContainer> CreateIfNotExistsAsync(

var properties = new ContainerProperties(containerName, partitionKeyPath);
CosmosDBSqlContainerResource container = await CreateNewNoSqlContainerAsync(cosmosDb, database, properties);

return new TemporaryNoSqlContainer(cosmosClient, containerClient, container, createdByUs: true, options, logger);
}
}
Expand Down Expand Up @@ -532,7 +684,7 @@ private static async Task<CosmosDBAccountResource> GetCosmosDbResourceAsync(Reso
{
var arm = new ArmClient(credential);
CosmosDBAccountResource cosmosDb = arm.GetCosmosDBAccountResource(cosmosDbAccountResourceId);

return await cosmosDb.GetAsync();
}

Expand All @@ -542,14 +694,14 @@ private static async Task CleanContainerOnSetupAsync(Container container, Tempor
{
return;
}

if (options.OnSetup.Items is OnSetupNoSqlContainer.CleanIfExisted)
{
await ForEachItemAsync(container, async (id, partitionKey, _) =>
{
logger.LogDebug("[Test:Setup] Delete Azure Cosmos NoSql item '{ItemId}' {PartitionKey} in container '{DatabaseName}/{ContainerName}'", id, partitionKey, container.Database.Id, container.Id);
using ResponseMessage response = await container.DeleteItemStreamAsync(id, partitionKey);

if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.NotFound)
{
throw new RequestFailedException(
Expand Down Expand Up @@ -605,7 +757,7 @@ public async ValueTask DisposeAsync()
{
_logger.LogDebug("[Test:Teardown] Delete Azure Cosmos NoSql '{ContainerName}' container in database '{DatabaseName}'", _container.Id.Name, _container.Id.Parent?.Name);
await _container.DeleteAsync(WaitUntil.Completed);
}));
}));
}
else
{
Expand All @@ -632,7 +784,7 @@ await ForEachItemAsync((id, key, _) =>
{
_logger.LogDebug("[Test:Teardown] Delete Azure Cosmos NoSql item '{ItemId}' {PartitionKey} in NoSql container '{DatabaseName}/{ContainerName}'", id, key, Client.Database.Id, Client.Id);
using ResponseMessage response = await Client.DeleteItemStreamAsync(id, key);

if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.NotFound)
{
throw new RequestFailedException(
Expand All @@ -654,7 +806,7 @@ await ForEachItemAsync((id, key, doc) =>
{
_logger.LogDebug("[Test:Teardown] Delete Azure Cosmos NoSql item '{ItemId}' {PartitionKey} in NoSql container '{DatabaseName}/{ContainerName}'", id, key, Client.Database.Id, Client.Id);
using ResponseMessage response = await Client.DeleteItemStreamAsync(id, key);

if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.NotFound)
{
throw new RequestFailedException(
Expand Down
Loading
Loading