|
|
|
@ -0,0 +1,314 @@
|
|
|
|
|
using Connected.Entities.Storage;
|
|
|
|
|
using Connected.Notifications.Events;
|
|
|
|
|
using Connected.ServiceModel;
|
|
|
|
|
using Connected.ServiceModel.Transactions;
|
|
|
|
|
using Moq;
|
|
|
|
|
using System.Data;
|
|
|
|
|
using System.Linq.Expressions;
|
|
|
|
|
using static Common.Types.TaxRates.TaxRateOps;
|
|
|
|
|
using static Common.Types.TestUtils;
|
|
|
|
|
|
|
|
|
|
namespace Common.Types.TaxRates.Ops;
|
|
|
|
|
|
|
|
|
|
public class UpdateTests
|
|
|
|
|
{
|
|
|
|
|
[TestClass]
|
|
|
|
|
public class OnInvoke
|
|
|
|
|
{
|
|
|
|
|
private static IEnumerable<object[]> TaxRates => TaxRateSamples.CombinedTaxRateSet
|
|
|
|
|
.Select(e => new object[] { e.Id, e })
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
[TestMethod("Update updates only matching id")]
|
|
|
|
|
[DynamicData(nameof(TaxRates))]
|
|
|
|
|
public async Task Update_Invoke_UpdatesMatchingEntity(int id, ITaxRate expected)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Setup
|
|
|
|
|
*/
|
|
|
|
|
var dataSet = TaxRates.Select(e => e[1] as TaxRate).ToList();
|
|
|
|
|
|
|
|
|
|
var instanceFaker = new InstanceFaker<Update>();
|
|
|
|
|
|
|
|
|
|
var databaseContextProviderFake = instanceFaker.GetMock<IStorageProvider>()!;
|
|
|
|
|
|
|
|
|
|
Expression<Action<IStorageProvider>> updateFunction = (IStorageProvider e) => e.Open<TaxRate>().Update(It.IsAny<TaxRate>(), It.IsAny<UpdateTaxRateArgs>(), It.IsAny<Func<Task<TaxRate?>>>());
|
|
|
|
|
|
|
|
|
|
TaxRate? updatedEntity = null;
|
|
|
|
|
|
|
|
|
|
_ = databaseContextProviderFake
|
|
|
|
|
.Setup(updateFunction)
|
|
|
|
|
.Callback<TaxRate, UpdateTaxRateArgs, Func<Task<TaxRate>>>((e, _, _) => updatedEntity = e);
|
|
|
|
|
|
|
|
|
|
var taxRateCacheFake = instanceFaker.GetMock<ITaxRateCache>()!;
|
|
|
|
|
|
|
|
|
|
taxRateCacheFake.SetupIEnumerable(dataSet);
|
|
|
|
|
|
|
|
|
|
var args = new UpdateTaxRateArgs
|
|
|
|
|
{
|
|
|
|
|
Id = id
|
|
|
|
|
};
|
|
|
|
|
/*
|
|
|
|
|
* Test
|
|
|
|
|
*/
|
|
|
|
|
var serviceFunction = instanceFaker.Instance;
|
|
|
|
|
await serviceFunction.Invoke(args);
|
|
|
|
|
/*
|
|
|
|
|
* Assert
|
|
|
|
|
*/
|
|
|
|
|
databaseContextProviderFake.Verify(updateFunction, Times.Once());
|
|
|
|
|
Assert.AreEqual(expected, updatedEntity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod("Update throws if tax rate does not exist")]
|
|
|
|
|
public async Task Update_Invoke_ThrowsException_WhenEntityDoesNotExist()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Setup
|
|
|
|
|
*/
|
|
|
|
|
var dataSet = TaxRateSamples.GetEmptyTaxRateSet();
|
|
|
|
|
|
|
|
|
|
var instanceFaker = new InstanceFaker<Update>();
|
|
|
|
|
|
|
|
|
|
var databaseContextProviderFake = instanceFaker.GetMock<IStorageProvider>()!;
|
|
|
|
|
|
|
|
|
|
Expression<Action<IStorageProvider>> updateFunction = (IStorageProvider e) => e.Open<TaxRate>().Update(It.IsAny<TaxRate>(), It.IsAny<UpdateTaxRateArgs>(), It.IsAny<Func<Task<TaxRate?>>>());
|
|
|
|
|
|
|
|
|
|
_ = databaseContextProviderFake.Setup(updateFunction);
|
|
|
|
|
|
|
|
|
|
var taxRateCacheFake = instanceFaker.GetMock<ITaxRateCache>()!;
|
|
|
|
|
|
|
|
|
|
taxRateCacheFake.SetupIEnumerable(dataSet);
|
|
|
|
|
/*
|
|
|
|
|
* Generate empty arguments, as the dataset is empty either way
|
|
|
|
|
*/
|
|
|
|
|
var args = new UpdateTaxRateArgs();
|
|
|
|
|
/*
|
|
|
|
|
* Test
|
|
|
|
|
*/
|
|
|
|
|
var serviceFunction = instanceFaker.Instance;
|
|
|
|
|
/*
|
|
|
|
|
* Assert
|
|
|
|
|
*/
|
|
|
|
|
await AssertExtensions.Throws(async () => await serviceFunction.Invoke(args));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod("Update invokes concurrent update exactly once")]
|
|
|
|
|
public async Task Update_Invoke_InvokesConcurrentUpdate_ExactlyOnce()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Setup
|
|
|
|
|
*/
|
|
|
|
|
var dataSet = TaxRateSamples.GetEmptyTaxRateSet();
|
|
|
|
|
|
|
|
|
|
var instanceFaker = new InstanceFaker<Update>();
|
|
|
|
|
|
|
|
|
|
var databaseContextProviderFake = instanceFaker.GetMock<IStorageProvider>()!;
|
|
|
|
|
|
|
|
|
|
Expression<Action<IStorageProvider>> updateFunction = (IStorageProvider e) => e.Open<TaxRate>().Update(It.IsAny<TaxRate>(), It.IsAny<UpdateTaxRateArgs>(), It.IsAny<Func<Task<TaxRate?>>>());
|
|
|
|
|
|
|
|
|
|
_ = databaseContextProviderFake.Setup(updateFunction);
|
|
|
|
|
|
|
|
|
|
var taxRateCacheFake = instanceFaker.GetMock<ITaxRateCache>()!;
|
|
|
|
|
|
|
|
|
|
taxRateCacheFake.SetupIEnumerable(dataSet);
|
|
|
|
|
/*
|
|
|
|
|
* Arguments are irrelevant for this test
|
|
|
|
|
*/
|
|
|
|
|
var args = new UpdateTaxRateArgs();
|
|
|
|
|
/*
|
|
|
|
|
* Test
|
|
|
|
|
*/
|
|
|
|
|
var serviceFunction = instanceFaker.Instance;
|
|
|
|
|
await serviceFunction.Invoke(args);
|
|
|
|
|
/*
|
|
|
|
|
* Assert
|
|
|
|
|
*/
|
|
|
|
|
databaseContextProviderFake.Verify(updateFunction, Times.Exactly(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod("Update loads data exactly once when version is current")]
|
|
|
|
|
public async Task Update_Invoke_LoadsEntityExactlyOnce_WhenVersionIsCurrent()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Setup
|
|
|
|
|
*/
|
|
|
|
|
var dataSet = TaxRateSamples.GetEmptyTaxRateSet();
|
|
|
|
|
|
|
|
|
|
var instanceFaker = new InstanceFaker<Update>();
|
|
|
|
|
|
|
|
|
|
var databaseContextProviderFake = instanceFaker.GetMock<IStorageProvider>()!;
|
|
|
|
|
|
|
|
|
|
Expression<Action<IStorageProvider>> updateFunction = (IStorageProvider e) => e.Open<TaxRate>().Update(It.IsAny<TaxRate>(), It.IsAny<UpdateTaxRateArgs>(), It.IsAny<Func<Task<TaxRate?>>>());
|
|
|
|
|
|
|
|
|
|
_ = databaseContextProviderFake.Setup(updateFunction);
|
|
|
|
|
|
|
|
|
|
var taxRateCacheFake = instanceFaker.GetMock<ITaxRateCache>()!;
|
|
|
|
|
|
|
|
|
|
taxRateCacheFake.SetupIEnumerable(dataSet);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Arguments are irrelevant for this test
|
|
|
|
|
*/
|
|
|
|
|
var args = new UpdateTaxRateArgs();
|
|
|
|
|
/*
|
|
|
|
|
* Test
|
|
|
|
|
*/
|
|
|
|
|
var serviceFunction = instanceFaker.Instance;
|
|
|
|
|
await serviceFunction.Invoke(args);
|
|
|
|
|
/*
|
|
|
|
|
* Assert
|
|
|
|
|
*/
|
|
|
|
|
taxRateCacheFake.Verify(e => e.GetEnumerator(), Times.Exactly(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod("Update loads data twice when version is mismatched")]
|
|
|
|
|
public async Task Update_Invoke_LoadsEntityExactlyTwice_WhenVersionIsMismatched()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Setup
|
|
|
|
|
*/
|
|
|
|
|
var dataSet = TaxRateSamples.GetEmptyTaxRateSet();
|
|
|
|
|
|
|
|
|
|
var instanceFaker = new InstanceFaker<Update>();
|
|
|
|
|
|
|
|
|
|
var databaseContextProviderFake = instanceFaker.GetMock<IStorageProvider>()!;
|
|
|
|
|
|
|
|
|
|
Expression<Action<IStorageProvider>> updateFunction = (IStorageProvider e) => e.Open<TaxRate>().Update(It.IsAny<TaxRate>(), It.IsAny<UpdateTaxRateArgs>(), It.IsAny<Func<Task<TaxRate?>>>());
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Simulate version mismatch by invoking the retry once
|
|
|
|
|
*/
|
|
|
|
|
_ = databaseContextProviderFake.Setup(updateFunction).Callback<TaxRate, UpdateTaxRateArgs, Func<Task<TaxRate?>>>((_, _, e) => e.Invoke());
|
|
|
|
|
|
|
|
|
|
var taxRateCacheFake = instanceFaker.GetMock<ITaxRateCache>()!;
|
|
|
|
|
|
|
|
|
|
taxRateCacheFake.SetupIEnumerable(dataSet);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Arguments are irrelevant for this test
|
|
|
|
|
*/
|
|
|
|
|
var args = new UpdateTaxRateArgs();
|
|
|
|
|
/*
|
|
|
|
|
* Test
|
|
|
|
|
*/
|
|
|
|
|
var serviceFunction = instanceFaker.Instance;
|
|
|
|
|
await serviceFunction.Invoke(args);
|
|
|
|
|
/*
|
|
|
|
|
* Assert
|
|
|
|
|
*/
|
|
|
|
|
taxRateCacheFake.Verify(e => e.GetEnumerator(), Times.Exactly(2));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestClass]
|
|
|
|
|
public class OnCommit
|
|
|
|
|
{
|
|
|
|
|
[TestMethod("Update commit invokes updated event exactly once")]
|
|
|
|
|
public async Task Update_Commit_InvokedUpdated_ExactlyOnce()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Setup
|
|
|
|
|
*/
|
|
|
|
|
var instanceFaker = new InstanceFaker<Update>();
|
|
|
|
|
|
|
|
|
|
var taxRateCacheFake = instanceFaker.GetMock<ITaxRateCache>()!;
|
|
|
|
|
|
|
|
|
|
_ = taxRateCacheFake.Setup(e => e.Refresh(It.IsAny<int>()));
|
|
|
|
|
|
|
|
|
|
var eventServiceFake = instanceFaker.GetMock<IEventService>()!;
|
|
|
|
|
|
|
|
|
|
_ = eventServiceFake.Setup(e => e.Enqueue(It.IsAny<ITaxRateService?>(), ServiceEvents.Updated, It.IsAny<int>()));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Argument values are irrelevant for the test
|
|
|
|
|
*/
|
|
|
|
|
var args = new UpdateTaxRateArgs();
|
|
|
|
|
/*
|
|
|
|
|
* Test
|
|
|
|
|
*/
|
|
|
|
|
var serviceFunction = instanceFaker.Instance;
|
|
|
|
|
serviceFunction.SetArguments(args);
|
|
|
|
|
await ((ITransactionClient)serviceFunction).Commit();
|
|
|
|
|
/*
|
|
|
|
|
* Assert
|
|
|
|
|
*/
|
|
|
|
|
eventServiceFake.Verify(e => e.Enqueue(It.IsAny<ITaxRateService?>(), ServiceEvents.Updated, It.IsAny<int>()), Times.Exactly(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod("Update commit refreshes cache exactly once")]
|
|
|
|
|
public async Task Update_Commit_RefreshesCache_ExactlyOnce()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Setup
|
|
|
|
|
*/
|
|
|
|
|
var instanceFaker = new InstanceFaker<Update>();
|
|
|
|
|
|
|
|
|
|
var taxRateCacheFake = instanceFaker.GetMock<ITaxRateCache>()!;
|
|
|
|
|
|
|
|
|
|
_ = taxRateCacheFake.Setup(e => e.Refresh(It.IsAny<int>()));
|
|
|
|
|
|
|
|
|
|
var eventServiceFake = instanceFaker.GetMock<IEventService>()!;
|
|
|
|
|
|
|
|
|
|
_ = eventServiceFake.Setup(e => e.Enqueue(It.IsAny<ITaxRateService?>(), ServiceEvents.Updated, It.IsAny<int>()));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Argument values are irrelevant for the test
|
|
|
|
|
*/
|
|
|
|
|
var args = new UpdateTaxRateArgs();
|
|
|
|
|
/*
|
|
|
|
|
* Test
|
|
|
|
|
*/
|
|
|
|
|
var serviceFunction = instanceFaker.Instance;
|
|
|
|
|
serviceFunction.SetArguments(args);
|
|
|
|
|
await ((ITransactionClient)serviceFunction).Commit();
|
|
|
|
|
/*
|
|
|
|
|
* Assert
|
|
|
|
|
*/
|
|
|
|
|
taxRateCacheFake.Verify(e => e.Refresh(It.IsAny<int>()), Times.Exactly(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod("Update commit fires updated event after notifying cache")]
|
|
|
|
|
public async Task Update_Commit_InvokedLastUpdatedEvent_AfterNotifyingCache()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Setup
|
|
|
|
|
* Mockbehavior string ensures no unplanned functions are called on the services.
|
|
|
|
|
* If you receive an error about function calls not properly set up, and function
|
|
|
|
|
* calls look ok, it's probably the sequence throwing the exception. This is a
|
|
|
|
|
* failure in user code, not the test.
|
|
|
|
|
*/
|
|
|
|
|
var sequence = new MockSequence();
|
|
|
|
|
|
|
|
|
|
var instanceFaker = new InstanceFaker<Update>();
|
|
|
|
|
|
|
|
|
|
var taxRateCacheFake = instanceFaker.GetMock<ITaxRateCache>()!;
|
|
|
|
|
|
|
|
|
|
_ = taxRateCacheFake
|
|
|
|
|
.InSequence(sequence)
|
|
|
|
|
.Setup(e => e.Refresh(It.IsAny<int>()));
|
|
|
|
|
|
|
|
|
|
var eventServiceFake = instanceFaker.GetMock<IEventService>()!;
|
|
|
|
|
|
|
|
|
|
_ = eventServiceFake
|
|
|
|
|
.InSequence(sequence)
|
|
|
|
|
.Setup(e => e.Enqueue(It.IsAny<ITaxRateService?>(), ServiceEvents.Updated, It.IsAny<int>()));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Argument values are irrelevant for the test
|
|
|
|
|
*/
|
|
|
|
|
var args = new UpdateTaxRateArgs();
|
|
|
|
|
/*
|
|
|
|
|
* Test
|
|
|
|
|
*/
|
|
|
|
|
var serviceFunction = instanceFaker.Instance;
|
|
|
|
|
serviceFunction.SetArguments(args);
|
|
|
|
|
await ((ITransactionClient)serviceFunction).Commit();
|
|
|
|
|
/*
|
|
|
|
|
* Assert
|
|
|
|
|
*/
|
|
|
|
|
taxRateCacheFake.Verify(e => e.Refresh(It.IsAny<int>()), Times.AtLeastOnce());
|
|
|
|
|
eventServiceFake.VerifyEvent<ITaxRateService, int>(ServiceEvents.Updated);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|