From 23de31161f1d2ef1582946c6d007cb27f5baeb8c Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Fri, 24 Jan 2025 08:42:18 +0100 Subject: [PATCH 1/3] chore: deprecate unnecessary az cosmos nosql functionality --- .../04-Azure/04-Storage/02-cosmos.mdx | 10 +- .../TemporaryNoSqlContainer.cs | 182 ++++++++++++++++-- .../Storage/TemporaryNoSqlContainerTests.cs | 40 ++-- .../Storage/NoSqlItemFilterTests.cs | 2 + .../TemporaryNoSqlContainerOptionsTests.cs | 11 +- 5 files changed, 211 insertions(+), 34 deletions(-) diff --git a/docs/preview/03-Features/04-Azure/04-Storage/02-cosmos.mdx b/docs/preview/03-Features/04-Azure/04-Storage/02-cosmos.mdx index 89c30496..c5b01da3 100644 --- a/docs/preview/03-Features/04-Azure/04-Storage/02-cosmos.mdx +++ b/docs/preview/03-Features/04-Azure/04-Storage/02-cosmos.mdx @@ -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. // -------------------------------------------------------- @@ -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: diff --git a/src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs b/src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs index 9f8a6f1a..5dcd7a2d 100644 --- a/src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs +++ b/src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs @@ -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 { /// /// Represents the available options when the is created. /// internal enum OnSetupNoSqlContainer { LeaveExisted = 0, CleanIfExisted, CleanIfMatched } - + /// /// Represents the available options when the is deleted. /// internal enum OnTeardownNoSqlContainer { CleanIfCreated = 0, CleanAll, CleanIfMatched } + /// + /// Represents a generic dictionary-like type which defines an arbitrary set of properties on a NoSql item as key-value pairs. + /// + public class NoSqlItem : JObject + { + /// + /// Initializes a new instance of the class. + /// + internal NoSqlItem( + string id, + PartitionKey partitionKey, + JObject properties) + : base(properties) + { + ArgumentNullException.ThrowIfNull(id); + + Id = id; + PartitionKey = partitionKey; + } + + /// + /// Gets the unique identifier to distinguish items. + /// + public string Id { get; } + + /// + /// Gets the key to group items together in partitions. + /// + public PartitionKey PartitionKey { get; } + } + /// /// Represents a filter to match against a stored NoSql item in the upon setup or teardown. /// + [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 _filter; @@ -104,7 +138,7 @@ public static NoSqlItemFilter Where(Func itemFilter) } using var body = new MemoryStream(Encoding.UTF8.GetBytes(json.ToString())); - + var item = client.ClientOptions.Serializer.FromStream(body); if (item is null) { @@ -147,7 +181,8 @@ internal bool IsMatch(string itemId, PartitionKey partitionKey, JObject item, Co /// public class OnSetupNoSqlContainerOptions { - private readonly List _filters = new(); + private readonly List _typedFilters = new(); + private readonly List> _genericFilters = new(); /// /// Gets the configurable setup option on what to do with existing NoSql items in the Azure NoSql container upon the test fixture creation. @@ -183,6 +218,8 @@ public OnSetupNoSqlContainerOptions CleanAllItems() /// The filters to match NoSql items that should be removed. /// Thrown when the is null. /// Thrown when one or more is null. + [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); @@ -193,7 +230,58 @@ public OnSetupNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter[] } Items = OnSetupNoSqlContainer.CleanIfMatched; - _filters.AddRange(filters); + _typedFilters.AddRange(filters); + + return this; + } + + /// + /// Configures the to delete the NoSql items + /// upon the test fixture creation that match any of the configured . + /// + /// + /// Multiple calls will be aggregated together in an OR expression. + /// + /// The filters to match NoSql items that should be removed. + /// Thrown when the is null. + /// Thrown when one or more is null. + public OnSetupNoSqlContainerOptions CleanMatchingItems(params Func[] 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; + } + + /// + /// Configures the to delete the NoSql items + /// upon the test fixture creation that match any of the configured . + /// + /// + /// Multiple calls will be aggregated together in an OR expression. + /// + /// The custom type of the NoSql item. + /// The filters to match NoSql items that should be removed. + /// Thrown when the is null. + /// Thrown when one or more is null. + public OnSetupNoSqlContainerOptions CleanMatchingItems(params Func[] 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; } @@ -203,7 +291,10 @@ public OnSetupNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter[] /// 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)); } } @@ -212,7 +303,8 @@ internal bool IsMatched(string itemId, PartitionKey partitionKey, JObject itemSt /// public class OnTeardownNoSqlContainerOptions { - private readonly List _filters = new(); + private readonly List _typedFilters = new(); + private readonly List> _genericFilters = new(); /// /// Gets the configurable setup option on what to do with existing NoSql items in the Azure NoSql container upon the test fixture deletion. @@ -250,6 +342,8 @@ public OnTeardownNoSqlContainerOptions CleanAllItems() /// The filters to match NoSql items that should be removed. /// Thrown when the is null. /// Thrown when the contains null. + [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); @@ -260,7 +354,62 @@ public OnTeardownNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter } Items = OnTeardownNoSqlContainer.CleanIfMatched; - _filters.AddRange(filters); + _typedFilters.AddRange(filters); + + return this; + } + + /// + /// Configures the to delete the NoSql items + /// upon disposal that match any of the configured . + /// + /// + /// 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. + /// + /// The filters to match NoSql items that should be removed. + /// Thrown when the is null. + /// Thrown when the contains null. + public OnTeardownNoSqlContainerOptions CleanMatchingItems(params Func[] 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; + } + + /// + /// Configures the to delete the NoSql items + /// upon disposal that match any of the configured . + /// + /// + /// 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. + /// + /// The custom type of the NoSql item. + /// The filters to match NoSql items that should be removed. + /// Thrown when the is null. + /// Thrown when the contains null. + public OnTeardownNoSqlContainerOptions CleanMatchingItems(params Func[] 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; } @@ -270,7 +419,10 @@ public OnTeardownNoSqlContainerOptions CleanMatchingItems(params NoSqlItemFilter /// 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)); } } @@ -501,7 +653,7 @@ public static async Task CreateIfNotExistsAsync( var properties = new ContainerProperties(containerName, partitionKeyPath); CosmosDBSqlContainerResource container = await CreateNewNoSqlContainerAsync(cosmosDb, database, properties); - + return new TemporaryNoSqlContainer(cosmosClient, containerClient, container, createdByUs: true, options, logger); } } @@ -532,7 +684,7 @@ private static async Task GetCosmosDbResourceAsync(Reso { var arm = new ArmClient(credential); CosmosDBAccountResource cosmosDb = arm.GetCosmosDBAccountResource(cosmosDbAccountResourceId); - + return await cosmosDb.GetAsync(); } @@ -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( @@ -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 { @@ -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( @@ -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( diff --git a/src/Arcus.Testing.Tests.Integration/Storage/TemporaryNoSqlContainerTests.cs b/src/Arcus.Testing.Tests.Integration/Storage/TemporaryNoSqlContainerTests.cs index ada3cd5f..0870b353 100644 --- a/src/Arcus.Testing.Tests.Integration/Storage/TemporaryNoSqlContainerTests.cs +++ b/src/Arcus.Testing.Tests.Integration/Storage/TemporaryNoSqlContainerTests.cs @@ -8,12 +8,14 @@ using Xunit; using Xunit.Abstractions; +#pragma warning disable CS0618 // Ignore obsolete warnings that we added ourselves, should be removed upon releasing v2.0. + namespace Arcus.Testing.Tests.Integration.Storage { [Collection(TestCollections.NoSql)] public class TemporaryNoSqlContainerTests : IntegrationTest { - private string[] PartitionKeyPaths => new[] + private static string[] PartitionKeyPaths => new[] { "/" + nameof(Ship.Destination) + "/" + nameof(Destination.Country) }; @@ -120,9 +122,17 @@ public async Task CreateTempNoSqlContainerWithCleanMatchingOnSetup_WhenExistingI // Act await WhenTempContainerCreatedAsync(containerName, options => { - options.OnSetup.CleanMatchingItems(CreateMatchingFilter(createdMatched)) - .CleanMatchingItems(CreateMatchingFilter(createdMatched)) - .CleanMatchingItems(CreateMatchingFilter(CreateShip())); + if (Bogus.Random.Bool()) + { + options.OnSetup.CleanMatchingItems(CreateDeprecatedMatchingFilter(createdMatched)) + .CleanMatchingItems(CreateDeprecatedMatchingFilter(createdMatched)) + .CleanMatchingItems(CreateDeprecatedMatchingFilter(CreateShip())); + } + else + { + options.OnSetup.CleanMatchingItems(item => item.Id == createdMatched.Id) + .CleanMatchingItems((Ship item) => item.GetPartitionKey() == createdNotMatched.GetPartitionKey()); + } }); // Assert @@ -142,9 +152,17 @@ public async Task CreateTempNoSqlContainerWithCleanMatchingOnTeardown_WhenExisti TemporaryNoSqlContainer container = await WhenTempContainerCreatedAsync(containerName, options => { - options.OnTeardown.CleanMatchingItems(CreateMatchingFilter(createdMatched)) - .CleanMatchingItems(CreateMatchingFilter(createdMatched)) - .CleanMatchingItems(CreateMatchingFilter(CreateShip())); + if (Bogus.Random.Bool()) + { + options.OnTeardown.CleanMatchingItems(CreateDeprecatedMatchingFilter(createdMatched)) + .CleanMatchingItems(CreateDeprecatedMatchingFilter(createdMatched)) + .CleanMatchingItems(CreateDeprecatedMatchingFilter(CreateShip())); + } + else + { + options.OnTeardown.CleanMatchingItems(item => item.Id == createdMatched.Id) + .CleanMatchingItems((Ship item) => item.GetPartitionKey() == createdMatched.GetPartitionKey()); + } }); Ship createdByUs = await AddItemAsync(container); @@ -161,7 +179,7 @@ public async Task CreateTempNoSqlContainerWithCleanMatchingOnTeardown_WhenExisti await context.ShouldStoreItemAsync(containerName, createdNotMatched); } - private static NoSqlItemFilter CreateMatchingFilter(Ship item) + private static NoSqlItemFilter CreateDeprecatedMatchingFilter(Ship item) { return Bogus.Random.Int(1, 5) switch { @@ -182,7 +200,7 @@ public async Task CreateTempNoSqlContainerWithCleanAllOnTeardown_WhenExistingIte string containerName = await WhenContainerAlreadyAvailableAsync(context); Ship createdBefore = await context.WhenItemAvailableAsync(containerName, CreateShip("before")); - + TemporaryNoSqlContainer container = await WhenTempContainerCreatedAsync(containerName); container.OnTeardown.CleanAllItems(); await context.ShouldStoreItemAsync(containerName, createdBefore); @@ -206,7 +224,7 @@ private NoSqlTestContext GivenCosmosNoSql() return NoSqlTestContext.Given(Configuration, Logger); } - private async Task WhenContainerAlreadyAvailableAsync(NoSqlTestContext context) + private static async Task WhenContainerAlreadyAvailableAsync(NoSqlTestContext context) { return await context.WhenContainerNameAvailableAsync(PartitionKeyPaths.Single()); } @@ -215,7 +233,7 @@ private async Task WhenTempContainerCreatedAsync( string containerName, Action configureOptions = null) { - var container = + var container = configureOptions is null ? await TemporaryNoSqlContainer.CreateIfNotExistsAsync(NoSql.ResourceId, NoSql.DatabaseName, containerName, PartitionKeyPaths.Single(), Logger) : await TemporaryNoSqlContainer.CreateIfNotExistsAsync(NoSql.ResourceId, NoSql.DatabaseName, containerName, PartitionKeyPaths.Single(), Logger, configureOptions); diff --git a/src/Arcus.Testing.Tests.Unit/Storage/NoSqlItemFilterTests.cs b/src/Arcus.Testing.Tests.Unit/Storage/NoSqlItemFilterTests.cs index 5eacaafb..2af58b65 100644 --- a/src/Arcus.Testing.Tests.Unit/Storage/NoSqlItemFilterTests.cs +++ b/src/Arcus.Testing.Tests.Unit/Storage/NoSqlItemFilterTests.cs @@ -2,6 +2,8 @@ using Bogus; using Xunit; +#pragma warning disable CS0618 // Ignore obsolete warnings that we added ourselves, should be removed upon releasing v2.0. + namespace Arcus.Testing.Tests.Unit.Storage { public class NoSqlItemFilterTests diff --git a/src/Arcus.Testing.Tests.Unit/Storage/TemporaryNoSqlContainerOptionsTests.cs b/src/Arcus.Testing.Tests.Unit/Storage/TemporaryNoSqlContainerOptionsTests.cs index ff27d7a0..51fce3d5 100644 --- a/src/Arcus.Testing.Tests.Unit/Storage/TemporaryNoSqlContainerOptionsTests.cs +++ b/src/Arcus.Testing.Tests.Unit/Storage/TemporaryNoSqlContainerOptionsTests.cs @@ -1,6 +1,9 @@ using System; +using Bogus; using Xunit; +#pragma warning disable CS0618 // Ignore obsolete warnings that we added ourselves, should be removed upon releasing v2.0. + namespace Arcus.Testing.Tests.Unit.Storage { public class TemporaryNoSqlContainerOptionsTests @@ -12,7 +15,9 @@ public void OnSetup_WithNullFilter_Fails() var options = new TemporaryNoSqlContainerOptions(); // Act / Assert - Assert.ThrowsAny(() => options.OnSetup.CleanMatchingItems(null)); + Assert.ThrowsAny(() => options.OnSetup.CleanMatchingItems((Func) null)); + Assert.ThrowsAny(() => options.OnSetup.CleanMatchingItems((Func) null)); + Assert.ThrowsAny(() => options.OnSetup.CleanMatchingItems((NoSqlItemFilter) null)); Assert.ThrowsAny(() => options.OnSetup.CleanMatchingItems(NoSqlItemFilter.IdEqual("some-id"), null)); } @@ -23,7 +28,9 @@ public void OnTeardown_WithNullFilter_Fails() var options = new TemporaryNoSqlContainerOptions(); // Act / Assert - Assert.ThrowsAny(() => options.OnTeardown.CleanMatchingItems(null)); + Assert.ThrowsAny(() => options.OnTeardown.CleanMatchingItems((Func) null)); + Assert.ThrowsAny(() => options.OnTeardown.CleanMatchingItems((Func) null)); + Assert.ThrowsAny(() => options.OnTeardown.CleanMatchingItems((NoSqlItemFilter) null)); Assert.ThrowsAny(() => options.OnTeardown.CleanMatchingItems(NoSqlItemFilter.IdEqual("some-id"), null)); } } From c671736bcfda25a5c8a5a6ad243a42e761e5837c Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Fri, 24 Jan 2025 08:44:15 +0100 Subject: [PATCH 2/3] pr-fix: style indentation --- src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs b/src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs index 5dcd7a2d..f1154ac1 100644 --- a/src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs +++ b/src/Arcus.Testing.Storage.Cosmos/TemporaryNoSqlContainer.cs @@ -294,7 +294,7 @@ internal bool IsMatched(string itemId, PartitionKey partitionKey, JObject itemSt var item = new NoSqlItem(itemId, partitionKey, itemStream); return _typedFilters.Exists(filter => filter.IsMatch(itemId, partitionKey, itemStream, client)) - || _genericFilters.Exists(filter => filter(item)); + || _genericFilters.Exists(filter => filter(item)); } } From 99bf080a6218deebee0ed376b3b63b0ffa2664cf Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Fri, 24 Jan 2025 08:45:58 +0100 Subject: [PATCH 3/3] pr-fix: test security plus implementation coverage --- .../Storage/TemporaryNoSqlContainerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Arcus.Testing.Tests.Integration/Storage/TemporaryNoSqlContainerTests.cs b/src/Arcus.Testing.Tests.Integration/Storage/TemporaryNoSqlContainerTests.cs index 0870b353..95dce7e3 100644 --- a/src/Arcus.Testing.Tests.Integration/Storage/TemporaryNoSqlContainerTests.cs +++ b/src/Arcus.Testing.Tests.Integration/Storage/TemporaryNoSqlContainerTests.cs @@ -160,7 +160,7 @@ public async Task CreateTempNoSqlContainerWithCleanMatchingOnTeardown_WhenExisti } else { - options.OnTeardown.CleanMatchingItems(item => item.Id == createdMatched.Id) + options.OnTeardown.CleanMatchingItems(item => item.PartitionKey == createdMatched.GetPartitionKey()) .CleanMatchingItems((Ship item) => item.GetPartitionKey() == createdMatched.GetPartitionKey()); } }); @@ -258,7 +258,7 @@ private static Ship CreateShip(string header = "") .RuleFor(s => s.Id, f => header + "item-" + f.Random.Guid()) .RuleFor(s => s.BoatName, f => f.Person.FirstName) .RuleFor(s => s.CrewMembers, f => f.Random.Int(1, 10)) - .RuleFor(s => s.Destination, f => new Destination { Country = f.Address.Country() }) + .RuleFor(s => s.Destination, f => new Destination { Country = f.Random.Guid() + f.Address.Country() }) .Generate(); }