From 394aa9341a9ce1c1420e2919eb0cc152fcde0b3b Mon Sep 17 00:00:00 2001 From: Andrei Fangli Date: Tue, 24 Aug 2021 19:05:13 +0300 Subject: [PATCH] Refactored InMemoryCloudTable to StubCloudTable --- ...bleTests.cs => BaseStubCloudTableTests.cs} | 10 +- ... StubCloudTableQueryComparisonTestData.cs} | 2 +- ...s => StubCloudTableBatchOperationTests.cs} | 2 +- ...leEntityDeleteTableBatchOperationTests.cs} | 2 +- ...yInsertOrMergeTableBatchOperationTests.cs} | 2 +- ...nsertOrReplaceTableBatchOperationTests.cs} | 2 +- ...leEntityInsertTableBatchOperationTests.cs} | 2 +- ...bleEntityMergeTableBatchOperationTests.cs} | 2 +- ...eEntityReplaceTableBatchOperationTests.cs} | 3 +- ...EntityRetrieveTableBatchOperationTests.cs} | 2 +- ...s => StubCloudTableBatchOperationTests.cs} | 2 +- ...leEntityDeleteTableBatchOperationTests.cs} | 2 +- ...yInsertOrMergeTableBatchOperationTests.cs} | 2 +- ...nsertOrReplaceTableBatchOperationTests.cs} | 2 +- ...leEntityInsertTableBatchOperationTests.cs} | 2 +- ...bleEntityMergeTableBatchOperationTests.cs} | 2 +- ...eEntityReplaceTableBatchOperationTests.cs} | 2 +- ...EntityRetrieveTableBatchOperationTests.cs} | 2 +- ...udTableEntityDeleteTableOperationTests.cs} | 2 +- ...EntityInsertOrMergeTableOperationTests.cs} | 2 +- ...tityInsertOrReplaceTableOperationTests.cs} | 2 +- ...udTableEntityInsertTableOperationTests.cs} | 2 +- ...oudTableEntityMergeTableOperationTests.cs} | 2 +- ...dTableEntityReplaceTableOperationTests.cs} | 2 +- ...TableEntityRetrieveTableOperationTests.cs} | 2 +- ...sts.cs => StubCloudTableOperationTests.cs} | 2 +- ...udTableEntityDeleteTableOperationTests.cs} | 2 +- ...EntityInsertOrMergeTableOperationTests.cs} | 2 +- ...tityInsertOrReplaceTableOperationTests.cs} | 2 +- ...udTableEntityInsertTableOperationTests.cs} | 2 +- ...oudTableEntityMergeTableOperationTests.cs} | 2 +- ...dTableEntityReplaceTableOperationTests.cs} | 2 +- ...TableEntityRetrieveTableOperationTests.cs} | 2 +- ...sts.cs => StubCloudTableOperationTests.cs} | 2 +- ...s => StubCloudTableQuerySegmentedTests.cs} | 4 +- ...udTableTests.cs => StubCloudTableTests.cs} | 17 +- ...s => StubCloudTableQuerySegmentedTests.cs} | 4 +- ...ryTests.cs => StubCloudTableQueryTests.cs} | 6 +- ...udTableTests.cs => StubCloudTableTests.cs} | 17 +- CloudStub/Core/StubTable.cs | 2 +- CloudStub/Core/StubTableQuery.cs | 2 +- .../Core/StubTableQueryContinuationToken.cs | 3 + .../PropertyFilterNodeFactory.cs | 26 +- .../FilterParser/FilterNodes/AndFilterNode.cs | 4 +- .../FilterNodes/EqualFilterNode.cs | 6 +- .../FilterParser/FilterNodes/FilterNode.cs | 4 +- .../FilterNodes/GreaterThanFilterNode.cs | 6 +- .../GreaterThanOrEqualFilterNode.cs | 6 +- .../FilterNodes/LessThanFilterNode.cs | 6 +- .../FilterNodes/LessThanOrEqualFilterNode.cs | 6 +- .../FilterNodes/NotEqualFilterNode.cs | 6 +- .../FilterParser/FilterNodes/NotFilterNode.cs | 4 +- .../FilterParser/FilterNodes/OrFilterNode.cs | 4 +- .../FilterNodes/PropertyCheckFilterNode.cs | 4 +- .../FilterNodes/PropertyFilterNode.cs | 120 +-- CloudStub/FilterParser/FilterTokenParser.cs | 4 +- ...nMemoryCloudTable.cs => StubCloudTable.cs} | 745 +++++++++++------- .../AddTableOperationExecutor.cs | 59 -- .../DeleteTableOperationExecutor.cs | 98 ++- .../EditTableOperationExecutor.cs | 48 -- .../ITableOperationExecutorContext.cs | 12 - .../InsertOrMergeTableOperationExecutor.cs | 107 ++- .../InsertOrReplaceTableOperationExecutor.cs | 104 +-- .../InsertTableOperationExecutor.cs | 116 +-- .../MergeTableOperationExecutor.cs | 105 +-- .../ReplaceTableOperationExecutor.cs | 101 ++- .../RetrieveTableOperationExecutor.cs | 111 ++- .../TableOperations/TableOperationExecutor.cs | 107 ++- 68 files changed, 1128 insertions(+), 923 deletions(-) rename CloudStub.Tests/{BaseInMemoryCloudTableTests.cs => BaseStubCloudTableTests.cs} (86%) rename CloudStub.Tests/{InMemoryCloudTableQueryComparisonTestData.cs => StubCloudTableQueryComparisonTestData.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Async/{InMemoryCloudTableBatchOperationTests.cs => StubCloudTableBatchOperationTests.cs} (98%) rename CloudStub.Tests/TableBatchOperationTests/Async/{InMemoryCloudTableEntityDeleteTableBatchOperationTests.cs => StubCloudTableEntityDeleteTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Async/{InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests.cs => StubCloudTableEntityInsertOrMergeTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Async/{InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs => StubCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Async/{InMemoryCloudTableEntityInsertTableBatchOperationTests.cs => StubCloudTableEntityInsertTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Async/{InMemoryCloudTableEntityMergeTableBatchOperationTests.cs => StubCloudTableEntityMergeTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Async/{InMemoryCloudTableEntityReplaceTableBatchOperationTests.cs => StubCloudTableEntityReplaceTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Async/{InMemoryCloudTableEntityRetrieveTableBatchOperationTests.cs => StubCloudTableEntityRetrieveTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Sync/{InMemoryCloudTableBatchOperationTests.cs => StubCloudTableBatchOperationTests.cs} (98%) rename CloudStub.Tests/TableBatchOperationTests/Sync/{InMemoryCloudTableEntityDeleteTableBatchOperationTests.cs => StubCloudTableEntityDeleteTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Sync/{InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests.cs => StubCloudTableEntityInsertOrMergeTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Sync/{InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs => StubCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Sync/{InMemoryCloudTableEntityInsertTableBatchOperationTests.cs => StubCloudTableEntityInsertTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Sync/{InMemoryCloudTableEntityMergeTableBatchOperationTests.cs => StubCloudTableEntityMergeTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Sync/{InMemoryCloudTableEntityReplaceTableBatchOperationTests.cs => StubCloudTableEntityReplaceTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableBatchOperationTests/Sync/{InMemoryCloudTableEntityRetrieveTableBatchOperationTests.cs => StubCloudTableEntityRetrieveTableBatchOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Async/{InMemoryCloudTableEntityDeleteTableOperationTests.cs => StubCloudTableEntityDeleteTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Async/{InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs => StubCloudTableEntityInsertOrMergeTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Async/{InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs => StubCloudTableEntityInsertOrReplaceTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Async/{InMemoryCloudTableEntityInsertTableOperationTests.cs => StubCloudTableEntityInsertTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Async/{InMemoryCloudTableEntityMergeTableOperationTests.cs => StubCloudTableEntityMergeTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Async/{InMemoryCloudTableEntityReplaceTableOperationTests.cs => StubCloudTableEntityReplaceTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Async/{InMemoryCloudTableEntityRetrieveTableOperationTests.cs => StubCloudTableEntityRetrieveTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Async/{InMemoryCloudTableOperationTests.cs => StubCloudTableOperationTests.cs} (84%) rename CloudStub.Tests/TableOperationTests/Sync/{InMemoryCloudTableEntityDeleteTableOperationTests.cs => StubCloudTableEntityDeleteTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Sync/{InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs => StubCloudTableEntityInsertOrMergeTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Sync/{InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs => StubCloudTableEntityInsertOrReplaceTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Sync/{InMemoryCloudTableEntityInsertTableOperationTests.cs => StubCloudTableEntityInsertTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Sync/{InMemoryCloudTableEntityMergeTableOperationTests.cs => StubCloudTableEntityMergeTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Sync/{InMemoryCloudTableEntityReplaceTableOperationTests.cs => StubCloudTableEntityReplaceTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Sync/{InMemoryCloudTableEntityRetrieveTableOperationTests.cs => StubCloudTableEntityRetrieveTableOperationTests.cs} (99%) rename CloudStub.Tests/TableOperationTests/Sync/{InMemoryCloudTableOperationTests.cs => StubCloudTableOperationTests.cs} (83%) rename CloudStub.Tests/TableTests/Async/{InMemoryCloudTableQuerySegmentedTests.cs => StubCloudTableQuerySegmentedTests.cs} (99%) rename CloudStub.Tests/TableTests/Async/{InMemoryCloudTableTests.cs => StubCloudTableTests.cs} (93%) rename CloudStub.Tests/TableTests/Sync/{InMemoryCloudTableQuerySegmentedTests.cs => StubCloudTableQuerySegmentedTests.cs} (99%) rename CloudStub.Tests/TableTests/Sync/{InMemoryCloudTableQueryTests.cs => StubCloudTableQueryTests.cs} (99%) rename CloudStub.Tests/TableTests/Sync/{InMemoryCloudTableTests.cs => StubCloudTableTests.cs} (93%) rename CloudStub/{InMemoryCloudTable.cs => StubCloudTable.cs} (51%) delete mode 100644 CloudStub/TableOperations/AddTableOperationExecutor.cs delete mode 100644 CloudStub/TableOperations/EditTableOperationExecutor.cs delete mode 100644 CloudStub/TableOperations/ITableOperationExecutorContext.cs diff --git a/CloudStub.Tests/BaseInMemoryCloudTableTests.cs b/CloudStub.Tests/BaseStubCloudTableTests.cs similarity index 86% rename from CloudStub.Tests/BaseInMemoryCloudTableTests.cs rename to CloudStub.Tests/BaseStubCloudTableTests.cs index 3e7fe4b..7386b98 100644 --- a/CloudStub.Tests/BaseInMemoryCloudTableTests.cs +++ b/CloudStub.Tests/BaseStubCloudTableTests.cs @@ -1,19 +1,21 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using CloudStub.Core; +using CloudStub.Core.StorageHandlers; using Microsoft.Azure.Cosmos.Table; namespace CloudStub.Tests { - public abstract class BaseInMemoryCloudTableTests : AzureStorageUnitTest + public abstract class BaseStubCloudTableTests : AzureStorageUnitTest { /// A temporary flag to easily switch between in-memory cloud table and actual Azure Storage Table. protected static bool UseInMemory { get; } = true; - protected BaseInMemoryCloudTableTests() + protected BaseStubCloudTableTests() => CloudTable = GetCloudTable(TestTableName); - protected new string TestTableName { get; } = (nameof(BaseInMemoryCloudTableTests) + "TestTable" + Guid.NewGuid().ToString().Replace("-", "")).Substring(0, 63); + protected new string TestTableName { get; } = (nameof(BaseStubCloudTableTests) + "TestTable" + Guid.NewGuid().ToString().Replace("-", "")).Substring(0, 63); protected CloudTable CloudTable { get; } @@ -57,7 +59,7 @@ protected static async Task> GetAllEntitiesAsy protected static CloudTable GetCloudTable(string tableName) => UseInMemory ? - new InMemoryCloudTable(tableName) : + new StubCloudTable(new StubTable(tableName, new InMemoryTableStorageHandler())) : CloudStorageAccount .Parse(AzureStorageConnectionString) .CreateCloudTableClient() diff --git a/CloudStub.Tests/InMemoryCloudTableQueryComparisonTestData.cs b/CloudStub.Tests/StubCloudTableQueryComparisonTestData.cs similarity index 99% rename from CloudStub.Tests/InMemoryCloudTableQueryComparisonTestData.cs rename to CloudStub.Tests/StubCloudTableQueryComparisonTestData.cs index 69c4166..776253b 100644 --- a/CloudStub.Tests/InMemoryCloudTableQueryComparisonTestData.cs +++ b/CloudStub.Tests/StubCloudTableQueryComparisonTestData.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests { - public class InMemoryCloudTableQueryComparisonTestData : IEnumerable + public class StubCloudTableQueryComparisonTestData : IEnumerable { public IEnumerator GetEnumerator() => MatchingTypeComparisonTestData diff --git a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableBatchOperationTests.cs similarity index 98% rename from CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableBatchOperationTests.cs index 9c487b6..16eb188 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableBatchOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Async { - public class InMemoryCloudTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteBatchAsync_WhenBatchIsNull_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityDeleteTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityDeleteTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityDeleteTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityDeleteTableBatchOperationTests.cs index 09ee581..b59c6a5 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityDeleteTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityDeleteTableBatchOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Async { - public class InMemoryCloudTableEntityDeleteTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityDeleteTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertOrMergeTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertOrMergeTableBatchOperationTests.cs index 590f97a..36ac4d2 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertOrMergeTableBatchOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Async { - public class InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertOrMergeTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs index 1e62a38..7392006 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Async { - public class InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertOrReplaceTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertTableBatchOperationTests.cs index 1f2719b..216811a 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityInsertTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityInsertTableBatchOperationTests.cs @@ -7,7 +7,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Async { - public class InMemoryCloudTableEntityInsertTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityMergeTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityMergeTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityMergeTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityMergeTableBatchOperationTests.cs index 5caf706..e1814a5 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityMergeTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityMergeTableBatchOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Async { - public class InMemoryCloudTableEntityMergeTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityMergeTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityReplaceTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityReplaceTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityReplaceTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityReplaceTableBatchOperationTests.cs index d36eabe..9c9a108 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityReplaceTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityReplaceTableBatchOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Async { - public class InMemoryCloudTableEntityReplaceTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityReplaceTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() @@ -69,7 +69,6 @@ public void TableOperation_WhenETagIsMissing_ThrowsException() [Fact] public async Task ExecuteAsync_WhenETagsIsWildcard_ReplacesEntity() { - var startTime = DateTimeOffset.UtcNow; var testEntity = new TestEntity { PartitionKey = "partition-key", diff --git a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityRetrieveTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityRetrieveTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityRetrieveTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityRetrieveTableBatchOperationTests.cs index 5dce8e4..3232673 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Async/InMemoryCloudTableEntityRetrieveTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Async/StubCloudTableEntityRetrieveTableBatchOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Async { - public class InMemoryCloudTableEntityRetrieveTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityRetrieveTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ReturnsNullResult() diff --git a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableBatchOperationTests.cs similarity index 98% rename from CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableBatchOperationTests.cs index 984beb8..a117fdf 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableBatchOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Sync { - public class InMemoryCloudTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public void ExecuteBatch_WhenBatchIsNull_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityDeleteTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityDeleteTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityDeleteTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityDeleteTableBatchOperationTests.cs index ff37403..7811044 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityDeleteTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityDeleteTableBatchOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Sync { - public class InMemoryCloudTableEntityDeleteTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityDeleteTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertOrMergeTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertOrMergeTableBatchOperationTests.cs index 976c7bc..476b6d4 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertOrMergeTableBatchOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Sync { - public class InMemoryCloudTableEntityInsertOrMergeTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertOrMergeTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs index 710acc7..14994b0 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertOrReplaceTableBatchOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Sync { - public class InMemoryCloudTableEntityInsertOrReplaceTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertOrReplaceTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertTableBatchOperationTests.cs index 303e812..0854f9f 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityInsertTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityInsertTableBatchOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Sync { - public class InMemoryCloudTableEntityInsertTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityMergeTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityMergeTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityMergeTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityMergeTableBatchOperationTests.cs index 305c9b8..20db8d7 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityMergeTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityMergeTableBatchOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Sync { - public class InMemoryCloudTableEntityMergeTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityMergeTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityReplaceTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityReplaceTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityReplaceTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityReplaceTableBatchOperationTests.cs index a15add5..5fe7d8b 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityReplaceTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityReplaceTableBatchOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Sync { - public class InMemoryCloudTableEntityReplaceTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityReplaceTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityRetrieveTableBatchOperationTests.cs b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityRetrieveTableBatchOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityRetrieveTableBatchOperationTests.cs rename to CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityRetrieveTableBatchOperationTests.cs index b7fa359..e502819 100644 --- a/CloudStub.Tests/TableBatchOperationTests/Sync/InMemoryCloudTableEntityRetrieveTableBatchOperationTests.cs +++ b/CloudStub.Tests/TableBatchOperationTests/Sync/StubCloudTableEntityRetrieveTableBatchOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableBatchOperationTests.Sync { - public class InMemoryCloudTableEntityRetrieveTableBatchOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityRetrieveTableBatchOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ReturnsNullResult() diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityDeleteTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityDeleteTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityDeleteTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityDeleteTableOperationTests.cs index b950686..44b959a 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityDeleteTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityDeleteTableOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableOperationTests.Async { - public class InMemoryCloudTableEntityDeleteTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityDeleteTableOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertOrMergeTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertOrMergeTableOperationTests.cs index 039c6bf..fc22f29 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertOrMergeTableOperationTests.cs @@ -7,7 +7,7 @@ namespace CloudStub.Tests.TableOperationTests.Async { - public class InMemoryCloudTableEntityInsertOrMergeTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertOrMergeTableOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertOrReplaceTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertOrReplaceTableOperationTests.cs index 670ed7c..ec3d6be 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertOrReplaceTableOperationTests.cs @@ -7,7 +7,7 @@ namespace CloudStub.Tests.TableOperationTests.Async { - public class InMemoryCloudTableEntityInsertOrReplaceTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertOrReplaceTableOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertTableOperationTests.cs index d791218..16c027b 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityInsertTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityInsertTableOperationTests.cs @@ -8,7 +8,7 @@ namespace CloudStub.Tests.TableOperationTests.Async { - public class InMemoryCloudTableEntityInsertTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertTableOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityMergeTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityMergeTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityMergeTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityMergeTableOperationTests.cs index 4977efa..f63f4df 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityMergeTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityMergeTableOperationTests.cs @@ -7,7 +7,7 @@ namespace CloudStub.Tests.TableOperationTests.Async { - public class InMemoryCloudTableEntityMergeTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityMergeTableOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityReplaceTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityReplaceTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityReplaceTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityReplaceTableOperationTests.cs index 27ee64f..e35d3b3 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityReplaceTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityReplaceTableOperationTests.cs @@ -7,7 +7,7 @@ namespace CloudStub.Tests.TableOperationTests.Async { - public class InMemoryCloudTableEntityReplaceTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityReplaceTableOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityRetrieveTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityRetrieveTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityRetrieveTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityRetrieveTableOperationTests.cs index 7ba251e..b6f0685 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableEntityRetrieveTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableEntityRetrieveTableOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableOperationTests.Async { - public class InMemoryCloudTableEntityRetrieveTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityRetrieveTableOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenTableDoesNotExist_ReturnsNullResult() diff --git a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableOperationTests.cs similarity index 84% rename from CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Async/StubCloudTableOperationTests.cs index af71d66..ab96fda 100644 --- a/CloudStub.Tests/TableOperationTests/Async/InMemoryCloudTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Async/StubCloudTableOperationTests.cs @@ -4,7 +4,7 @@ namespace CloudStub.Tests.TableOperationTests.Async { - public class InMemoryCloudTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableOperationTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteAsync_WhenOperationIsNull_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityDeleteTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityDeleteTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityDeleteTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityDeleteTableOperationTests.cs index c1e9129..b034618 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityDeleteTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityDeleteTableOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableOperationTests.Sync { - public class InMemoryCloudTableEntityDeleteTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityDeleteTableOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertOrMergeTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertOrMergeTableOperationTests.cs index 3f28d73..a463aee 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrMergeTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertOrMergeTableOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableOperationTests.Sync { - public class InMemoryCloudTableEntityInsertOrMergeTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertOrMergeTableOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertOrReplaceTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertOrReplaceTableOperationTests.cs index 0c29444..8dc78a4 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertOrReplaceTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertOrReplaceTableOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableOperationTests.Sync { - public class InMemoryCloudTableEntityInsertOrReplaceTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertOrReplaceTableOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertTableOperationTests.cs index 22ec301..32635a7 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityInsertTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityInsertTableOperationTests.cs @@ -7,7 +7,7 @@ namespace CloudStub.Tests.TableOperationTests.Sync { - public class InMemoryCloudTableEntityInsertTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityInsertTableOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityMergeTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityMergeTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityMergeTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityMergeTableOperationTests.cs index 0e4e0af..b0cb4c9 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityMergeTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityMergeTableOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableOperationTests.Sync { - public class InMemoryCloudTableEntityMergeTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityMergeTableOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityReplaceTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityReplaceTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityReplaceTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityReplaceTableOperationTests.cs index b9a4cf9..93acb20 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityReplaceTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityReplaceTableOperationTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableOperationTests.Sync { - public class InMemoryCloudTableEntityReplaceTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityReplaceTableOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ThrowsException() diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityRetrieveTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityRetrieveTableOperationTests.cs similarity index 99% rename from CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityRetrieveTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityRetrieveTableOperationTests.cs index 2640d76..da23613 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableEntityRetrieveTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableEntityRetrieveTableOperationTests.cs @@ -5,7 +5,7 @@ namespace CloudStub.Tests.TableOperationTests.Sync { - public class InMemoryCloudTableEntityRetrieveTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableEntityRetrieveTableOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenTableDoesNotExist_ReturnsNullResult() diff --git a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableOperationTests.cs b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableOperationTests.cs similarity index 83% rename from CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableOperationTests.cs rename to CloudStub.Tests/TableOperationTests/Sync/StubCloudTableOperationTests.cs index e1759ad..fcb7ade 100644 --- a/CloudStub.Tests/TableOperationTests/Sync/InMemoryCloudTableOperationTests.cs +++ b/CloudStub.Tests/TableOperationTests/Sync/StubCloudTableOperationTests.cs @@ -3,7 +3,7 @@ namespace CloudStub.Tests.TableOperationTests.Sync { - public class InMemoryCloudTableOperationTests : BaseInMemoryCloudTableTests + public class StubCloudTableOperationTests : BaseStubCloudTableTests { [Fact] public void Execute_WhenOperationIsNull_ThrowsException() diff --git a/CloudStub.Tests/TableTests/Async/InMemoryCloudTableQuerySegmentedTests.cs b/CloudStub.Tests/TableTests/Async/StubCloudTableQuerySegmentedTests.cs similarity index 99% rename from CloudStub.Tests/TableTests/Async/InMemoryCloudTableQuerySegmentedTests.cs rename to CloudStub.Tests/TableTests/Async/StubCloudTableQuerySegmentedTests.cs index 0f5987d..e710080 100644 --- a/CloudStub.Tests/TableTests/Async/InMemoryCloudTableQuerySegmentedTests.cs +++ b/CloudStub.Tests/TableTests/Async/StubCloudTableQuerySegmentedTests.cs @@ -7,7 +7,7 @@ namespace CloudStub.Tests.TableTests.Async { - public class InMemoryCloudTableQuerySegmentedTests : BaseInMemoryCloudTableTests + public class StubCloudTableQuerySegmentedTests : BaseStubCloudTableTests { [Fact] public async Task ExecuteQuerySegmentedAsync_WhenThereAreNoFilters_ReturnsAllItems() @@ -312,7 +312,7 @@ await CloudTable.ExecuteAsync(TableOperation.Insert(new TestQueryEntity } [Theory] - [ClassData(typeof(InMemoryCloudTableQueryComparisonTestData))] + [ClassData(typeof(StubCloudTableQueryComparisonTestData))] public async Task ExecuteQuerySegmentedAsync_WhenUsingComparisonFilteOperator_MayReturnEntities(string propertyName, object propertyValue, string filterOperator, object filterValue, bool returnsEntity) { await CloudTable.CreateIfNotExistsAsync(); diff --git a/CloudStub.Tests/TableTests/Async/InMemoryCloudTableTests.cs b/CloudStub.Tests/TableTests/Async/StubCloudTableTests.cs similarity index 93% rename from CloudStub.Tests/TableTests/Async/InMemoryCloudTableTests.cs rename to CloudStub.Tests/TableTests/Async/StubCloudTableTests.cs index e90ec94..134cbfa 100644 --- a/CloudStub.Tests/TableTests/Async/InMemoryCloudTableTests.cs +++ b/CloudStub.Tests/TableTests/Async/StubCloudTableTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableTests.Async { - public class InMemoryCloudTableTests : BaseInMemoryCloudTableTests + public class StubCloudTableTests : BaseStubCloudTableTests { [Fact] public void TableName_GetsTheSameNameWhichWasProvided() @@ -14,20 +14,6 @@ public void TableName_GetsTheSameNameWhichWasProvided() Assert.Equal(TestTableName, CloudTable.Name); } - [Fact] - public void CreateAsync_WhenTableNameIsNull_ThrowsException() - { - var exception = Assert.Throws("tableName", () => GetCloudTable(null)); - Assert.Equal(new ArgumentNullException("tableName").Message, exception.Message); - } - - [Fact] - public void CreateAsync_WhenTableNameIsEmpty_ThrowsException() - { - var exception = Assert.Throws("tableName", () => GetCloudTable("")); - Assert.Equal(new ArgumentException("The argument must not be empty string.", "tableName").Message, exception.Message); - } - [Fact] public async Task ExistsAsync_WhenTableDoesNotExist_ReturnsFalse() { @@ -138,7 +124,6 @@ public async Task CreateAsync_WhenTableNameIsReserved_ThrowsException(string tab } [Theory] - [InlineData(" ")] [InlineData("t")] [InlineData("tt")] [InlineData("testTableNameHavingALengthOf63CharactersSomeOfThemAreJustExtra1s")] diff --git a/CloudStub.Tests/TableTests/Sync/InMemoryCloudTableQuerySegmentedTests.cs b/CloudStub.Tests/TableTests/Sync/StubCloudTableQuerySegmentedTests.cs similarity index 99% rename from CloudStub.Tests/TableTests/Sync/InMemoryCloudTableQuerySegmentedTests.cs rename to CloudStub.Tests/TableTests/Sync/StubCloudTableQuerySegmentedTests.cs index 810cf6a..4fb0ccf 100644 --- a/CloudStub.Tests/TableTests/Sync/InMemoryCloudTableQuerySegmentedTests.cs +++ b/CloudStub.Tests/TableTests/Sync/StubCloudTableQuerySegmentedTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableTests.Sync { - public class InMemoryCloudTableQuerySegmentedTests : BaseInMemoryCloudTableTests + public class StubCloudTableQuerySegmentedTests : BaseStubCloudTableTests { [Fact] public void ExecuteQuerySegmented_WhenThereAreNoFilters_ReturnsAllItems() @@ -311,7 +311,7 @@ public void ExecuteQuerySegmented_WhenUsingPropertyNameFilterOnExistantProperty_ } [Theory] - [ClassData(typeof(InMemoryCloudTableQueryComparisonTestData))] + [ClassData(typeof(StubCloudTableQueryComparisonTestData))] public void ExecuteQuerySegmented_WhenUsingComparisonFilteOperator_MayReturnEntities(string propertyName, object propertyValue, string filterOperator, object filterValue, bool returnsEntity) { CloudTable.CreateIfNotExists(); diff --git a/CloudStub.Tests/TableTests/Sync/InMemoryCloudTableQueryTests.cs b/CloudStub.Tests/TableTests/Sync/StubCloudTableQueryTests.cs similarity index 99% rename from CloudStub.Tests/TableTests/Sync/InMemoryCloudTableQueryTests.cs rename to CloudStub.Tests/TableTests/Sync/StubCloudTableQueryTests.cs index fc23dc1..8e730b5 100644 --- a/CloudStub.Tests/TableTests/Sync/InMemoryCloudTableQueryTests.cs +++ b/CloudStub.Tests/TableTests/Sync/StubCloudTableQueryTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableTests.Sync { - public class InMemoryCloudTableQueryTests : BaseInMemoryCloudTableTests + public class StubCloudTableQueryTests : BaseStubCloudTableTests { [Fact] public void ExecuteQuery_WhenThereAreNoFilters_ReturnsAllItems() @@ -311,8 +311,8 @@ public void ExecuteQuery_WhenUsingPropertyNameFilterOnExistantProperty_ReturnsNo } [Theory] - [ClassData(typeof(InMemoryCloudTableQueryComparisonTestData))] - public void ExecuteQuery_WhenUsingComparisonFilteOperator_MayReturnEntities(string propertyName, object propertyValue, string filterOperator, object filterValue, bool returnsEntity) + [ClassData(typeof(StubCloudTableQueryComparisonTestData))] + public void ExecuteQuery_WhenUsingComparisonFilterOperator_MayReturnEntities(string propertyName, object propertyValue, string filterOperator, object filterValue, bool returnsEntity) { CloudTable.CreateIfNotExists(); CloudTable.Execute(TableOperation.Insert(new DynamicTableEntity diff --git a/CloudStub.Tests/TableTests/Sync/InMemoryCloudTableTests.cs b/CloudStub.Tests/TableTests/Sync/StubCloudTableTests.cs similarity index 93% rename from CloudStub.Tests/TableTests/Sync/InMemoryCloudTableTests.cs rename to CloudStub.Tests/TableTests/Sync/StubCloudTableTests.cs index 16af9e2..1eb1bf4 100644 --- a/CloudStub.Tests/TableTests/Sync/InMemoryCloudTableTests.cs +++ b/CloudStub.Tests/TableTests/Sync/StubCloudTableTests.cs @@ -6,7 +6,7 @@ namespace CloudStub.Tests.TableTests.Sync { - public class InMemoryCloudTableTests : BaseInMemoryCloudTableTests + public class StubCloudTableTests : BaseStubCloudTableTests { [Fact] public void TableName_GetsTheSameNameWhichWasProvided() @@ -14,20 +14,6 @@ public void TableName_GetsTheSameNameWhichWasProvided() Assert.Equal(TestTableName, CloudTable.Name); } - [Fact] - public void Create_WhenTableNameIsNull_ThrowsException() - { - var exception = Assert.Throws("tableName", () => GetCloudTable(null)); - Assert.Equal(new ArgumentNullException("tableName").Message, exception.Message); - } - - [Fact] - public void Create_WhenTableNameIsEmpty_ThrowsException() - { - var exception = Assert.Throws("tableName", () => GetCloudTable("")); - Assert.Equal(new ArgumentException("The argument must not be empty string.", "tableName").Message, exception.Message); - } - [Fact(Skip = "CloudTable.Exists cannot be overridden.")] public void Exists_WhenTableDoesNotExist_ReturnsFalse() { @@ -138,7 +124,6 @@ public void Create_WhenTableNameIsReserved_ThrowsException(string tableName) } [Theory] - [InlineData(" ")] [InlineData("t")] [InlineData("tt")] [InlineData("testTableNameHavingALengthOf63CharactersSomeOfThemAreJustExtra1s")] diff --git a/CloudStub/Core/StubTable.cs b/CloudStub/Core/StubTable.cs index 179f3ce..62db2a6 100644 --- a/CloudStub/Core/StubTable.cs +++ b/CloudStub/Core/StubTable.cs @@ -439,7 +439,7 @@ public StubTableBatchOperation BatchOperation() } ); - private StubEntity _SelectPropertiesFromEntity(StubEntity entity, IReadOnlyCollection selectedPropertyNames) + private StubEntity _SelectPropertiesFromEntity(StubEntity entity, IEnumerable selectedPropertyNames) { var projectEntity = new StubEntity(entity.PartitionKey, entity.RowKey, entity.Timestamp.Value, entity.ETag); if (selectedPropertyNames is null) diff --git a/CloudStub/Core/StubTableQuery.cs b/CloudStub/Core/StubTableQuery.cs index 29026d9..bd9db3f 100644 --- a/CloudStub/Core/StubTableQuery.cs +++ b/CloudStub/Core/StubTableQuery.cs @@ -7,7 +7,7 @@ public class StubTableQuery { public Func Filter { get; set; } - public IReadOnlyCollection SelectedProperties { get; set; } + public IEnumerable SelectedProperties { get; set; } public int? PageSize { get; set; } } diff --git a/CloudStub/Core/StubTableQueryContinuationToken.cs b/CloudStub/Core/StubTableQueryContinuationToken.cs index 6744f29..6bd2e2e 100644 --- a/CloudStub/Core/StubTableQueryContinuationToken.cs +++ b/CloudStub/Core/StubTableQueryContinuationToken.cs @@ -5,6 +5,9 @@ public class StubTableQueryContinuationToken internal StubTableQueryContinuationToken(StubEntity lastStubEntity) => (LastPartitionKey, LastRowKey) = (lastStubEntity.PartitionKey, lastStubEntity.RowKey); + internal StubTableQueryContinuationToken(string lastPartitionKey, string lastRowKey) + => (LastPartitionKey, LastRowKey) = (lastPartitionKey, lastRowKey); + public string LastPartitionKey { get; } public string LastRowKey { get; } } diff --git a/CloudStub/FilterParser/FilterNodeFactories/PropertyFilterNodeFactory.cs b/CloudStub/FilterParser/FilterNodeFactories/PropertyFilterNodeFactory.cs index 56ada3f..1e26a88 100644 --- a/CloudStub/FilterParser/FilterNodeFactories/PropertyFilterNodeFactory.cs +++ b/CloudStub/FilterParser/FilterNodeFactories/PropertyFilterNodeFactory.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using CloudStub.Core; using CloudStub.FilterParser.FilterNodes; -using Microsoft.Azure.Cosmos.Table; namespace CloudStub.FilterParser.FilterNodeFactories { @@ -24,22 +24,22 @@ public FilterNode Create(IReadOnlyList tokens) } } - private static EntityProperty _GetEntityProperty(string value) + private static StubEntityProperty _GetEntityProperty(string value) { - EntityProperty result; + StubEntityProperty result; if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intResult)) - result = EntityProperty.GeneratePropertyForInt(intResult); + result = new StubEntityProperty(intResult); else if (value.EndsWith("L", StringComparison.OrdinalIgnoreCase) && long.TryParse(value.Substring(0, value.Length - 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var longResult)) - result = EntityProperty.GeneratePropertyForLong(longResult); + result = new StubEntityProperty(longResult); else if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue)) - result = EntityProperty.GeneratePropertyForDouble(doubleValue); + result = new StubEntityProperty(doubleValue); else if (bool.TryParse(value, out var boolResult)) - result = EntityProperty.GeneratePropertyForBool(boolResult); + result = new StubEntityProperty(boolResult); else if (value.StartsWith("x'", StringComparison.OrdinalIgnoreCase) && value.EndsWith("'", StringComparison.OrdinalIgnoreCase)) { var binaryValue = value.Substring("x'".Length, value.Length - "x'".Length - "'".Length); - result = EntityProperty.GeneratePropertyForByteArray( + result = new StubEntityProperty( Enumerable .Range(0, binaryValue.Length / 2) .Select(arrayIndex => Convert.ToByte(binaryValue.Substring(arrayIndex * 2, 2), 16)) @@ -49,25 +49,25 @@ private static EntityProperty _GetEntityProperty(string value) else if (value.StartsWith("guid'", StringComparison.OrdinalIgnoreCase) && value.EndsWith("'", StringComparison.OrdinalIgnoreCase)) { var guidValue = value.Substring("guid'".Length, value.Length - "guid'".Length - "'".Length); - result = EntityProperty.GeneratePropertyForGuid(Guid.Parse(guidValue)); + result = new StubEntityProperty(Guid.Parse(guidValue)); } else if (value.StartsWith("datetime'", StringComparison.OrdinalIgnoreCase) && value.EndsWith("'", StringComparison.OrdinalIgnoreCase)) { var dateTimeValue = value.Substring("datetime'".Length, value.Length - "datetime'".Length - "'".Length); - result = EntityProperty.GeneratePropertyForDateTimeOffset(DateTimeOffset.Parse(dateTimeValue, CultureInfo.InvariantCulture)); + result = new StubEntityProperty(DateTimeOffset.Parse(dateTimeValue, CultureInfo.InvariantCulture).DateTime); } else if (value.StartsWith("'", StringComparison.OrdinalIgnoreCase) && value.EndsWith("'", StringComparison.OrdinalIgnoreCase)) { var stringValue = value.Substring("'".Length, value.Length - "'".Length - "'".Length); - result = EntityProperty.GeneratePropertyForString(stringValue); + result = new StubEntityProperty(stringValue); } else - result = EntityProperty.GeneratePropertyForString(value); + result = new StubEntityProperty(value); return result; } - private static FilterNode _GetOperatorNode(string propertyName, FilterToken @operator, EntityProperty filterValue) + private static FilterNode _GetOperatorNode(string propertyName, FilterToken @operator, StubEntityProperty filterValue) { switch (@operator.Code) { diff --git a/CloudStub/FilterParser/FilterNodes/AndFilterNode.cs b/CloudStub/FilterParser/FilterNodes/AndFilterNode.cs index e31b150..f6ad4b1 100644 --- a/CloudStub/FilterParser/FilterNodes/AndFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/AndFilterNode.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { @@ -11,7 +11,7 @@ public AndFilterNode(IEnumerable operands) public IEnumerable Operands { get; } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var result = Operands.All(operand => operand.Apply(entity)); return result; diff --git a/CloudStub/FilterParser/FilterNodes/EqualFilterNode.cs b/CloudStub/FilterParser/FilterNodes/EqualFilterNode.cs index 3b22bac..331eff1 100644 --- a/CloudStub/FilterParser/FilterNodes/EqualFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/EqualFilterNode.cs @@ -1,15 +1,15 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { internal class EqualFilterNode : PropertyFilterNode { - public EqualFilterNode(string propertyName, EntityProperty filterValue) + public EqualFilterNode(string propertyName, StubEntityProperty filterValue) : base(propertyName, filterValue) { } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var compareResult = Compare(GetValueFromEntity(entity), FilterValue); var result = compareResult == 0; diff --git a/CloudStub/FilterParser/FilterNodes/FilterNode.cs b/CloudStub/FilterParser/FilterNodes/FilterNode.cs index 4dac18d..0654bc5 100644 --- a/CloudStub/FilterParser/FilterNodes/FilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/FilterNode.cs @@ -1,9 +1,9 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { internal abstract class FilterNode { - public abstract bool Apply(DynamicTableEntity entity); + public abstract bool Apply(StubEntity entity); } } \ No newline at end of file diff --git a/CloudStub/FilterParser/FilterNodes/GreaterThanFilterNode.cs b/CloudStub/FilterParser/FilterNodes/GreaterThanFilterNode.cs index 1173aef..4280aaf 100644 --- a/CloudStub/FilterParser/FilterNodes/GreaterThanFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/GreaterThanFilterNode.cs @@ -1,15 +1,15 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { internal class GreaterThanFilterNode : PropertyFilterNode { - public GreaterThanFilterNode(string propertyName, EntityProperty filterValue) + public GreaterThanFilterNode(string propertyName, StubEntityProperty filterValue) : base(propertyName, filterValue) { } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var compareResult = Compare(GetValueFromEntity(entity), FilterValue); var result = compareResult > 0; diff --git a/CloudStub/FilterParser/FilterNodes/GreaterThanOrEqualFilterNode.cs b/CloudStub/FilterParser/FilterNodes/GreaterThanOrEqualFilterNode.cs index f6d8673..496071c 100644 --- a/CloudStub/FilterParser/FilterNodes/GreaterThanOrEqualFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/GreaterThanOrEqualFilterNode.cs @@ -1,15 +1,15 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { internal class GreaterThanOrEqualFilterNode : PropertyFilterNode { - public GreaterThanOrEqualFilterNode(string propertyName, EntityProperty filterValue) + public GreaterThanOrEqualFilterNode(string propertyName, StubEntityProperty filterValue) : base(propertyName, filterValue) { } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var compareResult = Compare(GetValueFromEntity(entity), FilterValue); var result = compareResult >= 0; diff --git a/CloudStub/FilterParser/FilterNodes/LessThanFilterNode.cs b/CloudStub/FilterParser/FilterNodes/LessThanFilterNode.cs index c555820..86cfe90 100644 --- a/CloudStub/FilterParser/FilterNodes/LessThanFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/LessThanFilterNode.cs @@ -1,15 +1,15 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { internal class LessThanFilterNode : PropertyFilterNode { - public LessThanFilterNode(string propertyName, EntityProperty filterValue) + public LessThanFilterNode(string propertyName, StubEntityProperty filterValue) : base(propertyName, filterValue) { } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var compareResult = Compare(GetValueFromEntity(entity), FilterValue); var result = compareResult != null && compareResult < 0; diff --git a/CloudStub/FilterParser/FilterNodes/LessThanOrEqualFilterNode.cs b/CloudStub/FilterParser/FilterNodes/LessThanOrEqualFilterNode.cs index af20566..3b23949 100644 --- a/CloudStub/FilterParser/FilterNodes/LessThanOrEqualFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/LessThanOrEqualFilterNode.cs @@ -1,15 +1,15 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { internal class LessThanOrEqualFilterNode : PropertyFilterNode { - public LessThanOrEqualFilterNode(string propertyName, EntityProperty filterValue) + public LessThanOrEqualFilterNode(string propertyName, StubEntityProperty filterValue) : base(propertyName, filterValue) { } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var compareResult = Compare(GetValueFromEntity(entity), FilterValue); var result = compareResult != null && compareResult <= 0; diff --git a/CloudStub/FilterParser/FilterNodes/NotEqualFilterNode.cs b/CloudStub/FilterParser/FilterNodes/NotEqualFilterNode.cs index f9059e5..5a8393c 100644 --- a/CloudStub/FilterParser/FilterNodes/NotEqualFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/NotEqualFilterNode.cs @@ -1,15 +1,15 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { internal class NotEqualFilterNode : PropertyFilterNode { - public NotEqualFilterNode(string propertyName, EntityProperty filterValue) + public NotEqualFilterNode(string propertyName, StubEntityProperty filterValue) : base(propertyName, filterValue) { } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var compareResult = Compare(GetValueFromEntity(entity), FilterValue); var result = compareResult != null && compareResult != 0; diff --git a/CloudStub/FilterParser/FilterNodes/NotFilterNode.cs b/CloudStub/FilterParser/FilterNodes/NotFilterNode.cs index 94b658f..9bb8f33 100644 --- a/CloudStub/FilterParser/FilterNodes/NotFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/NotFilterNode.cs @@ -1,4 +1,4 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { @@ -9,7 +9,7 @@ public NotFilterNode(FilterNode operand) public FilterNode Operand { get; set; } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var operandResult = Operand.Apply(entity); var result = !operandResult; diff --git a/CloudStub/FilterParser/FilterNodes/OrFilterNode.cs b/CloudStub/FilterParser/FilterNodes/OrFilterNode.cs index 2424122..3476225 100644 --- a/CloudStub/FilterParser/FilterNodes/OrFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/OrFilterNode.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { @@ -11,7 +11,7 @@ public OrFilterNode(IEnumerable operands) public IEnumerable Operands { get; } - public override bool Apply(DynamicTableEntity entity) + public override bool Apply(StubEntity entity) { var result = Operands.Any(operand => operand.Apply(entity)); return result; diff --git a/CloudStub/FilterParser/FilterNodes/PropertyCheckFilterNode.cs b/CloudStub/FilterParser/FilterNodes/PropertyCheckFilterNode.cs index 8c8f54e..dabab09 100644 --- a/CloudStub/FilterParser/FilterNodes/PropertyCheckFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/PropertyCheckFilterNode.cs @@ -1,11 +1,11 @@ -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { internal class PropertyCheckFilterNode : EqualFilterNode { public PropertyCheckFilterNode(string propertyName) - : base(propertyName, EntityProperty.GeneratePropertyForBool(true)) + : base(propertyName, new StubEntityProperty(true)) { } } diff --git a/CloudStub/FilterParser/FilterNodes/PropertyFilterNode.cs b/CloudStub/FilterParser/FilterNodes/PropertyFilterNode.cs index 60270e7..e9fef88 100644 --- a/CloudStub/FilterParser/FilterNodes/PropertyFilterNode.cs +++ b/CloudStub/FilterParser/FilterNodes/PropertyFilterNode.cs @@ -1,5 +1,5 @@ using System; -using Microsoft.Azure.Cosmos.Table; +using CloudStub.Core; namespace CloudStub.FilterParser.FilterNodes { @@ -7,7 +7,7 @@ internal abstract class PropertyFilterNode : FilterNode { private const int MismatchingTypeComparisonResult = -1; - public PropertyFilterNode(string propertyName, EntityProperty filterValue) + public PropertyFilterNode(string propertyName, StubEntityProperty filterValue) { PropertyName = propertyName; FilterValue = filterValue; @@ -15,126 +15,128 @@ public PropertyFilterNode(string propertyName, EntityProperty filterValue) public string PropertyName { get; } - public EntityProperty FilterValue { get; } + public StubEntityProperty FilterValue { get; } - protected EntityProperty GetValueFromEntity(DynamicTableEntity entity) + protected StubEntityProperty GetValueFromEntity(StubEntity entity) { - EntityProperty entityProperty; + StubEntityProperty entityProperty; if (string.Equals(PropertyName, nameof(entity.PartitionKey), StringComparison.Ordinal)) - entityProperty = EntityProperty.GeneratePropertyForString(entity.PartitionKey); + entityProperty = new StubEntityProperty(entity.PartitionKey); else if (string.Equals(PropertyName, nameof(entity.RowKey), StringComparison.Ordinal)) - entityProperty = EntityProperty.GeneratePropertyForString(entity.RowKey); + entityProperty = new StubEntityProperty(entity.RowKey); else if (string.Equals(PropertyName, nameof(entity.ETag), StringComparison.Ordinal)) - entityProperty = EntityProperty.GeneratePropertyForString(entity.ETag); + entityProperty = new StubEntityProperty(entity.ETag); else if (string.Equals(PropertyName, nameof(entity.Timestamp), StringComparison.Ordinal)) - entityProperty = EntityProperty.GeneratePropertyForDateTimeOffset(entity.Timestamp); + entityProperty = new StubEntityProperty(entity.Timestamp.Value); else if (!entity.Properties.TryGetValue(PropertyName, out entityProperty)) entityProperty = null; return entityProperty; } - protected static int? Compare(EntityProperty propertyValue, EntityProperty filterValue) + protected static int? Compare(StubEntityProperty propertyValue, StubEntityProperty filterValue) { - if (propertyValue == null || filterValue == null) + if (propertyValue is null || filterValue is null) return null; - else if (propertyValue.PropertyAsObject == null && filterValue.PropertyAsObject == null) + else if (propertyValue.Value is null && filterValue.Value is null) return 0; - else if (propertyValue.PropertyAsObject == null || filterValue.PropertyAsObject == null) + else if (propertyValue.Value is null || filterValue.Value is null) return null; else - switch (propertyValue.PropertyType) + switch (propertyValue.Type) { - case EdmType.Int32: - return _CompareInt32(propertyValue.Int32Value.Value, filterValue); + case StubEntityPropertyType.Int32: + return _CompareInt32((int)propertyValue.Value, filterValue); - case EdmType.Int64: - return _CompareInt64(propertyValue.Int64Value.Value, filterValue); + case StubEntityPropertyType.Int64: + return _CompareInt64((long)propertyValue.Value, filterValue); - case EdmType.Double: - return _CompareDouble(propertyValue.DoubleValue.Value, filterValue); + case StubEntityPropertyType.Double: + return _CompareDouble((double)propertyValue.Value, filterValue); - case EdmType.Boolean: - return _CompareBoolean(propertyValue.BooleanValue.Value, filterValue); + case StubEntityPropertyType.Boolean: + return _CompareBoolean((bool)propertyValue.Value, filterValue); - case EdmType.DateTime: - return _CompareDateTimeOffset(propertyValue.DateTimeOffsetValue.Value, filterValue); + case StubEntityPropertyType.DateTime: + return _CompareDateTimeOffset((DateTime)propertyValue.Value, filterValue); - case EdmType.Guid: - return _CompareGuid(propertyValue.GuidValue.Value, filterValue); + case StubEntityPropertyType.Guid: + return _CompareGuid((Guid)propertyValue.Value, filterValue); - case EdmType.Binary: - return _CompareBinary(propertyValue.BinaryValue, filterValue); + case StubEntityPropertyType.Binary: + return _CompareBinary((byte[])propertyValue.Value, filterValue); - case EdmType.String: - return _CompareString(propertyValue.StringValue, filterValue); + case StubEntityPropertyType.String: + return _CompareString((string)propertyValue.Value, filterValue); default: return null; } } - private static int _CompareInt32(int propertyValue, EntityProperty filterValue) - => filterValue.PropertyType == EdmType.Int32 ? propertyValue.CompareTo(filterValue.Int32Value.Value) : MismatchingTypeComparisonResult; + private static int _CompareInt32(int propertyValue, StubEntityProperty filterValue) + => filterValue.Type == StubEntityPropertyType.Int32 ? propertyValue.CompareTo((int)filterValue.Value) : MismatchingTypeComparisonResult; - private static int _CompareInt64(long propertyValue, EntityProperty filterValue) + private static int _CompareInt64(long propertyValue, StubEntityProperty filterValue) { - switch (filterValue.PropertyType) + switch (filterValue.Type) { - case EdmType.Int32: - return propertyValue.CompareTo(filterValue.Int32Value.Value); + case StubEntityPropertyType.Int32: + return propertyValue.CompareTo((int)filterValue.Value); - case EdmType.Int64: - return propertyValue.CompareTo(filterValue.Int64Value.Value); + case StubEntityPropertyType.Int64: + return propertyValue.CompareTo((long)filterValue.Value); default: return MismatchingTypeComparisonResult; } } - private static int _CompareDouble(double propertyValue, EntityProperty filterValue) + private static int _CompareDouble(double propertyValue, StubEntityProperty filterValue) { - switch (filterValue.PropertyType) + switch (filterValue.Type) { - case EdmType.Int32: - return propertyValue.CompareTo(filterValue.Int32Value.Value); + case StubEntityPropertyType.Int32: + return propertyValue.CompareTo((int)filterValue.Value); - case EdmType.Int64: - return propertyValue.CompareTo(filterValue.Int64Value.Value); + case StubEntityPropertyType.Int64: + return propertyValue.CompareTo((long)filterValue.Value); - case EdmType.Double: - return propertyValue.CompareTo(filterValue.DoubleValue.Value); + case StubEntityPropertyType.Double: + return propertyValue.CompareTo((double)filterValue.Value); default: return MismatchingTypeComparisonResult; } } - private static int _CompareBoolean(bool propertyValue, EntityProperty filterValue) - => filterValue.PropertyType == EdmType.Boolean ? propertyValue.CompareTo(filterValue.BooleanValue.Value) : MismatchingTypeComparisonResult; + private static int _CompareBoolean(bool propertyValue, StubEntityProperty filterValue) + => filterValue.Type == StubEntityPropertyType.Boolean ? propertyValue.CompareTo((bool)filterValue.Value) : MismatchingTypeComparisonResult; - private static int _CompareDateTimeOffset(DateTimeOffset propertyValue, EntityProperty filterValue) - => filterValue.PropertyType == EdmType.DateTime ? propertyValue.CompareTo(filterValue.DateTimeOffsetValue.Value) : MismatchingTypeComparisonResult; + private static int _CompareDateTimeOffset(DateTime propertyValue, StubEntityProperty filterValue) + => filterValue.Type == StubEntityPropertyType.DateTime ? propertyValue.CompareTo((DateTime)filterValue.Value) : MismatchingTypeComparisonResult; - private static int _CompareGuid(Guid propertyValue, EntityProperty filterValue) - => filterValue.PropertyType == EdmType.Guid ? propertyValue.CompareTo(filterValue.GuidValue.Value) : MismatchingTypeComparisonResult; + private static int _CompareGuid(Guid propertyValue, StubEntityProperty filterValue) + => filterValue.Type == StubEntityPropertyType.Guid ? propertyValue.CompareTo((Guid)filterValue.Value) : MismatchingTypeComparisonResult; - private static int _CompareBinary(byte[] propertyValue, EntityProperty filterValue) + private static int _CompareBinary(byte[] propertyValue, StubEntityProperty filterValue) { - if (filterValue.PropertyType == EdmType.Binary) + if (filterValue.Type == StubEntityPropertyType.Binary) { + var compareValue = (byte[])filterValue.Value; + var index = 0; var comparisonResult = 0; - while (comparisonResult == 0 && index < propertyValue.Length && index < filterValue.BinaryValue.Length) + while (comparisonResult == 0 && index < propertyValue.Length && index < compareValue.Length) { - comparisonResult = propertyValue[index].CompareTo(filterValue.BinaryValue[index]); + comparisonResult = propertyValue[index].CompareTo(compareValue[index]); index++; } if (index < propertyValue.Length) return 1; - else if (index < filterValue.BinaryValue.Length) + else if (index < compareValue.Length) return -1; else return comparisonResult; @@ -143,7 +145,7 @@ private static int _CompareBinary(byte[] propertyValue, EntityProperty filterVal return MismatchingTypeComparisonResult; } - private static int _CompareString(string propertyValue, EntityProperty filterValue) - => filterValue.PropertyType == EdmType.String ? StringComparer.Ordinal.Compare(propertyValue, filterValue.StringValue) : MismatchingTypeComparisonResult; + private static int _CompareString(string propertyValue, StubEntityProperty filterValue) + => filterValue.Type == StubEntityPropertyType.String ? StringComparer.Ordinal.Compare(propertyValue, (string)filterValue.Value) : MismatchingTypeComparisonResult; } } \ No newline at end of file diff --git a/CloudStub/FilterParser/FilterTokenParser.cs b/CloudStub/FilterParser/FilterTokenParser.cs index 625484f..1cd9539 100644 --- a/CloudStub/FilterParser/FilterTokenParser.cs +++ b/CloudStub/FilterParser/FilterTokenParser.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using CloudStub.Core; using CloudStub.FilterParser.FilterNodeFactories; -using Microsoft.Azure.Cosmos.Table; namespace CloudStub.FilterParser { @@ -26,7 +26,7 @@ public FilterTokenParser() _rootFilterNodeFactory = rootFilterNodeFactory; } - public Func Parse(IReadOnlyList tokens) + public Func Parse(IReadOnlyList tokens) { if (tokens.Any()) { diff --git a/CloudStub/InMemoryCloudTable.cs b/CloudStub/StubCloudTable.cs similarity index 51% rename from CloudStub/InMemoryCloudTable.cs rename to CloudStub/StubCloudTable.cs index 84c1c09..8642e9b 100644 --- a/CloudStub/InMemoryCloudTable.cs +++ b/CloudStub/StubCloudTable.cs @@ -5,6 +5,8 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using CloudStub.FilterParser; using CloudStub.TableOperations; using Microsoft.Azure.Cosmos; @@ -13,14 +15,19 @@ namespace CloudStub { - public class InMemoryCloudTable : CloudTable + public class StubCloudTable : CloudTable { - private static readonly ConstructorInfo _tableQuerySegmentConstructor = typeof(TableQuerySegment) - .GetTypeInfo() - .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance) - .Single(constructor => constructor.GetParameters().Select(parameter => parameter.ParameterType).SequenceEqual(new[] { typeof(List) })); - private static readonly PropertyInfo _continuationTokenProperty = typeof(TableQuerySegment) - .GetRuntimeProperty(nameof(TableQuerySegment.ContinuationToken)); + const int DefaultPageSize = 1000; + + private static class TableQuerySegmentInfo + { + public static readonly ConstructorInfo TableQuerySegmentConstructor = typeof(TableQuerySegment) + .GetTypeInfo() + .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance) + .Single(constructor => constructor.GetParameters().Select(parameter => parameter.ParameterType).SequenceEqual(new[] { typeof(List) })); + public static readonly PropertyInfo ContinuationTokenProperty = typeof(TableQuerySegment) + .GetRuntimeProperty(nameof(TableQuerySegment.ContinuationToken)); + } private static class TableQuerySegmentInfo { @@ -33,35 +40,21 @@ private static class TableQuerySegmentInfo .GetRuntimeProperty(nameof(TableQuerySegment.ContinuationToken)); } - private readonly object _locker; - private bool _tableExists; + private readonly StubTable _stubTable; private readonly IReadOnlyDictionary _tableOperationExecutors; - private readonly IDictionary> _entitiesByPartitionKey; - public InMemoryCloudTable(string tableName) - : base(new Uri($"https://unit.test/{tableName}")) - { - if (tableName == null) - throw new ArgumentNullException(nameof(tableName)); - else if (tableName.Length == 0) - throw new ArgumentException("The argument must not be empty string.", nameof(tableName)); - - _locker = new object(); - _tableExists = false; - _entitiesByPartitionKey = new SortedDictionary>(StringComparer.Ordinal); - - var context = new TableOperationExecutorContext(this); - _tableOperationExecutors = new Dictionary + public StubCloudTable(StubTable stubTable) + : base(new Uri($"https://unit.test/{stubTable.Name}")) + => (_stubTable, _tableOperationExecutors) = (stubTable, new Dictionary { - { TableOperationType.Insert, new InsertTableOperationExecutor(context) }, - { TableOperationType.InsertOrReplace, new InsertOrReplaceTableOperationExecutor(context) }, - { TableOperationType.InsertOrMerge, new InsertOrMergeTableOperationExecutor(context) }, - { TableOperationType.Replace, new ReplaceTableOperationExecutor(context) }, - { TableOperationType.Merge, new MergeTableOperationExecutor(context) }, - { TableOperationType.Delete, new DeleteTableOperationExecutor(context) }, - { TableOperationType.Retrieve, new RetrieveTableOperationExecutor(context) } - }; - } + { TableOperationType.Insert, new InsertTableOperationExecutor(stubTable) }, + { TableOperationType.InsertOrReplace, new InsertOrReplaceTableOperationExecutor(stubTable) }, + { TableOperationType.InsertOrMerge, new InsertOrMergeTableOperationExecutor(stubTable) }, + { TableOperationType.Replace, new ReplaceTableOperationExecutor(stubTable) }, + { TableOperationType.Merge, new MergeTableOperationExecutor(stubTable) }, + { TableOperationType.Delete, new DeleteTableOperationExecutor(stubTable) }, + { TableOperationType.Retrieve, new RetrieveTableOperationExecutor(stubTable) } + }); public override void Create(IndexingMode? indexingMode, int? throughput = null, int? defaultTimeToLive = null) => Create(null, null, null, throughput, defaultTimeToLive); @@ -78,11 +71,18 @@ public override void Create(TableRequestOptions requestOptions = null, Operation if (!Regex.IsMatch(Name, "^[A-Za-z][A-Za-z0-9]{2,62}$")) throw InvalidResourceNameException(); - lock (_locker) - if (_tableExists) + var result = _stubTable.Create(); + switch (result) + { + case StubTableCreateResult.Success: + break; + + case StubTableCreateResult.TableAlreadyExists: throw TableAlreadyExistsException(); - else - _tableExists = true; + + default: + throw new InvalidOperationException($"Operation result {result} not handled."); + } } public override Task CreateAsync() @@ -115,13 +115,18 @@ public override Task CreateAsync(TableRequestOptions requestOptions, OperationCo if (!Regex.IsMatch(Name, "^[A-Za-z][A-Za-z0-9]{2,62}$")) return Task.FromException(InvalidResourceNameException()); - lock (_locker) - if (_tableExists) + var result = _stubTable.Create(); + switch (result) + { + case StubTableCreateResult.Success: + return Task.CompletedTask; + + case StubTableCreateResult.TableAlreadyExists: return Task.FromException(TableAlreadyExistsException()); - else - _tableExists = true; - return Task.CompletedTask; + default: + return Task.FromException(new InvalidOperationException($"Operation result {result} not handled.")); + } } public override bool CreateIfNotExists(IndexingMode indexingMode, int? throughput = null, int? defaultTimeToLive = null) @@ -129,11 +134,17 @@ public override bool CreateIfNotExists(IndexingMode indexingMode, int? throughpu public override bool CreateIfNotExists(TableRequestOptions requestOptions = null, OperationContext operationContext = null, string serializedIndexingPolicy = null, int? throughput = null, int? defaultTimeToLive = null) { - lock (_locker) + var result = _stubTable.Create(); + switch (result) { - var result = !_tableExists; - _tableExists = true; - return result; + case StubTableCreateResult.Success: + return true; + + case StubTableCreateResult.TableAlreadyExists: + return false; + + default: + throw new InvalidOperationException($"Operation result {result} not handled."); } } @@ -154,11 +165,17 @@ public override Task CreateIfNotExistsAsync(IndexingMode indexingMode, int public override Task CreateIfNotExistsAsync(TableRequestOptions requestOptions, OperationContext operationContext, string serializedIndexingPolicy, int? throughput, int? defaultTimeToLive, CancellationToken cancellationToken) { - lock (_locker) + var result = _stubTable.Create(); + switch (result) { - var result = Task.FromResult(!_tableExists); - _tableExists = true; - return result; + case StubTableCreateResult.Success: + return Task.FromResult(true); + + case StubTableCreateResult.TableAlreadyExists: + return Task.FromResult(false); + + default: + return Task.FromException(new InvalidOperationException($"Operation result {result} not handled.")); } } @@ -172,17 +189,21 @@ public override Task ExistsAsync(TableRequestOptions requestOptions, Opera => ExistsAsync(requestOptions, operationContext, CancellationToken.None); public override Task ExistsAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) - => Task.FromResult(_tableExists); + => Task.FromResult(_stubTable.Exists()); public override void Delete(TableRequestOptions requestOptions = null, OperationContext operationContext = null) { - lock (_locker) + var result = _stubTable.Delete(); + switch (result) { - if (!_tableExists) + case StubTableDeleteResult.Success: + break; + + case StubTableDeleteResult.TableDoesNotExist: throw ResourceNotFoundException(); - _tableExists = false; - _entitiesByPartitionKey.Clear(); + default: + throw new InvalidOperationException($"Operation result {result} not handled."); } } @@ -197,24 +218,33 @@ public override Task DeleteAsync(TableRequestOptions requestOptions, OperationCo public override Task DeleteAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) { - lock (_locker) + var result = _stubTable.Delete(); + switch (result) { - if (!_tableExists) + case StubTableDeleteResult.Success: + return Task.CompletedTask; + + case StubTableDeleteResult.TableDoesNotExist: return Task.FromException(ResourceNotFoundException()); - _tableExists = false; - _entitiesByPartitionKey.Clear(); - return Task.CompletedTask; + default: + return Task.FromException(new InvalidOperationException($"Operation result {result} not handled.")); } } public override bool DeleteIfExists(TableRequestOptions requestOptions = null, OperationContext operationContext = null) { - lock (_locker) + var result = _stubTable.Delete(); + switch (result) { - var result = _tableExists; - _tableExists = false; - return result; + case StubTableDeleteResult.Success: + return true; + + case StubTableDeleteResult.TableDoesNotExist: + return false; + + default: + throw new InvalidOperationException($"Operation result {result} not handled."); } } @@ -229,11 +259,17 @@ public override Task DeleteIfExistsAsync(TableRequestOptions requestOption public override Task DeleteIfExistsAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) { - lock (_locker) + var result = _stubTable.Delete(); + switch (result) { - var result = Task.FromResult(_tableExists); - _tableExists = false; - return result; + case StubTableDeleteResult.Success: + return Task.FromResult(true); + + case StubTableDeleteResult.TableDoesNotExist: + return Task.FromResult(false); + + default: + return Task.FromException(new InvalidOperationException($"Operation result {result} not handled.")); } } @@ -243,15 +279,9 @@ public override TableResult Execute(TableOperation operation, TableRequestOption throw new ArgumentNullException(nameof(operation)); if (_tableOperationExecutors.TryGetValue(operation.OperationType, out var tableOperationExecutor)) - lock (_locker) - { - var exception = tableOperationExecutor.Validate(operation, operationContext); - if (exception != null) - throw exception; - return tableOperationExecutor.Execute(operation, operationContext); - } - - throw new NotImplementedException(); + return tableOperationExecutor.Execute(operation, operationContext); + else + throw new InvalidOperationException($"Operation type {operation.OperationType} not handled."); } public override Task ExecuteAsync(TableOperation operation) @@ -266,15 +296,24 @@ public override Task ExecuteAsync(TableOperation operation, TableRe public override Task ExecuteAsync(TableOperation operation, TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) { if (_tableOperationExecutors.TryGetValue(operation.OperationType, out var tableOperationExecutor)) - lock (_locker) + try { - var exception = tableOperationExecutor.Validate(operation, operationContext); - if (exception != null) - return Task.FromException(exception); return Task.FromResult(tableOperationExecutor.Execute(operation, operationContext)); } - - return Task.FromException(new NotImplementedException()); + catch (StorageException storageException) + { + return Task.FromException(storageException); + } + catch (InvalidOperationException invalidOperationException) + { + return Task.FromException(invalidOperationException); + } + catch (ArgumentException argumentException) + { + return Task.FromException(argumentException); + } + else + throw new NotImplementedException($"Operation type {operation.OperationType} not handled."); } public override TableBatchResult ExecuteBatch(TableBatchOperation batch, TableRequestOptions requestOptions = null, OperationContext operationContext = null) @@ -282,26 +321,47 @@ public override TableBatchResult ExecuteBatch(TableBatchOperation batch, TableRe if (batch == null) throw new ArgumentNullException(nameof(batch)); - lock (_locker) - { - var batchOperationException = _ValidateBatchOperation(batch) ?? _ValidateOperationsInBatch(batch, operationContext); - if (batchOperationException != null) - throw batchOperationException; - - var result = new TableBatchResult(); - result.AddRange( - batch - .Select( - tableOperation => _tableOperationExecutors.TryGetValue(tableOperation.OperationType, out var tableOperationExecutor) - ? tableOperationExecutor.Execute( - tableOperation.OperationType == TableOperationType.Retrieve ? _WithoutSelectionList(tableOperation) : tableOperation, - operationContext - ) - : null - ) - ); + var batchOperationException = _ValidateBatchOperation(batch); + if (batchOperationException is object) + throw batchOperationException; - return result; + var stubTableBatchOperation = _stubTable.BatchOperation(); + var callbacks = batch + .Select((operation, operationIndex) => + { + if (_tableOperationExecutors.TryGetValue(operation.OperationType, out var operationExecutor)) + return operationExecutor.BatchCallback(stubTableBatchOperation, operation, operationContext, operationIndex); + else + throw new NotImplementedException($"Operation type {operation.OperationType} not handled."); + }) + .ToList(); + var result = stubTableBatchOperation.Execute(); + + switch (result.OperationResult) + { + case StubTableBatchOperationResult.Success: + case StubTableBatchOperationResult.Failed: + var tableBatchResult = new TableBatchResult(); + tableBatchResult.AddRange(callbacks.Select((callback, callbackIndex) => callback(result.IndividualOperationResults[callbackIndex]))); + return tableBatchResult; + + case StubTableBatchOperationResult.TableDoesNotExist when batch.All(tableOperation => tableOperation.OperationType == TableOperationType.Retrieve): + return new TableBatchResult + { + new TableResult + { + HttpStatusCode = 404, + Etag = null, + Result = null + } + }; + case StubTableBatchOperationResult.TableDoesNotExist when batch.First().OperationType == TableOperationType.Insert: + throw TableDoesNotExistForBatchInsertException(0); + case StubTableBatchOperationResult.TableDoesNotExist: + throw TableDoesNotExistForBatchException(0); + + default: + throw new NotImplementedException($"Operation type {result.OperationResult} not handled."); } } @@ -319,88 +379,123 @@ public override Task ExecuteBatchAsync(TableBatchOperation bat if (batch == null) throw new ArgumentNullException(nameof(batch)); - lock (_locker) - { - var batchOperationException = _ValidateBatchOperation(batch) ?? _ValidateOperationsInBatch(batch, operationContext); - if (batchOperationException != null) - return Task.FromException(batchOperationException); - - var result = new TableBatchResult(); - result.AddRange( - batch - .Select( - tableOperation => _tableOperationExecutors.TryGetValue(tableOperation.OperationType, out var tableOperationExecutor) - ? tableOperationExecutor.Execute( - tableOperation.OperationType == TableOperationType.Retrieve ? _WithoutSelectionList(tableOperation) : tableOperation, - operationContext - ) - : null - ) - ); + var batchOperationException = _ValidateBatchOperation(batch); + if (batchOperationException is object) + return Task.FromException(batchOperationException); - return Task.FromResult(result); + var stubTableBatchOperation = _stubTable.BatchOperation(); + var callbacks = batch + .Select((operation, operationIndex) => + { + if (_tableOperationExecutors.TryGetValue(operation.OperationType, out var operationExecutor)) + return operationExecutor.BatchCallback(stubTableBatchOperation, operation, operationContext, operationIndex); + else + throw new NotImplementedException($"Operation type {operation.OperationType} not handled."); + }) + .ToList(); + var result = stubTableBatchOperation.Execute(); + + switch (result.OperationResult) + { + case StubTableBatchOperationResult.Success: + case StubTableBatchOperationResult.Failed: + var tableBatchResult = new TableBatchResult(); + tableBatchResult.AddRange(callbacks.Select((callback, callbackIndex) => callback(result.IndividualOperationResults[callbackIndex]))); + return Task.FromResult(tableBatchResult); + + case StubTableBatchOperationResult.TableDoesNotExist when batch.All(tableOperation => tableOperation.OperationType == TableOperationType.Retrieve): + return Task.FromResult(new TableBatchResult + { + new TableResult + { + HttpStatusCode = 404, + Etag = null, + Result = null + } + }); + case StubTableBatchOperationResult.TableDoesNotExist when batch.First().OperationType == TableOperationType.Insert: + return Task.FromException(TableDoesNotExistForBatchInsertException(0)); + case StubTableBatchOperationResult.TableDoesNotExist: + return Task.FromException(TableDoesNotExistForBatchException(0)); + + default: + throw new NotImplementedException($"Operation type {result.OperationResult} not handled."); } } - private static Exception _ValidateBatchOperation(TableBatchOperation batch) + public override IEnumerable ExecuteQuery(TableQuery query, TableRequestOptions requestOptions = null, OperationContext operationContext = null) { - if (batch.Count == 0) - return new InvalidOperationException("Cannot execute an empty batch operation"); - if (batch.Count > 100) - return new InvalidOperationException("The maximum number of operations allowed in one batch has been exceeded."); - if (batch.Count > 1 && batch.Where(operation => operation.OperationType == TableOperationType.Retrieve).Skip(1).Any()) - return new ArgumentException("A batch transaction with a retrieve operation cannot contain any other operations."); - if (batch.GroupBy(operation => operation.GetPartitionKey()).Skip(1).Any()) - return new ArgumentException("All entities in a given batch must have the same partition key."); - - int? duplicateIndex = null; - var operationIndex = 0; - var addedRowKeys = new HashSet(StringComparer.Ordinal); - while (operationIndex < batch.Count && duplicateIndex == null) - if (batch[operationIndex].Entity != null && !addedRowKeys.Add(batch[operationIndex].Entity.RowKey)) - duplicateIndex = operationIndex; - else - operationIndex++; - if (duplicateIndex != null) - return MultipleOperationsChangeSameEntityException(duplicateIndex.Value); - - return null; - } + var stubTableQuery = new StubTableQuery + { + Filter = _GetFilter(query.FilterString), + PageSize = query.TakeCount ?? DefaultPageSize, + SelectedProperties = query.SelectColumns + }; + var continuationToken = default(StubTableQueryContinuationToken); - private Exception _ValidateOperationsInBatch(TableBatchOperation batch, OperationContext operationContext) - => batch - .Select((tableOperation, tableOperationIndex) => - _tableOperationExecutors.TryGetValue(tableOperation.OperationType, out var tableOperationExecutor) - ? tableOperationExecutor.ValidateForBatch(tableOperation, operationContext, tableOperationIndex) - : null - ) - .FirstOrDefault(exception => exception != null); + var entities = new List(); + do + { + var result = _stubTable.Query(stubTableQuery, continuationToken); + switch (result.OperationResult) + { + case StubTableQueryResult.Success: + continuationToken = result.ContinuationToken; + entities.AddRange(result.Entities.Select(stubEntity => + { + var dynamicEntity = _GetDynamicEntity(stubEntity); + _EnsurePropertiesExist(dynamicEntity.Properties, query.SelectColumns); + return dynamicEntity; + })); + break; + + default: + throw new NotImplementedException($"Operation type {result.OperationResult} not handled."); - private TableOperation _WithoutSelectionList(TableOperation tableOperation) - => TableOperation.Retrieve(tableOperation.GetPartitionKey(), tableOperation.GetRowKey(), tableOperation.GetEntityResolver()); + } + } while (continuationToken is object && (stubTableQuery.PageSize is null || entities.Count < stubTableQuery.PageSize)); + if (stubTableQuery.PageSize is object && entities.Count > stubTableQuery.PageSize) + entities.RemoveRange(stubTableQuery.PageSize.Value, entities.Count - stubTableQuery.PageSize.Value); - public override IEnumerable ExecuteQuery(TableQuery query, TableRequestOptions requestOptions = null, OperationContext operationContext = null) - { - lock (_locker) - return _QueryAllEntities(query.FilterString, query.TakeCount, query.SelectColumns).ToList(); + return entities; } public override IEnumerable ExecuteQuery(TableQuery query, EntityResolver resolver, TableRequestOptions requestOptions = null, OperationContext operationContext = null) { - TResult GetConcreteEntity(DynamicTableEntity existingEntity) => + TResult GetConcreteEntity(StubEntity stubEntity) => resolver( - existingEntity.PartitionKey, - existingEntity.RowKey, - existingEntity.Timestamp, - existingEntity.Properties, - existingEntity.ETag + stubEntity.PartitionKey, + stubEntity.RowKey, + stubEntity.Timestamp.Value, + _EnsurePropertiesExist(_GetEntityProperties(stubEntity.Properties), query.SelectColumns), + stubEntity.ETag ); - lock (_locker) + var stubTableQuery = new StubTableQuery { - var result = _QueryAllEntities(query.FilterString, query.TakeCount, query.SelectColumns); - return result.Select(GetConcreteEntity).ToList(); - } + Filter = _GetFilter(query.FilterString), + PageSize = query.TakeCount ?? DefaultPageSize, + SelectedProperties = query.SelectColumns + }; + var continuationToken = default(StubTableQueryContinuationToken); + + var entities = new List(); + do + { + var result = _stubTable.Query(stubTableQuery, continuationToken); + switch (result.OperationResult) + { + case StubTableQueryResult.Success: + continuationToken = result.ContinuationToken; + entities.AddRange(result.Entities.Select(GetConcreteEntity)); + break; + + default: + throw new NotImplementedException($"Operation type {result.OperationResult} not handled."); + } + } while (continuationToken is object); + + return entities; } public override IEnumerable ExecuteQuery(TableQuery query, TableRequestOptions requestOptions = null, OperationContext operationContext = null) @@ -421,10 +516,45 @@ public override IEnumerable ExecuteQuery(TableQuery< public override TableQuerySegment ExecuteQuerySegmented(TableQuery query, TableContinuationToken token, TableRequestOptions requestOptions = null, OperationContext operationContext = null) { - lock (_locker) + var stubTableQuery = new StubTableQuery { - var result = _QueryEntitiesSegmented(query.FilterString, query.TakeCount, query.SelectColumns, token); - return _CreateTableQuerySegment(result); + Filter = _GetFilter(query.FilterString), + PageSize = query.TakeCount ?? DefaultPageSize, + SelectedProperties = query.SelectColumns + }; + var continuationToken = token is null ? default : new StubTableQueryContinuationToken(token.NextPartitionKey, token.NextRowKey); + + var result = _stubTable.Query(stubTableQuery, continuationToken); + switch (result.OperationResult) + { + case StubTableQueryResult.Success: + var resultSegment = (TableQuerySegment)TableQuerySegmentInfo.TableQuerySegmentConstructor.Invoke( + new[] + { + result + .Entities + .Select(stubEntity => + { + var dynamicEntity =_GetDynamicEntity(stubEntity); + _EnsurePropertiesExist(dynamicEntity.Properties, query.SelectColumns); + return dynamicEntity; + }) + .ToList() + } + ); + + if (result.ContinuationToken is object) + TableQuerySegmentInfo.ContinuationTokenProperty.SetValue(resultSegment, new TableContinuationToken + { + NextPartitionKey = result.ContinuationToken.LastPartitionKey, + NextRowKey = result.ContinuationToken.LastRowKey, + TargetLocation = StorageLocation.Primary + }); + + return resultSegment; + + default: + throw new NotImplementedException($"Operation type {result.OperationResult} not handled."); } } @@ -439,20 +569,41 @@ public override TableQuerySegment ExecuteQuerySegmented(Tabl public override TableQuerySegment ExecuteQuerySegmented(TableQuery query, EntityResolver resolver, TableContinuationToken token, TableRequestOptions requestOptions = null, OperationContext operationContext = null) { - TResult GetConcreteEntity(DynamicTableEntity existingEntity) => + TResult GetConcreteEntity(StubEntity stubEntity) => resolver( - existingEntity.PartitionKey, - existingEntity.RowKey, - existingEntity.Timestamp, - existingEntity.Properties, - existingEntity.ETag + stubEntity.PartitionKey, + stubEntity.RowKey, + stubEntity.Timestamp.Value, + _EnsurePropertiesExist(_GetEntityProperties(stubEntity.Properties), query.SelectColumns), + stubEntity.ETag ); - lock (_locker) + var stubTableQuery = new StubTableQuery { - var result = _QueryEntitiesSegmented(query.FilterString, query.TakeCount, query.SelectColumns, token); - var typedResult = new QueryResult(result.Entities.Select(GetConcreteEntity).ToList(), result.ContinuationToken); - return _CreateTableQuerySegment(typedResult); + Filter = _GetFilter(query.FilterString), + PageSize = query.TakeCount ?? DefaultPageSize, + SelectedProperties = query.SelectColumns + }; + var continuationToken = token is null ? default : new StubTableQueryContinuationToken(token.NextPartitionKey, token.NextRowKey); + + var result = _stubTable.Query(stubTableQuery, continuationToken); + switch (result.OperationResult) + { + case StubTableQueryResult.Success: + var resultSegment = (TableQuerySegment)TableQuerySegmentInfo.TableQuerySegmentConstructor.Invoke(new[] { result.Entities.Select(GetConcreteEntity).ToList() }); + + if (result.ContinuationToken is object) + TableQuerySegmentInfo.ContinuationTokenProperty.SetValue(resultSegment, new TableContinuationToken + { + NextPartitionKey = result.ContinuationToken.LastPartitionKey, + NextRowKey = result.ContinuationToken.LastRowKey, + TargetLocation = StorageLocation.Primary + }); + + return resultSegment; + + default: + throw new NotImplementedException($"Operation type {result.OperationResult} not handled."); } } @@ -476,10 +627,45 @@ public override Task> ExecuteQuerySegmente public override Task> ExecuteQuerySegmentedAsync(TableQuery query, TableContinuationToken token, TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) { - lock (_locker) + var stubTableQuery = new StubTableQuery { - var result = _QueryEntitiesSegmented(query.FilterString, query.TakeCount, query.SelectColumns, token); - return Task.FromResult(_CreateTableQuerySegment(result)); + Filter = _GetFilter(query.FilterString), + PageSize = query.TakeCount ?? DefaultPageSize, + SelectedProperties = query.SelectColumns + }; + var continuationToken = token is null ? default : new StubTableQueryContinuationToken(token.NextPartitionKey, token.NextRowKey); + + var result = _stubTable.Query(stubTableQuery, continuationToken); + switch (result.OperationResult) + { + case StubTableQueryResult.Success: + var resultSegment = (TableQuerySegment)TableQuerySegmentInfo.TableQuerySegmentConstructor.Invoke( + new[] + { + result + .Entities + .Select(stubEntity => + { + var dynamicEntity = _GetDynamicEntity(stubEntity); + _EnsurePropertiesExist(dynamicEntity.Properties, query.SelectColumns); + return dynamicEntity; + }) + .ToList() + } + ); + + if (result.ContinuationToken is object) + TableQuerySegmentInfo.ContinuationTokenProperty.SetValue(resultSegment, new TableContinuationToken + { + NextPartitionKey = result.ContinuationToken.LastPartitionKey, + NextRowKey = result.ContinuationToken.LastRowKey, + TargetLocation = StorageLocation.Primary + }); + + return Task.FromResult(resultSegment); + + default: + throw new NotImplementedException($"Operation type {result.OperationResult} not handled."); } } @@ -513,20 +699,41 @@ public override Task> ExecuteQuerySegmentedAsync> ExecuteQuerySegmentedAsync(TableQuery query, EntityResolver resolver, TableContinuationToken token, TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) { - TResult GetConcreteEntity(DynamicTableEntity existingEntity) => + TResult GetConcreteEntity(StubEntity stubEntity) => resolver( - existingEntity.PartitionKey, - existingEntity.RowKey, - existingEntity.Timestamp, - existingEntity.Properties, - existingEntity.ETag + stubEntity.PartitionKey, + stubEntity.RowKey, + stubEntity.Timestamp.Value, + _EnsurePropertiesExist(_GetEntityProperties(stubEntity.Properties), query.SelectColumns), + stubEntity.ETag ); - lock (_locker) + var stubTableQuery = new StubTableQuery + { + Filter = _GetFilter(query.FilterString), + PageSize = query.TakeCount ?? DefaultPageSize, + SelectedProperties = query.SelectColumns + }; + var continuationToken = token is null ? default : new StubTableQueryContinuationToken(token.NextPartitionKey, token.NextRowKey); + + var result = _stubTable.Query(stubTableQuery, continuationToken); + switch (result.OperationResult) { - var result = _QueryEntitiesSegmented(query.FilterString, query.TakeCount, query.SelectColumns, token); - var typedResult = new QueryResult(result.Entities.Select(GetConcreteEntity).ToList(), result.ContinuationToken); - return Task.FromResult(_CreateTableQuerySegment(typedResult)); + case StubTableQueryResult.Success: + var resultSegment = (TableQuerySegment)TableQuerySegmentInfo.TableQuerySegmentConstructor.Invoke(new[] { result.Entities.Select(GetConcreteEntity).ToList() }); + + if (result.ContinuationToken is object) + TableQuerySegmentInfo.ContinuationTokenProperty.SetValue(resultSegment, new TableContinuationToken + { + NextPartitionKey = result.ContinuationToken.LastPartitionKey, + NextRowKey = result.ContinuationToken.LastRowKey, + TargetLocation = StorageLocation.Primary + }); + + return Task.FromResult(resultSegment); + + default: + throw new NotImplementedException($"Operation type {result.OperationResult} not handled."); } } @@ -579,112 +786,94 @@ public override Task SetPermissionsAsync(TablePermissions permissions, TableRequ public override Task SetPermissionsAsync(TablePermissions permissions, TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) => throw new NotImplementedException(); - private QueryResult _QueryEntitiesSegmented(string filterString, int? takeCount, IEnumerable selectColumns, TableContinuationToken continuationToken) + private static DynamicTableEntity _GetDynamicEntity(StubEntity stubEntity) + => new DynamicTableEntity + { + PartitionKey = stubEntity.PartitionKey, + RowKey = stubEntity.RowKey, + ETag = stubEntity.ETag, + Timestamp = stubEntity.Timestamp.Value, + Properties = _GetEntityProperties(stubEntity.Properties) + }; + + private static IDictionary _GetEntityProperties(IEnumerable> properties) { - const int defaultPageSize = 1000; - - var projection = selectColumns != null && selectColumns.Any() - ? entity => entity.Clone(selectColumns) - : new Func(CloudTableExtensions.Clone); - - var allEntities = _entitiesByPartitionKey.Values.SelectMany(partitionedEntities => partitionedEntities.Values); - var filteredEntities = _ApplyFilter(allEntities, filterString).Select(projection); - var remainingEntities = continuationToken != null - ? filteredEntities - .SkipWhile( - entity => string.Compare(entity.PartitionKey, continuationToken.NextPartitionKey, StringComparison.Ordinal) <= 0 - && string.Compare(entity.RowKey, continuationToken.NextRowKey, StringComparison.Ordinal) < 0 - ) - : filteredEntities; - - var pageSize = takeCount != null ? Math.Min(defaultPageSize, takeCount.Value) : defaultPageSize; - var entitiesPage = remainingEntities.Take(pageSize + 1).ToList(); - var lastEntity = default(DynamicTableEntity); - if (entitiesPage.Count > pageSize) + return properties.ToDictionary(pair => pair.Key, pair => _GetEntityProperty(pair.Value), StringComparer.Ordinal); + + EntityProperty _GetEntityProperty(StubEntityProperty property) { - lastEntity = entitiesPage[pageSize]; - entitiesPage.RemoveAt(pageSize); - } - var nextContinuationToken = lastEntity != null - ? new TableContinuationToken + switch (property.Type) { - NextPartitionKey = lastEntity.PartitionKey, - NextRowKey = lastEntity.RowKey, - TargetLocation = StorageLocation.Primary - } - : null; + case StubEntityPropertyType.Binary: + return EntityProperty.GeneratePropertyForByteArray((byte[])property.Value); - return new QueryResult(entitiesPage, nextContinuationToken); - } + case StubEntityPropertyType.Boolean: + return EntityProperty.GeneratePropertyForBool((bool)property.Value); - private IEnumerable _QueryAllEntities(string filterString, int? takeCount, IEnumerable selectColumns) - { - var projection = selectColumns != null && selectColumns.Any() - ? entity => entity.Clone(selectColumns) - : new Func(CloudTableExtensions.Clone); + case StubEntityPropertyType.Int32: + return EntityProperty.GeneratePropertyForInt((int)property.Value); - var allEntities = _entitiesByPartitionKey.Values.SelectMany(partitionedEntities => partitionedEntities.Values); - var filteredEntities = _ApplyFilter(allEntities, filterString).Select(projection); - var entitiesPage = takeCount.HasValue ? filteredEntities.Take(takeCount.Value) : filteredEntities; + case StubEntityPropertyType.Int64: + return EntityProperty.GeneratePropertyForLong((long)property.Value); - return entitiesPage; - } + case StubEntityPropertyType.Double: + return EntityProperty.GeneratePropertyForDouble((double)property.Value); - private IEnumerable _ApplyFilter(IEnumerable entities, string filterString) - { - var scanner = new FilterTokenScanner(); - var tokens = scanner.Scan(filterString ?? string.Empty); - var parser = new FilterTokenParser(); - var predicate = parser.Parse(tokens); - var result = entities.Where(predicate); - return result; - } + case StubEntityPropertyType.Guid: + return EntityProperty.GeneratePropertyForGuid((Guid)property.Value); - private static TableQuerySegment _CreateTableQuerySegment(QueryResult result) - { - var resultSegment = (TableQuerySegment)_tableQuerySegmentConstructor.Invoke(new[] { result.Entities }); + case StubEntityPropertyType.DateTime: + return EntityProperty.GeneratePropertyForDateTimeOffset((DateTime)property.Value); - if (result.ContinuationToken != null) - _continuationTokenProperty.SetValue(resultSegment, result.ContinuationToken); + case StubEntityPropertyType.String: + return EntityProperty.GeneratePropertyForString((string)property.Value); - return resultSegment; + default: + throw new NotImplementedException($"Operation type {property.Type} not handled."); + } + } } - private static TableQuerySegment _CreateTableQuerySegment(QueryResult result) + private static IDictionary _EnsurePropertiesExist(IDictionary properties, IEnumerable selectedProperties) { - var resultSegment = (TableQuerySegment)TableQuerySegmentInfo.TableQuerySegmentConstructor.Invoke(new[] { result.Entities }); - - if (result.ContinuationToken != null) - TableQuerySegmentInfo.ContinuationTokenProperty.SetValue(resultSegment, result.ContinuationToken); - - return resultSegment; + if (selectedProperties is object) + foreach (var selectedProperty in selectedProperties) + if (!properties.ContainsKey(selectedProperty)) + properties.Add(selectedProperty, EntityProperty.CreateEntityPropertyFromObject(null)); + return properties; } - private sealed class TableOperationExecutorContext : ITableOperationExecutorContext + private static Exception _ValidateBatchOperation(TableBatchOperation batch) { - private readonly InMemoryCloudTable _inMemoryCloudTable; - - public TableOperationExecutorContext(InMemoryCloudTable inMemoryCloudTable) - => _inMemoryCloudTable = inMemoryCloudTable; + if (batch.Count == 0) + return new InvalidOperationException("Cannot execute an empty batch operation"); + if (batch.Count > 100) + return new InvalidOperationException("The maximum number of operations allowed in one batch has been exceeded."); + if (batch.Count > 1 && batch.Where(operation => operation.OperationType == TableOperationType.Retrieve).Skip(1).Any()) + return new ArgumentException("A batch transaction with a retrieve operation cannot contain any other operations."); + if (batch.GroupBy(operation => operation.GetPartitionKey()).Skip(1).Any()) + return new ArgumentException("All entities in a given batch must have the same partition key."); - public bool TableExists - => _inMemoryCloudTable._tableExists; + int? duplicateIndex = null; + var operationIndex = 0; + var addedRowKeys = new HashSet(StringComparer.Ordinal); + while (operationIndex < batch.Count && duplicateIndex == null) + if (batch[operationIndex].Entity != null && !addedRowKeys.Add(batch[operationIndex].Entity.RowKey)) + duplicateIndex = operationIndex; + else + operationIndex++; + if (duplicateIndex != null) + return MultipleOperationsChangeSameEntityException(duplicateIndex.Value); - public IDictionary> Entities - => _inMemoryCloudTable._entitiesByPartitionKey; + return null; } - private sealed class QueryResult + private static Func _GetFilter(string filterString) { - public QueryResult(IReadOnlyList entities, TableContinuationToken continuationToken) - { - Entities = entities; - ContinuationToken = continuationToken; - } - - public IReadOnlyList Entities { get; } - - public TableContinuationToken ContinuationToken { get; } + var scanner = new FilterTokenScanner(); + var tokens = scanner.Scan(filterString ?? string.Empty); + var parser = new FilterTokenParser(); + return parser.Parse(tokens); } } } \ No newline at end of file diff --git a/CloudStub/TableOperations/AddTableOperationExecutor.cs b/CloudStub/TableOperations/AddTableOperationExecutor.cs deleted file mode 100644 index ab91a0e..0000000 --- a/CloudStub/TableOperations/AddTableOperationExecutor.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Azure.Cosmos.Table; -using static CloudStub.StorageExceptionFactory; - -namespace CloudStub.TableOperations -{ - internal abstract class AddTableOperationExecutor : TableOperationExecutor - { - protected AddTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) - { - } - - protected abstract Exception GetMissingPartitionKeyException(); - - protected abstract Exception GetMissingRowKeyException(); - - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) - { - if (!Context.TableExists) - return TableDoesNotExistException(); - - var entity = tableOperation.Entity; - - if (entity.PartitionKey == null) - return GetMissingPartitionKeyException(); - if (entity.PartitionKey.Length > (1 << 10)) - return PropertyValueTooLargeException(); - if (!entity.PartitionKey.All(IsValidKeyCharacter)) - return InvalidPartitionKeyException(entity.PartitionKey); - - if (entity.RowKey == null) - return GetMissingRowKeyException(); - if (entity.RowKey.Length > (1 << 10)) - return PropertyValueTooLargeException(); - if (!entity.RowKey.All(IsValidKeyCharacter)) - return InvalidRowKeyException(entity.RowKey); - - var entityPropertyException = ValidateEntityProperties(entity); - if (entityPropertyException != null) - return entityPropertyException; - - return null; - } - - protected IDictionary GetPartition(ITableEntity entity) - { - if (!Context.Entities.TryGetValue(entity.PartitionKey, out var entitiesByRowKey)) - { - entitiesByRowKey = new SortedList(StringComparer.Ordinal); - Context.Entities.Add(entity.PartitionKey, entitiesByRowKey); - } - - return entitiesByRowKey; - } - } -} \ No newline at end of file diff --git a/CloudStub/TableOperations/DeleteTableOperationExecutor.cs b/CloudStub/TableOperations/DeleteTableOperationExecutor.cs index 176baaf..a0fde2c 100644 --- a/CloudStub/TableOperations/DeleteTableOperationExecutor.cs +++ b/CloudStub/TableOperations/DeleteTableOperationExecutor.cs @@ -1,4 +1,6 @@ using System; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using Microsoft.Azure.Cosmos.Table; using static CloudStub.StorageExceptionFactory; @@ -6,83 +8,95 @@ namespace CloudStub.TableOperations { internal sealed class DeleteTableOperationExecutor : TableOperationExecutor { - public DeleteTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) + public DeleteTableOperationExecutor(StubTable stubTable) + : base(stubTable) { } - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) + public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - if (!Context.TableExists) - return TableDoesNotExistException(); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Delete requires a valid PartitionKey"); + throw new ArgumentNullException("Delete requires a valid PartitionKey"); var partitionKeyException = ValidateKeyProperty(tableOperation.Entity.PartitionKey); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Delete requires a valid RowKey"); + throw new ArgumentNullException("Delete requires a valid RowKey"); var rowKeyException = ValidateKeyProperty(tableOperation.Entity.RowKey); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; + + var result = StubTable.Delete(GetStubEntity(tableOperation.Entity)); + switch (result.OperationResult) + { + case StubTableDeleteOperationResult.Success: + return _GetTableResult(result); - if (!Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) - || !partition.TryGetValue(tableOperation.Entity.RowKey, out var existingEntity)) - return ResourceNotFoundException(); + case StubTableDeleteOperationResult.TableDoesNotExist: + throw TableDoesNotExistException(); - if (tableOperation.Entity.ETag != "*" && !StringComparer.OrdinalIgnoreCase.Equals(tableOperation.Entity.ETag, existingEntity.ETag)) - return PreconditionFailedException(); + case StubTableDeleteOperationResult.EntityDoesNotExists: + throw ResourceNotFoundException(); - return null; + case StubTableDeleteOperationResult.EtagsDoNotMatch: + throw PreconditionFailedException(); + + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } } - public override Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex) + public override Func BatchCallback(StubTableBatchOperation batchOperation, TableOperation tableOperation, OperationContext operationContext, int operationIndex) { - if (!Context.TableExists) - return TableDoesNotExistForBatchException(operationIndex); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Delete requires a valid PartitionKey"); + throw new ArgumentNullException("Delete requires a valid PartitionKey"); var partitionKeyException = ValidateBatckKeyProperty(tableOperation.Entity.PartitionKey, operationIndex); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Delete requires a valid RowKey"); + throw new ArgumentNullException("Delete requires a valid RowKey"); var rowKeyException = ValidateBatckKeyProperty(tableOperation.Entity.RowKey, operationIndex); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; + + batchOperation.Delete(GetStubEntity(tableOperation.Entity)); + return operationResult => + { + var result = (StubTableDeleteOperationDataResult)operationResult; + switch (result.OperationResult) + { + case StubTableDeleteOperationResult.Success: + return _GetTableResult(result); - if (!Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) - || !partition.TryGetValue(tableOperation.Entity.RowKey, out var existingEntity)) - return ResourceNotFoundForBatchException(); + case StubTableDeleteOperationResult.TableDoesNotExist: + throw TableDoesNotExistForBatchException(operationIndex); - if (tableOperation.Entity.ETag != "*" && !StringComparer.OrdinalIgnoreCase.Equals(tableOperation.Entity.ETag, existingEntity.ETag)) - return PreconditionFailedForBatchException(operationIndex); + case StubTableDeleteOperationResult.EntityDoesNotExists: + throw ResourceNotFoundForBatchException(); - return null; - } + case StubTableDeleteOperationResult.EtagsDoNotMatch: + throw PreconditionFailedForBatchException(operationIndex); - public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) - { - var partition = Context.Entities[tableOperation.Entity.PartitionKey]; - var existingEntity = partition[tableOperation.Entity.RowKey]; - partition.Remove(tableOperation.Entity.RowKey); + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } + }; + } - return new TableResult + private static TableResult _GetTableResult(StubTableDeleteOperationDataResult result) + => new TableResult { HttpStatusCode = 204, Etag = null, Result = new TableEntity { - PartitionKey = existingEntity.PartitionKey, - RowKey = existingEntity.RowKey, - ETag = existingEntity.ETag, - Timestamp = default(DateTimeOffset) + PartitionKey = result.Entity.PartitionKey, + RowKey = result.Entity.RowKey, + ETag = result.Entity.ETag, + Timestamp = default } }; - } } } \ No newline at end of file diff --git a/CloudStub/TableOperations/EditTableOperationExecutor.cs b/CloudStub/TableOperations/EditTableOperationExecutor.cs deleted file mode 100644 index 13d406e..0000000 --- a/CloudStub/TableOperations/EditTableOperationExecutor.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Linq; -using Microsoft.Azure.Cosmos.Table; -using static CloudStub.StorageExceptionFactory; - -namespace CloudStub.TableOperations -{ - internal abstract class EditTableOperationExecutor : TableOperationExecutor - { - protected EditTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) - { - } - - protected abstract string PartitionKeyErrorMessage { get; } - - protected abstract string RowKeyErrorMessage { get; } - - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) - { - if (!Context.TableExists) - return TableDoesNotExistException(); - - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException(PartitionKeyErrorMessage); - if (!tableOperation.Entity.PartitionKey.All(IsValidKeyCharacter)) - return InvalidPartitionKeyException(tableOperation.Entity.PartitionKey); - - if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException(RowKeyErrorMessage); - if (!tableOperation.Entity.RowKey.All(IsValidKeyCharacter)) - return InvalidRowKeyException(tableOperation.Entity.RowKey); - - var entityPropertyException = ValidateEntityProperties(tableOperation.Entity); - if (entityPropertyException != null) - return entityPropertyException; - - if (!Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) - || !partition.TryGetValue(tableOperation.Entity.RowKey, out var existingEntity)) - return ResourceNotFoundException(); - - if (tableOperation.Entity.ETag != "*" && !StringComparer.OrdinalIgnoreCase.Equals(tableOperation.Entity.ETag, existingEntity.ETag)) - return PreconditionFailedException(); - - return null; - } - } -} \ No newline at end of file diff --git a/CloudStub/TableOperations/ITableOperationExecutorContext.cs b/CloudStub/TableOperations/ITableOperationExecutorContext.cs deleted file mode 100644 index 56b11dc..0000000 --- a/CloudStub/TableOperations/ITableOperationExecutorContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Microsoft.Azure.Cosmos.Table; - -namespace CloudStub.TableOperations -{ - internal interface ITableOperationExecutorContext - { - bool TableExists { get; } - - IDictionary> Entities { get; } - } -} \ No newline at end of file diff --git a/CloudStub/TableOperations/InsertOrMergeTableOperationExecutor.cs b/CloudStub/TableOperations/InsertOrMergeTableOperationExecutor.cs index 0665069..434a412 100644 --- a/CloudStub/TableOperations/InsertOrMergeTableOperationExecutor.cs +++ b/CloudStub/TableOperations/InsertOrMergeTableOperationExecutor.cs @@ -1,5 +1,6 @@ using System; -using System.Collections.Generic; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using Microsoft.Azure.Cosmos.Table; using static CloudStub.StorageExceptionFactory; @@ -7,101 +8,99 @@ namespace CloudStub.TableOperations { internal sealed class InsertOrMergeTableOperationExecutor : TableOperationExecutor { - public InsertOrMergeTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) + public InsertOrMergeTableOperationExecutor(StubTable stubTable) + : base(stubTable) { } - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) + public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - if (!Context.TableExists) - return TableDoesNotExistException(); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Upserts require a valid PartitionKey"); + throw new ArgumentNullException("Upserts require a valid PartitionKey"); if (tableOperation.Entity.PartitionKey.Length > (1 << 10)) - return PropertyValueTooLargeException(); + throw PropertyValueTooLargeException(); var partitionKeyException = ValidateKeyProperty(tableOperation.Entity.PartitionKey); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Upserts require a valid RowKey"); + throw new ArgumentNullException("Upserts require a valid RowKey"); if (tableOperation.Entity.RowKey.Length > (1 << 10)) - return PropertyValueTooLargeException(); + throw PropertyValueTooLargeException(); var rowKeyException = ValidateKeyProperty(tableOperation.Entity.RowKey); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityProperties(tableOperation.Entity); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; + + var result = StubTable.InsertOrMerge(GetStubEntity(tableOperation.Entity)); + switch (result.OperationResult) + { + case StubTableInsertOrMergeOperationResult.Success: + return _GetTableResult(result); + + case StubTableInsertOrMergeOperationResult.TableDoesNotExist: + throw TableDoesNotExistException(); - return null; + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } } - public override Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex) + public override Func BatchCallback(StubTableBatchOperation batchOperation, TableOperation tableOperation, OperationContext operationContext, int operationIndex) { - if (!Context.TableExists) - return TableDoesNotExistForBatchException(operationIndex); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Upserts require a valid PartitionKey"); + throw new ArgumentNullException("Upserts require a valid PartitionKey"); if (tableOperation.Entity.PartitionKey.Length > (1 << 10)) - return PropertyValueTooLargeForBatchException(operationIndex); + throw PropertyValueTooLargeForBatchException(operationIndex); var partitionKeyException = ValidateBatckKeyProperty(tableOperation.Entity.PartitionKey, operationIndex); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Upserts require a valid RowKey"); + throw new ArgumentNullException("Upserts require a valid RowKey"); if (tableOperation.Entity.RowKey.Length > (1 << 10)) - return PropertyValueTooLargeForBatchException(operationIndex); + throw PropertyValueTooLargeForBatchException(operationIndex); var rowKeyException = ValidateBatckKeyProperty(tableOperation.Entity.RowKey, operationIndex); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityPropertiesForBatch(tableOperation.Entity, operationContext, operationIndex); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; - return null; - } + batchOperation.InsertOrMerge(GetStubEntity(tableOperation.Entity)); + return operationResult => + { + var result = (StubTableInsertOrMergeOperationDataResult)operationResult; + switch (result.OperationResult) + { + case StubTableInsertOrMergeOperationResult.Success: + return _GetTableResult(result); - public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) - { - var dynamicEntity = GetDynamicEntity(tableOperation.Entity); - var partition = _GetPartition(dynamicEntity); + case StubTableInsertOrMergeOperationResult.TableDoesNotExist: + throw TableDoesNotExistForBatchException(operationIndex); - if (partition.TryGetValue(dynamicEntity.RowKey, out var existingEntity)) - foreach (var property in existingEntity.Properties) - if (!dynamicEntity.Properties.ContainsKey(property.Key)) - dynamicEntity.Properties.Add(property); - partition[dynamicEntity.RowKey] = dynamicEntity; + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } + }; + } - return new TableResult + private static TableResult _GetTableResult(StubTableInsertOrMergeOperationDataResult result) + => new TableResult { HttpStatusCode = 204, - Etag = dynamicEntity.ETag, + Etag = result.Entity.ETag, Result = new TableEntity { - PartitionKey = dynamicEntity.PartitionKey, - RowKey = dynamicEntity.RowKey, - ETag = dynamicEntity.ETag, + PartitionKey = result.Entity.PartitionKey, + RowKey = result.Entity.RowKey, + ETag = result.Entity.ETag, Timestamp = default(DateTimeOffset) } }; - } - - private IDictionary _GetPartition(ITableEntity entity) - { - if (!Context.Entities.TryGetValue(entity.PartitionKey, out var entitiesByRowKey)) - { - entitiesByRowKey = new SortedList(StringComparer.Ordinal); - Context.Entities.Add(entity.PartitionKey, entitiesByRowKey); - } - - return entitiesByRowKey; - } } } \ No newline at end of file diff --git a/CloudStub/TableOperations/InsertOrReplaceTableOperationExecutor.cs b/CloudStub/TableOperations/InsertOrReplaceTableOperationExecutor.cs index a1cdaa7..635f1b9 100644 --- a/CloudStub/TableOperations/InsertOrReplaceTableOperationExecutor.cs +++ b/CloudStub/TableOperations/InsertOrReplaceTableOperationExecutor.cs @@ -1,5 +1,6 @@ using System; -using System.Collections.Generic; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using Microsoft.Azure.Cosmos.Table; using static CloudStub.StorageExceptionFactory; @@ -7,96 +8,99 @@ namespace CloudStub.TableOperations { internal sealed class InsertOrReplaceTableOperationExecutor : TableOperationExecutor { - public InsertOrReplaceTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) + public InsertOrReplaceTableOperationExecutor(StubTable stubTable) + : base(stubTable) { } - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) + public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - if (!Context.TableExists) - return TableDoesNotExistException(); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Upserts require a valid PartitionKey"); + throw new ArgumentNullException("Upserts require a valid PartitionKey"); if (tableOperation.Entity.PartitionKey.Length > (1 << 10)) - return PropertyValueTooLargeException(); + throw PropertyValueTooLargeException(); var partitionKeyException = ValidateKeyProperty(tableOperation.Entity.PartitionKey); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Upserts require a valid RowKey"); + throw new ArgumentNullException("Upserts require a valid RowKey"); if (tableOperation.Entity.RowKey.Length > (1 << 10)) - return PropertyValueTooLargeException(); + throw PropertyValueTooLargeException(); var rowKeyException = ValidateKeyProperty(tableOperation.Entity.RowKey); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityProperties(tableOperation.Entity); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; + + var result = StubTable.InsertOrReplace(GetStubEntity(tableOperation.Entity)); + switch (result.OperationResult) + { + case StubTableInsertOrReplaceOperationResult.Success: + return _GetTableResult(result); + + case StubTableInsertOrReplaceOperationResult.TableDoesNotExist: + throw TableDoesNotExistException(); - return null; + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } } - public override Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex) + public override Func BatchCallback(StubTableBatchOperation batchOperation, TableOperation tableOperation, OperationContext operationContext, int operationIndex) { - if (!Context.TableExists) - return TableDoesNotExistForBatchException(operationIndex); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Upserts require a valid PartitionKey"); + throw new ArgumentNullException("Upserts require a valid PartitionKey"); if (tableOperation.Entity.PartitionKey.Length > (1 << 10)) - return PropertyValueTooLargeForBatchException(operationIndex); + throw PropertyValueTooLargeForBatchException(operationIndex); var partitionKeyException = ValidateBatckKeyProperty(tableOperation.Entity.PartitionKey, operationIndex); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Upserts require a valid RowKey"); + throw new ArgumentNullException("Upserts require a valid RowKey"); if (tableOperation.Entity.RowKey.Length > (1 << 10)) - return PropertyValueTooLargeForBatchException(operationIndex); + throw PropertyValueTooLargeForBatchException(operationIndex); var rowKeyException = ValidateBatckKeyProperty(tableOperation.Entity.RowKey, operationIndex); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityPropertiesForBatch(tableOperation.Entity, operationContext, operationIndex); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; - return null; - } + batchOperation.InsertOrReplace(GetStubEntity(tableOperation.Entity)); + return operationResult => + { + var result = (StubTableInsertOrReplaceOperationDataResult)operationResult; + switch (result.OperationResult) + { + case StubTableInsertOrReplaceOperationResult.Success: + return _GetTableResult(result); - public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) - { - var dynamicEntity = GetDynamicEntity(tableOperation.Entity); - var partition = _GetPartition(dynamicEntity); - partition[dynamicEntity.RowKey] = dynamicEntity; + case StubTableInsertOrReplaceOperationResult.TableDoesNotExist: + throw TableDoesNotExistForBatchException(operationIndex); - return new TableResult + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } + }; + } + + private static TableResult _GetTableResult(StubTableInsertOrReplaceOperationDataResult result) + => new TableResult { HttpStatusCode = 204, - Etag = dynamicEntity.ETag, + Etag = result.Entity.ETag, Result = new TableEntity { - PartitionKey = dynamicEntity.PartitionKey, - RowKey = dynamicEntity.RowKey, - ETag = dynamicEntity.ETag, + PartitionKey = result.Entity.PartitionKey, + RowKey = result.Entity.RowKey, + ETag = result.Entity.ETag, Timestamp = default(DateTimeOffset) } }; - } - - private IDictionary _GetPartition(ITableEntity entity) - { - if (!Context.Entities.TryGetValue(entity.PartitionKey, out var entitiesByRowKey)) - { - entitiesByRowKey = new SortedList(StringComparer.Ordinal); - Context.Entities.Add(entity.PartitionKey, entitiesByRowKey); - } - - return entitiesByRowKey; - } } } \ No newline at end of file diff --git a/CloudStub/TableOperations/InsertTableOperationExecutor.cs b/CloudStub/TableOperations/InsertTableOperationExecutor.cs index 5ae100f..c2f71fa 100644 --- a/CloudStub/TableOperations/InsertTableOperationExecutor.cs +++ b/CloudStub/TableOperations/InsertTableOperationExecutor.cs @@ -1,6 +1,7 @@ using System; -using System.Collections.Generic; using System.Linq; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using Microsoft.Azure.Cosmos.Table; using static CloudStub.StorageExceptionFactory; @@ -8,87 +9,87 @@ namespace CloudStub.TableOperations { internal sealed class InsertTableOperationExecutor : TableOperationExecutor { - public InsertTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) + public InsertTableOperationExecutor(StubTable stubTable) + : base(stubTable) { } - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) + public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - if (!Context.TableExists) - return TableDoesNotExistException(); - if (tableOperation.Entity.PartitionKey == null) - return PropertiesWithoutValueException(); + throw PropertiesWithoutValueException(); if (tableOperation.Entity.PartitionKey.Length > (1 << 10)) - return PropertyValueTooLargeException(); + throw PropertyValueTooLargeException(); if (!tableOperation.Entity.PartitionKey.All(IsValidKeyCharacter)) - return InvalidPartitionKeyException(tableOperation.Entity.PartitionKey); + throw InvalidPartitionKeyException(tableOperation.Entity.PartitionKey); if (tableOperation.Entity.RowKey == null) - return PropertiesWithoutValueException(); + throw PropertiesWithoutValueException(); if (tableOperation.Entity.RowKey.Length > (1 << 10)) - return PropertyValueTooLargeException(); + throw PropertyValueTooLargeException(); if (!tableOperation.Entity.RowKey.All(IsValidKeyCharacter)) - return InvalidRowKeyException(tableOperation.Entity.RowKey); + throw InvalidRowKeyException(tableOperation.Entity.RowKey); var entityPropertyException = ValidateEntityProperties(tableOperation.Entity); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; + + var result = StubTable.Insert(GetStubEntity(tableOperation.Entity)); + switch (result.OperationResult) + { + case StubTableInsertOperationResult.Success: + return _GetTableResult(result); - if (Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) && partition.ContainsKey(tableOperation.Entity.RowKey)) - return EntityAlreadyExistsException(); + case StubTableInsertOperationResult.TableDoesNotExist: + throw TableDoesNotExistException(); - return null; + case StubTableInsertOperationResult.EntityAlreadyExists: + throw EntityAlreadyExistsException(); + + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } } - public override Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex) + public override Func BatchCallback(StubTableBatchOperation batchOperation, TableOperation tableOperation, OperationContext operationContext, int operationIndex) { - if (!Context.TableExists) - return TableDoesNotExistForBatchInsertException(operationIndex); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Upserts require a valid PartitionKey"); + throw new ArgumentNullException("Upserts require a valid PartitionKey"); if (tableOperation.Entity.PartitionKey.Length > (1 << 10)) - return PropertyValueTooLargeForBatchException(operationIndex); + throw PropertyValueTooLargeForBatchException(operationIndex); var partitionKeyException = _ValidateBatckPartitionKeyProperty(tableOperation.Entity.PartitionKey, operationIndex); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Upserts require a valid RowKey"); + throw new ArgumentNullException("Upserts require a valid RowKey"); if (tableOperation.Entity.RowKey.Length > (1 << 10)) - return PropertyValueTooLargeForBatchException(operationIndex); + throw PropertyValueTooLargeForBatchException(operationIndex); var rowKeyException = _ValidateBatckRowKeyProperty(tableOperation.Entity.RowKey, operationIndex); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityPropertiesForBatch(tableOperation.Entity, operationContext, operationIndex); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; - if (Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) && partition.ContainsKey(tableOperation.Entity.RowKey)) - return EntityAlreadyExistsForBatchException(); + batchOperation.Insert(GetStubEntity(tableOperation.Entity)); + return operationResult => + { + var result = (StubTableInsertOperationDataResult)operationResult; + switch (result.OperationResult) + { + case StubTableInsertOperationResult.Success: + return _GetTableResult(result); - return null; - } + case StubTableInsertOperationResult.TableDoesNotExist: + throw TableDoesNotExistForBatchInsertException(operationIndex); - public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) - { - var dynamicEntity = GetDynamicEntity(tableOperation.Entity); - var partition = _GetPartition(dynamicEntity); - partition.Add(dynamicEntity.RowKey, dynamicEntity); + case StubTableInsertOperationResult.EntityAlreadyExists: + throw EntityAlreadyExistsForBatchException(); - return new TableResult - { - HttpStatusCode = 204, - Etag = dynamicEntity.ETag, - Result = new TableEntity - { - PartitionKey = dynamicEntity.PartitionKey, - RowKey = dynamicEntity.RowKey, - ETag = dynamicEntity.ETag, - Timestamp = dynamicEntity.Timestamp + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); } }; } @@ -103,15 +104,18 @@ private StorageException _ValidateBatckRowKeyProperty(string value, int operatio .Select(@char => !IsValidKeyCharacter(@char) ? InvalidRowKeyForBatchException(value, operationIndex) : null) .FirstOrDefault(exception => exception != null); - private IDictionary _GetPartition(ITableEntity entity) - { - if (!Context.Entities.TryGetValue(entity.PartitionKey, out var entitiesByRowKey)) + private static TableResult _GetTableResult(StubTableInsertOperationDataResult result) + => new TableResult { - entitiesByRowKey = new SortedList(StringComparer.Ordinal); - Context.Entities.Add(entity.PartitionKey, entitiesByRowKey); - } - - return entitiesByRowKey; - } + HttpStatusCode = 204, + Etag = result.Entity.ETag, + Result = new TableEntity + { + PartitionKey = result.Entity.PartitionKey, + RowKey = result.Entity.RowKey, + ETag = result.Entity.ETag, + Timestamp = result.Entity.Timestamp.Value + } + }; } } \ No newline at end of file diff --git a/CloudStub/TableOperations/MergeTableOperationExecutor.cs b/CloudStub/TableOperations/MergeTableOperationExecutor.cs index bdf576f..29cdd15 100644 --- a/CloudStub/TableOperations/MergeTableOperationExecutor.cs +++ b/CloudStub/TableOperations/MergeTableOperationExecutor.cs @@ -1,4 +1,6 @@ using System; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using Microsoft.Azure.Cosmos.Table; using static CloudStub.StorageExceptionFactory; @@ -6,96 +8,103 @@ namespace CloudStub.TableOperations { internal sealed class MergeTableOperationExecutor : TableOperationExecutor { - public MergeTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) + public MergeTableOperationExecutor(StubTable stubTable) + : base(stubTable) { } - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) + public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - if (!Context.TableExists) - return TableDoesNotExistException(); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Merge requires a valid PartitionKey"); + throw new ArgumentNullException("Merge requires a valid PartitionKey"); var partitionKeyException = ValidateKeyProperty(tableOperation.Entity.PartitionKey); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Merge requires a valid RowKey"); + throw new ArgumentNullException("Merge requires a valid RowKey"); var rowKeyException = ValidateKeyProperty(tableOperation.Entity.RowKey); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityProperties(tableOperation.Entity); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; + + var result = StubTable.Merge(GetStubEntity(tableOperation.Entity)); + switch (result.OperationResult) + { + case StubTableMergeOperationResult.Success: + return _GetTableResult(result); - if (!Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) - || !partition.TryGetValue(tableOperation.Entity.RowKey, out var existingEntity)) - return ResourceNotFoundException(); + case StubTableMergeOperationResult.TableDoesNotExist: + throw TableDoesNotExistException(); - if (tableOperation.Entity.ETag != "*" && !StringComparer.OrdinalIgnoreCase.Equals(tableOperation.Entity.ETag, existingEntity.ETag)) - return PreconditionFailedException(); + case StubTableMergeOperationResult.EntityDoesNotExists: + throw ResourceNotFoundException(); - return null; + case StubTableMergeOperationResult.EtagsDoNotMatch: + throw PreconditionFailedException(); + + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } } - public override Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex) + public override Func BatchCallback(StubTableBatchOperation batchOperation, TableOperation tableOperation, OperationContext operationContext, int operationIndex) { - if (!Context.TableExists) - return TableDoesNotExistForBatchException(operationIndex); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Merge requires a valid PartitionKey"); + throw new ArgumentNullException("Merge requires a valid PartitionKey"); var partitionKeyException = ValidateBatckKeyProperty(tableOperation.Entity.PartitionKey, operationIndex); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Merge requires a valid RowKey"); + throw new ArgumentNullException("Merge requires a valid RowKey"); var rowKeyException = ValidateBatckKeyProperty(tableOperation.Entity.RowKey, operationIndex); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityProperties(tableOperation.Entity); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; - if (!Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) - || !partition.TryGetValue(tableOperation.Entity.RowKey, out var existingEntity)) - return ResourceNotFoundException(); + batchOperation.Merge(GetStubEntity(tableOperation.Entity)); + return operationResult => + { + var result = (StubTableMergeOperationDataResult)operationResult; + switch (result.OperationResult) + { + case StubTableMergeOperationResult.Success: + return _GetTableResult(result); - if (tableOperation.Entity.ETag != "*" && !StringComparer.OrdinalIgnoreCase.Equals(tableOperation.Entity.ETag, existingEntity.ETag)) - return PreconditionFailedException(); + case StubTableMergeOperationResult.TableDoesNotExist: + throw TableDoesNotExistForBatchException(operationIndex); - return null; - } + case StubTableMergeOperationResult.EntityDoesNotExists: + throw ResourceNotFoundException(); - public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) - { - var partition = Context.Entities[tableOperation.Entity.PartitionKey]; - var existingEntity = partition[tableOperation.Entity.RowKey]; + case StubTableMergeOperationResult.EtagsDoNotMatch: + throw PreconditionFailedException(); - var dynamicEntity = GetDynamicEntity(tableOperation.Entity); - foreach (var property in existingEntity.Properties) - if (!dynamicEntity.Properties.ContainsKey(property.Key)) - dynamicEntity.Properties.Add(property); - partition[dynamicEntity.RowKey] = dynamicEntity; + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } + }; + } - return new TableResult + private static TableResult _GetTableResult(StubTableMergeOperationDataResult result) + => new TableResult { HttpStatusCode = 204, - Etag = dynamicEntity.ETag, + Etag = result.Entity.ETag, Result = new TableEntity { - PartitionKey = dynamicEntity.PartitionKey, - RowKey = dynamicEntity.RowKey, - ETag = dynamicEntity.ETag, + PartitionKey = result.Entity.PartitionKey, + RowKey = result.Entity.RowKey, + ETag = result.Entity.ETag, Timestamp = default(DateTimeOffset) } }; - } } } \ No newline at end of file diff --git a/CloudStub/TableOperations/ReplaceTableOperationExecutor.cs b/CloudStub/TableOperations/ReplaceTableOperationExecutor.cs index 29e926b..2c3b143 100644 --- a/CloudStub/TableOperations/ReplaceTableOperationExecutor.cs +++ b/CloudStub/TableOperations/ReplaceTableOperationExecutor.cs @@ -1,4 +1,6 @@ using System; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using Microsoft.Azure.Cosmos.Table; using static CloudStub.StorageExceptionFactory; @@ -6,90 +8,103 @@ namespace CloudStub.TableOperations { internal sealed class ReplaceTableOperationExecutor : TableOperationExecutor { - public ReplaceTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) + public ReplaceTableOperationExecutor(StubTable stubTable) + : base(stubTable) { } - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) + public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - if (!Context.TableExists) - return TableDoesNotExistException(); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Replace requires a valid PartitionKey"); + throw new ArgumentNullException("Replace requires a valid PartitionKey"); var partitionKeyException = ValidateKeyProperty(tableOperation.Entity.PartitionKey); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Replace requires a valid RowKey"); + throw new ArgumentNullException("Replace requires a valid RowKey"); var rowKeyException = ValidateKeyProperty(tableOperation.Entity.RowKey); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityProperties(tableOperation.Entity); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; + + var result = StubTable.Replace(GetStubEntity(tableOperation.Entity)); + switch (result.OperationResult) + { + case StubTableReplaceOperationResult.Success: + return _GetTableResult(result); - if (!Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) - || !partition.TryGetValue(tableOperation.Entity.RowKey, out var existingEntity)) - return ResourceNotFoundException(); + case StubTableReplaceOperationResult.TableDoesNotExist: + throw TableDoesNotExistException(); - if (tableOperation.Entity.ETag != "*" && !StringComparer.OrdinalIgnoreCase.Equals(tableOperation.Entity.ETag, existingEntity.ETag)) - return PreconditionFailedException(); + case StubTableReplaceOperationResult.EntityDoesNotExists: + throw ResourceNotFoundException(); - return null; + case StubTableReplaceOperationResult.EtagsDoNotMatch: + throw PreconditionFailedException(); + + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } } - public override Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex) + public override Func BatchCallback(StubTableBatchOperation batchOperation, TableOperation tableOperation, OperationContext operationContext, int operationIndex) { - if (!Context.TableExists) - return TableDoesNotExistForBatchException(operationIndex); - if (tableOperation.Entity.PartitionKey == null) - return new ArgumentNullException("Replace requires a valid PartitionKey"); + throw new ArgumentNullException("Replace requires a valid PartitionKey"); var partitionKeyException = ValidateBatckKeyProperty(tableOperation.Entity.PartitionKey, operationIndex); if (partitionKeyException != null) - return partitionKeyException; + throw partitionKeyException; if (tableOperation.Entity.RowKey == null) - return new ArgumentNullException("Replace requires a valid RowKey"); + throw new ArgumentNullException("Replace requires a valid RowKey"); var rowKeyException = ValidateBatckKeyProperty(tableOperation.Entity.RowKey, operationIndex); if (rowKeyException != null) - return rowKeyException; + throw rowKeyException; var entityPropertyException = ValidateEntityProperties(tableOperation.Entity); if (entityPropertyException != null) - return entityPropertyException; + throw entityPropertyException; + + batchOperation.Replace(GetStubEntity(tableOperation.Entity)); + return operationResult => + { + var result = (StubTableReplaceOperationDataResult)operationResult; + switch (result.OperationResult) + { + case StubTableReplaceOperationResult.Success: + return _GetTableResult(result); - if (!Context.Entities.TryGetValue(tableOperation.Entity.PartitionKey, out var partition) - || !partition.TryGetValue(tableOperation.Entity.RowKey, out var existingEntity)) - return ResourceNotFoundException(); + case StubTableReplaceOperationResult.TableDoesNotExist: + throw TableDoesNotExistForBatchException(operationIndex); - if (tableOperation.Entity.ETag != "*" && !StringComparer.OrdinalIgnoreCase.Equals(tableOperation.Entity.ETag, existingEntity.ETag)) - return PreconditionFailedException(); + case StubTableReplaceOperationResult.EntityDoesNotExists: + throw ResourceNotFoundException(); - return null; - } + case StubTableReplaceOperationResult.EtagsDoNotMatch: + throw PreconditionFailedException(); - public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) - { - var dynamicEntity = GetDynamicEntity(tableOperation.Entity); - Context.Entities[tableOperation.Entity.PartitionKey][tableOperation.Entity.RowKey] = dynamicEntity; + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } + }; + } - return new TableResult + private static TableResult _GetTableResult(StubTableReplaceOperationDataResult result) + => new TableResult { HttpStatusCode = 204, - Etag = dynamicEntity.ETag, + Etag = result.Entity.ETag, Result = new TableEntity { - PartitionKey = dynamicEntity.PartitionKey, - RowKey = dynamicEntity.RowKey, - ETag = dynamicEntity.ETag, + PartitionKey = result.Entity.PartitionKey, + RowKey = result.Entity.RowKey, + ETag = result.Entity.ETag, Timestamp = default(DateTimeOffset) } }; - } } } \ No newline at end of file diff --git a/CloudStub/TableOperations/RetrieveTableOperationExecutor.cs b/CloudStub/TableOperations/RetrieveTableOperationExecutor.cs index 362d384..0f1c054 100644 --- a/CloudStub/TableOperations/RetrieveTableOperationExecutor.cs +++ b/CloudStub/TableOperations/RetrieveTableOperationExecutor.cs @@ -1,62 +1,101 @@ using System; -using System.Linq; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using Microsoft.Azure.Cosmos.Table; namespace CloudStub.TableOperations { internal sealed class RetrieveTableOperationExecutor : TableOperationExecutor { - public RetrieveTableOperationExecutor(ITableOperationExecutorContext context) - : base(context) + public RetrieveTableOperationExecutor(StubTable stubTable) + : base(stubTable) { } - public override Exception Validate(TableOperation tableOperation, OperationContext operationContext) + public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) { - if (tableOperation.GetPartitionKey() == null) - return new ArgumentNullException("partitionKey"); - if (tableOperation.GetRowKey() == null) - return new ArgumentNullException("rowkey"); + var partitionKey = tableOperation.GetPartitionKey(); + var rowKey = tableOperation.GetRowKey(); + var selectedProperties = tableOperation.GetSelectColumns(); - return null; - } + if (partitionKey is null) + throw new ArgumentNullException("partitionKey"); + if (rowKey is null) + throw new ArgumentNullException("rowkey"); - public override Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex) - => Validate(tableOperation, operationContext); + var result = StubTable.Retrieve(partitionKey, rowKey, selectedProperties); + switch (result.OperationResult) + { + case StubTableRetrieveOperationResult.Success: + return new TableResult + { + HttpStatusCode = 200, + Etag = result.Entity.ETag, + Result = _GetEntityRetrieveResult(result.Entity, tableOperation) + }; - public override TableResult Execute(TableOperation tableOperation, OperationContext operationContext) + case StubTableRetrieveOperationResult.TableDoesNotExist: + case StubTableRetrieveOperationResult.EntityDoesNotExists: + return new TableResult + { + HttpStatusCode = 404, + Etag = null, + Result = null + }; + + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } + } + + public override Func BatchCallback(StubTableBatchOperation batchOperation, TableOperation tableOperation, OperationContext operationContext, int operationIndex) { - if (Context.Entities.TryGetValue(tableOperation.GetPartitionKey(), out var partition) - && partition.TryGetValue(tableOperation.GetRowKey(), out var existingEntity)) - return new TableResult - { - HttpStatusCode = 200, - Etag = existingEntity.ETag, - Result = _GetEntityRetrieveResult(existingEntity, tableOperation) - }; + var partitionKey = tableOperation.GetPartitionKey(); + var rowKey = tableOperation.GetRowKey(); + + if (partitionKey is null) + throw new ArgumentNullException("partitionKey"); + if (rowKey is null) + throw new ArgumentNullException("rowkey"); - return new TableResult + batchOperation.Retrieve(partitionKey, rowKey); + return operationResult => { - HttpStatusCode = 404, - Etag = null, - Result = null + var result = (StubTableRetrieveOperationDataResult)operationResult; + switch (result.OperationResult) + { + case StubTableRetrieveOperationResult.Success: + return new TableResult + { + HttpStatusCode = 200, + Etag = result.Entity.ETag, + Result = _GetEntityRetrieveResult(result.Entity, tableOperation) + }; + + case StubTableRetrieveOperationResult.TableDoesNotExist: + case StubTableRetrieveOperationResult.EntityDoesNotExists: + return new TableResult + { + HttpStatusCode = 404, + Etag = null, + Result = null + }; + + default: + throw new InvalidOperationException($"Operation result {result.OperationResult} not handled."); + } }; } - private static object _GetEntityRetrieveResult(DynamicTableEntity existingEntity, TableOperation tableOperation) + private static object _GetEntityRetrieveResult(StubEntity stubEntity, TableOperation tableOperation) { - var selectColumns = tableOperation.GetSelectColumns(); - var entityProperties = selectColumns == null ? - existingEntity.Properties : - existingEntity.Properties.Where(property => selectColumns.Contains(property.Key, StringComparer.Ordinal)); - var entityResolver = tableOperation.GetEntityResolver(); var entityResult = entityResolver( - existingEntity.PartitionKey, - existingEntity.RowKey, - existingEntity.Timestamp, - entityProperties.ToDictionary(entityProperty => entityProperty.Key, entityProperty => entityProperty.Value, StringComparer.Ordinal), - existingEntity.ETag + stubEntity.PartitionKey, + stubEntity.RowKey, + stubEntity.Timestamp.Value, + GetEntityProperties(stubEntity.Properties), + stubEntity.ETag ); return entityResult; } diff --git a/CloudStub/TableOperations/TableOperationExecutor.cs b/CloudStub/TableOperations/TableOperationExecutor.cs index 731f2ae..a8be7a2 100644 --- a/CloudStub/TableOperations/TableOperationExecutor.cs +++ b/CloudStub/TableOperations/TableOperationExecutor.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using CloudStub.Core; +using CloudStub.Core.OperationResults; using Microsoft.Azure.Cosmos.Table; using static CloudStub.StorageExceptionFactory; @@ -8,36 +10,109 @@ namespace CloudStub.TableOperations { internal abstract class TableOperationExecutor { - protected TableOperationExecutor(ITableOperationExecutorContext context) - => Context = context; + protected TableOperationExecutor(StubTable stubTable) + => StubTable = stubTable; - protected ITableOperationExecutorContext Context { get; } - - public abstract Exception Validate(TableOperation tableOperation, OperationContext operationContext); - - public abstract Exception ValidateForBatch(TableOperation tableOperation, OperationContext operationContext, int operationIndex); + protected StubTable StubTable { get; } public abstract TableResult Execute(TableOperation tableOperation, OperationContext operationContext); - protected static DynamicTableEntity GetDynamicEntity(ITableEntity entity) + public abstract Func BatchCallback(StubTableBatchOperation batchOperation, TableOperation tableOperation, OperationContext operationContext, int operationIndex); + + protected static StubEntity GetStubEntity(ITableEntity entity) { var timestamp = DateTimeOffset.UtcNow; var properties = entity is DynamicTableEntity dynamicTableEntity - ? dynamicTableEntity.Properties.Where(pair => pair.Value.PropertyAsObject != null).ToDictionary(pair => pair.Key, pair => EntityProperty.CreateEntityPropertyFromObject(pair.Value.PropertyAsObject), StringComparer.Ordinal) + ? dynamicTableEntity.Properties.Where(pair => pair.Value.PropertyAsObject is object).ToDictionary(pair => pair.Key, pair => EntityProperty.CreateEntityPropertyFromObject(pair.Value.PropertyAsObject), StringComparer.Ordinal) : _GetProperties(entity); properties.Remove(nameof(TableEntity.PartitionKey)); properties.Remove(nameof(TableEntity.RowKey)); properties.Remove(nameof(TableEntity.Timestamp)); properties.Remove(nameof(TableEntity.ETag)); - return new DynamicTableEntity + var stubEntity = new StubEntity(entity.PartitionKey, entity.RowKey, entity.ETag); + foreach (var property in properties) + switch (property.Value.PropertyType) + { + case EdmType.Binary: + stubEntity.Properties.Add(property.Key, new StubEntityProperty(property.Value.BinaryValue)); + break; + + case EdmType.Boolean: + stubEntity.Properties.Add(property.Key, new StubEntityProperty(property.Value.BooleanValue.Value)); + break; + + case EdmType.Int32: + stubEntity.Properties.Add(property.Key, new StubEntityProperty(property.Value.Int32Value.Value)); + break; + + case EdmType.Int64: + stubEntity.Properties.Add(property.Key, new StubEntityProperty(property.Value.Int64Value.Value)); + break; + + case EdmType.Double: + stubEntity.Properties.Add(property.Key, new StubEntityProperty(property.Value.DoubleValue.Value)); + break; + + case EdmType.Guid: + stubEntity.Properties.Add(property.Key, new StubEntityProperty(property.Value.GuidValue.Value)); + break; + + case EdmType.DateTime: + stubEntity.Properties.Add(property.Key, new StubEntityProperty(property.Value.DateTime.Value)); + break; + + case EdmType.String: + stubEntity.Properties.Add(property.Key, new StubEntityProperty(property.Value.StringValue)); + break; + } + + return stubEntity; + } + + protected static IDictionary GetEntityProperties(IEnumerable> stubEntityProperties) + { + var properties = new Dictionary(StringComparer.Ordinal); + + foreach (var stubEntityProperty in stubEntityProperties) { - PartitionKey = entity.PartitionKey, - RowKey = entity.RowKey, - ETag = $"{timestamp:o}-{Guid.NewGuid()}", - Timestamp = timestamp, - Properties = properties - }; + switch (stubEntityProperty.Value.Type) + { + case StubEntityPropertyType.Binary: + properties.Add(stubEntityProperty.Key, new EntityProperty((byte[])stubEntityProperty.Value.Value)); + break; + + case StubEntityPropertyType.Boolean: + properties.Add(stubEntityProperty.Key, new EntityProperty((bool)stubEntityProperty.Value.Value)); + break; + + case StubEntityPropertyType.Int32: + properties.Add(stubEntityProperty.Key, new EntityProperty((int)stubEntityProperty.Value.Value)); + break; + + case StubEntityPropertyType.Int64: + properties.Add(stubEntityProperty.Key, new EntityProperty((long)stubEntityProperty.Value.Value)); + break; + + case StubEntityPropertyType.Double: + properties.Add(stubEntityProperty.Key, new EntityProperty((double)stubEntityProperty.Value.Value)); + break; + + case StubEntityPropertyType.Guid: + properties.Add(stubEntityProperty.Key, new EntityProperty((Guid)stubEntityProperty.Value.Value)); + break; + + case StubEntityPropertyType.DateTime: + properties.Add(stubEntityProperty.Key, new EntityProperty((DateTime)stubEntityProperty.Value.Value)); + break; + + case StubEntityPropertyType.String: + properties.Add(stubEntityProperty.Key, new EntityProperty((string)stubEntityProperty.Value.Value)); + break; + } + } + + return properties; } protected static bool IsValidKeyCharacter(char @char)