From d37545db57f0227b09b4257989d92b21bfe456c3 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Sat, 15 Jan 2022 14:30:05 +0000 Subject: [PATCH] Move code to use Azure.Data.Tables * Update to 12.4.0 of Azure Data Tables * Update appveyor for VS2022 * Add notes about Azure.Data.Table changes to the readme --- .gitignore | 4 + NuGet.Config | 6 + Readme.md | 13 +- TableStorage.Abstractions.nuspec | 8 +- appveyor.yml | 18 +- .../Factory/ITableStoreFactory.cs | 2 +- .../Factory/TableStoreFactory.cs | 2 +- .../Models/PagedResult.cs | 5 +- .../Parsers/TimeStringParser.cs | 27 +- .../Store/ITableStore.cs | 30 +- .../Store/ITableStoreCommon.cs | 4 +- .../Store/ITableStoreDynamic.cs | 27 +- .../Store/ParseConnectionString.cs | 27 + .../Store/TableStore.cs | 829 ++++++------------ .../Store/TableStoreBase.cs | 396 +++++---- .../Store/TableStoreDynamic.cs | 232 +---- .../TableStorage.Abstractions.csproj | 8 +- .../Helpers/TestDataHelper.cs | 36 +- .../Helpers/TestTableEntity.cs | 11 +- .../Parsers/TimeStringParserTests.cs | 22 +- .../Store/TableStoreAsyncTests.cs | 56 -- .../Store/TableStoreDeleteTests.cs | 77 +- ...sts.cs => TableStoreDynamicDeleteTests.cs} | 31 +- ...sts.cs => TableStoreDynamicInsertTests.cs} | 197 ++--- .../Store/TableStoreDynamicTests.cs | 22 + .../Store/TableStoreDynamicUpdateTests.cs | 71 ++ .../Store/TableStoreInsertTests.cs | 209 +++-- .../Store/TableStoreQueryAsyncTests.cs | 557 ------------ .../Store/TableStoreQueryTests.cs | 591 ++++++++++++- .../Store/TableStoreTests.cs | 62 +- .../Store/TableStoreUpdateAsyncTests.cs | 101 --- .../Store/TableStoreUpdateTests.cs | 71 +- .../TableStorage.Abstractions.Tests.csproj | 12 +- 33 files changed, 1729 insertions(+), 2035 deletions(-) create mode 100644 NuGet.Config create mode 100644 src/TableStorage.Abstractions/Store/ParseConnectionString.cs delete mode 100644 tests/TableStorage.Abstractions.Tests/Store/TableStoreAsyncTests.cs rename tests/TableStorage.Abstractions.Tests/Store/{TableStoreDeleteAsyncTests.cs => TableStoreDynamicDeleteTests.cs} (64%) rename tests/TableStorage.Abstractions.Tests/Store/{TableStoreInsertAsyncTests.cs => TableStoreDynamicInsertTests.cs} (64%) create mode 100644 tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicTests.cs create mode 100644 tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicUpdateTests.cs delete mode 100644 tests/TableStorage.Abstractions.Tests/Store/TableStoreQueryAsyncTests.cs delete mode 100644 tests/TableStorage.Abstractions.Tests/Store/TableStoreUpdateAsyncTests.cs diff --git a/.gitignore b/.gitignore index 677c552..878b289 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,9 @@ **/obj **/bin /.vs +/.idea *.user *.nupkg +*.DotSettings + +__azurite_db_table__.json \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..3f0e003 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Readme.md b/Readme.md index 5e38dc7..7877ea8 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # TableStorage.Abstractions -Repository wrapper for Azure Table Storage in C# using the Microsoft.Azure.Cosmos.Table libraries and supporting .NET Standard 2.0. +Repository wrapper for Azure Table Storage in C# using the Azure.Data.Tables libraries and supporting .NET Standard 2.0 and tested in .NET Framework 4.8 and .NET 6.0 NuGet version @@ -45,9 +45,16 @@ public class TableStorageOptions Example entity: +__NOTE__: Azure.Data.Tables requires inheritance from the interface as the base class TableEntity is a sealed class. + ```C# -public class TestTableEntity : TableEntity +public class TestTableEntity : ITableEntity { + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } + public int Age { get; set; } public string Email { get; set; } @@ -281,4 +288,4 @@ __NOTE__: Currently only the basic methods are supported for this type of table, Most methods have a synchronous and asynchronous version. -The unit tests rely on using Azure Storage Emulator (which can be found here ). +The unit tests rely on using Azurite Emulator which is now bundled with Visual Studio 2022 details can be found on [Microsoft Docs](https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio) for other installations including Visual Studio Code and Docker. diff --git a/TableStorage.Abstractions.nuspec b/TableStorage.Abstractions.nuspec index 407f403..92c24e2 100644 --- a/TableStorage.Abstractions.nuspec +++ b/TableStorage.Abstractions.nuspec @@ -13,10 +13,12 @@ $releaseNotes$ - + + + + + - - diff --git a/appveyor.yml b/appveyor.yml index 9ba22f2..3c57704 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: 2.0.{build} -os: Visual Studio 2019 +image: Visual Studio 2022 configuration: Release # enable version patching with build version number @@ -11,26 +11,14 @@ dotnet_csproj: assembly_version: '{version}' file_version: '{version}' -# temporary workaround for getting Azure Storage Emulator 5.2 installed -install: - - ps: | - $msiPath = "$env:TEMP\MicrosoftAzureStorageEmulator.msi" - (New-Object Net.WebClient).DownloadFile('https://go.microsoft.com/fwlink/?linkid=717179&clcid=0x409', $msiPath) - cmd /c start /wait msiexec /i $msiPath /quiet - del $msiPath - # restore NuGet packages before running before_build: # Display .NET Core version - cmd: dotnet --version # Display minimal restore text - cmd: dotnet restore --verbosity m - # Prepare localdb - - cmd: SqlLocalDB.exe create MSSQLLocalDB - - cmd: SqlLocalDB.exe start MSSQLLocalDB - - cmd: SqlLocalDB.exe info MSSQLLocalDB - # Start the azure storage emulator - - '"%ProgramFiles(x86)%\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe" start' + # Start the azure storage emulator + - ps: start "${Env:ProgramFiles}\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\Microsoft\Azure Storage Emulator\azurite.exe" build: verbosity: minimal diff --git a/src/TableStorage.Abstractions/Factory/ITableStoreFactory.cs b/src/TableStorage.Abstractions/Factory/ITableStoreFactory.cs index eb64799..69a68f5 100644 --- a/src/TableStorage.Abstractions/Factory/ITableStoreFactory.cs +++ b/src/TableStorage.Abstractions/Factory/ITableStoreFactory.cs @@ -1,4 +1,4 @@ -using Microsoft.Azure.Cosmos.Table; +using Azure.Data.Tables; using TableStorage.Abstractions.Store; namespace TableStorage.Abstractions.Factory diff --git a/src/TableStorage.Abstractions/Factory/TableStoreFactory.cs b/src/TableStorage.Abstractions/Factory/TableStoreFactory.cs index b1f7590..39736bc 100644 --- a/src/TableStorage.Abstractions/Factory/TableStoreFactory.cs +++ b/src/TableStorage.Abstractions/Factory/TableStoreFactory.cs @@ -1,4 +1,4 @@ -using Microsoft.Azure.Cosmos.Table; +using Azure.Data.Tables; using TableStorage.Abstractions.Store; namespace TableStorage.Abstractions.Factory diff --git a/src/TableStorage.Abstractions/Models/PagedResult.cs b/src/TableStorage.Abstractions/Models/PagedResult.cs index 7850b54..20e4f86 100644 --- a/src/TableStorage.Abstractions/Models/PagedResult.cs +++ b/src/TableStorage.Abstractions/Models/PagedResult.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Collections.ObjectModel; namespace TableStorage.Abstractions.Models { @@ -9,10 +8,10 @@ public class PagedResult public IReadOnlyCollection Items { get; } public bool IsFinalPage { get; } - internal PagedResult(IList results, string continuationToken, bool isFinalPage) + internal PagedResult(IReadOnlyCollection results, string continuationToken, bool isFinalPage) { ContinuationToken = continuationToken; - Items = new ReadOnlyCollection(results); + Items = results; IsFinalPage = isFinalPage; } } diff --git a/src/TableStorage.Abstractions/Parsers/TimeStringParser.cs b/src/TableStorage.Abstractions/Parsers/TimeStringParser.cs index ac33204..171cad5 100644 --- a/src/TableStorage.Abstractions/Parsers/TimeStringParser.cs +++ b/src/TableStorage.Abstractions/Parsers/TimeStringParser.cs @@ -8,33 +8,38 @@ namespace TableStorage.Abstractions.Parsers { internal static class TimeStringParser { + private const string SecondsSuffix = "s"; private const string MinuteSuffix = "m"; private const string HourSuffix = "h"; private const string DaySuffix = "d"; public static DateTime GetTimeAgo(string ago) { - DateTime result; - if (ago.SafeEndsWith(MinuteSuffix, StringComparison.OrdinalIgnoreCase)) + return DateTime.SpecifyKind(SystemTime.UtcNow().Subtract(GetTimeAgoTimeSpan(ago)), DateTimeKind.Utc); + } + + public static TimeSpan GetTimeAgoTimeSpan(string ago) + { + TimeSpan result; + if (ago.SafeEndsWith(SecondsSuffix, StringComparison.OrdinalIgnoreCase)) + { + var timePart = ago.SubstringBeforeValue(SecondsSuffix); + result = TimeSpan.FromSeconds(int.Parse(timePart)); + } + else if (ago.SafeEndsWith(MinuteSuffix, StringComparison.OrdinalIgnoreCase)) { var timePart = ago.SubstringBeforeValue(MinuteSuffix); - var fromMinutes = TimeSpan.FromMinutes(int.Parse(timePart)); - - result = SystemTime.UtcNow().Subtract(fromMinutes); + result = TimeSpan.FromMinutes(int.Parse(timePart)); } else if (ago.SafeEndsWith(HourSuffix, StringComparison.OrdinalIgnoreCase)) { var timePart = ago.SubstringBeforeValue(HourSuffix); - var fromHours = TimeSpan.FromHours(int.Parse(timePart)); - - result = SystemTime.UtcNow().Subtract(fromHours); + result = TimeSpan.FromHours(int.Parse(timePart)); } else if (ago.SafeEndsWith(DaySuffix, StringComparison.OrdinalIgnoreCase)) { var timePart = ago.SubstringBeforeValue(DaySuffix); - var fromDays = TimeSpan.FromDays(int.Parse(timePart)); - - result = SystemTime.UtcNow().Subtract(fromDays); + result = TimeSpan.FromDays(int.Parse(timePart)); } else { diff --git a/src/TableStorage.Abstractions/Store/ITableStore.cs b/src/TableStorage.Abstractions/Store/ITableStore.cs index fca127f..71b0f2f 100644 --- a/src/TableStorage.Abstractions/Store/ITableStore.cs +++ b/src/TableStorage.Abstractions/Store/ITableStore.cs @@ -102,6 +102,12 @@ public interface ITableStore : ITableStoreCommon /// Task DeleteByPartitionAsync(string partitionKey); + /// + /// Delete all records in the table + /// + /// + void DeleteAll(); + /// /// Delete all records in the table /// @@ -161,8 +167,7 @@ public interface ITableStore : ITableStoreCommon /// Size of the page. /// The next page token. /// The Paged Result - PagedResult GetByPartitionKeyPaged(string partitionKey, int pageSize = 100, - string continuationTokenJson = null); + PagedResult GetByPartitionKeyPaged(string partitionKey, int pageSize = 100, string continuationTokenJson = null); /// /// Get the records by partition key, paged @@ -171,8 +176,7 @@ PagedResult GetByPartitionKeyPaged(string partitionKey, int pageSize = 100, /// Size of the page. /// The next page token. /// The Paged Result - Task> GetByPartitionKeyPagedAsync(string partitionKey, int pageSize = 100, - string continuationTokenJson = null); + Task> GetByPartitionKeyPagedAsync(string partitionKey, int pageSize = 100, string continuationTokenJson = null); /// /// Get the records by row key @@ -209,18 +213,16 @@ Task> GetByPartitionKeyPagedAsync(string partitionKey, int pageSi /// /// The row key. /// Size of the page. - /// The next page token. - PagedResult GetByRowKeyPaged(string rowKey, int pageSize = 100, - string continuationTokenJson = null); + /// The next page token. + PagedResult GetByRowKeyPaged(string rowKey, int pageSize = 100, string continuationToken = null); /// /// Get the records by row key /// /// The row key. /// Size of the page. - /// The next page token. - Task> GetByRowKeyPagedAsync(string rowKey, int pageSize = 100, - string continuationTokenJson = null); + /// The next page token. + Task> GetByRowKeyPagedAsync(string rowKey, int pageSize = 100, string continuationToken = null); /// /// Get all the records in the table @@ -238,17 +240,17 @@ Task> GetByRowKeyPagedAsync(string rowKey, int pageSize = 100, /// Gets all records paged. /// /// Size of the page. - /// The page token. + /// The page token. /// The Paged Result - PagedResult GetAllRecordsPaged(int pageSize = 100, string pageToken = null); + PagedResult GetAllRecordsPaged(int pageSize = 100, string continuationToken = null); /// /// Gets all records in the table, paged /// /// Size of the page. - /// The page token + /// The page token /// The Paged Result - Task> GetAllRecordsPagedAsync(int pageSize = 100, string pageToken = null); + Task> GetAllRecordsPagedAsync(int pageSize = 100, string continuationToken = null); /// /// Get the records and filter by a given predicate diff --git a/src/TableStorage.Abstractions/Store/ITableStoreCommon.cs b/src/TableStorage.Abstractions/Store/ITableStoreCommon.cs index 1b014ff..5a7eb94 100644 --- a/src/TableStorage.Abstractions/Store/ITableStoreCommon.cs +++ b/src/TableStorage.Abstractions/Store/ITableStoreCommon.cs @@ -17,13 +17,13 @@ public interface ITableStoreCommon /// /// Does the table exist /// - /// + /// A boolean denoting if the table exists bool TableExists(); /// /// Does the table exist /// - /// + /// A boolean denoting if the table exists Task TableExistsAsync(); /// diff --git a/src/TableStorage.Abstractions/Store/ITableStoreDynamic.cs b/src/TableStorage.Abstractions/Store/ITableStoreDynamic.cs index 6c15aaf..e540c59 100644 --- a/src/TableStorage.Abstractions/Store/ITableStoreDynamic.cs +++ b/src/TableStorage.Abstractions/Store/ITableStoreDynamic.cs @@ -1,4 +1,4 @@ -using Microsoft.Azure.Cosmos.Table; +using Azure.Data.Tables; using System.Collections.Generic; using System.Threading.Tasks; @@ -6,53 +6,54 @@ namespace TableStorage.Abstractions.Store { public interface ITableStoreDynamic : ITableStoreCommon { + /// /// Insert an record /// /// The record to insert - void Insert(T record) where T : class, ITableEntity; + void Insert(T record) where T : class, ITableEntity, new(); /// /// Insert an record /// /// The record to insert - Task InsertAsync(T record) where T : class, ITableEntity; + Task InsertAsync(T record) where T : class, ITableEntity, new(); /// /// Insert multiple records /// /// The records to insert - void Insert(IEnumerable records) where T : class, ITableEntity; + void Insert(IEnumerable records) where T : class, ITableEntity, new(); /// /// Insert multiple records /// /// The records to insert - Task InsertAsync(IEnumerable records) where T : class, ITableEntity; + Task InsertAsync(IEnumerable records) where T : class, ITableEntity, new(); /// /// Inserts or replaces the record /// /// - void InsertOrReplace(T record) where T : class, ITableEntity; + void InsertOrReplace(T record) where T : class, ITableEntity, new(); /// /// Inserts or replaces the record /// /// - Task InsertOrReplaceAsync(T record) where T : class, ITableEntity; + Task InsertOrReplaceAsync(T record) where T : class, ITableEntity, new(); /// /// Update an record /// /// The record to update - void Update(T record) where T : class, ITableEntity; + void Update(T record) where T : class, ITableEntity, new(); /// /// Update an record /// /// The record to update - Task UpdateAsync(T record) where T : class, ITableEntity; + Task UpdateAsync(T record) where T : class, ITableEntity, new(); /// /// Delete a record @@ -72,7 +73,7 @@ public interface ITableStoreDynamic : ITableStoreCommon /// /// /// The record found or null if not found - T GetRecord(string partitionKey, string rowKey) where T : class, ITableEntity; + T GetRecord(string partitionKey, string rowKey) where T : class, ITableEntity, new(); /// /// Get an record by partition and row key @@ -80,19 +81,19 @@ public interface ITableStoreDynamic : ITableStoreCommon /// /// /// The record found or null if not found - Task GetRecordAsync(string partitionKey, string rowKey) where T : class, ITableEntity; + Task GetRecordAsync(string partitionKey, string rowKey) where T : class, ITableEntity, new(); /// /// Get all the records in the table /// /// All records - IEnumerable GetAllRecords(); + IEnumerable GetAllRecords(); /// /// Get all the records in the table /// /// All records - Task> GetAllRecordsAsync(); + Task> GetAllRecordsAsync(); /// /// Get the records by partition key diff --git a/src/TableStorage.Abstractions/Store/ParseConnectionString.cs b/src/TableStorage.Abstractions/Store/ParseConnectionString.cs new file mode 100644 index 0000000..e1953bd --- /dev/null +++ b/src/TableStorage.Abstractions/Store/ParseConnectionString.cs @@ -0,0 +1,27 @@ +using System; + +namespace TableStorage.Abstractions.Store +{ + internal static class ParseConnectionString + { + /// + /// As CloudStorageAccount.Parse is not available in Azure.Data.Tables and the equivalent StorageConnectionString + /// is an internal class this functionality can only be obtained by custom code or reflection + /// Hopefully the need to do this in the future will be a public parse method in Azure Storage Common library + /// + /// The connection string + /// + public static Uri GetTableEndpoint(string storageConnectionString) + { + var storageConnectionStringType = Type.GetType("Azure.Storage.StorageConnectionString, Azure.Storage.Common"); + + var storageConnectionStringObject = storageConnectionStringType?.GetMethod("Parse", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) + ?.Invoke(null, new object[] { storageConnectionString }); + + var tableEndpoint = storageConnectionStringType?.GetProperty("TableEndpoint", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) + ?.GetValue(storageConnectionStringObject); + + return tableEndpoint as Uri; + } + } +} \ No newline at end of file diff --git a/src/TableStorage.Abstractions/Store/TableStore.cs b/src/TableStorage.Abstractions/Store/TableStore.cs index ecdf366..921e9d4 100644 --- a/src/TableStorage.Abstractions/Store/TableStore.cs +++ b/src/TableStorage.Abstractions/Store/TableStore.cs @@ -1,5 +1,5 @@ -using Microsoft.Azure.Cosmos.Table; -using Newtonsoft.Json; +using Azure; +using Azure.Data.Tables; using System; using System.Collections.Generic; using System.Linq; @@ -43,125 +43,150 @@ public TableStore(string tableName, string storageConnectionString, TableStorage #endregion Construction - #region Synchronous Methods - /// - /// Insert an record + /// Delete a record /// - /// The record to insert - public void Insert(T record) + /// The record to delete + public void Delete(T record) { EnsureRecord(record); - var operation = TableOperation.Insert(record); - CloudTable.Execute(operation); + CloudTable.DeleteEntity(record.PartitionKey, record.RowKey); } - /// - /// Insert multiple records - /// - /// The records to insert - public void Insert(IEnumerable records) + public void DeleteAll() { - if (records == null) - { - throw new ArgumentNullException(nameof(records)); - } + var queryResults = CloudTable.Query(); - var partitionKeySeparation = records.GroupBy(x => x.PartitionKey) - .OrderBy(g => g.Key) - .Select(g => g.AsEnumerable()).SelectMany(entry => entry.Partition(MaxPartitionSize)).ToList(); - - foreach (var entry in partitionKeySeparation) + if (queryResults.Any()) { - var operation = new TableBatchOperation(); - entry.ToList().ForEach(operation.Insert); - - if (operation.Any()) - { - CloudTable.ExecuteBatch(operation); - } + var deleteEntitiesBatch = new List(); + deleteEntitiesBatch.AddRange(queryResults.Select(e => new TableTransactionAction(TableTransactionActionType.Delete, e))); + CloudTable.SubmitTransaction(deleteEntitiesBatch); } } /// - /// Inserts or replaces the record + /// Delete all records in the table /// - /// - public void InsertOrReplace(T record) + public async Task DeleteAllAsync() { - EnsureRecord(record); + var records = await GetAllRecordsAsync().ConfigureAwait(false); - var operation = TableOperation.InsertOrReplace(record); - CloudTable.Execute(operation); + if (records.Any()) + { + var deleteEntitiesBatch = new List(); + deleteEntitiesBatch.AddRange(records.Select(e => new TableTransactionAction(TableTransactionActionType.Delete, e))); + await CloudTable.SubmitTransactionAsync(deleteEntitiesBatch).ConfigureAwait(false); + } } /// - /// Update an record + /// Delete an entry /// - /// The record to update - public void Update(T record) + /// The record to delete + public Task DeleteAsync(T record) { EnsureRecord(record); - var operation = TableOperation.Merge(record); - CloudTable.Execute(operation); + return CloudTable.DeleteEntityAsync(record.PartitionKey, record.RowKey); } /// - /// Update an record using the wildcard etag + /// Delete records by partition key /// - /// The record to update - public void UpdateUsingWildcardEtag(T record) + /// + public async Task DeleteByPartitionAsync(string partitionKey) { - EnsureRecord(record); + var deleteQuery = BuildGetByPartitionQuery(partitionKey); - record.ETag = "*"; - Update(record); + var deleteEntitiesBatch = new List(); + + deleteEntitiesBatch.AddRange(deleteQuery.Select(e => new TableTransactionAction(TableTransactionActionType.Delete, e))); + + await CloudTable.SubmitTransactionAsync(deleteEntitiesBatch).ConfigureAwait(false); } /// - /// Delete a record + /// Delete a record using the wildcard etag /// /// The record to delete - public void Delete(T record) + public void DeleteUsingWildcardEtag(T record) { EnsureRecord(record); - var operation = TableOperation.Delete(record); - CloudTable.Execute(operation); + record.ETag = ETag.All; + Delete(record); } /// /// Delete a record using the wildcard etag /// /// The record to delete - public void DeleteUsingWildcardEtag(T record) + public Task DeleteUsingWildcardEtagAsync(T record) { EnsureRecord(record); - record.ETag = "*"; - Delete(record); + record.ETag = ETag.All; + + return DeleteAsync(record); } /// - /// Get an record by partition and row key + /// Get all the records in the table /// - /// - /// - /// The record found or null if not found - public T GetRecord(string partitionKey, string rowKey) + /// All records + public IEnumerable GetAllRecords() { - EnsurePartitionKey(partitionKey); + var query = CloudTable.Query(); + foreach (var result in query) + { + yield return result; + } + } - EnsureRowKey(rowKey); + /// + /// Get all the records in the table + /// + /// All records + public async Task> GetAllRecordsAsync() + { + var queryResults = CloudTable.QueryAsync(); + return await queryResults.ToListAsync(); + } - // Create a retrieve operation that takes a customer record. - var retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey); + /// + /// Gets all records in the table, paged + /// + /// Size of the page. + /// The continuation token + /// The Paged Result + public PagedResult GetAllRecordsPaged(int pageSize = 100, string continuationToken = null) + { + var query = CloudTable.Query().AsPages(continuationToken, pageSize).FirstOrDefault(); + return CreatePagedResult(query?.Values ?? new List(), query?.ContinuationToken); + } - // Execute the operation. - var retrievedResult = CloudTable.Execute(retrieveOperation); - return retrievedResult.Result as T; + public async Task> GetAllRecordsPagedAsync(int pageSize = 100, string pageToken = null) + { + var query = await CloudTable.QueryAsync().AsPages(pageToken, pageSize).FirstOrDefaultAsync(); + return CreatePagedResult(query?.Values ?? new List(), query?.ContinuationToken); + } + + /// + /// Get the records via observable + /// + /// The observable for the results + public IObservable GetAllRecordsObservable() + { + return Observable.Create(o => + { + foreach (var result in GetAllRecords()) + { + o.OnNext(result); + } + return Disposable.Empty; + }); } /// @@ -173,19 +198,8 @@ public IEnumerable GetByPartitionKey(string partitionKey) { EnsurePartitionKey(partitionKey); - TableContinuationToken continuationToken = null; - - var query = BuildGetByPartitionQuery(partitionKey); - - var allItems = new List(); - do - { - var items = ExecuteQuerySegment(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; + var query = BuildGetByPartitionQuery(partitionKey); + return query; } /// @@ -198,19 +212,33 @@ public IEnumerable GetByPartitionKey(string partitionKey, string ago) { EnsurePartitionKey(partitionKey); - TableContinuationToken continuationToken = null; - var query = BuildGetByPartitionAndTimeQuery(partitionKey, ago); + return query; + } - var allItems = new List(); - do - { - var items = ExecuteQuerySegment(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); + /// + /// Get the records by partition key + /// + /// The partition key + /// The records found + public async Task> GetByPartitionKeyAsync(string partitionKey) + { + EnsurePartitionKey(partitionKey); + + var queryResults = CloudTable.QueryAsync(filter: $"PartitionKey eq '{partitionKey}'"); + + return await queryResults.ToListAsync(); + } + + public async Task> GetByPartitionKeyAsync(string partitionKey, string ago) + { + EnsurePartitionKey(partitionKey); + + var utcTime = new DateTimeOffset(TimeStringParser.GetTimeAgo(ago), TimeSpan.Zero); + + var queryResults = CloudTable.QueryAsync(x => x.PartitionKey == partitionKey && x.Timestamp >= utcTime); - return allItems; + return await queryResults.ToListAsync(); } /// @@ -218,24 +246,29 @@ public IEnumerable GetByPartitionKey(string partitionKey, string ago) /// /// The partition key. /// Size of the page. - /// The next page token. + /// The next page token. /// The Paged Result - public PagedResult GetByPartitionKeyPaged(string partitionKey, int pageSize = 100, string continuationTokenJson = null) + public PagedResult GetByPartitionKeyPaged(string partitionKey, int pageSize = 100, string continuationToken = null) { EnsurePartitionKey(partitionKey); - var continuationToken = DeserializeContinuationToken(continuationTokenJson); - - var query = BuildGetByPartitionQuery(partitionKey); - query.TakeCount = pageSize; - - var allItems = new List(); + var query = BuildGetByPartitionQuery(partitionKey).AsPages(continuationToken, pageSize).FirstOrDefault(); + return CreatePagedResult(query?.Values ?? new List(), query?.ContinuationToken); + } - var items = ExecuteQuerySegment(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); + /// + /// Get the records by partition key, paged + /// + /// The partition key. + /// Size of the page. + /// The next page token. + /// The Paged Result + public async Task> GetByPartitionKeyPagedAsync(string partitionKey, int pageSize = 100, string continuationToken = null) + { + EnsurePartitionKey(partitionKey); - return CreatePagedResult(continuationToken, allItems); + var query = await BuildGetByPartitionQueryAsync(partitionKey).AsPages(continuationToken, pageSize).FirstOrDefaultAsync(); + return CreatePagedResult(query?.Values ?? new List(), query?.ContinuationToken); } /// @@ -247,19 +280,8 @@ public IEnumerable GetByRowKey(string rowKey) { EnsureRowKey(rowKey); - TableContinuationToken continuationToken = null; - - var query = BuildGetByRowKeyQuery(rowKey); - - var allItems = new List(); - do - { - var items = ExecuteQuerySegment(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; + var query = BuildGetByRowKeyQuery(rowKey); + return query; } /// @@ -272,84 +294,79 @@ public IEnumerable GetByRowKey(string rowKey, string ago) { EnsureRowKey(rowKey); - TableContinuationToken continuationToken = null; - var query = BuildGetByRowKeyAndTimeQuery(rowKey, ago); - - var allItems = new List(); - do - { - var items = ExecuteQuerySegment(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; + return query; } /// /// Get the records by row key /// - /// The row key. - /// Size of the page. - /// The next page token. - /// The Paged Result - public PagedResult GetByRowKeyPaged(string rowKey, int pageSize = 100, string continuationTokenJson = null) + /// The row key + /// The records found + public async Task> GetByRowKeyAsync(string rowKey) { EnsureRowKey(rowKey); - TableContinuationToken continuationToken = DeserializeContinuationToken(continuationTokenJson); + var queryResults = CloudTable.QueryAsync(filter: $"RowKey eq '{rowKey}'"); + + return await queryResults.ToListAsync(); + } - var query = BuildGetByRowKeyQuery(rowKey); - query.TakeCount = pageSize; + public async Task> GetByRowKeyAsync(string rowKey, string ago) + { + EnsureRowKey(rowKey); + + var utcTime = new DateTimeOffset(TimeStringParser.GetTimeAgo(ago), TimeSpan.Zero); + + var queryResults = CloudTable.QueryAsync(x => x.RowKey == rowKey && x.Timestamp >= utcTime); + + return await queryResults.ToListAsync(); + } + + public PagedResult GetByRowKeyPaged(string rowKey, int pageSize = 100, string continuationToken = null) + { + EnsureRowKey(rowKey); - var allItems = new List(); + var query = BuildGetByRowKeyQuery(rowKey).AsPages(continuationToken, pageSize).FirstOrDefault(); + return CreatePagedResult(query?.Values ?? new List(), query?.ContinuationToken); + } - var items = ExecuteQuerySegment(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); + public async Task> GetByRowKeyPagedAsync(string rowKey, int pageSize = 100, string continuationToken = null) + { + EnsureRowKey(rowKey); - return CreatePagedResult(continuationToken, allItems); + var query = await BuildGetByRowKeyQueryAsync(rowKey).AsPages(continuationToken, pageSize).FirstOrDefaultAsync(); + return CreatePagedResult(query?.Values ?? new List(), query?.ContinuationToken); } /// - /// Get all the records in the table + /// Get an record by partition and row key /// - /// All records - public IEnumerable GetAllRecords() + /// + /// + /// The record found or null if not found + public T GetRecord(string partitionKey, string rowKey) { - var query = new TableQuery(); + EnsurePartitionKey(partitionKey); - var token = new TableContinuationToken(); - var segment = CloudTable.ExecuteQuerySegmented(query, token); - while (token != null) - { - foreach (var result in segment) - { - yield return result; - } - token = segment.ContinuationToken; - segment = CloudTable.ExecuteQuerySegmented(query, token); - } + EnsureRowKey(rowKey); + + return CloudTable.GetEntity(partitionKey, rowKey); } /// - /// Gets all records in the table, paged + /// Get an record by partition and row key /// - /// Size of the page. - /// The page token - /// The Paged Result - - public PagedResult GetAllRecordsPaged(int pageSize = 100, string pageToken = null) + /// + /// + /// The record found or null if not found + public async Task GetRecordAsync(string partitionKey, string rowKey) { - var query = new TableQuery { TakeCount = pageSize }; + EnsurePartitionKey(partitionKey); + + EnsureRowKey(rowKey); - var allItems = new List(); - var continuationToken = DeserializeContinuationToken(pageToken); - var items = CloudTable.ExecuteQuerySegmented(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - return CreatePagedResult(continuationToken, allItems); + return await CloudTable.GetEntityAsync(partitionKey, rowKey); } /// @@ -370,7 +387,9 @@ public IEnumerable GetRecordsByFilter(Func filter) /// The records filtered public IEnumerable GetRecordsByFilter(Func filter, string ago) { - bool CombineFilter(T x) => filter(x) && x.Timestamp >= TimeStringParser.GetTimeAgo(ago); + var utcTime = new DateTimeOffset(TimeStringParser.GetTimeAgo(ago), TimeSpan.Zero); + + bool CombineFilter(T x) => filter(x) && x.Timestamp >= utcTime; return GetAllRecords().Where(CombineFilter); } @@ -397,25 +416,47 @@ public IEnumerable GetRecordsByFilter(Func filter, int start, int pa /// The records filtered public IEnumerable GetRecordsByFilter(Func filter, int start, int pageSize, string ago) { - bool CombineFilter(T x) => filter(x) && x.Timestamp >= TimeStringParser.GetTimeAgo(ago); + var utcTime = new DateTimeOffset(TimeStringParser.GetTimeAgo(ago), TimeSpan.Zero); + + bool CombineFilter(T x) => filter(x) && x.Timestamp >= utcTime; + var items = GetRecordsByFilter(CombineFilter); return items.Page(start, pageSize); } /// - /// Get the records via observable + /// Get the records and filter by a given predicate /// - /// The observable for the results - public IObservable GetAllRecordsObservable() + /// The filter to apply + /// The start record + /// The page size + /// The records filtered + public async Task> GetRecordsByFilterAsync(Func filter, int start, int pageSize) { - return Observable.Create(o => - { - foreach (var result in GetAllRecords()) - { - o.OnNext(result); - } - return Disposable.Empty; - }); + var allRecords = await GetAllRecordsAsync(); + var data = allRecords.Where(filter).Page(start, pageSize); + + return data; + } + + /// + /// Get the records and filter by a given predicate and time in the past + /// + /// The filter to apply + /// The start record + /// The page size + /// The time in the past to search e.g. 10m, 1h, etc. + /// The records filtered + public async Task> GetRecordsByFilterAsync(Func filter, int start, int pageSize, string ago) + { + var utcTime = new DateTimeOffset(TimeStringParser.GetTimeAgo(ago), TimeSpan.Zero); + + bool CombineFilter(T x) => filter(x) && x.Timestamp >= utcTime; + + var allRecords = await GetAllRecordsAsync(); + var data = allRecords.Where(CombineFilter).Page(start, pageSize); + + return data; } /// @@ -447,7 +488,8 @@ public IObservable GetRecordsByFilterObservable(Func filter, int sta /// The observable for the results public IObservable GetRecordsByFilterObservable(Func filter, int start, int pageSize, string ago) { - bool CombineFilter(T x) => filter(x) && x.Timestamp >= TimeStringParser.GetTimeAgo(ago); + var utcTime = new DateTimeOffset(TimeStringParser.GetTimeAgo(ago), TimeSpan.Zero); + bool CombineFilter(T x) => filter(x) && x.Timestamp >= utcTime; return Observable.Create(o => { @@ -459,32 +501,25 @@ public IObservable GetRecordsByFilterObservable(Func filter, int sta }); } - #endregion Synchronous Methods - - #region Asynchronous Methods - /// /// Insert an record /// /// The record to insert - public async Task InsertAsync(T record) + public void Insert(T record) { EnsureRecord(record); - - var operation = TableOperation.Insert(record); - - await CloudTable.ExecuteAsync(operation).ConfigureAwait(false); + CloudTable.AddEntity(record); } /// /// Insert multiple records /// /// The records to insert - public async Task InsertAsync(IEnumerable records) + public void Insert(IEnumerable records) { if (records == null) { - throw new ArgumentNullException(nameof(records)); + throw new ArgumentNullException(nameof(records), "Records cannot be null"); } var partitionKeySeparation = records.GroupBy(x => x.PartitionKey) @@ -493,392 +528,130 @@ public async Task InsertAsync(IEnumerable records) foreach (var entry in partitionKeySeparation) { - var operation = new TableBatchOperation(); - entry.ToList().ForEach(operation.Insert); + var addEntitiesBatch = new List(); + addEntitiesBatch.AddRange(entry.Select(e => new TableTransactionAction(TableTransactionActionType.Add, e))); - if (operation.Any()) - { - await CloudTable.ExecuteBatchAsync(operation).ConfigureAwait(false); - } + CloudTable.SubmitTransaction(addEntitiesBatch); } } /// - /// Inserts or replaces the record - /// - /// - /// - public async Task InsertOrReplaceAsync(T record) - { - EnsureRecord(record); - - var operation = TableOperation.InsertOrReplace(record); - - await CloudTable.ExecuteAsync(operation).ConfigureAwait(false); - } - - /// - /// Update an record - /// - /// The record to update - public async Task UpdateAsync(T record) - { - EnsureRecord(record); - - var operation = TableOperation.Merge(record); - - await CloudTable.ExecuteAsync(operation).ConfigureAwait(false); - } - - /// - /// Update an record using the wildcard etag - /// - /// The record to update - public async Task UpdateUsingWildcardEtagAsync(T record) - { - EnsureRecord(record); - - record.ETag = "*"; - await UpdateAsync(record).ConfigureAwait(false); - } - - /// - /// Delete an entry - /// - /// The record to delete - public async Task DeleteAsync(T record) - { - EnsureRecord(record); - - var operation = TableOperation.Delete(record); - - await CloudTable.ExecuteAsync(operation).ConfigureAwait(false); - } - - /// - /// Delete a record using the wildcard etag + /// Insert an record /// - /// The record to delete - public async Task DeleteUsingWildcardEtagAsync(T record) + /// The record to insert + public Task InsertAsync(T record) { EnsureRecord(record); - record.ETag = "*"; - - await DeleteAsync(record).ConfigureAwait(false); + return CloudTable.AddEntityAsync(record); } /// - /// Delete records by partition key - /// - /// - public async Task DeleteByPartitionAsync(string partitionKey) - { - var deleteQuery = BuildGetByPartitionQuery(partitionKey); - - TableContinuationToken continuationToken = null; - do - { - var tableQueryResult = CloudTable.ExecuteQuerySegmentedAsync(deleteQuery, continuationToken); - continuationToken = tableQueryResult.Result.ContinuationToken; - - // Split result into chunks of 100s - var rowsChunked = tableQueryResult.Result.ToList().Partition(100); - - // Delete each chunk in a batch - foreach (var rows in rowsChunked) - { - TableBatchOperation tableBatchOperation = new TableBatchOperation(); - foreach (var row in rows) - { - tableBatchOperation.Add(TableOperation.Delete(row)); - } - await CloudTable.ExecuteBatchAsync(tableBatchOperation); - } - } - while (continuationToken != null); - } - - /// - /// Delete all records in the table + /// Insert multiple records /// - public async Task DeleteAllAsync() + /// The records to insert + public async Task InsertAsync(IEnumerable records) { - var records = await GetAllRecordsAsync().ConfigureAwait(false); - var partitionKeys = records.Select(x => x.PartitionKey).Distinct().ToList(); - foreach (var key in partitionKeys) + if (records == null) { - await DeleteByPartitionAsync(key); + throw new ArgumentNullException(nameof(records)); } - } - - /// - /// Get an record by partition and row key - /// - /// - /// - /// The record found or null if not found - public async Task GetRecordAsync(string partitionKey, string rowKey) - { - EnsurePartitionKey(partitionKey); - - EnsureRowKey(rowKey); - - // Create a retrieve operation that takes a customer record. - var retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey); - - // Execute the operation. - var retrievedResult = await CloudTable.ExecuteAsync(retrieveOperation).ConfigureAwait(false); - return retrievedResult.Result as T; - } - - /// - /// Get the records by partition key - /// - /// The partition key - /// The records found - public async Task> GetByPartitionKeyAsync(string partitionKey) - { - EnsurePartitionKey(partitionKey); - - TableContinuationToken continuationToken = null; - - var query = BuildGetByPartitionQuery(partitionKey); - - var allItems = new List(); - do - { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; - } - - public async Task> GetByPartitionKeyAsync(string partitionKey, string ago) - { - EnsurePartitionKey(partitionKey); - - TableContinuationToken continuationToken = null; - - var query = BuildGetByPartitionAndTimeQuery(partitionKey, ago); + var partitionKeySeparation = records.GroupBy(x => x.PartitionKey) + .OrderBy(g => g.Key) + .Select(g => g.AsEnumerable()).SelectMany(entry => entry.Partition(MaxPartitionSize)).ToList(); - var allItems = new List(); - do + await foreach (var entry in partitionKeySeparation.ToAsyncEnumerable()) { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; - } - - /// - /// Get the records by partition key, paged - /// - /// The partition key. - /// Size of the page. - /// The next page token. - /// The Paged Result - public async Task> GetByPartitionKeyPagedAsync(string partitionKey, int pageSize = 100, string continuationTokenJson = null) - { - EnsurePartitionKey(partitionKey); - - var continuationToken = DeserializeContinuationToken(continuationTokenJson); - - var query = BuildGetByPartitionQuery(partitionKey); - query.TakeCount = pageSize; - - var allItems = new List(); - - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - - return CreatePagedResult(continuationToken, allItems); + var addEntitiesBatch = new List(); + addEntitiesBatch.AddRange(entry.Select(e => new TableTransactionAction(TableTransactionActionType.Add, e))); + await CloudTable.SubmitTransactionAsync(addEntitiesBatch).ConfigureAwait(false); + } } /// - /// Get the records by row key + /// Inserts or replaces the record /// - /// The row key - /// The records found - public async Task> GetByRowKeyAsync(string rowKey) - { - EnsureRowKey(rowKey); - - TableContinuationToken continuationToken = null; - - var query = BuildGetByRowKeyQuery(rowKey); - - var allItems = new List(); - do - { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; - } - - public async Task> GetByRowKeyAsync(string rowKey, string ago) + /// + public void InsertOrReplace(T record) { - EnsureRowKey(rowKey); - - TableContinuationToken continuationToken = null; - - var query = BuildGetByRowKeyAndTimeQuery(rowKey, ago); - - var allItems = new List(); - do - { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); + EnsureRecord(record); - return allItems; + CloudTable.UpsertEntity(record); } /// - /// Get the records by row key + /// Inserts or replaces the record /// - /// The row key. - /// Size of the page. - /// The next page token. - public async Task> GetByRowKeyPagedAsync(string rowKey, int pageSize = 100, string continuationTokenJson = null) + /// + /// + public Task InsertOrReplaceAsync(T record) { - EnsureRowKey(rowKey); - - var continuationToken = DeserializeContinuationToken(continuationTokenJson); - - var query = BuildGetByRowKeyQuery(rowKey); - query.TakeCount = pageSize; - - var allItems = new List(); - - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); + EnsureRecord(record); - return CreatePagedResult(continuationToken, allItems); + return CloudTable.UpsertEntityAsync(record); } /// - /// Get all the records in the table + /// Update an record /// - /// All records - public async Task> GetAllRecordsAsync() + /// The record to update + public void Update(T record) { - TableContinuationToken continuationToken = null; - - var query = new TableQuery(); - - var allItems = new List(); - do - { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); + EnsureRecord(record); - return allItems; + CloudTable.UpdateEntity(record, record.ETag); } /// - /// Gets all records in the table, paged + /// Update an record /// - /// The Paged Result - public async Task> GetAllRecordsPagedAsync(int pageSize = 100, string pageToken = null) + /// The record to update + public Task UpdateAsync(T record) { - var query = new TableQuery { TakeCount = pageSize }; - - var allItems = new List(); - var continuationToken = DeserializeContinuationToken(pageToken); - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - return CreatePagedResult(continuationToken, allItems); + EnsureRecord(record); + return CloudTable.UpdateEntityAsync(record, record.ETag); } /// - /// Get the records and filter by a given predicate + /// Update an record using the wildcard etag /// - /// The filter to apply - /// The start record - /// The page size - /// The records filtered - public async Task> GetRecordsByFilterAsync(Func filter, int start, int pageSize) + /// The record to update + public void UpdateUsingWildcardEtag(T record) { - var allRecords = GetAllRecords(); - var data = allRecords.Where(filter).Page(start, pageSize); + EnsureRecord(record); - return await Task.FromResult(data); + record.ETag = ETag.All; + Update(record); } /// - /// Get the records and filter by a given predicate and time in the past + /// Update an record using the wildcard etag /// - /// The filter to apply - /// The start record - /// The page size - /// The time in the past to search e.g. 10m, 1h, etc. - /// The records filtered - public async Task> GetRecordsByFilterAsync(Func filter, int start, int pageSize, string ago) + /// The record to update + public Task UpdateUsingWildcardEtagAsync(T record) { - bool CombineFilter(T x) => filter(x) && x.Timestamp >= TimeStringParser.GetTimeAgo(ago); - var allRecords = GetAllRecords(); - var data = allRecords.Where(CombineFilter).Page(start, pageSize); + EnsureRecord(record); - return await Task.FromResult(data); + record.ETag = ETag.All; + return UpdateAsync(record); } - #endregion Asynchronous Methods - #region Helpers - /// - /// Builds the get by partition query. - /// - /// The partition key. - /// The table query - private static TableQuery BuildGetByPartitionQuery(string partitionKey) - { - var query = new TableQuery().Where( - TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey)); - return query; - } - /// /// Builds the get by partition query. /// /// The partition key. /// The time in the past to search e.g. 10m, 1h, etc. /// The table query - private static TableQuery BuildGetByPartitionAndTimeQuery(string partitionKey, string ago) + //private static TableQuery BuildGetByPartitionAndTimeQuery(string partitionKey, string ago) + private Pageable BuildGetByPartitionAndTimeQuery(string partitionKey, string ago) { var utcTime = new DateTimeOffset(TimeStringParser.GetTimeAgo(ago), TimeSpan.Zero); - var query = new TableQuery().Where(TableQuery.CombineFilters(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey), - TableOperators.And, - TableQuery.GenerateFilterConditionForDate("Timestamp", QueryComparisons.GreaterThanOrEqual, utcTime))); - return query; - } - - /// - /// Build the row key table query - /// - /// The row key - /// The table query - private static TableQuery BuildGetByRowKeyQuery(string rowKey) - { - var query = new TableQuery().Where(TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, rowKey)); - return query; + return CloudTable.Query(x => x.PartitionKey == partitionKey && x.Timestamp >= utcTime); } /// @@ -887,47 +660,23 @@ private static TableQuery BuildGetByRowKeyQuery(string rowKey) /// The row key /// The time in the past to search e.g. 10m, 1h, etc. /// The table query - private static TableQuery BuildGetByRowKeyAndTimeQuery(string rowKey, string ago) + //private static TableQuery BuildGetByRowKeyAndTimeQuery(string rowKey, string ago) + private Pageable BuildGetByRowKeyAndTimeQuery(string rowKey, string ago) { var utcTime = new DateTimeOffset(TimeStringParser.GetTimeAgo(ago), TimeSpan.Zero); - var query = new TableQuery().Where(TableQuery.CombineFilters(TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, rowKey), - TableOperators.And, - TableQuery.GenerateFilterConditionForDate("Timestamp", QueryComparisons.GreaterThanOrEqual, utcTime))); - return query; + return CloudTable.Query(x => x.RowKey == rowKey && x.Timestamp >= utcTime); } /// /// Create a paged result /// - /// The continuation token /// The items + /// The continuation token /// The paged result - private static PagedResult CreatePagedResult(TableContinuationToken continuationToken, IList items) - { - var continuationTokenJson = continuationToken != null ? JsonConvert.SerializeObject(continuationToken) : null; - return new PagedResult(items, continuationTokenJson, continuationToken == null); - } - - /// - /// Deserialize the continuation token - /// - /// The json string containing the continuation token - /// The continuation token - private static TableContinuationToken DeserializeContinuationToken(string continuationTokenJson) - { - TableContinuationToken continuationToken = null; - if (!string.IsNullOrEmpty(continuationTokenJson)) - { - continuationToken = JsonConvert.DeserializeObject(continuationTokenJson); - } - return continuationToken; - } - - private TableQuerySegment ExecuteQuerySegment(TableQuery query, TableContinuationToken continuationToken) + private static PagedResult CreatePagedResult(IReadOnlyCollection items, string continuationToken = null) { - var items = CloudTable.ExecuteQuerySegmented(query, continuationToken); - return items; + return new PagedResult(items, continuationToken, continuationToken == null); } #endregion Helpers diff --git a/src/TableStorage.Abstractions/Store/TableStoreBase.cs b/src/TableStorage.Abstractions/Store/TableStoreBase.cs index 6d5a198..635bb9d 100644 --- a/src/TableStorage.Abstractions/Store/TableStoreBase.cs +++ b/src/TableStorage.Abstractions/Store/TableStoreBase.cs @@ -1,5 +1,6 @@ -using FluentValidation; -using Microsoft.Azure.Cosmos.Table; +using Azure; +using Azure.Data.Tables; +using FluentValidation; using System; using System.Collections.Generic; using System.Linq; @@ -7,232 +8,247 @@ using System.Threading.Tasks; using TableStorage.Abstractions.Validators; -namespace TableStorage.Abstractions.Store -{ - public class TableStoreBase - { - /// - /// The max size for a single partition to be added to Table Storage - /// - protected const int MaxPartitionSize = 100; - - /// - /// The cloud table - /// - protected readonly CloudTable CloudTable; - - /// - /// Constructor - /// - /// The table name - /// The connection string - protected TableStoreBase(string tableName, string storageConnectionString) : this(tableName, storageConnectionString, new TableStorageOptions()) - { - } - - /// - /// Constructor - /// - /// The table name - /// The connection string - /// Table storage options - protected TableStoreBase(string tableName, string storageConnectionString, TableStorageOptions options) - { - if (string.IsNullOrWhiteSpace(tableName)) - { - throw new ArgumentException("Table name cannot be null or empty", nameof(tableName)); - } - - if (string.IsNullOrWhiteSpace(storageConnectionString)) - { - throw new ArgumentException("Table connection string cannot be null or empty", nameof(storageConnectionString)); - } - - if (options == null) - { - throw new ArgumentNullException(nameof(options), "Table storage options cannot be null"); - } - - var validator = new TableStorageOptionsValidator(); - validator.ValidateAndThrow(options); +namespace TableStorage.Abstractions.Store; - OptimisePerformance(storageConnectionString, options); - var cloudTableClient = CreateTableClient(storageConnectionString, options.Retries, options.RetryWaitTimeInSeconds); - - CloudTable = cloudTableClient.GetTableReference(tableName); - - if (options.EnsureTableExists) - { - if (!TableExists()) - { - CreateTable(); - } - } - } +public class TableStoreBase +{ + /// + /// The max size for a single partition to be added to Table Storage + /// + protected const int MaxPartitionSize = 100; + + /// + /// The cloud table + /// + protected readonly TableClient CloudTable; + + private readonly TableServiceClient _cloudTableService; + private readonly string _tableName; + + /// + /// Constructor + /// + /// The table name + /// The connection string + protected TableStoreBase(string tableName, string storageConnectionString) : this(tableName, storageConnectionString, new TableStorageOptions()) + { + } - /// - /// Settings to improve performance - /// - private static void OptimisePerformance(string storageConnectionString, TableStorageOptions options) + /// + /// Constructor + /// + /// The table name + /// The connection string + /// Table storage options + protected TableStoreBase(string tableName, string storageConnectionString, TableStorageOptions options) + { + if (string.IsNullOrWhiteSpace(tableName)) { - var account = CloudStorageAccount.Parse(storageConnectionString); - var tableServicePoint = ServicePointManager.FindServicePoint(account.TableEndpoint); - tableServicePoint.UseNagleAlgorithm = options.UseNagleAlgorithm; - tableServicePoint.Expect100Continue = options.Expect100Continue; - tableServicePoint.ConnectionLimit = options.ConnectionLimit; + throw new ArgumentException("Table name cannot be null or empty", nameof(tableName)); } - /// - /// Create the table client - /// - /// The connection string - /// Number of retries - /// Wait time between retries in seconds - /// The table client - private static CloudTableClient CreateTableClient(string connectionString, int retries, double retryWaitTimeInSeconds) + if (string.IsNullOrWhiteSpace(storageConnectionString)) { - var cloudStorageAccount = CloudStorageAccount.Parse(connectionString); - - var requestOptions = new TableRequestOptions - { - RetryPolicy = new ExponentialRetry(TimeSpan.FromSeconds(retryWaitTimeInSeconds), retries) - }; - - var cloudTableClient = cloudStorageAccount.CreateCloudTableClient(); - cloudTableClient.DefaultRequestOptions = requestOptions; - return cloudTableClient; + throw new ArgumentException("Table connection string cannot be null or empty", nameof(storageConnectionString)); } - /// - /// Create the table - /// - public void CreateTable() + if (options == null) { - CloudTable.CreateIfNotExists(); + throw new ArgumentNullException(nameof(options), "Table storage options cannot be null"); } - /// - /// Create the table - /// - public async Task CreateTableAsync() - { - await CloudTable.CreateIfNotExistsAsync().ConfigureAwait(false); - } + var validator = new TableStorageOptionsValidator(); + validator.ValidateAndThrow(options); - /// - /// Does the table exist - /// - /// - public bool TableExists() - { - return CloudTable.Exists(); - } + OptimisePerformance(storageConnectionString, options); + (_cloudTableService, CloudTable) = CreateTableClient(storageConnectionString, tableName, options.Retries, options.RetryWaitTimeInSeconds); - /// - /// Does the table exist - /// - /// - public async Task TableExistsAsync() - { - return await CloudTable.ExistsAsync().ConfigureAwait(false); - } + _tableName = tableName; - /// - /// Delete the table - /// - public void DeleteTable() + if (options.EnsureTableExists) { - CloudTable.DeleteIfExists(); + //if (!TableExists()) + //{ + CreateTable(); + //} } + } - /// - /// Delete the table - /// - public async Task DeleteTableAsync() - { - await CloudTable.DeleteIfExistsAsync().ConfigureAwait(false); - } + /// + /// Settings to improve performance + /// + private static void OptimisePerformance(string storageConnectionString, TableStorageOptions options) + { + var endpoint = ParseConnectionString.GetTableEndpoint(storageConnectionString); + var tableServicePoint = ServicePointManager.FindServicePoint(endpoint); + tableServicePoint.UseNagleAlgorithm = options.UseNagleAlgorithm; + tableServicePoint.Expect100Continue = options.Expect100Continue; + tableServicePoint.ConnectionLimit = options.ConnectionLimit; + } - /// - /// Get the number of the records in the table - /// - /// The record count - public int GetRecordCount() + /// + /// Create the table client + /// + /// The connection string + /// The name of the table + /// Number of retries + /// Wait time between retries in seconds + /// The table client + private static (TableServiceClient serviceClient, TableClient tableClient) CreateTableClient(string connectionString, string tableName, int retries, double retryWaitTimeInSeconds) + { + var options = new TableClientOptions { - TableContinuationToken continuationToken = null; + Retry = + { + MaxRetries = retries, + Delay = TimeSpan.FromSeconds(retryWaitTimeInSeconds), + Mode = Azure.Core.RetryMode.Exponential + } + }; - var query = new TableQuery().Select(new List { "PartitionKey" }); + var serviceClient = new TableServiceClient(connectionString, options); + var tableClient = serviceClient.GetTableClient(tableName); + return (serviceClient, tableClient); + } - var recordCount = 0; - do - { - var items = CloudTable.ExecuteQuerySegmented(query, continuationToken); - continuationToken = items.ContinuationToken; + /// + /// Create the table + /// + public void CreateTable() + { + CloudTable.CreateIfNotExists(); + } - recordCount += items.Count(); - } while (continuationToken != null); + /// + /// Create the table + /// + public Task CreateTableAsync() + { + return CloudTable.CreateIfNotExistsAsync(); + } - return recordCount; - } + /// + /// Does the table exist + /// + /// + public bool TableExists() + { + return _cloudTableService.Query(e => e.Name == _tableName).Any(); + } - /// - /// Get the number of the records in the table - /// - /// The record count - public async Task GetRecordCountAsync() - { - TableContinuationToken continuationToken = null; + /// + /// Does the table exist + /// + /// + public async Task TableExistsAsync() + { + return (await _cloudTableService.QueryAsync(e => e.Name == _tableName).ToListAsync()).Any(); + } - var query = new TableQuery().Select(new List { "PartitionKey" }); + /// + /// Delete the table + /// + public void DeleteTable() + { + CloudTable.Delete(); + } - var recordCount = 0; - do - { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; + /// + /// Delete the table + /// + public Task DeleteTableAsync() + { + return CloudTable.DeleteAsync(); + } - recordCount += items.Count(); - } while (continuationToken != null); + /// + /// Get the number of the records in the table + /// + /// The record count + public int GetRecordCount() + { + return CloudTable.Query(select: new List { "PartitionKey" }).Count(); + } - return recordCount; - } + /// + /// Get the number of the records in the table + /// + /// The record count + public async Task GetRecordCountAsync() + { + return await (CloudTable.QueryAsync(select: new List { "PartitionKey" })).CountAsync(); + } - #region Helpers + #region Helpers - /// - /// Ensures the partition key is not null. - /// - /// The partition key. - /// partitionKey - protected void EnsurePartitionKey(string partitionKey) + /// + /// Ensures the partition key is not null. + /// + /// The partition key. + /// partitionKey + protected void EnsurePartitionKey(string partitionKey) + { + if (string.IsNullOrWhiteSpace(partitionKey)) { - if (string.IsNullOrWhiteSpace(partitionKey)) - { - throw new ArgumentNullException(nameof(partitionKey)); - } + throw new ArgumentNullException(nameof(partitionKey), "PartitionKey cannot be null or empty"); } + } - /// - /// Ensures the row key is not null. - /// - /// The row key. - /// rowKey - protected void EnsureRowKey(string rowKey) + /// + /// Ensures the row key is not null. + /// + /// The row key. + /// rowKey + protected void EnsureRowKey(string rowKey) + { + if (string.IsNullOrWhiteSpace(rowKey)) { - if (string.IsNullOrWhiteSpace(rowKey)) - { - throw new ArgumentNullException(nameof(rowKey)); - } + throw new ArgumentNullException(nameof(rowKey), "RowKey cannot be null or empty"); } + } - protected void EnsureRecord(T record) + protected void EnsureRecord(T record) + { + if (record == null) { - if (record == null) - { - throw new ArgumentNullException(nameof(record)); - } + throw new ArgumentNullException(nameof(record), "Record cannot be null"); } + } - #endregion Helpers + /// + /// Builds the get by partition query. + /// + /// The partition key. + /// The table query + //private static TableQuery BuildGetByPartitionQuery(string partitionKey) + protected Pageable BuildGetByPartitionQuery(string partitionKey) where T : class, ITableEntity, new() + { + var queryResults = CloudTable.Query(filter: $"PartitionKey eq '{partitionKey}'"); + return queryResults; } + + protected AsyncPageable BuildGetByPartitionQueryAsync(string partitionKey) where T : class, ITableEntity, new() + { + var queryResults = CloudTable.QueryAsync(filter: $"PartitionKey eq '{partitionKey}'"); + return queryResults; + } + + /// + /// Build the row key table query + /// + /// The row key + /// The table query + protected Pageable BuildGetByRowKeyQuery(string rowKey) where T : class, ITableEntity, new() + { + var queryResults = CloudTable.Query(filter: $"RowKey eq '{rowKey}'"); + return queryResults; + } + + protected AsyncPageable BuildGetByRowKeyQueryAsync(string rowKey) where T : class, ITableEntity, new() + { + var queryResults = CloudTable.QueryAsync(filter: $"RowKey eq '{rowKey}'"); + return queryResults; + } + + #endregion Helpers } \ No newline at end of file diff --git a/src/TableStorage.Abstractions/Store/TableStoreDynamic.cs b/src/TableStorage.Abstractions/Store/TableStoreDynamic.cs index 83485e6..485178b 100644 --- a/src/TableStorage.Abstractions/Store/TableStoreDynamic.cs +++ b/src/TableStorage.Abstractions/Store/TableStoreDynamic.cs @@ -1,4 +1,4 @@ -using Microsoft.Azure.Cosmos.Table; +using Azure.Data.Tables; using System; using System.Collections.Generic; using System.Linq; @@ -34,36 +34,32 @@ public TableStoreDynamic(string tableName, string storageConnectionString, Table /// Insert an record /// /// The record to insert - public void Insert(T record) where T : class, ITableEntity + public void Insert(T record) where T : class, ITableEntity, new() { EnsureRecord(record); - var operation = TableOperation.Insert(record); - CloudTable.Execute(operation); + CloudTable.AddEntity(record); } /// /// Insert an record /// /// The record to insert - public Task InsertAsync(T record) where T : class, ITableEntity + public Task InsertAsync(T record) where T : class, ITableEntity, new() { EnsureRecord(record); - - var operation = TableOperation.Insert(record); - - return CloudTable.ExecuteAsync(operation); + return CloudTable.AddEntityAsync(record); } /// /// Insert multiple records /// /// The records to insert - public void Insert(IEnumerable records) where T : class, ITableEntity + public void Insert(IEnumerable records) where T : class, ITableEntity, new() { if (records == null) { - throw new ArgumentNullException(nameof(records)); + throw new ArgumentNullException(nameof(records), "Records cannot be null"); } var partitionKeySeparation = records.GroupBy(x => x.PartitionKey) @@ -72,13 +68,7 @@ public void Insert(IEnumerable records) where T : class, ITableEntity foreach (var entry in partitionKeySeparation) { - var operation = new TableBatchOperation(); - entry.ToList().ForEach(operation.Insert); - - if (operation.Any()) - { - CloudTable.ExecuteBatch(operation); - } + entry.ToList().ForEach(x => CloudTable.AddEntity(x)); } } @@ -86,7 +76,7 @@ public void Insert(IEnumerable records) where T : class, ITableEntity /// Insert multiple records /// /// The records to insert - public async Task InsertAsync(IEnumerable records) where T : class, ITableEntity + public async Task InsertAsync(IEnumerable records) where T : class, ITableEntity, new() { if (records == null) { @@ -99,12 +89,9 @@ public async Task InsertAsync(IEnumerable records) where T : class, ITable foreach (var entry in partitionKeySeparation) { - var operation = new TableBatchOperation(); - entry.ToList().ForEach(operation.Insert); - - if (operation.Any()) + await foreach (T qEntity in entry.ToAsyncEnumerable()) { - await CloudTable.ExecuteBatchAsync(operation).ConfigureAwait(false); + await CloudTable.AddEntityAsync(qEntity); } } } @@ -113,50 +100,44 @@ public async Task InsertAsync(IEnumerable records) where T : class, ITable /// Inserts or replaces the record /// /// - public void InsertOrReplace(T record) where T : class, ITableEntity + public void InsertOrReplace(T record) where T : class, ITableEntity, new() { EnsureRecord(record); - var operation = TableOperation.InsertOrReplace(record); - CloudTable.Execute(operation); + CloudTable.UpsertEntity(record); } /// /// Inserts or replaces the record /// /// - public Task InsertOrReplaceAsync(T record) where T : class, ITableEntity + public Task InsertOrReplaceAsync(T record) where T : class, ITableEntity, new() { EnsureRecord(record); - var operation = TableOperation.InsertOrReplace(record); - - return CloudTable.ExecuteAsync(operation); + return CloudTable.UpsertEntityAsync(record); } /// /// Update an record /// /// The record to update - public void Update(T record) where T : class, ITableEntity + public void Update(T record) where T : class, ITableEntity, new() { EnsureRecord(record); - var operation = TableOperation.Merge(record); - CloudTable.Execute(operation); + CloudTable.UpdateEntity(record, record.ETag); } /// /// Update an record /// /// The record to update - public Task UpdateAsync(T record) where T : class, ITableEntity + public Task UpdateAsync(T record) where T : class, ITableEntity, new() { EnsureRecord(record); - var operation = TableOperation.Merge(record); - - return CloudTable.ExecuteAsync(operation); + return CloudTable.UpdateEntityAsync(record, record.ETag); } /// @@ -167,8 +148,7 @@ public void Delete(T record) where T : class, ITableEntity { EnsureRecord(record); - var operation = TableOperation.Delete(record); - CloudTable.Execute(operation); + CloudTable.DeleteEntity(record.PartitionKey, record.RowKey); } /// @@ -179,9 +159,7 @@ public Task DeleteAsync(T record) where T : class, ITableEntity { EnsureRecord(record); - var operation = TableOperation.Delete(record); - - return CloudTable.ExecuteAsync(operation); + return CloudTable.DeleteEntityAsync(record.PartitionKey, record.RowKey); } /// @@ -190,18 +168,13 @@ public Task DeleteAsync(T record) where T : class, ITableEntity /// /// /// The record found or null if not found - public T GetRecord(string partitionKey, string rowKey) where T : class, ITableEntity + public T GetRecord(string partitionKey, string rowKey) where T : class, ITableEntity, new() { EnsurePartitionKey(partitionKey); EnsureRowKey(rowKey); - // Create a retrieve operation that takes a customer record. - var retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey); - - // Execute the operation. - var retrievedResult = CloudTable.Execute(retrieveOperation); - return retrievedResult.Result as T; + return CloudTable.GetEntity(partitionKey, rowKey); } /// @@ -210,61 +183,26 @@ public T GetRecord(string partitionKey, string rowKey) where T : class, ITabl /// /// /// The record found or null if not found - public async Task GetRecordAsync(string partitionKey, string rowKey) where T : class, ITableEntity + public async Task GetRecordAsync(string partitionKey, string rowKey) where T : class, ITableEntity, new() { EnsurePartitionKey(partitionKey); EnsureRowKey(rowKey); - // Create a retrieve operation that takes a customer record. - var retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey); - - // Execute the operation. - var retrievedResult = await CloudTable.ExecuteAsync(retrieveOperation).ConfigureAwait(false); - - return retrievedResult.Result as T; + return await CloudTable.GetEntityAsync(partitionKey, rowKey); } - /// - /// Get all the records in the table - /// - /// All records - public IEnumerable GetAllRecords() + public IEnumerable GetAllRecords() { - var query = new TableQuery(); - - var token = new TableContinuationToken(); - var segment = CloudTable.ExecuteQuerySegmented(query, token); - while (token != null) - { - foreach (var result in segment) - { - yield return result; - } - token = segment.ContinuationToken; - segment = CloudTable.ExecuteQuerySegmented(query, token); - } + var query = CloudTable.Query(); + return query; } - /// - /// Get all the records in the table - /// - /// All records - public async Task> GetAllRecordsAsync() + public async Task> GetAllRecordsAsync() { - TableContinuationToken continuationToken = null; - - var query = new TableQuery(); - - var allItems = new List(); - do - { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); + var queryResults = CloudTable.QueryAsync(); - return allItems; + return await queryResults.ToListAsync(); } /// @@ -276,19 +214,8 @@ public async Task> GetAllRecordsAsync() { EnsurePartitionKey(partitionKey); - TableContinuationToken continuationToken = null; - - var query = BuildGetByPartitionQuery(partitionKey); - - var allItems = new List(); - do - { - var items = ExecuteQuerySegment(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; + var query = BuildGetByPartitionQuery(partitionKey); + return query; } /// @@ -300,19 +227,9 @@ public async Task> GetAllRecordsAsync() { EnsurePartitionKey(partitionKey); - TableContinuationToken continuationToken = null; - - var query = BuildGetByPartitionQuery(partitionKey); - - var allItems = new List(); - do - { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, CreateEntityResolver(), continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); + var queryResults = CloudTable.QueryAsync(filter: $"PartitionKey eq '{partitionKey}'"); - return allItems; + return await queryResults.ToListAsync(); } /// @@ -324,19 +241,7 @@ public async Task> GetAllRecordsAsync() { EnsureRowKey(rowKey); - TableContinuationToken continuationToken = null; - - var query = BuildGetByRowKeyQuery(rowKey); - - var allItems = new List(); - do - { - var items = ExecuteQuerySegment(query, continuationToken); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; + return BuildGetByRowKeyQuery(rowKey); } /// @@ -347,70 +252,9 @@ public async Task> GetAllRecordsAsync() public async Task> GetByRowKeyAsync(string rowKey) where T : class, ITableEntity, new() { EnsureRowKey(rowKey); + var queryResults = CloudTable.QueryAsync(filter: $"RowKey eq '{rowKey}'"); - TableContinuationToken continuationToken = null; - - var query = BuildGetByRowKeyQuery(rowKey); - - var allItems = new List(); - do - { - var items = await CloudTable.ExecuteQuerySegmentedAsync(query, CreateEntityResolver(), continuationToken).ConfigureAwait(false); - continuationToken = items.ContinuationToken; - allItems.AddRange(items); - } while (continuationToken != null); - - return allItems; - } - - #region Helpers - - /// - /// Create the entity resolver for type T - /// - /// The entity resolver - private static EntityResolver CreateEntityResolver() where T : class, ITableEntity, new() - { - return (pk, rk, ts, props, etag) => - { - var resolvedEntity = new T { PartitionKey = pk, RowKey = rk, Timestamp = ts, ETag = etag }; - resolvedEntity.ReadEntity(props, null); - return resolvedEntity; - }; - } - - private TableQuerySegment ExecuteQuerySegment(TableQuery query, TableContinuationToken continuationToken) where T : class, ITableEntity, new() - { - var items = CloudTable.ExecuteQuerySegmented(query, CreateEntityResolver(), continuationToken); - return items; + return await queryResults.ToListAsync(); } - - /// - /// Build the row key table query - /// - /// The row key - /// The table query - private static TableQuery BuildGetByRowKeyQuery(string rowKey) - { - var filter = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, rowKey); - - var query = new TableQuery().Where(filter); - - return query; - } - - /// - /// Builds the get by partition query. - /// - /// The partition key. - /// The table query - private static TableQuery BuildGetByPartitionQuery(string partitionKey) - { - var query = new TableQuery().Where( - TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey)); - return query; - } - - #endregion Helpers } } \ No newline at end of file diff --git a/src/TableStorage.Abstractions/TableStorage.Abstractions.csproj b/src/TableStorage.Abstractions/TableStorage.Abstractions.csproj index 63cc10c..ee4f697 100644 --- a/src/TableStorage.Abstractions/TableStorage.Abstractions.csproj +++ b/src/TableStorage.Abstractions/TableStorage.Abstractions.csproj @@ -9,10 +9,12 @@ 1.0.0 - - + + + + + - diff --git a/tests/TableStorage.Abstractions.Tests/Helpers/TestDataHelper.cs b/tests/TableStorage.Abstractions.Tests/Helpers/TestDataHelper.cs index f6888fa..c2ba71b 100644 --- a/tests/TableStorage.Abstractions.Tests/Helpers/TestDataHelper.cs +++ b/tests/TableStorage.Abstractions.Tests/Helpers/TestDataHelper.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using TableStorage.Abstractions.Parsers; using TableStorage.Abstractions.Store; using Useful.Extensions; @@ -29,6 +30,40 @@ public static async Task SetupRecords(ITableStore tableStorage) await tableStorage.InsertAsync(entityList).ConfigureAwait(false); } + public static async Task SetupRecordsAgo(ITableStore tableStorage, string ago) + { + await tableStorage.DeleteAllAsync(); + + var entityList = new List + { + new TestTableEntity("Kevin", "Bacon") {Age = 21, Email = "kevin.bacon@something.com"}, + new TestTableEntity("Steven", "Jones") {Age = 32, Email = "steven.jones@somewhere.com"} + }; + + await tableStorage.CreateTableAsync().ConfigureAwait(false); + await tableStorage.InsertAsync(entityList).ConfigureAwait(false); + + await Task.Delay(TimeStringParser.GetTimeAgoTimeSpan(ago)); + + var anotherEntityList = new List + { + new TestTableEntity("Liam", "Matthews") {Age = 28, Email = "liam.matthews@something.com"}, + new TestTableEntity("Mary", "Gates") {Age = 45, Email = "mary.gates@somewhere.com"} + }; + + await tableStorage.InsertAsync(anotherEntityList).ConfigureAwait(false); + } + + public static void SetupLotsOfRecords(int count, ITableStore tableStorage) + { + tableStorage.CreateTable(); + for (var i = 0; i < count; i++) + { + var entry = new TestTableEntity($"name{i}", $"surname{count}") { Age = 32, Email = $"surname{count}@somewhere.com" }; + tableStorage.Insert(entry); + } + } + public static async Task SetupRecords(ITableStoreDynamic tableStorage) { var entityList = new List @@ -49,7 +84,6 @@ public static async Task SetupRecords(ITableStoreDynamic tableStorage) await tableStorage.InsertAsync(entityList).ConfigureAwait(false); } - public static void SetupRowKeyRecords(ITableStore tableStorage) { var entityList = new List diff --git a/tests/TableStorage.Abstractions.Tests/Helpers/TestTableEntity.cs b/tests/TableStorage.Abstractions.Tests/Helpers/TestTableEntity.cs index 208b6a1..63cf93c 100644 --- a/tests/TableStorage.Abstractions.Tests/Helpers/TestTableEntity.cs +++ b/tests/TableStorage.Abstractions.Tests/Helpers/TestTableEntity.cs @@ -1,8 +1,10 @@ -using Microsoft.Azure.Cosmos.Table; +using Azure; +using Azure.Data.Tables; +using System; namespace TableStorage.Abstractions.Tests.Helpers { - public class TestTableEntity : TableEntity + public class TestTableEntity : ITableEntity { public int Age { get; set; } public string Email { get; set; } @@ -16,5 +18,10 @@ public TestTableEntity(string name, string surname) PartitionKey = surname; RowKey = name; } + + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } } } \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Parsers/TimeStringParserTests.cs b/tests/TableStorage.Abstractions.Tests/Parsers/TimeStringParserTests.cs index 5451e17..589df4f 100644 --- a/tests/TableStorage.Abstractions.Tests/Parsers/TimeStringParserTests.cs +++ b/tests/TableStorage.Abstractions.Tests/Parsers/TimeStringParserTests.cs @@ -13,6 +13,21 @@ public TimeStringParserTests() SystemTime.UtcNow = () => new DateTime(2018, 01, 01, 08, 10, 00); } + [Theory] + [InlineData("30s")] + [InlineData("30S")] + public void given_time_string_parser_get_time_ago_when_30s_then_the_result_is_30_seconds_in_the_past(string ago) + { + // Arrange + var expected = new DateTime(2018, 01, 01, 08, 9, 30); + + // Act + var result = TimeStringParser.GetTimeAgo(ago); + + // Assert + result.Should().Be(expected); + } + [Theory] [InlineData("10m")] [InlineData("10M")] @@ -66,18 +81,19 @@ public void given_time_string_parser_get_time_ago_when_value_does_not_contain_va Action act = () => TimeStringParser.GetTimeAgo("1"); // Assert - act.Should().Throw().WithMessage("Time ago value '1' is invalid. Values must be in the format of 1m, 1h, 1d.\r\nParameter Name: ago"); + act.Should().Throw().WithMessage("Time ago value '1' is invalid. Values must be in the format of 1m, 1h, 1d.*"); } [Fact] public void given_time_string_parser_get_time_ago_when_value_contains_additional_characters_after_the_valid_time_then_an_exception_is_thrown() { // Arrange + var invalidAgo = "1hiudadlj"; // Act - Action act = () => TimeStringParser.GetTimeAgo("1hdfyskdhfkds"); + Action act = () => TimeStringParser.GetTimeAgo(invalidAgo); // Assert - act.Should().Throw().WithMessage("Time ago value '1hdfyskdhfkds' is invalid. Values must be in the format of 1m, 1h, 1d.\r\nParameter Name: ago"); + act.Should().Throw().WithMessage($"Time ago value '{invalidAgo}' is invalid. Values must be in the format of 1m, 1h, 1d.*"); } } } \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreAsyncTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreAsyncTests.cs deleted file mode 100644 index 372bf67..0000000 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreAsyncTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using FluentAssertions; -using System; -using System.Threading.Tasks; -using TableStorage.Abstractions.Store; -using TableStorage.Abstractions.Tests.Helpers; -using Xunit; - -namespace TableStorage.Abstractions.Tests.Store -{ - public partial class TableStoreAsyncTests : IDisposable - { - private const string TableName = "TestTableAsync"; - private const string ConnectionString = "UseDevelopmentStorage=true"; - private readonly ITableStore _tableStorage; - private readonly ITableStoreDynamic _tableStorageDynamic; - private readonly TableStorageOptions _tableStorageOptions = new TableStorageOptions(); - - public TableStoreAsyncTests() - { - _tableStorage = new TableStore(TableName, ConnectionString, _tableStorageOptions); - _tableStorageDynamic = new TableStoreDynamic(TableName, ConnectionString); - } - - public void Dispose() - { - _tableStorage.DeleteTable(); - } - - [Fact] - public async Task table_does_exist_then_exist_check_returns_true() - { - // Arrange - await _tableStorage.DeleteTableAsync(); - await _tableStorage.CreateTableAsync(); - - // Act - var result = await _tableStorage.TableExistsAsync(); - - // Assert - result.Should().BeTrue(); - } - - [Fact] - public async Task table_does_not_exist_then_exist_check_returns_false() - { - // Arrange - await _tableStorage.DeleteTableAsync(); - - // Act - var result = await _tableStorage.TableExistsAsync(); - - // Assert - result.Should().BeFalse(); - } - } -} \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreDeleteTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDeleteTests.cs index 32f32c8..6036870 100644 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreDeleteTests.cs +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDeleteTests.cs @@ -1,7 +1,7 @@ -using System; +using FluentAssertions; +using System; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using TableStorage.Abstractions.Tests.Helpers; using Xunit; @@ -17,18 +17,7 @@ public void delete_with_null_record_throws_exception() Action act = () => _tableStorage.Delete(null as TestTableEntity); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); - } - - [Fact] - public void delete_dynamic_with_null_record_throws_exception() - { - // Arrange - // Act - Action act = () => _tableStorageDynamic.Delete(null as TestTableEntity); - - // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + act.Should().Throw().WithMessage("Record cannot be null*"); } [Fact] @@ -48,40 +37,75 @@ public async Task delete_an_entry_and_the_record_count_should_decrease() result.Count().Should().Be(1); } - [Fact] - public async Task delete_a_dynamic_entry_and_the_record_count_should_decrease() + public void delete_using_wild_card_etag_when_entity_is_null_then_throws_an_exception() { // Arrange - await TestDataHelper.SetupRecords(_tableStorageDynamic); - var item = _tableStorageDynamic.GetRecord("Smith", "John"); + // Act + Action act = () => _tableStorage.DeleteUsingWildcardEtag(null as TestTableEntity); + // Assert + act.Should().Throw().WithMessage("Record cannot be null*"); + } + + [Fact] + public void delete_async_with_null_record_throws_exception() + { + // Arrange // Act + Func act = async () => await _tableStorage.DeleteAsync(null as TestTableEntity); - _tableStorageDynamic.Delete(item); + // Assert + act.Should().ThrowAsync().WithMessage("Value cannot be null.\r\nParameter name: record"); + } - var result = _tableStorageDynamic.GetByPartitionKey("Smith"); + [Fact] + public async Task delete_async_an_entry_and_the_record_count_should_decrease() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + var item = await _tableStorage.GetRecordAsync("Smith", "John"); + + // Act + await _tableStorage.DeleteAsync(item); + + var result = await _tableStorage.GetByPartitionKeyAsync("Smith"); // Assert result.Count().Should().Be(1); } [Fact] - public void delete_using_wild_card_etag_when_entity_is_null_then_throws_an_exception() + public async Task delete_all_leaves_no_records_in_the_table() { // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + // Act - Action act = () => _tableStorage.DeleteUsingWildcardEtag(null as TestTableEntity); + _tableStorage.DeleteAll(); - // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + // + _tableStorage.GetRecordCount().Should().Be(0); + } + + [Fact] + public async Task delete_all_async_leaves_no_records_in_the_table() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + await _tableStorage.DeleteAllAsync(); + + // + (await _tableStorage.GetRecordCountAsync()).Should().Be(0); } [Fact] public async Task delete_all_records_and_the_record_count_should_be_zero() { // Arrange - await TestDataHelper.SetupRecords(_tableStorageDynamic); + await TestDataHelper.SetupRecords(_tableStorage); // Act await _tableStorage.DeleteAllAsync(); @@ -91,11 +115,12 @@ public async Task delete_all_records_and_the_record_count_should_be_zero() result.Count().Should().Be(0); } + [Fact] public async Task delete_records_by_partitionkey_and_record_count_by_partition_should_be_zero() { // Arrange - await TestDataHelper.SetupRecords(_tableStorageDynamic); + await TestDataHelper.SetupRecords(_tableStorage); // Act await _tableStorage.DeleteByPartitionAsync("Smith"); diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreDeleteAsyncTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicDeleteTests.cs similarity index 64% rename from tests/TableStorage.Abstractions.Tests/Store/TableStoreDeleteAsyncTests.cs rename to tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicDeleteTests.cs index 0c48c0b..81f3d94 100644 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreDeleteAsyncTests.cs +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicDeleteTests.cs @@ -7,44 +7,45 @@ namespace TableStorage.Abstractions.Tests.Store { - public partial class TableStoreAsyncTests + public partial class TableStoreDynamicTests { [Fact] - public void delete_async_with_null_record_throws_exception() + public void delete_dynamic_with_null_record_throws_exception() { // Arrange // Act - Func act = async () => await _tableStorage.DeleteAsync(null as TestTableEntity); + Action act = () => _tableStorageDynamic.Delete(null as TestTableEntity); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + act.Should().Throw().WithMessage("Record cannot be null*"); } [Fact] - public void delete_async_dynamic_with_null_record_throws_exception() + public async Task delete_a_dynamic_entry_and_the_record_count_should_decrease() { // Arrange + await TestDataHelper.SetupRecords(_tableStorageDynamic); + var item = _tableStorageDynamic.GetRecord("Smith", "John"); + // Act - Func act = async () => await _tableStorageDynamic.DeleteAsync(null as TestTableEntity); + + _tableStorageDynamic.Delete(item); + + var result = _tableStorageDynamic.GetByPartitionKey("Smith"); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + result.Count().Should().Be(1); } [Fact] - public async Task delete_async_an_entry_and_the_record_count_should_decrease() + public void delete_async_dynamic_with_null_record_throws_exception() { // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - var item = await _tableStorage.GetRecordAsync("Smith", "John"); - // Act - await _tableStorage.DeleteAsync(item); - - var result = await _tableStorage.GetByPartitionKeyAsync("Smith"); + Func act = async () => await _tableStorageDynamic.DeleteAsync(null as TestTableEntity); // Assert - result.Count().Should().Be(1); + act.Should().ThrowAsync().WithMessage("Record cannot be null*"); } [Fact] diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreInsertAsyncTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicInsertTests.cs similarity index 64% rename from tests/TableStorage.Abstractions.Tests/Store/TableStoreInsertAsyncTests.cs rename to tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicInsertTests.cs index 377e492..d8b91bd 100644 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreInsertAsyncTests.cs +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicInsertTests.cs @@ -8,158 +8,169 @@ namespace TableStorage.Abstractions.Tests.Store { - public partial class TableStoreAsyncTests + public partial class TableStoreDynamicTests { [Fact] - public void insert_async_with_null_record_throws_exception() + public void insert_dynamic_with_null_record_throws_exception() { // Arrange // Act - Func act = async () => await _tableStorage.InsertAsync(null as TestTableEntity); + Action act = () => _tableStorageDynamic.Insert(null as TestTableEntity); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + act.Should().Throw().WithMessage("Record cannot be null*"); } [Fact] - public void insert_async_dynamic_with_null_record_throws_exception() + public void insert_dynamic_with_null_for_multiple_records_throws_exception() { // Arrange // Act - Func act = async () => await _tableStorageDynamic.InsertAsync(null as TestTableEntity); + Action act = () => _tableStorageDynamic.Insert(null as IEnumerable); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + act.Should().Throw().WithMessage("Records cannot be null*"); } [Fact] - public async Task insert_record_into_the_table_async_inserts_with_a_count_greater_than_zero() + public void insert_multiple_dynamic_records_into_the_table_and_record_count_should_be_greater_than_zero() { // Arrange - var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; + var entityList = new List + { + new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"}, + new TestTableEntity("Jane", "Smith") {Age = 28, Email = "jane.smith@something.com"} + }; // Act - await _tableStorage.InsertAsync(testEntity); - var result = await _tableStorage.GetByRowKeyAsync("John"); + _tableStorageDynamic.Insert(entityList); + var result = _tableStorageDynamic.GetByPartitionKey("Smith").ToList(); // Assert - result.Count().Should().BeGreaterThan(0); + result.Count.Should().BeGreaterThan(0); } [Fact] - public async Task insert_dynamic_record_into_the_table_async_inserts_with_a_count_greater_than_zero() + public async Task insert_with_empty_list_of_dynamic_records_does_not_insert_records_to_the_table() { // Arrange - var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; + await TestDataHelper.SetupRecords(_tableStorageDynamic); // Act - await _tableStorageDynamic.InsertAsync(testEntity); - var result = await _tableStorageDynamic.GetByRowKeyAsync("John"); + _tableStorageDynamic.Insert(new List()); + var result = _tableStorageDynamic.GetAllRecords().ToList(); // Assert - result.Count().Should().BeGreaterThan(0); + result.Count.Should().Be(4); } [Fact] - public void insert_async_with_null_for_multiple_records_throws_exception() + public void insert_multiple_dynamic_records_with_different_partition_keys_inserts_the_expected_count() { // Arrange + var entryList = TestDataHelper.GetMultiplePartitionKeyRecords(); + // Act - Func act = async () => await _tableStorage.InsertAsync(null as IEnumerable); + _tableStorageDynamic.Insert(entryList); + var result = _tableStorageDynamic.GetAllRecords().ToList(); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: records"); + result.Count.Should().Be(entryList.Count); } [Fact] - public void insert_async_dynamic_with_null_for_multiple_records_throws_exception() + public void insert_multiple_dynamic_records_with_same_partition_key_and_more_than_the_100_max_batch_size_still_inserts_all_the_records() { // Arrange + var entryList = TestDataHelper.GetMoreThanMaxSinglePartitionRecords(); + // Act - Func act = async () => await _tableStorageDynamic.InsertAsync(null as IEnumerable); + _tableStorageDynamic.Insert(entryList); + var result = _tableStorageDynamic.GetAllRecords().ToList(); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: records"); + result.Count.Should().Be(entryList.Count); } [Fact] - public async Task insert_or_replace_async_record_into_the_table_when_record_does_not_exist_and_record_count_should_be_greater_than_zero() + public void insert_multiple_dynamic_records_with_multiple_partition_keys_and_more_than_the_100_max_batch_size_in_for_all_and_still_inserts_all_the_records() { // Arrange - var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; + var entryList = TestDataHelper.GetMoreThanMaxMultiplePartitionRecords(); // Act - await _tableStorage.InsertOrReplaceAsync(testEntity); + _tableStorageDynamic.Insert(entryList); + var result = _tableStorageDynamic.GetAllRecords().ToList(); - var result = (await _tableStorage.GetByRowKeyAsync("John")).ToList(); + // Assert + result.Count.Should().Be(entryList.Count); + } + + [Fact] + public void insert_async_dynamic_with_null_record_throws_exception() + { + // Arrange + // Act + Func act = async () => await _tableStorageDynamic.InsertAsync(null as TestTableEntity); // Assert - result.Count.Should().BeGreaterThan(0); + act.Should().ThrowAsync().WithMessage("Value cannot be null.\r\nParameter name: record"); } [Fact] - public async Task insert_or_replace_async_dynamic_record_into_the_table_when_record_does_not_exist_and_record_count_should_be_greater_than_zero() + public async Task insert_dynamic_record_into_the_table_async_inserts_with_a_count_greater_than_zero() { // Arrange var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; // Act - await _tableStorageDynamic.InsertOrReplaceAsync(testEntity); - - var result = (await _tableStorageDynamic.GetByRowKeyAsync("John")).ToList(); + await _tableStorageDynamic.InsertAsync(testEntity); + var result = await _tableStorageDynamic.GetByRowKeyAsync("John"); // Assert - result.Count.Should().BeGreaterThan(0); + result.Count().Should().BeGreaterThan(0); } [Fact] - public async Task insert_or_replace_async_record_into_the_table_when_record_does_exist_and_record_should_have_updated_fields() + public void insert_async_dynamic_with_null_for_multiple_records_throws_exception() { // Arrange - var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; - await _tableStorage.InsertAsync(testEntity); // Act - testEntity = new TestTableEntity("John", "Smith") { Age = 45, Email = "john.smith@something.com" }; - await _tableStorage.InsertOrReplaceAsync(testEntity); - - var result = (await _tableStorage.GetByRowKeyAsync("John")).ToList(); + Func act = async () => await _tableStorageDynamic.InsertAsync(null as IEnumerable); // Assert - result[0].Age.Should().Be(45); + act.Should().ThrowAsync().WithMessage("Records cannot be null*"); } [Fact] - public async Task insert_or_replace_async_dynamic_record_into_the_table_when_record_does_exist_and_record_should_have_updated_fields() + public async Task insert_or_replace_async_dynamic_record_into_the_table_when_record_does_not_exist_and_record_count_should_be_greater_than_zero() { // Arrange var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; - await _tableStorageDynamic.InsertAsync(testEntity); + // Act - testEntity = new TestTableEntity("John", "Smith") { Age = 45, Email = "john.smith@something.com" }; await _tableStorageDynamic.InsertOrReplaceAsync(testEntity); var result = (await _tableStorageDynamic.GetByRowKeyAsync("John")).ToList(); // Assert - result[0].Age.Should().Be(45); + result.Count.Should().BeGreaterThan(0); } [Fact] - public async Task insert_async_multiple_records_into_the_table_and_record_count_should_be_greater_than_zero() + public async Task insert_or_replace_async_dynamic_record_into_the_table_when_record_does_exist_and_record_should_have_updated_fields() { // Arrange - var entityList = new List - { - new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"}, - new TestTableEntity("Jane", "Smith") {Age = 28, Email = "jane.smith@something.com"} - }; - + var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; + await _tableStorageDynamic.InsertAsync(testEntity); // Act - await _tableStorage.InsertAsync(entityList); - var result = await _tableStorage.GetByPartitionKeyAsync("Smith"); + testEntity = new TestTableEntity("John", "Smith") { Age = 45, Email = "john.smith@something.com" }; + await _tableStorageDynamic.InsertOrReplaceAsync(testEntity); + + var result = (await _tableStorageDynamic.GetByRowKeyAsync("John")).ToList(); // Assert - result.Count().Should().BeGreaterThan(0); + result[0].Age.Should().Be(45); } [Fact] @@ -180,47 +191,6 @@ public async Task insert_async_multiple_dynamic_records_into_the_table_and_recor result.Count().Should().BeGreaterThan(0); } - [Fact] - public async Task insert_async_with_empty_list_of_records_does_not_insert_records_to_the_table() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - await _tableStorage.InsertAsync(new List()); - var result = await _tableStorage.GetAllRecordsAsync(); - - // Assert - result.Count().Should().Be(4); - } - - [Fact] - public async Task insert_async_dynamic_with_empty_list_of_records_does_not_insert_records_to_the_table() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorageDynamic); - - // Act - await _tableStorageDynamic.InsertAsync(new List()); - var result = await _tableStorageDynamic.GetAllRecordsAsync(); - - // Assert - result.Count().Should().Be(4); - } - - [Fact] - public async Task insert_async_multiple_records_with_different_partition_keys_inserts_the_expected_count() - { - // Arrange - var entryList = TestDataHelper.GetMultiplePartitionKeyRecords(); - - // Act - await _tableStorage.InsertAsync(entryList); - var result = await _tableStorage.GetAllRecordsAsync(); - - // Assert - result.Count().Should().Be(entryList.Count); - } [Fact] public async Task insert_async_multiple_dynamic_records_with_different_partition_keys_inserts_the_expected_count() @@ -237,24 +207,24 @@ public async Task insert_async_multiple_dynamic_records_with_different_partition } [Fact] - public async Task insert_async_multiple_records_with_same_partition_key_and_more_than_the_100_max_batch_size_still_inserts_all_the_records() + public async Task insert_async_multiple_dynamic_records_with_same_partition_key_and_more_than_the_100_max_batch_size_still_inserts_all_the_records() { // Arrange var entryList = TestDataHelper.GetMoreThanMaxSinglePartitionRecords(); // Act - await _tableStorage.InsertAsync(entryList); - var result = await _tableStorage.GetAllRecordsAsync(); + await _tableStorageDynamic.InsertAsync(entryList); + var result = await _tableStorageDynamic.GetAllRecordsAsync(); // Assert result.Count().Should().Be(entryList.Count); } [Fact] - public async Task insert_async_multiple_dynamic_records_with_same_partition_key_and_more_than_the_100_max_batch_size_still_inserts_all_the_records() + public async Task insert_async_multiple_dynamic_records_with_multiple_partition_keys_and_more_than_the_100_max_batch_size_in_for_all_and_still_inserts_all_the_records() { // Arrange - var entryList = TestDataHelper.GetMoreThanMaxSinglePartitionRecords(); + var entryList = TestDataHelper.GetMoreThanMaxMultiplePartitionRecords(); // Act await _tableStorageDynamic.InsertAsync(entryList); @@ -265,31 +235,34 @@ public async Task insert_async_multiple_dynamic_records_with_same_partition_key_ } [Fact] - public async Task insert_async_multiple_records_with_multiple_partition_keys_and_more_than_the_100_max_batch_size_in_for_all_and_still_inserts_all_the_records() + public void insert_dynamic_record_into_the_table_and_record_count_should_be_greater_than_zero() { // Arrange - var entryList = TestDataHelper.GetMoreThanMaxMultiplePartitionRecords(); + var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; // Act - await _tableStorage.InsertAsync(entryList); - var result = await _tableStorage.GetAllRecordsAsync(); + _tableStorageDynamic.Insert(testEntity); + + var result = _tableStorageDynamic.GetByRowKey("John").ToList(); // Assert - result.Count().Should().Be(entryList.Count); + result.Count.Should().BeGreaterThan(0); } [Fact] - public async Task insert_async_multiple_dynamic_records_with_multiple_partition_keys_and_more_than_the_100_max_batch_size_in_for_all_and_still_inserts_all_the_records() + public void insert_or_replace_dynamic_record_into_the_table_when_record_does_exist_and_record_should_have_updated_fields() { // Arrange - var entryList = TestDataHelper.GetMoreThanMaxMultiplePartitionRecords(); - + var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; + _tableStorageDynamic.Insert(testEntity); // Act - await _tableStorageDynamic.InsertAsync(entryList); - var result = await _tableStorageDynamic.GetAllRecordsAsync(); + testEntity = new TestTableEntity("John", "Smith") { Age = 45, Email = "john.smith@something.com" }; + _tableStorageDynamic.InsertOrReplace(testEntity); + + var result = _tableStorageDynamic.GetByRowKey("John").ToList(); // Assert - result.Count().Should().Be(entryList.Count); + result[0].Age.Should().Be(45); } } } \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicTests.cs new file mode 100644 index 0000000..ca00a37 --- /dev/null +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicTests.cs @@ -0,0 +1,22 @@ +using System; +using TableStorage.Abstractions.Store; + +namespace TableStorage.Abstractions.Tests.Store +{ + public partial class TableStoreDynamicTests : IDisposable + { + private readonly ITableStoreDynamic _tableStorageDynamic; + private const string TableName = "TestTableDynamic"; + private const string ConnectionString = "UseDevelopmentStorage=true"; + + public TableStoreDynamicTests() + { + _tableStorageDynamic = new TableStoreDynamic(TableName, ConnectionString); + } + + public void Dispose() + { + _tableStorageDynamic.DeleteTable(); + } + } +} diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicUpdateTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicUpdateTests.cs new file mode 100644 index 0000000..0dfab0b --- /dev/null +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreDynamicUpdateTests.cs @@ -0,0 +1,71 @@ +using FluentAssertions; +using System; +using System.Threading.Tasks; +using TableStorage.Abstractions.Tests.Helpers; +using Xunit; + +namespace TableStorage.Abstractions.Tests.Store +{ + public partial class TableStoreDynamicTests + { + [Fact] + public void update_dynamic_with_null_record_throws_exception() + { + // Arrange + // Act + Action act = () => _tableStorageDynamic.Update(null as TestTableEntity); + + // Assert + act.Should().Throw().WithMessage("Record cannot be null*"); + } + + [Fact] + public void update_async_dynamic_with_null_record_throws_exception() + { + // Arrange + // Act + Func act = async () => await _tableStorageDynamic.UpdateAsync(null as TestTableEntity); + + // Assert + act.Should().ThrowAsync().WithMessage("Record cannot be null*"); + } + + [Fact] + public async Task update_a_dynamic_record_in_the_table_and_the_change_should_be_recorded() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorageDynamic); + + // Act + var item = _tableStorageDynamic.GetRecord("Smith", "John"); + + item.Age = 22; + + _tableStorageDynamic.Update(item); + + var item2 = _tableStorageDynamic.GetRecord("Smith", "John"); + + // Assert + item2.Age.Should().Be(22); + } + + [Fact] + public async Task update_async_a_dynamic_record_in_the_table_and_the_change_should_be_recorded() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorageDynamic); + + // Act + var item = await _tableStorageDynamic.GetRecordAsync("Smith", "John"); + + item.Age = 22; + + await _tableStorageDynamic.UpdateAsync(item); + + var item2 = await _tableStorageDynamic.GetRecordAsync("Smith", "John"); + + // Assert + item2.Age.Should().Be(22); + } + } +} \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreInsertTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreInsertTests.cs index f5be3ef..e299ae4 100644 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreInsertTests.cs +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreInsertTests.cs @@ -18,18 +18,7 @@ public void insert_with_null_record_throws_exception() Action act = () => _tableStorage.Insert(null as TestTableEntity); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); - } - - [Fact] - public void insert_dynamic_with_null_record_throws_exception() - { - // Arrange - // Act - Action act = () => _tableStorageDynamic.Insert(null as TestTableEntity); - - // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + act.Should().Throw().WithMessage("Record cannot be null*"); } [Fact] @@ -48,250 +37,260 @@ public void insert_record_into_the_table_and_record_count_should_be_greater_than } [Fact] - public void insert_dynamic_record_into_the_table_and_record_count_should_be_greater_than_zero() + public void insert_or_replace_record_into_the_table_when_record_does_not_exist_and_record_count_should_be_greater_than_zero() { // Arrange var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; // Act - _tableStorageDynamic.Insert(testEntity); + _tableStorage.InsertOrReplace(testEntity); - var result = _tableStorageDynamic.GetByRowKey("John").ToList(); + var result = _tableStorage.GetByRowKey("John").ToList(); // Assert result.Count.Should().BeGreaterThan(0); } [Fact] - public void insert_or_replace_record_into_the_table_when_record_does_not_exist_and_record_count_should_be_greater_than_zero() + public void insert_or_replace_record_into_the_table_when_record_does_exist_and_record_should_have_updated_fields() { // Arrange var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; - + _tableStorage.Insert(testEntity); // Act + testEntity = new TestTableEntity("John", "Smith") { Age = 45, Email = "john.smith@something.com" }; _tableStorage.InsertOrReplace(testEntity); var result = _tableStorage.GetByRowKey("John").ToList(); // Assert - result.Count.Should().BeGreaterThan(0); + result[0].Age.Should().Be(45); } [Fact] - public void insert_or_replace_dynamic_record_into_the_table_when_record_does_not_exist_and_record_count_should_be_greater_than_zero() + public void insert_with_null_for_multiple_records_throws_exception() { // Arrange - var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; - // Act - _tableStorageDynamic.InsertOrReplace(testEntity); + Action act = () => _tableStorage.Insert(null as IEnumerable); + + // Assert + act.Should().Throw().WithMessage("Records cannot be null*"); + } + + [Fact] + public void insert_multiple_records_into_the_table_and_record_count_should_be_greater_than_zero() + { + // Arrange + var entityList = new List + { + new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"}, + new TestTableEntity("Jane", "Smith") {Age = 28, Email = "jane.smith@something.com"} + }; - var result = _tableStorageDynamic.GetByRowKey("John").ToList(); + // Act + _tableStorage.Insert(entityList); + var result = _tableStorage.GetByPartitionKey("Smith").ToList(); // Assert result.Count.Should().BeGreaterThan(0); } [Fact] - public void insert_or_replace_record_into_the_table_when_record_does_exist_and_record_should_have_updated_fields() + public async Task insert_with_empty_list_of_records_does_not_insert_records_to_the_table() { // Arrange - var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; - _tableStorage.Insert(testEntity); - // Act - testEntity = new TestTableEntity("John", "Smith") { Age = 45, Email = "john.smith@something.com" }; - _tableStorage.InsertOrReplace(testEntity); + await TestDataHelper.SetupRecords(_tableStorage); - var result = _tableStorage.GetByRowKey("John").ToList(); + // Act + _tableStorage.Insert(new List()); + var result = _tableStorage.GetAllRecords().ToList(); // Assert - result[0].Age.Should().Be(45); + result.Count.Should().Be(4); } [Fact] - public void insert_or_replace_dynamic_record_into_the_table_when_record_does_exist_and_record_should_have_updated_fields() + public void insert_multiple_records_with_different_partition_keys_inserts_the_expected_count() { // Arrange - var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; - _tableStorageDynamic.Insert(testEntity); - // Act - testEntity = new TestTableEntity("John", "Smith") { Age = 45, Email = "john.smith@something.com" }; - _tableStorageDynamic.InsertOrReplace(testEntity); + var entryList = TestDataHelper.GetMultiplePartitionKeyRecords(); - var result = _tableStorageDynamic.GetByRowKey("John").ToList(); + // Act + _tableStorage.Insert(entryList); + var result = _tableStorage.GetAllRecords().ToList(); // Assert - result[0].Age.Should().Be(45); + result.Count.Should().Be(entryList.Count); } [Fact] - public void insert_with_null_for_multiple_records_throws_exception() + public void insert_multiple_records_with_same_partition_key_and_more_than_the_100_max_batch_size_still_inserts_all_the_records() { // Arrange + var entryList = TestDataHelper.GetMoreThanMaxSinglePartitionRecords(); + // Act - Action act = () => _tableStorage.Insert(null as IEnumerable); + _tableStorage.Insert(entryList); + var result = _tableStorage.GetAllRecords().ToList(); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: records"); + result.Count.Should().Be(entryList.Count); } [Fact] - public void insert_dynamic_with_null_for_multiple_records_throws_exception() + public void insert_multiple_records_with_multiple_partition_keys_and_more_than_the_100_max_batch_size_in_for_all_and_still_inserts_all_the_records() { // Arrange + var entryList = TestDataHelper.GetMoreThanMaxMultiplePartitionRecords(); + // Act - Action act = () => _tableStorageDynamic.Insert(null as IEnumerable); + _tableStorage.Insert(entryList); + var result = _tableStorage.GetAllRecords().ToList(); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: records"); + result.Count.Should().Be(entryList.Count); } [Fact] - public void insert_multiple_records_into_the_table_and_record_count_should_be_greater_than_zero() + public void insert_async_with_null_record_throws_exception() { // Arrange - var entityList = new List - { - new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"}, - new TestTableEntity("Jane", "Smith") {Age = 28, Email = "jane.smith@something.com"} - }; - // Act - _tableStorage.Insert(entityList); - var result = _tableStorage.GetByPartitionKey("Smith").ToList(); + Func act = async () => await _tableStorage.InsertAsync(null as TestTableEntity); // Assert - result.Count.Should().BeGreaterThan(0); + act.Should().ThrowAsync().WithMessage("Value cannot be null.\r\nParameter name: record"); } [Fact] - public void insert_multiple_dynamic_records_into_the_table_and_record_count_should_be_greater_than_zero() + public async Task insert_record_into_the_table_async_inserts_with_a_count_greater_than_zero() { // Arrange - var entityList = new List - { - new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"}, - new TestTableEntity("Jane", "Smith") {Age = 28, Email = "jane.smith@something.com"} - }; + var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; // Act - _tableStorageDynamic.Insert(entityList); - var result = _tableStorageDynamic.GetByPartitionKey("Smith").ToList(); + await _tableStorage.InsertAsync(testEntity); + var result = await _tableStorage.GetByRowKeyAsync("John"); // Assert - result.Count.Should().BeGreaterThan(0); + result.Count().Should().BeGreaterThan(0); } [Fact] - public async Task insert_with_empty_list_of_records_does_not_insert_records_to_the_table() + public void insert_async_with_null_for_multiple_records_throws_exception() { // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - // Act - _tableStorage.Insert(new List()); - var result = _tableStorage.GetAllRecords().ToList(); + Func act = async () => await _tableStorage.InsertAsync(null as IEnumerable); // Assert - result.Count.Should().Be(4); + act.Should().ThrowAsync().WithMessage("Value cannot be null.\r\nParameter name: records"); } [Fact] - public async Task insert_with_empty_list_of_dynamic_records_does_not_insert_records_to_the_table() + public async Task insert_or_replace_async_record_into_the_table_when_record_does_not_exist_and_record_count_should_be_greater_than_zero() { // Arrange - await TestDataHelper.SetupRecords(_tableStorageDynamic); + var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; // Act - _tableStorageDynamic.Insert(new List()); - var result = _tableStorageDynamic.GetAllRecords().ToList(); + await _tableStorage.InsertOrReplaceAsync(testEntity); + + var result = (await _tableStorage.GetByRowKeyAsync("John")).ToList(); // Assert - result.Count.Should().Be(4); + result.Count.Should().BeGreaterThan(0); } [Fact] - public void insert_multiple_records_with_different_partition_keys_inserts_the_expected_count() + public async Task insert_or_replace_async_record_into_the_table_when_record_does_exist_and_record_should_have_updated_fields() { // Arrange - var entryList = TestDataHelper.GetMultiplePartitionKeyRecords(); - + var testEntity = new TestTableEntity("John", "Smith") { Age = 21, Email = "john.smith@something.com" }; + await _tableStorage.InsertAsync(testEntity); // Act - _tableStorage.Insert(entryList); - var result = _tableStorage.GetAllRecords().ToList(); + testEntity = new TestTableEntity("John", "Smith") { Age = 45, Email = "john.smith@something.com" }; + await _tableStorage.InsertOrReplaceAsync(testEntity); + + var result = (await _tableStorage.GetByRowKeyAsync("John")).ToList(); // Assert - result.Count.Should().Be(entryList.Count); + result[0].Age.Should().Be(45); } [Fact] - public void insert_multiple_dynamic_records_with_different_partition_keys_inserts_the_expected_count() + public async Task insert_async_multiple_records_into_the_table_and_record_count_should_be_greater_than_zero() { // Arrange - var entryList = TestDataHelper.GetMultiplePartitionKeyRecords(); + var entityList = new List + { + new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"}, + new TestTableEntity("Jane", "Smith") {Age = 28, Email = "jane.smith@something.com"} + }; // Act - _tableStorageDynamic.Insert(entryList); - var result = _tableStorageDynamic.GetAllRecords().ToList(); + await _tableStorage.InsertAsync(entityList); + var result = await _tableStorage.GetByPartitionKeyAsync("Smith"); // Assert - result.Count.Should().Be(entryList.Count); + result.Count().Should().BeGreaterThan(0); } [Fact] - public void insert_multiple_records_with_same_partition_key_and_more_than_the_100_max_batch_size_still_inserts_all_the_records() + public async Task insert_async_with_empty_list_of_records_does_not_insert_records_to_the_table() { // Arrange - var entryList = TestDataHelper.GetMoreThanMaxSinglePartitionRecords(); + await TestDataHelper.SetupRecords(_tableStorage); // Act - _tableStorage.Insert(entryList); - var result = _tableStorage.GetAllRecords().ToList(); + await _tableStorage.InsertAsync(new List()); + var result = await _tableStorage.GetAllRecordsAsync(); // Assert - result.Count.Should().Be(entryList.Count); + result.Count().Should().Be(4); } [Fact] - public void insert_multiple_dynamic_records_with_same_partition_key_and_more_than_the_100_max_batch_size_still_inserts_all_the_records() + public async Task insert_async_multiple_records_with_different_partition_keys_inserts_the_expected_count() { // Arrange - var entryList = TestDataHelper.GetMoreThanMaxSinglePartitionRecords(); + var entryList = TestDataHelper.GetMultiplePartitionKeyRecords(); // Act - _tableStorageDynamic.Insert(entryList); - var result = _tableStorageDynamic.GetAllRecords().ToList(); + await _tableStorage.InsertAsync(entryList); + var result = await _tableStorage.GetAllRecordsAsync(); // Assert - result.Count.Should().Be(entryList.Count); + result.Count().Should().Be(entryList.Count); } [Fact] - public void insert_multiple_records_with_multiple_partition_keys_and_more_than_the_100_max_batch_size_in_for_all_and_still_inserts_all_the_records() + public async Task insert_async_multiple_records_with_same_partition_key_and_more_than_the_100_max_batch_size_still_inserts_all_the_records() { // Arrange - var entryList = TestDataHelper.GetMoreThanMaxMultiplePartitionRecords(); + var entryList = TestDataHelper.GetMoreThanMaxSinglePartitionRecords(); // Act - _tableStorage.Insert(entryList); - var result = _tableStorage.GetAllRecords().ToList(); + await _tableStorage.InsertAsync(entryList); + var result = await _tableStorage.GetAllRecordsAsync(); // Assert - result.Count.Should().Be(entryList.Count); + result.Count().Should().Be(entryList.Count); } [Fact] - public void insert_multiple_dynamic_records_with_multiple_partition_keys_and_more_than_the_100_max_batch_size_in_for_all_and_still_inserts_all_the_records() + public async Task insert_async_multiple_records_with_multiple_partition_keys_and_more_than_the_100_max_batch_size_in_for_all_and_still_inserts_all_the_records() { // Arrange var entryList = TestDataHelper.GetMoreThanMaxMultiplePartitionRecords(); // Act - _tableStorageDynamic.Insert(entryList); - var result = _tableStorageDynamic.GetAllRecords().ToList(); + await _tableStorage.InsertAsync(entryList); + var result = await _tableStorage.GetAllRecordsAsync(); // Assert - result.Count.Should().Be(entryList.Count); + result.Count().Should().Be(entryList.Count); } } } \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreQueryAsyncTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreQueryAsyncTests.cs deleted file mode 100644 index a30cf3a..0000000 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreQueryAsyncTests.cs +++ /dev/null @@ -1,557 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using TableStorage.Abstractions.Tests.Helpers; -using Xunit; - -namespace TableStorage.Abstractions.Tests.Store -{ - public partial class TableStoreAsyncTests - { - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void get_record_async_with_null_or_empty_partition_key_throws_exception(string partitionKey) - { - // Arrange - // Act - Func act = async () => await _tableStorage.GetRecordAsync(partitionKey, "someRowKey"); - - // Assert - act.Should().Throw() - .WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void get_record_async_with_null_or_empty_row_key_throws_exception(string rowKey) - { - // Arrange - // Act - Func act = async () => await _tableStorage.GetRecordAsync("somePartitionKey", rowKey); - - // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: rowKey"); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void get_records_by_partition_key_async_with_null_or_empty_value_throws_exception(string partitionKey) - { - // Arrange - // Act - Func act = async () => await _tableStorage.GetByPartitionKeyAsync(partitionKey); - - // Assert - act.Should().Throw() - .WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void get_records_by_partition_key_paged_async_with_null_or_empty_value_throws_exception( - string partitionKey) - { - // Arrange - // Act - Func act = async () => await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey); - - // Assert - act.Should().Throw() - .WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); - } - - public static IEnumerable PartitionExpectedData - { - get - { - yield return new object[] - { - "Smith", new List - { - new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"}, - new TestTableEntity("Jane", "Smith") {Age = 28, Email = "jane.smith@something.com"} - } - }; - yield return new object[] - { - "Jones", new List - { - new TestTableEntity("Fred", "Jones") {Age = 32, Email = "fred.jones@somewhere.com"}, - new TestTableEntity("Bill", "Jones") {Age = 45, Email = "bill.jones@somewhere.com"} - } - }; - } - } - - public static IEnumerable PartitionExpectedDataPageOfOne - { - get - { - yield return new object[] - { - "Smith", new List - { - new TestTableEntity("Jane", "Smith") {Age = 28, Email = "jane.smith@something.com"} - } - }; - yield return new object[] - { - "Jones", new List - { - new TestTableEntity("Bill", "Jones") {Age = 45, Email = "bill.jones@somewhere.com"} - } - }; - } - } - - public static IEnumerable PartitionExpectedDataPageOfOneNextPage - { - get - { - yield return new object[] - { - "Smith", new List - { - new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"} - } - }; - yield return new object[] - { - "Jones", new List - { - new TestTableEntity("Fred", "Jones") {Age = 32, Email = "fred.jones@somewhere.com"} - } - }; - } - } - - [Theory] - [MemberData(nameof(PartitionExpectedData))] - public async Task get_records_by_partition_key_async_with_known_key_returns_the_expected_results( - string partitionKey, List expected) - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByPartitionKeyAsync(partitionKey); - - // Assert - results.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); - } - - [Theory] - [MemberData(nameof(PartitionExpectedData))] - public async Task get_records_by_partition_key_paged_async_with_known_key_returns_the_expected_results( - string partitionKey, List expected) - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey); - - // Assert - results.Items.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); - } - - [Theory] - [MemberData(nameof(PartitionExpectedDataPageOfOne))] - public async Task - get_records_by_partition_key_paged_async_with_known_key_returns_the_expected_results_and_expected_row_count( - string partitionKey, List expected) - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1); - - // Assert - results.Items.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); - } - - [Theory] - [MemberData(nameof(PartitionExpectedDataPageOfOneNextPage))] - public async Task - get_records_by_partition_key_paged_async_with_known_key_second_page_returns_the_expected_results_and_expected_row_count( - string partitionKey, List expected) - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1); - results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1, results.ContinuationToken); - - // Assert - results.Items.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); - } - - [Theory] - [InlineData("Smith")] - [InlineData("Jones")] - public async Task - get_records_by_partition_key_paged_async_with_known_key_returns_the_expected_results_with_final_page_annotated( - string partitionKey) - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1); - results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1, results.ContinuationToken); - - // Assert - results.IsFinalPage.Should().BeTrue(); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void get_records_by_row_key_async_with_null_or_empty_value_throws_exception(string rowKey) - { - // Arrange - // Act - Func act = async () => await _tableStorage.GetByRowKeyAsync(rowKey); - - // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: rowKey"); - } - - public static IEnumerable RowKeyExpectedData - { - get - { - yield return new object[] - { - "Bill", new List - { - new TestTableEntity("Bill", "Smith") {Age = 38, Email = "bill.smith@another.com"}, - new TestTableEntity("Bill", "Jones") {Age = 45, Email = "bill.jones@somewhere.com"}, - new TestTableEntity("Bill", "King") {Age = 45, Email = "bill.king@email.com"} - } - }; - yield return new object[] - { - "Fred", new List - { - new TestTableEntity("Fred", "Jones") {Age = 32, Email = "fred.jones@somewhere.com"}, - new TestTableEntity("Fred", "Bloggs") {Age = 32, Email = "fred.bloggs@email.com"} - } - }; - } - } - - public static IEnumerable RowKeyExpectedDataPageOfOne - { - get - { - yield return new object[] - { - "Bill", new List - { - new TestTableEntity("Bill", "Jones") {Age = 45, Email = "bill.jones@somewhere.com"} - } - }; - yield return new object[] - { - "Fred", new List - { - new TestTableEntity("Fred", "Bloggs") {Age = 32, Email = "fred.bloggs@email.com"} - } - }; - } - } - - public static IEnumerable RowKeyExpectedDataPageOfOneNextPage - { - get - { - yield return new object[] - { - "Bill", new List - { - new TestTableEntity("Bill", "King") {Age = 45, Email = "bill.king@email.com"} - } - }; - yield return new object[] - { - "Fred", new List - { - new TestTableEntity("Fred", "Jones") {Age = 32, Email = "fred.jones@somewhere.com"} - } - }; - } - } - - [Theory] - [MemberData(nameof(RowKeyExpectedData))] - public async Task get_records_by_row_key_async_with_known_key_returns_the_expected_results(string rowKey, - List expected) - { - // Arrange - TestDataHelper.SetupRowKeyRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByRowKeyAsync(rowKey); - - // Assert - results.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); - } - - [Theory] - [MemberData(nameof(RowKeyExpectedData))] - public async Task get_records_by_row_key_with_known_key_paged_async_returns_the_expected_results(string rowKey, - List expected) - { - // Arrange - TestDataHelper.SetupRowKeyRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByRowKeyPagedAsync(rowKey); - - // Assert - results.Items.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); - } - - [Theory] - [MemberData(nameof(RowKeyExpectedDataPageOfOne))] - public async Task - get_records_by_row_key_with_known_key_paged_async_returns_the_expected_results_and_expected_row_count( - string rowKey, List expected) - { - // Arrange - TestDataHelper.SetupRowKeyRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByRowKeyPagedAsync(rowKey, 1); - - // Assert - results.Items.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); - } - - [Theory] - [MemberData(nameof(RowKeyExpectedDataPageOfOneNextPage))] - public async Task - get_records_by_row_key_with_known_key_paged_async_second_page_returns_the_expected_results_and_expected_row_count( - string rowKey, List expected) - { - // Arrange - TestDataHelper.SetupRowKeyRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetByRowKeyPagedAsync(rowKey, 1); - results = await _tableStorage.GetByRowKeyPagedAsync(rowKey, 1, results.ContinuationToken); - - // Assert - results.Items.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); - } - - [Fact] - public async Task get_all_records_async_with_an_empty_table_returns_an_empty_list() - { - // Arrange - // Act - var results = await _tableStorage.GetAllRecordsAsync(); - - // Assert - results.Should().BeEmpty(); - } - - [Fact] - public async Task get_all_records_async_with_entries_returns_the_expected_count() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetAllRecordsAsync(); - - // Assert - results.Count().Should().Be(4); - } - - [Fact] - public async Task get_all_records_paged_async_with_an_empty_table_returns_an_empty_list() - { - // Arrange - // Act - var results = await _tableStorage.GetAllRecordsPagedAsync(); - - // Assert - results.Items.Should().BeEmpty(); - } - - [Fact] - public async Task get_all_records_with_entries_paged_async_does_not_repeat_results_when_paging() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results1 = await _tableStorage.GetAllRecordsPagedAsync(2); - - var emails = new List(); - emails.AddRange(results1.Items.Select(i => i.Email)); - - - var results2 = await _tableStorage.GetAllRecordsPagedAsync(2, results1.ContinuationToken); - emails.AddRange(results2.Items.Select(i => i.Email)); - - // Assert - emails.Distinct().Count().Should().Be(4); - } - - [Fact] - public async Task get_all_records_with_entries_paged_async_returns_the_expected_count() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetAllRecordsPagedAsync(); - - // Assert - results.Items.Count.Should().Be(4); - } - - [Fact] - public async Task get_all_records_with_entries_paged_async_returns_the_expected_count_when_given_page_size() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var results = await _tableStorage.GetAllRecordsPagedAsync(2); - - // Assert - results.Items.Count.Should().Be(2); - } - - [Fact] - public async Task get_record_async_with_an_entry_returns_the_expected_entry() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - var expected = new TestTableEntity("Bill", "Jones") {Age = 45, Email = "bill.jones@somewhere.com"}; - - // Act - var result = await _tableStorage.GetRecordAsync("Jones", "Bill"); - - // Assert - result.Should().BeEquivalentTo(expected, - op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) - .Excluding(o => o.SelectedMemberPath == "CompiledRead")); - } - - [Fact] - public async Task get_record_async_with_no_entry_returns_null() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var result = await _tableStorage.GetRecordAsync("surname", "first"); - - // Assert - result.Should().BeNull(); - } - - [Fact] - public async Task get_record_count_async_with_entries_returns_the_expected_count() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var result = await _tableStorage.GetRecordCountAsync(); - - // Assert - result.Should().Be(4); - } - - [Fact] - public async Task get_records_by_partition_key_async_with_unknown_key_returns_empty_list() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - var partitionKey = "something"; - - // Act - var result = await _tableStorage.GetByPartitionKeyAsync(partitionKey); - - // Assert - result.Should().BeEquivalentTo(new List()); - } - - [Fact] - public async Task get_records_by_partition_key_paged_async_with_unknown_key_returns_empty_list() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - var partitionKey = "something"; - - // Act - var result = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey); - - // Assert - result.Items.Should().BeEquivalentTo(new List()); - } - - [Fact] - public async Task get_records_by_row_key_async_with_unknown_key_returns_empty_list() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - var rowKey = "something"; - - // Act - var result = await _tableStorage.GetByRowKeyAsync(rowKey); - - // Assert - result.Should().BeEquivalentTo(new List()); - } - - [Fact] - public async Task get_records_by_row_key_paged_async_with_unknown_key_returns_empty_list() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - var rowKey = "something"; - - // Act - var result = await _tableStorage.GetByRowKeyPagedAsync(rowKey); - - // Assert - result.Items.Should().BeEquivalentTo(new List()); - } - } -} \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreQueryTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreQueryTests.cs index ec40c80..41d828e 100644 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreQueryTests.cs +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreQueryTests.cs @@ -1,4 +1,5 @@ -using FluentAssertions; +using Azure; +using FluentAssertions; using System; using System.Collections.Generic; using System.Linq; @@ -22,7 +23,7 @@ public void get_record_with_null_or_empty_partition_key_throws_exception(string Action act = () => _tableStorage.GetRecord(partitionKey, "someRowKey"); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); + act.Should().Throw().WithMessage("PartitionKey cannot be null or empty*"); } [Theory] @@ -36,7 +37,7 @@ public void get_record_with_null_or_empty_row_key_throws_exception(string rowKey Action act = () => _tableStorage.GetRecord("somePartitionKey", rowKey); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: rowKey"); + act.Should().Throw().WithMessage("RowKey cannot be null or empty*"); } [Fact] @@ -46,10 +47,62 @@ public async Task get_record_with_no_entry_returns_null() await TestDataHelper.SetupRecords(_tableStorage); // Act - var result = _tableStorage.GetRecord("surname", "first"); + Action act = () => _tableStorage.GetRecord("surname", "first"); // Assert - result.Should().BeNull(); + act.Should().Throw().WithMessage("The specified resource does not exist.*"); + } + + [Fact] + public async Task get_record_with_no_entry_returns_a_request_failed_exception_with_resource_not_found() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + Action result = () => _tableStorage.GetRecord("surname", "first"); + + // Assert + result.Should().Throw().WithMessage("*ResourceNotFound*"); + } + + [Fact] + public async Task get_records_by_filter_of_with_a_time_in_the_past_returns_the_expected_results() + { + // Arrange + const string ago = "10s"; + const string queryAgo = "5s"; + await TestDataHelper.SetupRecordsAgo(_tableStorage, ago); + var expected = new List + { + new TestTableEntity("Liam", "Matthews") {Age = 28, Email = "liam.matthews@something.com"} + }; + + // Act + var result = _tableStorage.GetRecordsByFilter(x => x.Age is >= 21 and < 29, queryAgo); + + // Assert + result.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Fact] + public async Task get_records_by_all_age_filter_with_a_time_in_the_past_returns_the_expected_results() + { + // Arrange + const string ago = "10s"; + const string queryAgo = "5s"; + await TestDataHelper.SetupRecordsAgo(_tableStorage, ago); + var expected = new List + { + new TestTableEntity("Liam", "Matthews") {Age = 28, Email = "liam.matthews@something.com"}, + new TestTableEntity("Mary", "Gates") {Age = 45, Email = "mary.gates@somewhere.com"} + }; + + // Act + var result = _tableStorage.GetRecordsByFilter(x => x.Age >= 1 && x.Age < 150, queryAgo); + + // Assert + result.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Fact] @@ -63,7 +116,7 @@ public async Task get_record_with_an_entry_returns_the_expected_entry() var result = _tableStorage.GetRecord("Jones", "Bill"); // Assert - result.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath == "CompiledRead")); + result.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path == "CompiledRead")); } [Theory] @@ -77,7 +130,7 @@ public void get_records_by_partition_key_with_null_or_empty_value_throws_excepti Action act = () => _tableStorage.GetByPartitionKey(partitionKey); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); + act.Should().Throw().WithMessage("PartitionKey cannot be null or empty*"); } [Theory] @@ -91,7 +144,7 @@ public void get_records_by_partition_key_paged_with_null_or_empty_value_throws_e Action act = () => _tableStorage.GetByPartitionKeyPaged(partitionKey); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); + act.Should().Throw().WithMessage("PartitionKey cannot be null or empty*"); } [Fact] @@ -174,14 +227,14 @@ public static IEnumerable PartitionExpectedDataPageOfOneNextPage { "Smith", new List { - new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"}, + new TestTableEntity("John", "Smith") {Age = 21, Email = "john.smith@something.com"} } }; yield return new object[] { "Jones", new List { - new TestTableEntity("Fred", "Jones") {Age = 32, Email = "fred.jones@somewhere.com"}, + new TestTableEntity("Fred", "Jones") {Age = 32, Email = "fred.jones@somewhere.com"} } }; } @@ -198,7 +251,7 @@ public async Task get_records_by_partition_key_with_known_key_returns_the_expect var results = _tableStorage.GetByPartitionKey(partitionKey); // Assert - results.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + results.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Theory] @@ -212,7 +265,7 @@ public async Task get_records_by_partition_key_paged_with_known_key_returns_the_ var results = _tableStorage.GetByPartitionKeyPaged(partitionKey); // Assert - results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Fact] @@ -244,7 +297,7 @@ public async Task get_records_by_partition_key_paged_with_known_key_returns_the_ var results = _tableStorage.GetByPartitionKeyPaged(partitionKey, pageSize: 1); // Assert - results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Theory] @@ -259,7 +312,7 @@ public async Task get_records_by_partition_key_paged_with_known_key_second_page_ results = _tableStorage.GetByPartitionKeyPaged(partitionKey, pageSize: 1, continuationTokenJson: results.ContinuationToken); // Assert - results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Theory] @@ -294,10 +347,9 @@ public void get_records_by_partition_key_paged_using_maximum_page_size() tableStore.Insert(records); } - var results = tableStore.GetByPartitionKeyPaged("x", pageSize: 1000); + var results = tableStore.GetByPartitionKeyPaged("x", 1000); var nextPageResults = - tableStore.GetByPartitionKeyPaged("x", pageSize: 1000, - continuationTokenJson: results.ContinuationToken); + tableStore.GetByPartitionKeyPaged("x", 1000, results.ContinuationToken); results.Items.Count.Should().Be(1000); nextPageResults.Items.Count.Should().Be(100); @@ -317,7 +369,7 @@ public void get_records_by_row_key_with_null_or_empty_value_throws_exception(str Action act = () => _tableStorage.GetByRowKey(rowKey); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: rowKey"); + act.Should().Throw().WithMessage("RowKey cannot be null or empty*"); } [Fact] @@ -425,7 +477,7 @@ public void get_records_by_row_key_with_known_key_returns_the_expected_results(s var results = _tableStorage.GetByRowKey(rowKey); // Assert - results.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + results.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Theory] @@ -439,7 +491,7 @@ public void get_records_by_row_key_with_known_key_paged_returns_the_expected_res var results = _tableStorage.GetByRowKeyPaged(rowKey); // Assert - results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Theory] @@ -453,7 +505,7 @@ public void get_records_by_row_key_with_known_key_paged_returns_the_expected_res var results = _tableStorage.GetByRowKeyPaged(rowKey, pageSize: 1); // Assert - results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Theory] @@ -465,10 +517,10 @@ public void get_records_by_row_key_with_known_key_paged_second_page_returns_the_ // Act var results = _tableStorage.GetByRowKeyPaged(rowKey, pageSize: 1); - results = _tableStorage.GetByRowKeyPaged(rowKey, pageSize: 1, continuationTokenJson: results.ContinuationToken); + results = _tableStorage.GetByRowKeyPaged(rowKey, 1, results.ContinuationToken); // Assert - results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + results.Items.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } [Fact] @@ -506,6 +558,21 @@ public async Task get_all_records_with_entries_returns_the_expected_count() results.Count().Should().Be(4); } + [Fact] + public void get_all_records_with_over_a_thousand_entries_returns_the_expected_count() + { + // Arrange + + const int recordCount = 1100; + TestDataHelper.SetupLotsOfRecords(recordCount, _tableStorage); + + // Act + var results = _tableStorage.GetAllRecords(); + + // Assert + results.Count().Should().Be(recordCount); + } + [Fact] public async Task get_all_records_with_entries_paged_returns_the_expected_count() { @@ -526,12 +593,27 @@ public async Task get_all_records_with_entries_paged_returns_the_expected_count_ await TestDataHelper.SetupRecords(_tableStorage); // Act - var results = _tableStorage.GetAllRecordsPaged(pageSize: 2); + var results = _tableStorage.GetAllRecordsPaged(2); // Assert results.Items.Count.Should().Be(2); } + [Fact] + public async Task get_all_records_with_entries_paged_returns_the_expected_page_when_given_continuation_token() + { + // Arrange + const int pageSize = 3; + await TestDataHelper.SetupRecords(_tableStorage); + var page1 = _tableStorage.GetAllRecordsPaged(pageSize); + + // Act + var results = _tableStorage.GetAllRecordsPaged(pageSize, page1.ContinuationToken); + + // Assert + results.Items.Count.Should().Be(1); + } + [Fact] public async Task get_record_count_with_entries_returns_the_expected_count() { @@ -545,6 +627,21 @@ public async Task get_record_count_with_entries_returns_the_expected_count() result.Should().Be(4); } + [Fact] + public void get_record_count_with_over_a_thousand_entries_returns_the_expected_count() + { + // Arrange + + const int recordCount = 1100; + TestDataHelper.SetupLotsOfRecords(recordCount, _tableStorage); + + // Act + var result = _tableStorage.GetRecordCount(); + + // Assert + result.Should().Be(recordCount); + } + [Fact] public async Task get_records_by_filter_with_a_given_filter_returns_the_expected_count() { @@ -574,9 +671,9 @@ public async Task get_records_by_filter_with_a_given_filter_returns_the_expected var result = _tableStorage.GetRecordsByFilter(x => x.Age >= 21 && x.Age < 29); // Assert - result.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + result.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); } - + public static IEnumerable FilterExpectedData { get @@ -609,7 +706,447 @@ public async Task get_records_by_filter_with_a_given_filter_and_page_returns_the var result = _tableStorage.GetRecordsByFilter(x => x.Age >= 21 && x.Age < 29, start, page); // Assert - result.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.SelectedMemberPath.EndsWith("CompiledRead"))); + result.Should().BeEquivalentTo(expected, op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag).Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void get_record_async_with_null_or_empty_partition_key_throws_exception(string partitionKey) + { + // Arrange + // Act + Func act = async () => await _tableStorage.GetRecordAsync(partitionKey, "someRowKey"); + + // Assert + act.Should().ThrowAsync() + .WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void get_record_async_with_null_or_empty_row_key_throws_exception(string rowKey) + { + // Arrange + // Act + Func act = async () => await _tableStorage.GetRecordAsync("somePartitionKey", rowKey); + + // Assert + act.Should().ThrowAsync().WithMessage("Value cannot be null.\r\nParameter name: rowKey"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void get_records_by_partition_key_async_with_null_or_empty_value_throws_exception(string partitionKey) + { + // Arrange + // Act + Func act = async () => await _tableStorage.GetByPartitionKeyAsync(partitionKey); + + // Assert + act.Should().ThrowAsync() + .WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void get_records_by_partition_key_paged_async_with_null_or_empty_value_throws_exception( + string partitionKey) + { + // Arrange + // Act + Func act = async () => await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey); + + // Assert + act.Should().ThrowAsync() + .WithMessage("Value cannot be null.\r\nParameter name: partitionKey"); + } + + [Theory] + [MemberData(nameof(PartitionExpectedData))] + public async Task get_records_by_partition_key_async_with_known_key_returns_the_expected_results( + string partitionKey, List expected) + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByPartitionKeyAsync(partitionKey); + + // Assert + results.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Theory] + [MemberData(nameof(PartitionExpectedData))] + public async Task get_records_by_partition_key_paged_async_with_known_key_returns_the_expected_results( + string partitionKey, List expected) + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey); + + // Assert + results.Items.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Theory] + [MemberData(nameof(PartitionExpectedDataPageOfOne))] + public async Task + get_records_by_partition_key_paged_async_with_known_key_returns_the_expected_results_and_expected_row_count( + string partitionKey, List expected) + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1); + + // Assert + results.Items.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Theory] + [MemberData(nameof(PartitionExpectedDataPageOfOneNextPage))] + public async Task + get_records_by_partition_key_paged_async_with_known_key_second_page_returns_the_expected_results_and_expected_row_count( + string partitionKey, List expected) + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1); + results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1, results.ContinuationToken); + + // Assert + results.Items.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Theory] + [InlineData("Smith")] + [InlineData("Jones")] + public async Task + get_records_by_partition_key_paged_async_with_known_key_returns_the_expected_results_with_final_page_annotated( + string partitionKey) + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1); + results = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey, 1, results.ContinuationToken); + + // Assert + results.IsFinalPage.Should().BeTrue(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void get_records_by_row_key_async_with_null_or_empty_value_throws_exception(string rowKey) + { + // Arrange + // Act + Func act = async () => await _tableStorage.GetByRowKeyAsync(rowKey); + + // Assert + act.Should().ThrowAsync().WithMessage("Value cannot be null.\r\nParameter name: rowKey"); + } + + [Theory] + [MemberData(nameof(RowKeyExpectedData))] + public async Task get_records_by_row_key_async_with_known_key_returns_the_expected_results(string rowKey, + List expected) + { + // Arrange + TestDataHelper.SetupRowKeyRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByRowKeyAsync(rowKey); + + // Assert + results.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Theory] + [MemberData(nameof(RowKeyExpectedData))] + public async Task get_records_by_row_key_with_known_key_paged_async_returns_the_expected_results(string rowKey, + List expected) + { + // Arrange + TestDataHelper.SetupRowKeyRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByRowKeyPagedAsync(rowKey); + + // Assert + results.Items.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Theory] + [MemberData(nameof(RowKeyExpectedDataPageOfOne))] + public async Task + get_records_by_row_key_with_known_key_paged_async_returns_the_expected_results_and_expected_row_count( + string rowKey, List expected) + { + // Arrange + TestDataHelper.SetupRowKeyRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByRowKeyPagedAsync(rowKey, 1); + + // Assert + results.Items.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Theory] + [MemberData(nameof(RowKeyExpectedDataPageOfOneNextPage))] + public async Task + get_records_by_row_key_with_known_key_paged_async_second_page_returns_the_expected_results_and_expected_row_count( + string rowKey, List expected) + { + // Arrange + TestDataHelper.SetupRowKeyRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetByRowKeyPagedAsync(rowKey, 1); + results = await _tableStorage.GetByRowKeyPagedAsync(rowKey, 1, results.ContinuationToken); + + // Assert + results.Items.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path.EndsWith("CompiledRead"))); + } + + [Fact] + public async Task get_all_records_async_with_an_empty_table_returns_an_empty_list() + { + // Arrange + // Act + var results = await _tableStorage.GetAllRecordsAsync(); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task get_all_records_async_with_entries_returns_the_expected_count() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetAllRecordsAsync(); + + // Assert + results.Count().Should().Be(4); + } + + [Fact] + public async Task get_all_records_paged_async_with_an_empty_table_returns_an_empty_list() + { + // Arrange + // Act + var results = await _tableStorage.GetAllRecordsPagedAsync(); + + // Assert + results.Items.Should().BeEmpty(); + } + + [Fact] + public async Task get_all_records_with_entries_paged_async_does_not_repeat_results_when_paging() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results1 = await _tableStorage.GetAllRecordsPagedAsync(2); + + var emails = new List(); + emails.AddRange(results1.Items.Select(i => i.Email)); + + var results2 = await _tableStorage.GetAllRecordsPagedAsync(2, results1.ContinuationToken); + emails.AddRange(results2.Items.Select(i => i.Email)); + + // Assert + emails.Distinct().Count().Should().Be(4); + } + + [Fact] + public async Task get_all_records_with_entries_paged_async_returns_the_expected_count() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetAllRecordsPagedAsync(); + + // Assert + results.Items.Count.Should().Be(4); + } + + [Fact] + public async Task get_all_records_with_entries_paged_async_returns_the_expected_count_when_given_page_size() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var results = await _tableStorage.GetAllRecordsPagedAsync(2); + + // Assert + results.Items.Count.Should().Be(2); + } + + [Fact] + public async Task get_record_async_with_an_entry_returns_the_expected_entry() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + var expected = new TestTableEntity("Bill", "Jones") { Age = 45, Email = "bill.jones@somewhere.com" }; + + // Act + var result = await _tableStorage.GetRecordAsync("Jones", "Bill"); + + // Assert + result.Should().BeEquivalentTo(expected, + op => op.Excluding(o => o.Timestamp).Excluding(o => o.ETag) + .Excluding(o => o.Path == "CompiledRead")); + } + + [Fact] + public async Task get_record_async_with_no_entry_returns_null() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + Func act = async () => await _tableStorage.GetRecordAsync("surname", "first"); + + // Assert + await act.Should().ThrowAsync().WithMessage("The specified resource does not exist.*"); + } + + [Fact] + public async Task get_record_async_with_no_entry_returns_a_request_failed_exception_with_resource_not_found() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + Func result = async () => await _tableStorage.GetRecordAsync("surname", "first"); + + // Assert + await result.Should().ThrowAsync().WithMessage("*ResourceNotFound*"); + } + + [Fact] + public async Task get_record_count_async_with_entries_returns_the_expected_count() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + + // Act + var result = await _tableStorage.GetRecordCountAsync(); + + // Assert + result.Should().Be(4); + } + + [Fact] + public async Task get_record_count_async_with_over_a_thousand_entries_returns_the_expected_count() + { + // Arrange + + const int recordCount = 1252; + TestDataHelper.SetupLotsOfRecords(recordCount, _tableStorage); + + // Act + var result = await _tableStorage.GetRecordCountAsync(); + + // Assert + result.Should().Be(recordCount); + } + + [Fact] + public async Task get_records_by_partition_key_async_with_unknown_key_returns_empty_list() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + var partitionKey = "something"; + + // Act + var result = await _tableStorage.GetByPartitionKeyAsync(partitionKey); + + // Assert + result.Should().BeEquivalentTo(new List()); + } + + [Fact] + public async Task get_records_by_partition_key_paged_async_with_unknown_key_returns_empty_list() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + var partitionKey = "something"; + + // Act + var result = await _tableStorage.GetByPartitionKeyPagedAsync(partitionKey); + + // Assert + result.Items.Should().BeEquivalentTo(new List()); + } + + [Fact] + public async Task get_records_by_row_key_async_with_unknown_key_returns_empty_list() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + var rowKey = "something"; + + // Act + var result = await _tableStorage.GetByRowKeyAsync(rowKey); + + // Assert + result.Should().BeEquivalentTo(new List()); + } + + [Fact] + public async Task get_records_by_row_key_paged_async_with_unknown_key_returns_empty_list() + { + // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + var rowKey = "something"; + + // Act + var result = await _tableStorage.GetByRowKeyPagedAsync(rowKey); + + // Assert + result.Items.Should().BeEquivalentTo(new List()); } } } \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreTests.cs index f553ef5..0d00d2b 100644 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreTests.cs +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreTests.cs @@ -1,24 +1,30 @@ using FluentAssertions; using FluentValidation; using System; +using System.Threading.Tasks; using TableStorage.Abstractions.Store; using TableStorage.Abstractions.Tests.Helpers; +using Useful.Extensions; using Xunit; +using Xunit.Abstractions; namespace TableStorage.Abstractions.Tests.Store { public partial class TableStoreTests : IDisposable { + private readonly ITestOutputHelper _testOutputHelper; private const string TableName = "TestTable"; private const string ConnectionString = "UseDevelopmentStorage=true"; + private readonly ITableStore _tableStorage; - private readonly ITableStoreDynamic _tableStorageDynamic; + private readonly TableStorageOptions _tableStorageOptions = new TableStorageOptions(); - public TableStoreTests() + public TableStoreTests(ITestOutputHelper testOutputHelper) { + _testOutputHelper = testOutputHelper; _tableStorage = new TableStore(TableName, ConnectionString, _tableStorageOptions); - _tableStorageDynamic = new TableStoreDynamic(TableName, ConnectionString); + SystemTime.UtcNow = () => DateTime.UtcNow; } public void Dispose() @@ -37,7 +43,7 @@ public void create_table_storage_with_empty_or_null_table_name_throws_exception( Action act = () => new TableStore(tablename, "somestring", _tableStorageOptions); // Assert - act.Should().Throw().WithMessage("Table name cannot be null or empty\r\nParameter name: tableName"); + act.Should().Throw().WithMessage("Table name cannot be null or empty*"); } [Theory] @@ -52,7 +58,7 @@ public void create_table_storage_with_empty_or_null_connection_string_throws_exc // Assert act.Should().Throw() - .WithMessage("Table connection string cannot be null or empty\r\nParameter name: storageConnectionString"); + .WithMessage("Table connection string cannot be null or empty*"); } [Fact] @@ -64,14 +70,15 @@ public void create_table_storage_with_null_table_options_throws_exception() // Assert act.Should().Throw() - .WithMessage("Table storage options cannot be null\r\nParameter name: options"); + .WithMessage("Table storage options cannot be null*"); } [Theory] [InlineData(-1)] [InlineData(0)] [InlineData(1)] - public void create_table_storage_with_table_option_connection_limit_less_than_2_then_throws_an_exception(int connectionLimit) + public void create_table_storage_with_table_option_connection_limit_less_than_2_then_throws_an_exception( + int connectionLimit) { // Arrange var options = new TableStorageOptions { ConnectionLimit = connectionLimit }; @@ -81,7 +88,8 @@ public void create_table_storage_with_table_option_connection_limit_less_than_2_ // Assert act.Should().Throw() - .WithMessage("Validation failed: \r\n -- ConnectionLimit: 'Connection Limit' must be greater than or equal to '2'."); + .WithMessage( + "Validation failed: \r\n -- ConnectionLimit: 'Connection Limit' must be greater than or equal to '2'. Severity: Error"); } [Theory] @@ -97,7 +105,7 @@ public void create_table_storage_with_table_options_retries_less_than_1_then_thr // Assert act.Should().Throw() - .WithMessage("Validation failed: \r\n -- Retries: 'Retries' must be greater than '0'."); + .WithMessage("Validation failed: \r\n -- Retries: 'Retries' must be greater than '0'. Severity: Error"); } [Theory] @@ -113,7 +121,8 @@ public void create_table_storage_with_table_options_retry_wait_in_seconds_less_t // Assert act.Should().Throw() - .WithMessage("Validation failed: \r\n -- RetryWaitTimeInSeconds: 'Retry Wait Time In Seconds' must be greater than '0'."); + .WithMessage( + "Validation failed: \r\n -- RetryWaitTimeInSeconds: 'Retry Wait Time In Seconds' must be greater than '0'. Severity: Error"); } [Theory] @@ -123,14 +132,16 @@ public void create_table_storage_with_table_options_retry_wait_in_seconds_less_t public void create_table_storage_with_multiple_invalid_table_options_throws_an_exception_with_all_invalid_entries(int connectionLimit, int retries, double retryTime) { // Arrange - var options = new TableStorageOptions { ConnectionLimit = connectionLimit, Retries = retries, RetryWaitTimeInSeconds = retryTime }; + var options = new TableStorageOptions + { ConnectionLimit = connectionLimit, Retries = retries, RetryWaitTimeInSeconds = retryTime }; // Act Action act = () => new TableStore("sometable", ConnectionString, options); // Assert act.Should().Throw() - .WithMessage("Validation failed: \r\n -- ConnectionLimit: 'Connection Limit' must be greater than or equal to '2'.\r\n -- Retries: 'Retries' must be greater than '0'.\r\n -- RetryWaitTimeInSeconds: 'Retry Wait Time In Seconds' must be greater than '0'."); + .WithMessage( + "Validation failed: \r\n -- ConnectionLimit: 'Connection Limit' must be greater than or equal to '2'. Severity: Error\r\n -- Retries: 'Retries' must be greater than '0'. Severity: Error\r\n -- RetryWaitTimeInSeconds: 'Retry Wait Time In Seconds' must be greater than '0'. Severity: Error"); } [Fact] @@ -158,5 +169,32 @@ public void table_does_not_exist_then_exist_check_returns_false() // Assert result.Should().BeFalse(); } + + [Fact] + public async Task table_does_exist_then_exist_async_check_returns_true() + { + // Arrange + await _tableStorage.DeleteTableAsync(); + await _tableStorage.CreateTableAsync(); + + // Act + var result = await _tableStorage.TableExistsAsync(); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public async Task table_does_not_exist_then_exist_async_check_returns_false() + { + // Arrange + await _tableStorage.DeleteTableAsync(); + + // Act + var result = await _tableStorage.TableExistsAsync(); + + // Assert + result.Should().BeFalse(); + } } } \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreUpdateAsyncTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreUpdateAsyncTests.cs deleted file mode 100644 index 6284f6f..0000000 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreUpdateAsyncTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -using FluentAssertions; -using System; -using System.Threading.Tasks; -using TableStorage.Abstractions.Tests.Helpers; -using Xunit; - -namespace TableStorage.Abstractions.Tests.Store -{ - public partial class TableStoreAsyncTests - { - [Fact] - public void update_async_with_null_record_throws_exception() - { - // Arrange - // Act - Func act = async () => await _tableStorage.UpdateAsync(null as TestTableEntity); - - // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); - } - - [Fact] - public void update_async_dynamic_with_null_record_throws_exception() - { - // Arrange - // Act - Func act = async () => await _tableStorageDynamic.UpdateAsync(null as TestTableEntity); - - // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); - } - - [Fact] - public async Task update_async_a_record_in_the_table_and_the_change_should_be_recorded() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var item = await _tableStorage.GetRecordAsync("Smith", "John"); - - item.Age = 22; - - await _tableStorage.UpdateAsync(item); - - var item2 = await _tableStorage.GetRecordAsync("Smith", "John"); - - // Assert - item2.Age.Should().Be(22); - } - - [Fact] - public async Task update_async_a_dynamic_record_in_the_table_and_the_change_should_be_recorded() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var item = await _tableStorageDynamic.GetRecordAsync("Smith", "John"); - - item.Age = 22; - - await _tableStorageDynamic.UpdateAsync(item); - - var item2 = await _tableStorageDynamic.GetRecordAsync("Smith", "John"); - - // Assert - item2.Age.Should().Be(22); - } - - [Fact] - public void update_using_wildcard_etag_with_null_record_throws_exception() - { - // Arrange - // Act - Func act = async () => await _tableStorage.UpdateUsingWildcardEtagAsync(null as TestTableEntity); - - // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); - } - - [Fact] - public async Task update_using_wildcard_etag_the_record_in_the_table_and_the_change_should_be_recorded() - { - // Arrange - await TestDataHelper.SetupRecords(_tableStorage); - - // Act - var item = await _tableStorage.GetRecordAsync("Smith", "John"); - - item.Age = 22; - - await _tableStorage.UpdateUsingWildcardEtagAsync(item); - - var item2 = await _tableStorage.GetRecordAsync("Smith", "John"); - - // Assert - item2.Age.Should().Be(22); - } - } -} \ No newline at end of file diff --git a/tests/TableStorage.Abstractions.Tests/Store/TableStoreUpdateTests.cs b/tests/TableStorage.Abstractions.Tests/Store/TableStoreUpdateTests.cs index 01415c8..7f4147e 100644 --- a/tests/TableStorage.Abstractions.Tests/Store/TableStoreUpdateTests.cs +++ b/tests/TableStorage.Abstractions.Tests/Store/TableStoreUpdateTests.cs @@ -13,25 +13,47 @@ public void update_with_null_record_throws_exception() { // Arrange // Act - Action act = () => _tableStorage.Update(null as TestTableEntity); + Action act = () => _tableStorage.Update(null); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + act.Should().Throw().WithMessage("Record cannot be null*"); } + [Fact] - public void update_dynamic_with_null_record_throws_exception() + public async Task update_a_record_in_the_table_and_the_change_should_be_recorded() { // Arrange + await TestDataHelper.SetupRecords(_tableStorage); + // Act - Action act = () => _tableStorageDynamic.Update(null as TestTableEntity); + var item = _tableStorage.GetRecord("Smith", "John"); + + item.Age = 22; + + _tableStorage.Update(item); + + var item2 = _tableStorage.GetRecord("Smith", "John"); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + item2.Age.Should().Be(22); } + + [Fact] - public async Task update_a_record_in_the_table_and_the_change_should_be_recorded() + public void update_using_wildcard_etag_with_null_record_throws_exception() + { + // Arrange + // Act + Action act = () => _tableStorage.UpdateUsingWildcardEtag(null); + + // Assert + act.Should().Throw().WithMessage("Record cannot be null*"); + } + + [Fact] + public async Task update_using_wildcard_etag_the_record_in_the_table_and_the_change_should_be_recorded() { // Arrange await TestDataHelper.SetupRecords(_tableStorage); @@ -41,7 +63,7 @@ public async Task update_a_record_in_the_table_and_the_change_should_be_recorded item.Age = 22; - _tableStorage.Update(item); + _tableStorage.UpdateUsingWildcardEtag(item); var item2 = _tableStorage.GetRecord("Smith", "John"); @@ -50,49 +72,60 @@ public async Task update_a_record_in_the_table_and_the_change_should_be_recorded } [Fact] - public async Task update_a_dynamic_record_in_the_table_and_the_change_should_be_recorded() + public void update_async_with_null_record_throws_exception() + { + // Arrange + // Act + Func act = async () => await _tableStorage.UpdateAsync(null); + + // Assert + act.Should().ThrowAsync().WithMessage("Record cannot be null*"); + } + + [Fact] + public async Task update_async_a_record_in_the_table_and_the_change_should_be_recorded() { // Arrange - await TestDataHelper.SetupRecords(_tableStorageDynamic); + await TestDataHelper.SetupRecords(_tableStorage); // Act - var item = _tableStorageDynamic.GetRecord("Smith", "John"); + var item = await _tableStorage.GetRecordAsync("Smith", "John"); item.Age = 22; - _tableStorageDynamic.Update(item); + await _tableStorage.UpdateAsync(item); - var item2 = _tableStorageDynamic.GetRecord("Smith", "John"); + var item2 = await _tableStorage.GetRecordAsync("Smith", "John"); // Assert item2.Age.Should().Be(22); } [Fact] - public void update_using_wildcard_etag_with_null_record_throws_exception() + public void update_using_wildcard_etag_async_with_null_record_throws_exception() { // Arrange // Act - Action act = () => _tableStorage.UpdateUsingWildcardEtag(null as TestTableEntity); + Func act = async () => await _tableStorage.UpdateUsingWildcardEtagAsync(null); // Assert - act.Should().Throw().WithMessage("Value cannot be null.\r\nParameter name: record"); + act.Should().ThrowAsync().WithMessage("Value cannot be null.\r\nParameter name: record"); } [Fact] - public async Task update_using_wildcard_etag_the_record_in_the_table_and_the_change_should_be_recorded() + public async Task update_using_wildcard_etag_async_the_record_in_the_table_and_the_change_should_be_recorded() { // Arrange await TestDataHelper.SetupRecords(_tableStorage); // Act - var item = _tableStorage.GetRecord("Smith", "John"); + var item = await _tableStorage.GetRecordAsync("Smith", "John"); item.Age = 22; - _tableStorage.UpdateUsingWildcardEtag(item); + await _tableStorage.UpdateUsingWildcardEtagAsync(item); - var item2 = _tableStorage.GetRecord("Smith", "John"); + var item2 = await _tableStorage.GetRecordAsync("Smith", "John"); // Assert item2.Age.Should().Be(22); diff --git a/tests/TableStorage.Abstractions.Tests/TableStorage.Abstractions.Tests.csproj b/tests/TableStorage.Abstractions.Tests/TableStorage.Abstractions.Tests.csproj index 5b95de2..78b73cd 100644 --- a/tests/TableStorage.Abstractions.Tests/TableStorage.Abstractions.Tests.csproj +++ b/tests/TableStorage.Abstractions.Tests/TableStorage.Abstractions.Tests.csproj @@ -1,7 +1,7 @@ - + - net461;netcoreApp2.0;netcoreApp2.1 + net48;net6.0 latest 1.0.0 1.0.0 @@ -10,18 +10,18 @@ - - + + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers -