From 8922aff53240af3cae300974b2d063413f5285df Mon Sep 17 00:00:00 2001 From: Malhar Khimsaria <96malhar@gmail.com> Date: Sun, 11 Aug 2024 22:24:18 -0700 Subject: [PATCH] Add the ability to mock TransactWrite and MultiTableTransactWrite operations --- .../DynamoDBv2/Custom/DataModel/Context.cs | 38 +- .../Custom/DataModel/IDynamoDBContext.cs | 8 +- .../Custom/DataModel/TransactWrite.cs | 406 +++++++++--------- .../Custom/DataModel/_async/Context.Async.cs | 9 +- .../_async/IDynamoDBContext.Async.cs | 2 +- .../DataModel/_async/TransactWrite.Async.cs | 40 +- .../Custom/DataModel/_bcl/Context.Sync.cs | 7 +- .../DataModel/_bcl/IDynamoDBContext.Sync.cs | 2 +- .../DataModel/_bcl/TransactWrite.Sync.cs | 40 +- .../MockabilityTests/TransactWriteTests.cs | 121 ++++++ 10 files changed, 380 insertions(+), 293 deletions(-) create mode 100644 sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockabilityTests/TransactWriteTests.cs diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs index 3012c9089c93..54d5e24300c9 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs @@ -342,52 +342,30 @@ public MultiTableTransactGet CreateMultiTableTransactGet(params TransactGet[] tr #region TransactWrite - /// - /// Creates a strongly-typed TransactWrite object, allowing - /// a transactional write operation against DynamoDB. - /// - /// Type of objects to write. - /// Empty strongly-typed TransactWrite object. - public TransactWrite CreateTransactWrite() + /// + public ITransactWrite CreateTransactWrite() { return CreateTransactWrite((TransactWriteConfig)null); } - /// - /// Creates a strongly-typed TransactWrite object, allowing - /// a transactional write operation against DynamoDB. - /// - /// Type of objects to write. - /// Config object which can be used to override that table used. - /// Empty strongly-typed TransactWrite object. + /// [Obsolete("Use the CreateTransactWrite overload that takes TransactWriteConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to CreateTransactWrite.")] - public TransactWrite CreateTransactWrite(DynamoDBOperationConfig operationConfig) + public ITransactWrite CreateTransactWrite(DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); return new TransactWrite(this, config); } - /// - /// Creates a strongly-typed TransactWrite object, allowing - /// a transactional write operation against DynamoDB. - /// - /// Type of objects to write. - /// Config object that can be used to override properties on the table's context for this request. - /// Empty strongly-typed TransactWrite object. - public TransactWrite CreateTransactWrite(TransactWriteConfig transactWriteConfig) + /// + public ITransactWrite CreateTransactWrite(TransactWriteConfig transactWriteConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(transactWriteConfig?.ToDynamoDBOperationConfig(), this.Config); return new TransactWrite(this, config); } - /// - /// Creates a MultiTableTransactWrite object, composed of multiple - /// individual TransactWrite objects. - /// - /// Individual TransactWrite objects. - /// Composite MultiTableTransactWrite object. - public MultiTableTransactWrite CreateMultiTableTransactWrite(params TransactWrite[] transactionParts) + /// + public IMultiTableTransactWrite CreateMultiTableTransactWrite(params ITransactWrite[] transactionParts) { return new MultiTableTransactWrite(transactionParts); } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs index d7a18828685c..557af941bc93 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs @@ -316,7 +316,7 @@ public partial interface IDynamoDBContext : IDisposable /// /// Type of objects to write. /// Empty strongly-typed TransactWrite object. - TransactWrite CreateTransactWrite(); + ITransactWrite CreateTransactWrite(); /// /// Creates a strongly-typed TransactWrite object, allowing @@ -326,7 +326,7 @@ public partial interface IDynamoDBContext : IDisposable /// Config object which can be used to override that table used. /// Empty strongly-typed TransactWrite object. [Obsolete("Use the CreateTransactWrite overload that takes TransactWriteConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to CreateTransactWrite.")] - TransactWrite CreateTransactWrite(DynamoDBOperationConfig operationConfig = null); + ITransactWrite CreateTransactWrite(DynamoDBOperationConfig operationConfig = null); /// /// Creates a strongly-typed TransactWrite object, allowing @@ -335,7 +335,7 @@ public partial interface IDynamoDBContext : IDisposable /// Type of objects to write. /// Config object that can be used to override properties on the table's context for this request. /// Empty strongly-typed TransactWrite object. - TransactWrite CreateTransactWrite(TransactWriteConfig transactWriteConfig); + ITransactWrite CreateTransactWrite(TransactWriteConfig transactWriteConfig); /// /// Creates a MultiTableTransactWrite object, composed of multiple @@ -343,7 +343,7 @@ public partial interface IDynamoDBContext : IDisposable /// /// Individual TransactWrite objects. /// Composite MultiTableTransactWrite object. - MultiTableTransactWrite CreateMultiTableTransactWrite(params TransactWrite[] transactionParts); + IMultiTableTransactWrite CreateMultiTableTransactWrite(params ITransactWrite[] transactionParts); #endregion } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/TransactWrite.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/TransactWrite.cs index 5aa70cea7464..42ff4668e246 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/TransactWrite.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/TransactWrite.cs @@ -24,53 +24,148 @@ namespace Amazon.DynamoDBv2.DataModel { + /// - /// Represents a non-generic object for writing/deleting/version-checking multiple items + /// Represents a non-generic interface for writing/deleting/version-checking multiple items /// in a single DynamoDB table in a transaction. /// - public abstract partial class TransactWrite + public partial interface ITransactWrite { - #region Internal/protected properties + } - internal DynamoDBContext Context { get; set; } - internal DynamoDBFlatConfig Config { get; set; } - internal DocumentTransactWrite DocumentTransaction { get; set; } + /// + /// Represents a generic interface for writing/deleting/version-checking multiple items + /// in a single DynamoDB table in a transaction. + /// + public interface ITransactWrite : ITransactWrite + { + /// + /// Creates a MultiTableTransactWrite object that is a combination + /// of the current TransactWrite and the specified TransactWrites. + /// + /// Other TransactWrite objects. + /// + /// MultiTableTransactWrite consisting of the multiple TransactWrite objects: + /// the current TransactWrite object and the passed-in TransactWrite objects. + /// + MultiTableTransactWrite Combine(params TransactWrite[] otherTransactionParts); - #endregion + /// + /// Add a number of items to be saved in the current transaction operation. + /// + /// Items to save. + void AddSaveItems(IEnumerable values); + /// + /// Add a single item to be saved in the current transaction operation. + /// + /// Item to save. + void AddSaveItem(T item); - #region Constructor + /// + /// Add a single item to be saved in the current transaction operation. + /// Item is identified by its hash primary key and will be updated using the update expression provided. + /// + /// Hash key of the item to delete. + /// Update expression to use. + /// Condition to check before the operation. + void AddSaveItem(object hashKey, Expression updateExpression, Expression conditionExpression = null); - internal TransactWrite(DynamoDBContext context, DynamoDBFlatConfig config) - { - Context = context; - Config = config; - } + /// + /// Add a single item to be saved in the current transaction operation. + /// Item is identified by its hash-and-range primary key and will be updated using the update expression provided. + /// + /// Hash key of the item to delete. + /// Range key of the item to delete. + /// Update expression to use. + /// Condition to check before the operation. + void AddSaveItem(object hashKey, object rangeKey, Expression updateExpression, Expression conditionExpression = null); - #endregion + /// + /// Add a number of items to be deleted in the current transaction operation. + /// + /// Items to be deleted. + void AddDeleteItems(IEnumerable values); + /// + /// Add a single item to be deleted in the current transaction operation. + /// + /// Item to be deleted. + void AddDeleteItem(T item); - #region Protected methods + /// + /// Add a single item to be deleted in the current transaction operation. + /// Item is identified by its hash primary key. + /// + /// Hash key of the item to delete. + void AddDeleteKey(object hashKey); /// - /// Executes a server call to write/delete/version-check the items requested in a transaction. + /// Add a single item to be deleted in the current transaction operation. + /// Item is identified by its hash primary key. /// - protected internal abstract void ExecuteHelper(); + /// Hash key of the item to delete. + /// Condition to check before the operation. + void AddDeleteKey(object hashKey, Expression conditionExpression); + + /// + /// Add a single item to be deleted in the current transaction operation. + /// Item is identified by its hash-and-range primary key. + /// + /// Hash key of the item to delete. + /// Range key of the item to delete. + void AddDeleteKey(object hashKey, object rangeKey); -#if AWS_ASYNC_API /// - /// Executes an asynchronous server call to write/delete/version-check the items requested in a transaction. + /// Add a single item to be deleted in the current transaction operation. + /// Item is identified by its hash-and-range primary key. /// - protected internal abstract Task ExecuteHelperAsync(CancellationToken cancellationToken); -#endif - #endregion + /// Hash key of the item to delete. + /// Range key of the item to delete. + /// Condition to check before the operation. + void AddDeleteKey(object hashKey, object rangeKey, Expression conditionExpression); + /// + /// Add a single item to be version checked in the current transaction operation. + /// The item must have a single property marked with the DynamoDBVersionAttribute. + /// + /// Item to be version checked. + void AddVersionCheckItem(T item); - #region Internal methods + /// + /// Add a number of items to be version checked in the current transaction operation. + /// All items must have a single property marked with the DynamoDBVersionAttribute. + /// + /// Items to be version checked. + void AddVersionCheckItems(IEnumerable items); - internal abstract void PopulateObjects(); + /// + /// Add a single item to be version checked in the current transaction operation. + /// Item is identified by its hash primary key. + /// + /// Hash key of the item to be version checked. + /// Version of the item. + void AddVersionCheckKey(object hashKey, object version); + + /// + /// Add a single item to be version checked in the current transaction operation. + /// Item is identified by its hash-and-range primary key. + /// + /// Hash key of the item to be version checked. + /// Range key of the item to be version checked. + /// Version of the item. + void AddVersionCheckKey(object hashKey, object rangeKey, object version); + } + + /// + /// Represents a non-generic object for writing/deleting/version-checking multiple items + /// in a single DynamoDB table in a transaction. + /// + public abstract partial class TransactWrite : ITransactWrite + { + internal DocumentTransactWrite DocumentTransaction { get; set; } - #endregion + internal abstract void PopulateObjects(); } /// @@ -80,33 +175,29 @@ internal TransactWrite(DynamoDBContext context, DynamoDBFlatConfig config) #if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(Amazon.DynamoDBv2.Custom.Internal.InternalConstants.RequiresUnreferencedCodeMessage)] #endif - public class TransactWrite : TransactWrite + public partial class TransactWrite : TransactWrite, ITransactWrite { - #region Public Combine methods + private readonly DynamoDBContext _context; + private readonly DynamoDBFlatConfig _config; + private readonly ItemStorageConfig _storageConfig; + private readonly List _objectItems = new(); - /// - /// Creates a MultiTableTransactWrite object that is a combination - /// of the current TransactWrite and the specified TransactWrites. - /// - /// Other TransactWrite objects. - /// - /// MultiTableTransactWrite consisting of the multiple TransactWrite objects: - /// the current TransactWrite object and the passed-in TransactWrite objects. - /// + internal TransactWrite(DynamoDBContext context, DynamoDBFlatConfig config) + { + _context = context; + _config = config; + _storageConfig = context.StorageConfigCache.GetConfig(config); + Table table = _context.GetTargetTable(_storageConfig, _config); + DocumentTransaction = table.CreateTransactWrite(); + } + + /// public MultiTableTransactWrite Combine(params TransactWrite[] otherTransactionParts) { return new MultiTableTransactWrite(this, otherTransactionParts); } - #endregion - - - #region Public Save methods - - /// - /// Add a number of items to be saved in the current transaction operation. - /// - /// Items to save. + /// public void AddSaveItems(IEnumerable values) { if (values == null) return; @@ -117,15 +208,12 @@ public void AddSaveItems(IEnumerable values) } } - /// - /// Add a single item to be saved in the current transaction operation. - /// - /// Item to save. + /// public void AddSaveItem(T item) { if (item == null) return; - ItemStorage storage = Context.ObjectToItemStorageHelper(item, StorageConfig, Config, keysOnly: false, Config.IgnoreNullValues ?? false); + ItemStorage storage = _context.ObjectToItemStorageHelper(item, _storageConfig, _config, keysOnly: false, _config.IgnoreNullValues ?? false); if (storage == null) return; Expression conditionExpression = CreateConditionExpressionForVersion(storage); SetNewVersion(storage); @@ -140,29 +228,16 @@ public void AddSaveItem(T item) OriginalObject = item, ItemStorage = storage }; - objectItems.Add(objectItem); + _objectItems.Add(objectItem); } - /// - /// Add a single item to be saved in the current transaction operation. - /// Item is identified by its hash primary key and will be updated using the update expression provided. - /// - /// Hash key of the item to delete. - /// Update expression to use. - /// Condition to check before the operation. + /// public void AddSaveItem(object hashKey, Expression updateExpression, Expression conditionExpression = null) { AddSaveItem(hashKey, rangeKey: null, updateExpression, conditionExpression); } - /// - /// Add a single item to be saved in the current transaction operation. - /// Item is identified by its hash-and-range primary key and will be updated using the update expression provided. - /// - /// Hash key of the item to delete. - /// Range key of the item to delete. - /// Update expression to use. - /// Condition to check before the operation. + /// public void AddSaveItem(object hashKey, object rangeKey, Expression updateExpression, Expression conditionExpression = null) { var operationConfig = conditionExpression != null @@ -173,18 +248,10 @@ public void AddSaveItem(object hashKey, object rangeKey, Expression updateExpres } : null; - DocumentTransaction.AddDocumentToUpdateHelper(Context.MakeKey(hashKey, rangeKey, StorageConfig, Config), updateExpression, operationConfig); + DocumentTransaction.AddDocumentToUpdateHelper(_context.MakeKey(hashKey, rangeKey, _storageConfig, _config), updateExpression, operationConfig); } - #endregion - - - #region Public Delete methods - - /// - /// Add a number of items to be deleted in the current transaction operation. - /// - /// Items to be deleted. + /// public void AddDeleteItems(IEnumerable values) { if (values == null) return; @@ -195,15 +262,12 @@ public void AddDeleteItems(IEnumerable values) } } - /// - /// Add a single item to be deleted in the current transaction operation. - /// - /// Item to be deleted. + /// public void AddDeleteItem(T item) { if (item == null) return; - ItemStorage storage = Context.ObjectToItemStorageHelper(item, StorageConfig, Config, keysOnly: true, Config.IgnoreNullValues ?? false); + ItemStorage storage = _context.ObjectToItemStorageHelper(item, _storageConfig, _config, keysOnly: true, _config.IgnoreNullValues ?? false); if (storage == null) return; Expression conditionExpression = CreateConditionExpressionForVersion(storage); @@ -214,45 +278,25 @@ public void AddDeleteItem(T item) }); } - /// - /// Add a single item to be deleted in the current transaction operation. - /// Item is identified by its hash primary key. - /// - /// Hash key of the item to delete. + /// public void AddDeleteKey(object hashKey) { AddDeleteKey(hashKey, conditionExpression: null); } - /// - /// Add a single item to be deleted in the current transaction operation. - /// Item is identified by its hash primary key. - /// - /// Hash key of the item to delete. - /// Condition to check before the operation. + /// public void AddDeleteKey(object hashKey, Expression conditionExpression) { AddDeleteKey(hashKey, rangeKey: null, conditionExpression); } - /// - /// Add a single item to be deleted in the current transaction operation. - /// Item is identified by its hash-and-range primary key. - /// - /// Hash key of the item to delete. - /// Range key of the item to delete. + /// public void AddDeleteKey(object hashKey, object rangeKey) { AddDeleteKey(hashKey, rangeKey, conditionExpression: null); } - /// - /// Add a single item to be deleted in the current transaction operation. - /// Item is identified by its hash-and-range primary key. - /// - /// Hash key of the item to delete. - /// Range key of the item to delete. - /// Condition to check before the operation. + /// public void AddDeleteKey(object hashKey, object rangeKey, Expression conditionExpression) { var operationConfig = conditionExpression != null @@ -263,26 +307,17 @@ public void AddDeleteKey(object hashKey, object rangeKey, Expression conditionEx } : null; - DocumentTransaction.AddKeyToDeleteHelper(Context.MakeKey(hashKey, rangeKey, StorageConfig, Config), operationConfig); + DocumentTransaction.AddKeyToDeleteHelper(_context.MakeKey(hashKey, rangeKey, _storageConfig, _config), operationConfig); } - #endregion - - - #region Public VersionCheck methods - - /// - /// Add a single item to be version checked in the current transaction operation. - /// The item must have a single property marked with the DynamoDBVersionAttribute. - /// - /// Item to be version checked. + /// public void AddVersionCheckItem(T item) { CheckUseVersioning(); if (item == null) return; - ItemStorage storage = Context.ObjectToItemStorageHelper(item, StorageConfig, Config, keysOnly: true, Config.IgnoreNullValues ?? false); + ItemStorage storage = _context.ObjectToItemStorageHelper(item, _storageConfig, _config, keysOnly: true, _config.IgnoreNullValues ?? false); if (storage == null) return; Expression conditionExpression = CreateConditionExpressionForVersion(storage); @@ -293,11 +328,7 @@ public void AddVersionCheckItem(T item) }); } - /// - /// Add a number of items to be version checked in the current transaction operation. - /// All items must have a single property marked with the DynamoDBVersionAttribute. - /// - /// Items to be version checked. + /// public void AddVersionCheckItems(IEnumerable items) { foreach (var item in items) @@ -306,40 +337,29 @@ public void AddVersionCheckItems(IEnumerable items) } } - /// - /// Add a single item to be version checked in the current transaction operation. - /// Item is identified by its hash primary key. - /// - /// Hash key of the item to be version checked. - /// Version of the item. + /// public void AddVersionCheckKey(object hashKey, object version) { AddVersionCheckKey(hashKey, rangeKey: null, version); } - /// - /// Add a single item to be version checked in the current transaction operation. - /// Item is identified by its hash-and-range primary key. - /// - /// Hash key of the item to be version checked. - /// Range key of the item to be version checked. - /// Version of the item. + /// public void AddVersionCheckKey(object hashKey, object rangeKey, object version) { CheckUseVersioning(); - Key key = Context.MakeKey(hashKey, rangeKey, StorageConfig, Config); - DynamoDBEntry versionEntry = Context.ToDynamoDBEntry(StorageConfig.VersionPropertyStorage, version, Config); + Key key = _context.MakeKey(hashKey, rangeKey, _storageConfig, _config); + DynamoDBEntry versionEntry = _context.ToDynamoDBEntry(_storageConfig.VersionPropertyStorage, version, _config); Primitive versionPrimitive = versionEntry?.AsPrimitive(); if (versionEntry != null && versionPrimitive == null) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Version property {0} must be Primitive.", - StorageConfig.VersionPropertyName)); + _storageConfig.VersionPropertyName)); } - ItemStorage storage = new ItemStorage(StorageConfig) + ItemStorage storage = new ItemStorage(_storageConfig) { CurrentVersion = versionPrimitive }; @@ -353,42 +373,14 @@ public void AddVersionCheckKey(object hashKey, object rangeKey, object version) }); } - #endregion - - - #region Constructor - - internal TransactWrite(DynamoDBContext context, DynamoDBFlatConfig config) - : base(context, config) - { - StorageConfig = context.StorageConfigCache.GetConfig(config); - Table table = Context.GetTargetTable(StorageConfig, Config); - DocumentTransaction = table.CreateTransactWrite(); - } - - #endregion - - - #region Internal/protected/private members - - private readonly List objectItems = new List(); - - internal ItemStorageConfig StorageConfig { get; set; } - - /// - /// Executes a server call to write/delete/version-check the items requested in a transaction. - /// - protected internal override void ExecuteHelper() + private void ExecuteHelper() { DocumentTransaction.ExecuteHelper(); PopulateObjects(); } #if AWS_ASYNC_API - /// - /// Executes an asynchronous server call to write/delete/version-check the items requested in a transaction. - /// - protected internal override async Task ExecuteHelperAsync(CancellationToken cancellationToken) + private async Task ExecuteHelperAsync(CancellationToken cancellationToken) { await DocumentTransaction.ExecuteHelperAsync(cancellationToken).ConfigureAwait(false); PopulateObjects(); @@ -397,27 +389,27 @@ protected internal override async Task ExecuteHelperAsync(CancellationToken canc internal override void PopulateObjects() { - foreach (var objectItem in objectItems) + foreach (var objectItem in _objectItems) { - objectItem.PopulateObject(Context, Config); + objectItem.PopulateObject(_context, _config); } } private bool ShouldUseVersioning() { - var skipVersionCheck = Config.SkipVersionCheck ?? false; - return !skipVersionCheck && StorageConfig.HasVersion; + var skipVersionCheck = _config.SkipVersionCheck ?? false; + return !skipVersionCheck && _storageConfig.HasVersion; } private void CheckUseVersioning() { - if (Config.SkipVersionCheck == true) + if (_config.SkipVersionCheck == true) { throw new InvalidOperationException( "Using DynamoDBContextConfig.SkipVersionCheck property with true value is not supported for this operation."); } - if (!StorageConfig.HasVersion) + if (!_storageConfig.HasVersion) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Object {0} does not have a versioning field, which is not supported for this operation.", @@ -439,86 +431,86 @@ private void SetNewVersion(ItemStorage storage) if (!ShouldUseVersioning()) return; DynamoDBContext.SetNewVersion(storage); } + } - #endregion + /// + /// Interface for writing/deleting/version-checking multiple items in multiple DynamoDB tables, + /// using multiple strongly-typed TransactWrite objects. + /// + public partial interface IMultiTableTransactWrite + { + /// + /// Add a TransactWrite object to the multi-table transaction request. + /// + /// TransactWrite to add. + void AddTransactionPart(ITransactWrite transactionPart); } /// /// Class for writing/deleting/version-checking multiple items in multiple DynamoDB tables, /// using multiple strongly-typed TransactWrite objects. /// - public partial class MultiTableTransactWrite + public partial class MultiTableTransactWrite : IMultiTableTransactWrite { - #region Private members - - private readonly List allTransactionParts; - - #endregion - - - #region Constructor + private readonly List allTransactionParts; /// /// Constructs a MultiTableTransactWrite object from a number of /// TransactWrite objects /// /// Collection of TransactWrite objects - public MultiTableTransactWrite(params TransactWrite[] transactionParts) + public MultiTableTransactWrite(params ITransactWrite[] transactionParts) { - allTransactionParts = new List(transactionParts); + allTransactionParts = new List(transactionParts); } - internal MultiTableTransactWrite(TransactWrite first, params TransactWrite[] rest) + internal MultiTableTransactWrite(ITransactWrite first, params ITransactWrite[] rest) { - allTransactionParts = new List(); + allTransactionParts = new List(); allTransactionParts.Add(first); allTransactionParts.AddRange(rest); } - #endregion - - - #region Public methods - - /// - /// Add a TransactWrite object to the multi-table transaction request. - /// - /// TransactWrite to add. - public void AddTransactionPart(TransactWrite transactionPart) + /// + public void AddTransactionPart(ITransactWrite transactionPart) { allTransactionParts.Add(transactionPart); } - internal void ExecuteHelper() + private void ExecuteHelper() { MultiTableDocumentTransactWrite transaction = new MultiTableDocumentTransactWrite(); + var errMsg = $"All transactionParts must be of type {nameof(TransactWrite)}"; foreach (var transactionPart in allTransactionParts) { - transaction.AddTransactionPart(transactionPart.DocumentTransaction); + var abstractTransactWrite = transactionPart as TransactWrite ?? throw new InvalidOperationException(errMsg); + transaction.AddTransactionPart(abstractTransactWrite.DocumentTransaction); } transaction.ExecuteHelper(); foreach (var transactionPart in allTransactionParts) { - transactionPart.PopulateObjects(); + var abstractTransactWrite = transactionPart as TransactWrite ?? throw new InvalidOperationException(errMsg); + abstractTransactWrite.PopulateObjects(); } } #if AWS_ASYNC_API - internal async Task ExecuteHelperAsync(CancellationToken cancellationToken) + private async Task ExecuteHelperAsync(CancellationToken cancellationToken) { MultiTableDocumentTransactWrite transaction = new MultiTableDocumentTransactWrite(); + var errMsg = $"All transactionParts must be of type {nameof(TransactWrite)}"; foreach (var transactionPart in allTransactionParts) { - transaction.AddTransactionPart(transactionPart.DocumentTransaction); + var abstractTransactWrite = transactionPart as TransactWrite ?? throw new InvalidOperationException(errMsg); + transaction.AddTransactionPart(abstractTransactWrite.DocumentTransaction); } await transaction.ExecuteHelperAsync(cancellationToken).ConfigureAwait(false); foreach (var transactionPart in allTransactionParts) { - transactionPart.PopulateObjects(); + var abstractTransactWrite = transactionPart as TransactWrite ?? throw new InvalidOperationException(errMsg); + abstractTransactWrite.PopulateObjects(); } } #endif - - #endregion } } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/Context.Async.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/Context.Async.cs index a408abb50e5a..84a255cc9eda 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/Context.Async.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/Context.Async.cs @@ -239,13 +239,8 @@ public Task ExecuteBatchGetAsync(params IBatchGet[] batches) #region TransactWrite async - /// - /// Issues a transactional write request with multiple TransactWrite objects. - /// - /// Configured TransactWrite objects. - /// Token which can be used to cancel the task. - /// A Task that can be used to poll or wait for results, or both. - public Task ExecuteTransactWriteAsync(TransactWrite[] transactionParts, CancellationToken cancellationToken = default(CancellationToken)) + /// + public Task ExecuteTransactWriteAsync(ITransactWrite[] transactionParts, CancellationToken cancellationToken = default(CancellationToken)) { MultiTableTransactWrite transaction = new MultiTableTransactWrite(transactionParts); return transaction.ExecuteAsync(cancellationToken); diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/IDynamoDBContext.Async.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/IDynamoDBContext.Async.cs index 1417c11689c8..22ad5936c4ea 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/IDynamoDBContext.Async.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/IDynamoDBContext.Async.cs @@ -467,7 +467,7 @@ partial interface IDynamoDBContext /// Configured TransactWrite objects. /// Token which can be used to cancel the task. /// A Task that can be used to poll or wait for results, or both. - Task ExecuteTransactWriteAsync(TransactWrite[] transactionParts, CancellationToken cancellationToken = default(CancellationToken)); + Task ExecuteTransactWriteAsync(ITransactWrite[] transactionParts, CancellationToken cancellationToken = default(CancellationToken)); #endregion diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/TransactWrite.Async.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/TransactWrite.Async.cs index f905dd149298..4910c851591d 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/TransactWrite.Async.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/TransactWrite.Async.cs @@ -18,46 +18,48 @@ namespace Amazon.DynamoDBv2.DataModel { - /// - /// Represents a non-generic object for writing/deleting/version-checking multiple items - /// in a single DynamoDB table in a transaction. - /// - public abstract partial class TransactWrite + public partial interface ITransactWrite { - #region Public methods - /// /// Executes a server call to write/delete/version-check the items requested in a transaction. /// /// Token which can be used to cancel the task. /// A Task that can be used to poll or wait for results, or both. - public Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) + Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); + } + + public abstract partial class TransactWrite : ITransactWrite + { + /// + public abstract Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); + } + + public partial class TransactWrite : TransactWrite, ITransactWrite + { + /// + public override Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { return ExecuteHelperAsync(cancellationToken); } - - #endregion } - /// - /// Class for writing/deleting/version-checking multiple items in multiple DynamoDB tables, - /// using multiple strongly-typed TransactWrite objects. - /// - public partial class MultiTableTransactWrite + public partial interface IMultiTableTransactWrite { - #region Public methods - /// /// Executes a multi-table transaction request against all configured TransactWrite objects. /// /// Token which can be used to cancel the task. /// /// A Task that can be used to poll or wait for results, or both. + public Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); + } + + public partial class MultiTableTransactWrite : IMultiTableTransactWrite + { + /// public Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { return ExecuteHelperAsync(cancellationToken); } - - #endregion } } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/Context.Sync.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/Context.Sync.cs index ea57d246b026..d60be12972e2 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/Context.Sync.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/Context.Sync.cs @@ -206,11 +206,8 @@ public void ExecuteBatchWrite(params IBatchWrite[] batches) #region Transact Write - /// - /// Issues a transactional write request with multiple TransactWrite objects. - /// - /// Configured TransactWrite objects. - public void ExecuteTransactWrite(params TransactWrite[] transactionParts) + /// + public void ExecuteTransactWrite(params ITransactWrite[] transactionParts) { MultiTableTransactWrite transaction = new MultiTableTransactWrite(transactionParts); transaction.Execute(); diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/IDynamoDBContext.Sync.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/IDynamoDBContext.Sync.cs index db4c41c25729..207c4a241b37 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/IDynamoDBContext.Sync.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/IDynamoDBContext.Sync.cs @@ -396,7 +396,7 @@ partial interface IDynamoDBContext /// Issues a transactional write request with multiple TransactWrite objects. /// /// Configured TransactWrite objects. - void ExecuteTransactWrite(params TransactWrite[] transactionParts); + void ExecuteTransactWrite(params ITransactWrite[] transactionParts); #endregion diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/TransactWrite.Sync.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/TransactWrite.Sync.cs index bb6242eaff20..4b166e850d38 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/TransactWrite.Sync.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/TransactWrite.Sync.cs @@ -15,41 +15,43 @@ namespace Amazon.DynamoDBv2.DataModel { - /// - /// Represents a non-generic object for writing/deleting/version-checking multiple items - /// in a single DynamoDB table in a transaction. - /// - public abstract partial class TransactWrite + public partial interface ITransactWrite { - #region Public methods - /// /// Executes a server call to write/delete/version-check the items requested in a transaction. /// - public void Execute() + void Execute(); + } + + public abstract partial class TransactWrite : ITransactWrite + { + /// + public abstract void Execute(); + } + + public partial class TransactWrite : TransactWrite, ITransactWrite + { + /// + public override void Execute() { ExecuteHelper(); } - - #endregion } - /// - /// Class for writing/deleting/version-checking multiple items in multiple DynamoDB tables, - /// using multiple strongly-typed TransactWrite objects. - /// - public partial class MultiTableTransactWrite + public partial interface IMultiTableTransactWrite { - #region Public methods - /// /// Executes a multi-table transaction request against all configured TransactWrite objects. /// + void Execute(); + } + + public partial class MultiTableTransactWrite : IMultiTableTransactWrite + { + /// public void Execute() { ExecuteHelper(); } - - #endregion } } diff --git a/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockabilityTests/TransactWriteTests.cs b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockabilityTests/TransactWriteTests.cs new file mode 100644 index 000000000000..24eef4f9602f --- /dev/null +++ b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockabilityTests/TransactWriteTests.cs @@ -0,0 +1,121 @@ +using Amazon.DynamoDBv2.DataModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Collections.Generic; + +namespace AWSSDK.UnitTests.DynamoDBv2.NetFramework.Custom.MockabilityTests +{ + [TestClass] + public class TransactWriteTests + { + [TestMethod] + public void TestMockability_TransactWrite() + { + var itemsToSave = new List(); + var inMemoryTable = new List(); + + var mockContext = new Mock(); + mockContext + .Setup(x => x.CreateTransactWrite()) + .Returns(CreateTransactWriteMock(itemsToSave, inMemoryTable)); + + var ddbContext = mockContext.Object; + var transactWrite = ddbContext.CreateTransactWrite(); + + Assert.AreEqual(0, inMemoryTable.Count); + Assert.AreEqual(0, itemsToSave.Count); + + transactWrite.AddSaveItem("item1"); + transactWrite.AddSaveItem("item2"); + Assert.AreEqual(2, itemsToSave.Count); + Assert.AreEqual(0, inMemoryTable.Count); + + transactWrite.Execute(); + Assert.AreEqual(0, itemsToSave.Count); + Assert.AreEqual(2, inMemoryTable.Count); + Assert.IsTrue(inMemoryTable.Contains("item1")); + Assert.IsTrue(inMemoryTable.Contains("item2")); + } + + [TestMethod] + public void TestMockability_MultiTableTransactWrite() + { + var itemsToSave_table1 = new List(); + var inMemory_table1 = new List(); + var transactWrite_table1 = CreateTransactWriteMock(itemsToSave_table1, inMemory_table1); + transactWrite_table1.AddSaveItem("item1"); + + var itemsToSave_table2 = new List(); + var inMemory_table2 = new List(); + var transactWrite_table2 = CreateTransactWriteMock(itemsToSave_table2, inMemory_table2); + transactWrite_table2.AddSaveItem("item2"); + + var mockContext = new Mock(); + mockContext + .Setup(x => x.CreateMultiTableTransactWrite()) + .Returns(CreateMultiTableTransactWriteMock()); + + var ddbContext = mockContext.Object; + var multiTransactWrite = ddbContext.CreateMultiTableTransactWrite(); + multiTransactWrite.AddTransactionPart(transactWrite_table1); + multiTransactWrite.AddTransactionPart(transactWrite_table2); + + Assert.AreEqual(0, inMemory_table1.Count); + Assert.AreEqual(0, inMemory_table2.Count); + + multiTransactWrite.Execute(); + Assert.AreEqual(1, inMemory_table1.Count); + Assert.AreEqual(1, inMemory_table2.Count); + Assert.IsTrue(inMemory_table1.Contains("item1")); + Assert.IsTrue(inMemory_table2.Contains("item2")); + } + + public ITransactWrite CreateTransactWriteMock(List itemsToSave, List inMemoryTable) + { + var transactWrite = new Mock>(); + + transactWrite + .Setup(x => x.AddSaveItem(It.IsAny())) + .Callback((T item) => itemsToSave.Add(item)); + + transactWrite. + Setup(x => x.Execute()) + .Callback(() => + { + foreach (var item in itemsToSave) + { + inMemoryTable.Add(item); + } + + itemsToSave.Clear(); + }); + + return transactWrite.Object; + } + + public IMultiTableTransactWrite CreateMultiTableTransactWriteMock() + { + var multiTransactWrite = new Mock(); + var transactionParts = new List(); + + multiTransactWrite + .Setup(x => x.AddTransactionPart(It.IsAny())) + .Callback((ITransactWrite transactionPart) => + { + transactionParts.Add(transactionPart); + }); + + multiTransactWrite + .Setup(x => x.Execute()) + .Callback(() => + { + foreach (var batch in transactionParts) + { + batch.Execute(); + } + }); + + return multiTransactWrite.Object; + } + } +}