From 457ab83e18089fcaa0cd1c3ec59bde29b3b9f7e4 Mon Sep 17 00:00:00 2001 From: Andrei Fangli Date: Sat, 26 Dec 2020 00:33:04 +0200 Subject: [PATCH] #18 Fixed DynamicEntity with null properties (closes #18) --- ...eEntityInsertOrMergeTableOperationTests.cs | 60 +++++++++++++++++++ ...ntityInsertOrReplaceTableOperationTests.cs | 60 +++++++++++++++++++ ...oudTableEntityInsertTableOperationTests.cs | 26 ++++++++ ...loudTableEntityMergeTableOperationTests.cs | 36 +++++++++++ ...udTableEntityReplaceTableOperationTests.cs | 36 +++++++++++ ...eEntityInsertOrMergeTableOperationTests.cs | 60 +++++++++++++++++++ ...ntityInsertOrReplaceTableOperationTests.cs | 60 +++++++++++++++++++ ...oudTableEntityInsertTableOperationTests.cs | 26 ++++++++ ...loudTableEntityMergeTableOperationTests.cs | 36 +++++++++++ ...udTableEntityReplaceTableOperationTests.cs | 36 +++++++++++ .../InsertOrMergeTableOperationExecutor.cs | 3 +- .../InsertOrReplaceTableOperationExecutor.cs | 2 +- .../InsertTableOperationExecutor.cs | 2 +- .../MergeTableOperationExecutor.cs | 2 +- .../ReplaceTableOperationExecutor.cs | 2 +- .../TableOperations/TableOperationExecutor.cs | 6 +- 16 files changed, 445 insertions(+), 8 deletions(-) diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs index 7b3f2f1..039c6bf 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -156,6 +157,65 @@ public async Task TableOperation_InsertOrMergeOperation_MergesEntities() Assert.Equal(default(DateTimeOffset), resultEntity.Timestamp); } + [Fact] + public async Task ExecuteAsync_WhenDynamicEntityHasNullProperties_TheyAreIgnored() + { + await CloudTable.CreateAsync(); + + await CloudTable.ExecuteAsync(TableOperation.InsertOrMerge(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = await GetAllEntitiesAsync(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + + [Fact] + public async Task ExecuteAsync_WhenDynamicEntityHasNullProperties_TheyAreIgnoredWhenEntityAlreadyExists() + { + await CloudTable.CreateAsync(); + await CloudTable.ExecuteAsync(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.GeneratePropertyForInt(1) } + } + })); + + await CloudTable.ExecuteAsync(TableOperation.InsertOrMerge(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = await GetAllEntitiesAsync(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.Equal(EntityProperty.GeneratePropertyForInt(1), actualProps[nameof(TestEntity.Int32Prop)]); + } + [Fact] public async Task ExecuteAsync_WhenPartitionKeyIsNull_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs index 9cdb341..670ed7c 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -156,6 +157,65 @@ public async Task ExecuteAsync_InserOperationWhenEntityHasOtherProperties_Insert Assert.False(actualProps.ContainsKey(nameof(TestEntity.DecimalProp))); } + [Fact] + public async Task ExecuteAsync_WhenDynamicEntityHasNullProperties_TheyAreIgnored() + { + await CloudTable.CreateAsync(); + + await CloudTable.ExecuteAsync(TableOperation.InsertOrReplace(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = await GetAllEntitiesAsync(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + + [Fact] + public async Task ExecuteAsync_WhenDynamicEntityHasNullProperties_TheyAreRemovedWhenEntityAlreadyExists() + { + await CloudTable.CreateAsync(); + await CloudTable.ExecuteAsync(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.GeneratePropertyForInt(1) } + } + })); + + await CloudTable.ExecuteAsync(TableOperation.InsertOrReplace(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = await GetAllEntitiesAsync(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + [Fact] public async Task ExecuteAsync_InsertOrReplaceOperationWhenPartitionKeyIsNull_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertTableOperationTests.cs index 24ae0bf..d791218 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; @@ -163,6 +164,31 @@ public async Task ExecuteAsync_InserOperationWhenEntityHasOtherProperties_Insert Assert.False(actualProps.ContainsKey(nameof(TestEntity.DecimalProp))); } + [Fact] + public async Task ExecuteAsync_WhenDynamicEntityHasNullProperties_TheyAreIgnored() + { + await CloudTable.CreateAsync(); + + await CloudTable.ExecuteAsync(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = await GetAllEntitiesAsync(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + [Fact] public async Task ExecuteAsync_WhenPartitionKeyIsNull_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityMergeTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityMergeTableOperationTests.cs index 4e7ca4e..4977efa 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityMergeTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityMergeTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -144,6 +145,41 @@ public async Task ExecuteAsync_WhenETagsMatch_MergesEntity() Assert.Equal(default(DateTimeOffset), resultEntity.Timestamp); } + [Fact] + public async Task ExecuteAsync_WhenDynamicEntityHasNullProperties_TheyAreIgnored() + { + await CloudTable.CreateAsync(); + var tableResult = await CloudTable.ExecuteAsync(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.GeneratePropertyForInt(1) } + } + })); + + await CloudTable.ExecuteAsync(TableOperation.Merge(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + ETag = tableResult.Etag, + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = await GetAllEntitiesAsync(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.Equal(EntityProperty.GeneratePropertyForInt(1), actualProps[nameof(TestEntity.Int32Prop)]); + } + [Fact] public async Task ExecuteAsync_WhenEntityDoesNotExist_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityReplaceTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityReplaceTableOperationTests.cs index 34454f7..27ee64f 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityReplaceTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityReplaceTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -138,6 +139,41 @@ public async Task ExecuteAsync_WhenETagsMatch_ReplacesEntity() Assert.Equal(default(DateTimeOffset), resultEntity.Timestamp); } + [Fact] + public async Task ExecuteAsync_WhenDynamicEntityHasNullProperties_TheyAreRemoved() + { + await CloudTable.CreateAsync(); + var tableResult = await CloudTable.ExecuteAsync(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.GeneratePropertyForInt(1) } + } + })); + + await CloudTable.ExecuteAsync(TableOperation.Replace(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + ETag = tableResult.Etag, + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = await GetAllEntitiesAsync(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + [Fact] public async Task ExecuteAsync_WhenEntityDoesNotExist_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs index 7432067..3f28d73 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -155,6 +156,65 @@ public void TableOperation_InsertOrMergeOperation_MergesEntities() Assert.Equal(default(DateTimeOffset), resultEntity.Timestamp); } + [Fact] + public void Execute_WhenDynamicEntityHasNullProperties_TheyAreIgnored() + { + CloudTable.Create(); + + CloudTable.Execute(TableOperation.InsertOrMerge(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = GetAllEntities(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + + [Fact] + public void Execute_WhenDynamicEntityHasNullProperties_TheyAreIgnoredWhenEntityAlreadyExists() + { + CloudTable.Create(); + CloudTable.Execute(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.GeneratePropertyForInt(1) } + } + })); + + CloudTable.Execute(TableOperation.InsertOrMerge(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = GetAllEntities(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.Equal(EntityProperty.GeneratePropertyForInt(1), actualProps[nameof(TestEntity.Int32Prop)]); + } + [Fact] public void Execute_WhenPartitionKeyIsNull_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs index 150c7b2..0c29444 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -155,6 +156,65 @@ public void Execute_InserOperationWhenEntityHasOtherProperties_InsertsEntity() Assert.False(actualProps.ContainsKey(nameof(TestEntity.DecimalProp))); } + [Fact] + public void Execute_WhenDynamicEntityHasNullProperties_TheyAreIgnored() + { + CloudTable.Create(); + + CloudTable.Execute(TableOperation.InsertOrReplace(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = GetAllEntities(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + + [Fact] + public void Execute_WhenDynamicEntityHasNullProperties_TheyAreRemovedWhenEntityAlreadyExists() + { + CloudTable.Create(); + CloudTable.Execute(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.GeneratePropertyForInt(1) } + } + })); + + CloudTable.Execute(TableOperation.InsertOrReplace(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = GetAllEntities(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + [Fact] public void Execute_InsertOrReplaceOperationWhenPartitionKeyIsNull_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertTableOperationTests.cs index ae2c122..22ec301 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Text.RegularExpressions; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -162,6 +163,31 @@ public void Execute_InserOperationWhenEntityHasOtherProperties_InsertsEntity() Assert.False(actualProps.ContainsKey(nameof(TestEntity.DecimalProp))); } + [Fact] + public void Execute_WhenDynamicEntityHasNullProperties_TheyAreIgnored() + { + CloudTable.Create(); + + CloudTable.Execute(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = GetAllEntities(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + [Fact] public void Execute_WhenPartitionKeyIsNull_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityMergeTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityMergeTableOperationTests.cs index e682015..0e4e0af 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityMergeTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityMergeTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -143,6 +144,41 @@ public void Execute_WhenETagsMatch_MergesEntity() Assert.Equal(default(DateTimeOffset), resultEntity.Timestamp); } + [Fact] + public void Execute_WhenDynamicEntityHasNullProperties_TheyAreIgnored() + { + CloudTable.Create(); + var tableResult = CloudTable.Execute(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.GeneratePropertyForInt(1) } + } + })); + + CloudTable.Execute(TableOperation.Merge(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + ETag = tableResult.Etag, + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = GetAllEntities(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.Equal(EntityProperty.GeneratePropertyForInt(1), actualProps[nameof(TestEntity.Int32Prop)]); + } + [Fact] public void Execute_WhenEntityDoesNotExist_ThrowsException() { diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityReplaceTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityReplaceTableOperationTests.cs index 5446b80..b9a4cf9 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityReplaceTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityReplaceTableOperationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using Microsoft.Azure.Cosmos.Table; using Xunit; @@ -137,6 +138,41 @@ public void Execute_WhenETagsMatch_ReplacesEntity() Assert.Equal(default(DateTimeOffset), resultEntity.Timestamp); } + [Fact] + public void Execute_WhenDynamicEntityHasNullProperties_TheyAreRemoved() + { + CloudTable.Create(); + var tableResult = CloudTable.Execute(TableOperation.Insert(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.GeneratePropertyForInt(1) } + } + })); + + CloudTable.Execute(TableOperation.Replace(new DynamicTableEntity + { + PartitionKey = "partition-key", + RowKey = "row-key", + ETag = tableResult.Etag, + Properties = new Dictionary(StringComparer.Ordinal) + { + { nameof(TestEntity.Int32Prop), EntityProperty.CreateEntityPropertyFromObject(null) } + } + })); + + var entities = GetAllEntities(); + var entity = Assert.Single(entities); + var actualProps = entity.WriteEntity(null); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.PartitionKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.RowKey))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Timestamp))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.ETag))); + Assert.False(actualProps.ContainsKey(nameof(TestEntity.Int32Prop))); + } + [Fact] public void Execute_WhenEntityDoesNotExist_ThrowsException() { diff --git a/CloudStub/TableOperations/InsertOrMergeTableOperationExecutor.cs b/CloudStub/TableOperations/InsertOrMergeTableOperationExecutor.cs index b6fdd7a..0665069 100644 --- a/CloudStub/TableOperations/InsertOrMergeTableOperationExecutor.cs +++ b/CloudStub/TableOperations/InsertOrMergeTableOperationExecutor.cs @@ -43,7 +43,6 @@ public override Exception Validate(TableOperation tableOperation, OperationConte public override Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex) { if (!Context.TableExists) - return TableDoesNotExistForBatchException(operationIndex); if (tableOperation.Entity.PartitionKey == null) @@ -71,7 +70,7 @@ public override Exception ValidateForBatch(TableOperation tableOperation, Operat public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - var dynamicEntity = GetDynamicEntity(tableOperation.Entity, operationContext); + var dynamicEntity = GetDynamicEntity(tableOperation.Entity); var partition = _GetPartition(dynamicEntity); if (partition.TryGetValue(dynamicEntity.RowKey, out var existingEntity)) diff --git a/CloudStub/TableOperations/InsertOrReplaceTableOperationExecutor.cs b/CloudStub/TableOperations/InsertOrReplaceTableOperationExecutor.cs index 5ea5cdb..a1cdaa7 100644 --- a/CloudStub/TableOperations/InsertOrReplaceTableOperationExecutor.cs +++ b/CloudStub/TableOperations/InsertOrReplaceTableOperationExecutor.cs @@ -70,7 +70,7 @@ public override Exception ValidateForBatch(TableOperation tableOperation, Operat public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - var dynamicEntity = GetDynamicEntity(tableOperation.Entity, operationContext); + var dynamicEntity = GetDynamicEntity(tableOperation.Entity); var partition = _GetPartition(dynamicEntity); partition[dynamicEntity.RowKey] = dynamicEntity; diff --git a/CloudStub/TableOperations/InsertTableOperationExecutor.cs b/CloudStub/TableOperations/InsertTableOperationExecutor.cs index ebc7cc3..5ae100f 100644 --- a/CloudStub/TableOperations/InsertTableOperationExecutor.cs +++ b/CloudStub/TableOperations/InsertTableOperationExecutor.cs @@ -75,7 +75,7 @@ public override Exception ValidateForBatch(TableOperation tableOperation, Operat public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - var dynamicEntity = GetDynamicEntity(tableOperation.Entity, operationContext); + var dynamicEntity = GetDynamicEntity(tableOperation.Entity); var partition = _GetPartition(dynamicEntity); partition.Add(dynamicEntity.RowKey, dynamicEntity); diff --git a/CloudStub/TableOperations/MergeTableOperationExecutor.cs b/CloudStub/TableOperations/MergeTableOperationExecutor.cs index 68bce15..bdf576f 100644 --- a/CloudStub/TableOperations/MergeTableOperationExecutor.cs +++ b/CloudStub/TableOperations/MergeTableOperationExecutor.cs @@ -78,7 +78,7 @@ public override TableResult Execute(TableOperation tableOperation, OperationCont var partition = Context.Entities[tableOperation.Entity.PartitionKey]; var existingEntity = partition[tableOperation.Entity.RowKey]; - var dynamicEntity = GetDynamicEntity(tableOperation.Entity, operationContext); + var dynamicEntity = GetDynamicEntity(tableOperation.Entity); foreach (var property in existingEntity.Properties) if (!dynamicEntity.Properties.ContainsKey(property.Key)) dynamicEntity.Properties.Add(property); diff --git a/CloudStub/TableOperations/ReplaceTableOperationExecutor.cs b/CloudStub/TableOperations/ReplaceTableOperationExecutor.cs index d074099..29e926b 100644 --- a/CloudStub/TableOperations/ReplaceTableOperationExecutor.cs +++ b/CloudStub/TableOperations/ReplaceTableOperationExecutor.cs @@ -75,7 +75,7 @@ public override Exception ValidateForBatch(TableOperation tableOperation, Operat public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - var dynamicEntity = GetDynamicEntity(tableOperation.Entity, operationContext); + var dynamicEntity = GetDynamicEntity(tableOperation.Entity); Context.Entities[tableOperation.Entity.PartitionKey][tableOperation.Entity.RowKey] = dynamicEntity; return new TableResult diff --git a/CloudStub/TableOperations/TableOperationExecutor.cs b/CloudStub/TableOperations/TableOperationExecutor.cs index bfe1326..731f2ae 100644 --- a/CloudStub/TableOperations/TableOperationExecutor.cs +++ b/CloudStub/TableOperations/TableOperationExecutor.cs @@ -19,11 +19,11 @@ protected TableOperationExecutor(ITableOperationExecutorContext context) public abstract TableResult Execute(TableOperation tableOperation, OperationContext operationContext); - protected static DynamicTableEntity GetDynamicEntity(ITableEntity entity, OperationContext operationContext) + protected static DynamicTableEntity GetDynamicEntity(ITableEntity entity) { var timestamp = DateTimeOffset.UtcNow; var properties = entity is DynamicTableEntity dynamicTableEntity - ? dynamicTableEntity.Clone().Properties + ? dynamicTableEntity.Properties.Where(pair => pair.Value.PropertyAsObject != null).ToDictionary(pair => pair.Key, pair => EntityProperty.CreateEntityPropertyFromObject(pair.Value.PropertyAsObject), StringComparer.Ordinal) : _GetProperties(entity); properties.Remove(nameof(TableEntity.PartitionKey)); properties.Remove(nameof(TableEntity.RowKey)); @@ -56,6 +56,7 @@ protected static Exception ValidateEntityProperties(ITableEntity entity) && property.Key != nameof(TableEntity.RowKey) && property.Key != nameof(TableEntity.Timestamp) && property.Key != nameof(TableEntity.ETag) + && property.Value.PropertyAsObject != null let exception = _ValidateEntityProperty(property.Key, property.Value) where exception != null select exception @@ -130,6 +131,7 @@ protected static Exception ValidateEntityPropertiesForBatch(ITableEntity entity, && property.Key != nameof(TableEntity.RowKey) && property.Key != nameof(TableEntity.Timestamp) && property.Key != nameof(TableEntity.ETag) + && property.Value.PropertyAsObject != null let exception = _ValidateEntityPropertyForBatch(property.Key, property.Value, operationIndex) where exception != null select exception