public void DeleteIfLatestWithoutCurrentData_Throws_IfStopsMakingProgress() { // Arrange IVersionMetadataMapper versionMapper = CreateMapper(); string id = "Id"; ConcurrentInnerStore innerStore = new ConcurrentInnerStore(id, new ConcurrentMetadataText("1", CreateMetadata(DateTimeOffset.MinValue, versionMapper, "ExistingKey", "ExistingValue"), "ExistingText")); IDictionary<string, string> updatedMetadata = CreateMetadata(DateTimeOffset.Now, versionMapper, "UpdatedKey", "UpdatedValue"); string updatedText = "UpdatedText"; // Simulate an erroneous inner store that returns false from TryDelete even though the ETag has not changed. // A correct inner store would throw rather that return false if retrying can't help. innerStore.OnReadingMetadata += (calls) => { if (calls == 1) { Assert.True(innerStore.TryDelete(id, "2")); Assert.True(innerStore.TryCreate(id, updatedMetadata, updatedText)); } }; innerStore.OnReadMetadata += (calls) => { if (calls == 0) { Assert.True(innerStore.TryUpdate(id, "1", updatedMetadata, updatedText)); } }; IVersionedMetadataTextStore product = CreateProductUnderTest(innerStore, versionMapper); // Act & Assert ExceptionAssert.ThrowsInvalidOperation(() => product.DeleteIfLatest(id, DateTimeOffset.MaxValue), "The operation stopped making progress."); }
public void DeleteIfLatestWithoutCurrentData_Deletes_IfOlderConcurrentlyExists() { // Arrange IVersionMetadataMapper versionMapper = CreateMapper(); string id = "Id"; ConcurrentInnerStore innerStore = new ConcurrentInnerStore(id, new ConcurrentMetadataText("1", CreateMetadata(DateTimeOffset.MinValue, versionMapper, "ExistingKey", "ExistingValue"), "ExistingText")); IDictionary<string, string> updatedMetadata = CreateMetadata(DateTimeOffset.Now, versionMapper, "UpdatedKey", "UpdatedValue"); string updatedText = "UpdatedText"; innerStore.OnReadingMetadata += (calls) => { if (calls == 0) { Assert.True(innerStore.TryUpdate(id, "1", updatedMetadata, updatedText)); } }; IVersionedMetadataTextStore product = CreateProductUnderTest(innerStore, versionMapper); // Act bool isLatest = product.DeleteIfLatest(id, DateTimeOffset.MaxValue); // Assert Assert.True(isLatest); ConcurrentMetadataText storedItem = innerStore.Read(id); Assert.Null(storedItem); }
public void CreateOrUpdateIfLatest_Throws_IfCreateKeepsLooping() { // Arrange IVersionMetadataMapper versionMapper = CreateMapper(); string id = "Id"; ConcurrentInnerStore innerStore = new ConcurrentInnerStore(id, new ConcurrentMetadataText("1", CreateMetadata(DateTimeOffset.MinValue, versionMapper, "ExistingKey", "ExistingValue"), "ExistingText")); IDictionary<string, string> updatedMetadata = CreateMetadata(DateTimeOffset.Now, versionMapper, "UpdatedKey", "UpdatedValue"); string updatedText = "UpdatedText"; // Simulate an erroneous inner store that returns false from TryCreate but null from Read even though the // item exists. A correct inner store would throw rather that return false if retrying can't help. innerStore.OnReadingMetadata += (_) => { Assert.True(innerStore.TryDelete(id, "1")); }; innerStore.OnReadMetadata += (_) => { Assert.True(innerStore.TryCreate(id, updatedMetadata, updatedText)); }; IVersionedMetadataTextStore product = CreateProductUnderTest(innerStore, versionMapper); DateTimeOffset newVersion = DateTimeOffset.MaxValue; IDictionary<string, string> newOtherMetadata = CreateMetadata("NewKey", "NewValue"); string newText = "NewText"; // Act & Assert ExceptionAssert.ThrowsInvalidOperation( () => product.CreateOrUpdateIfLatest(id, newVersion, newOtherMetadata, newText), "The operation gave up due to repeated failed creation attempts."); }
public void DeleteIfLatestWithCurrentData_Returns_IfConcurrentlyDeleted() { // Arrange IVersionMetadataMapper versionMapper = CreateMapper(); string id = "Id"; DateTimeOffset existingVersion = DateTimeOffset.Now; ConcurrentInnerStore innerStore = new ConcurrentInnerStore(id, new ConcurrentMetadataText("2", CreateMetadata(existingVersion, versionMapper, "ExistingKey", "ExistingValue"), "ExistingText")); innerStore.OnReadingMetadata += (calls) => { if (calls == 0) { Assert.True(innerStore.TryDelete(id, "2")); } }; IVersionedMetadataTextStore product = CreateProductUnderTest(innerStore, versionMapper); // Act bool isLatest = product.DeleteIfLatest(id, DateTimeOffset.MaxValue, "1", existingVersion); // Assert Assert.True(isLatest); ConcurrentMetadataText storedItem = innerStore.Read(id); Assert.Null(storedItem); }
public void CreateOrUpdateIfLatest_Updates_IfOlderConcurrentlyExists() { // Arrange IVersionMetadataMapper versionMapper = CreateMapper(); string id = "Id"; ConcurrentInnerStore innerStore = new ConcurrentInnerStore(id, new ConcurrentMetadataText("1", CreateMetadata(DateTimeOffset.Now, versionMapper, "ExistingKey", "ExistingValue"), "ExistingText")); IDictionary<string, string> updatedMetadata = CreateMetadata(DateTimeOffset.Now, versionMapper, "UpdatedKey", "UpdatedValue"); string updatedText = "UpdatedText"; innerStore.OnReadMetadata += (calls) => { if (calls == 0) { Assert.True(innerStore.TryUpdate(id, "1", updatedMetadata, updatedText)); } }; IVersionedMetadataTextStore product = CreateProductUnderTest(innerStore, versionMapper); DateTimeOffset newVersion = DateTimeOffset.MaxValue; IDictionary<string, string> newOtherMetadata = CreateMetadata("NewKey", "NewValue"); string newText = "NewText"; // Act bool isLatest = product.CreateOrUpdateIfLatest(id, newVersion, newOtherMetadata, newText); // Assert Assert.True(isLatest); ConcurrentMetadataText storedItem = innerStore.Read(id); IDictionary<string, string> expectedMetadata = CreateMetadata(newVersion, versionMapper, newOtherMetadata); ConcurrentMetadataText expectedItem = new ConcurrentMetadataText("3", expectedMetadata, newText); AssertEqual(expectedItem, storedItem); }