public WaitForBatchExpirationCommandsHandlerTests() { _expirationMonitoringPeriod = TimeSpan.FromMinutes(2); _eventsPublisherMock = new Mock <IEventPublisher>(); _batchRepositoryMock = new Mock <ICashoutsBatchRepository>(); _handler = new WaitForBatchExpirationCommandsHandler ( new SilentChaosKitty(), _expirationMonitoringPeriod, _batchRepositoryMock.Object ); _batch = CashoutsBatchAggregate.Start ( CashoutsBatchAggregate.GetNextId(), "Bitcoin", "LykkeBTC", "BTC", "HotWallet", 10, TimeSpan.FromMinutes(10) ); _batchRepositoryMock .Setup(x => x.GetAsync(It.Is <Guid>(p => p == _batch.BatchId))) .ReturnsAsync(() => _batch); }
public static CashoutsBatchEntity FromDomain(CashoutsBatchAggregate aggregate) { return(new CashoutsBatchEntity { ETag = string.IsNullOrEmpty(aggregate.Version) ? "*" : aggregate.Version, PartitionKey = GetPartitionKey(aggregate.BatchId), RowKey = GetRowKey(aggregate.BatchId), StartMoment = aggregate.StartMoment, LastCashoutAdditionMoment = aggregate.LastCashoutAdditionMoment, ExpirationMoment = aggregate.ExpirationMoment, ClosingMoment = aggregate.ClosingMoment, IdRevocationMoment = aggregate.IdRevocationMoment, FinishMoment = aggregate.FinishMoment, BatchId = aggregate.BatchId, BlockchainType = aggregate.BlockchainType, AssetId = aggregate.AssetId, BlockchainAssetId = aggregate.BlockchainAssetId, HotWalletAddress = aggregate.HotWalletAddress, CountThreshold = aggregate.CountThreshold, AgeThreshold = aggregate.AgeThreshold, Cashouts = aggregate.Cashouts .Select(BatchedCashoutEntity.FromDomain) .ToArray(), State = aggregate.State, ClosingReason = aggregate.ClosingReason }); }
public async Task Expire_Batch_If_Batch_Is_Have_To_Be_Expired() { // Arrange _batch.AddCashout(new BatchedCashoutValueType(Guid.NewGuid(), Guid.NewGuid(), "Destination", 100, 0, DateTime.UtcNow)); var batchEntity = CashoutsBatchEntity.FromDomain(_batch); batchEntity.StartMoment = DateTime.UtcNow - _batch.AgeThreshold - TimeSpan.FromSeconds(1); _batch = batchEntity.ToDomain(); // Act var handlingResult = await _handler.Handle ( new WaitForBatchExpirationCommand { BatchId = _batch.BatchId }, _eventsPublisherMock.Object ); // Assert Assert.False(handlingResult.Retry); Assert.Equal(CashoutsBatchState.Expired, _batch.State); _batchRepositoryMock.Verify(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId)), Times.Once); _eventsPublisherMock.Verify(x => x.PublishEvent(It.Is <BatchExpiredEvent>(p => p.BatchId == _batch.BatchId)), Times.Once); _eventsPublisherMock.VerifyNoOtherCalls(); }
public async Task Wait_For_The_Next_Batch_If_Current_Batch_Is_Already_Not_Filling() { // Arrange var clientId = Guid.NewGuid(); var cashoutId = Guid.NewGuid(); var batchEntity = CashoutsBatchEntity.FromDomain(_batch); batchEntity.State = CashoutsBatchState.Filled; _batch = batchEntity.ToDomain(); // Act var handlingResult = await _handler.Handle ( new AcceptCashoutCommand { BlockchainType = "Bitcoin", BlockchainAssetId = "BTC", AssetId = "LykkeBTC", Amount = 100, ClientId = clientId, OperationId = cashoutId, HotWalletAddress = "HotWallet", ToAddress = "Destination" }, _eventsPublisherMock.Object ); // Assert Assert.True(handlingResult.Retry); Assert.Equal(_cqrsSettings.RetryDelay, TimeSpan.FromMilliseconds(handlingResult.RetryDelay)); _batchRepositoryMock.Verify(x => x.SaveAsync(It.IsAny <CashoutsBatchAggregate>()), Times.Never); _eventsPublisherMock.VerifyNoOtherCalls(); }
public CashoutsBatchAggregate ToDomain() { return(CashoutsBatchAggregate.Restore( ETag, StartMoment, LastCashoutAdditionMoment, ExpirationMoment, ClosingMoment, IdRevocationMoment, FinishMoment, BatchId, BlockchainType, AssetId, BlockchainAssetId, HotWalletAddress, CountThreshold, AgeThreshold, Cashouts .Select(x => x.ToDomain()) .ToHashSet(), State, ClosingReason)); }
public async Task Batch_ActiveBatchIdRevokedEvent_Outputs_Aggregated() { Mock <IChaosKitty> chaosKittyMock = new Mock <IChaosKitty>(); Mock <ICashoutsBatchReadOnlyRepository> cashoutsBatchReadOnlyRepository = new Mock <ICashoutsBatchReadOnlyRepository>(); Mock <ICommandSender> commandSender = new Mock <ICommandSender>(); Guid batchId = Guid.NewGuid(); var cashoutAggregate = CashoutsBatchAggregate.Start( batchId, "Icon", "ICX", "ICX", "hx...", 21, TimeSpan.FromDays(1)); cashoutAggregate.AddCashout(new BatchedCashoutValueType(Guid.NewGuid(), Guid.NewGuid(), "hx1...", 1, 1, DateTime.UtcNow)); cashoutAggregate.AddCashout(new BatchedCashoutValueType(Guid.NewGuid(), Guid.NewGuid(), "hx1...", 2, 1, DateTime.UtcNow)); cashoutAggregate.AddCashout(new BatchedCashoutValueType(Guid.NewGuid(), Guid.NewGuid(), "hx2...", 2, 1, DateTime.UtcNow)); cashoutsBatchReadOnlyRepository.Setup(x => x.GetAsync(batchId)).ReturnsAsync(cashoutAggregate); var batchSaga = new BatchSaga(chaosKittyMock.Object, cashoutsBatchReadOnlyRepository.Object); ActiveBatchIdRevokedEvent @event = new ActiveBatchIdRevokedEvent() { BatchId = batchId }; await batchSaga.Handle(@event, commandSender.Object); commandSender.Verify(x => x.SendCommand <StartOneToManyOutputsExecutionCommand>( It.Is <StartOneToManyOutputsExecutionCommand>(y => CheckStartOneToManyOutputsExecutionCommand(y)), It.IsAny <string>(), It.IsAny <uint>())); }
private async Task <CommandHandlingResult> StartBatchedCashoutAsync( AcceptCashoutCommand command, IEventPublisher publisher, CashoutsAggregationConfiguration aggregationConfiguration) { if (await _closedBatchedCashoutRepository.IsCashoutClosedAsync(command.OperationId)) { return(CommandHandlingResult.Ok()); } var activeCashoutBatchId = await _activeCashoutsBatchIdRepository.GetActiveOrNextBatchId ( command.BlockchainType, command.BlockchainAssetId, command.HotWalletAddress, CashoutsBatchAggregate.GetNextId ); _chaosKitty.Meow(command.OperationId); var batch = await _cashoutsBatchRepository.GetOrAddAsync ( activeCashoutBatchId.BatchId, () => CashoutsBatchAggregate.Start ( activeCashoutBatchId.BatchId, command.BlockchainType, command.AssetId, command.BlockchainAssetId, command.HotWalletAddress, aggregationConfiguration.CountThreshold, aggregationConfiguration.AgeThreshold ) ); _chaosKitty.Meow(command.OperationId); var cashout = batch.Cashouts.SingleOrDefault(p => p.CashoutId == command.OperationId) ?? new BatchedCashoutValueType(command.OperationId, command.ClientId, command.ToAddress, command.Amount, batch.Cashouts.Count, DateTime.UtcNow); var isCashoutShouldWaitForNextBatch = !(batch.IsStillFillingUp || batch.Cashouts.Contains(cashout)); if (isCashoutShouldWaitForNextBatch) { return(CommandHandlingResult.Fail(_cqrsSettings.RetryDelay)); } var transitionResult = batch.AddCashout(cashout); if (transitionResult.ShouldSaveAggregate()) { await _cashoutsBatchRepository.SaveAsync(batch); _chaosKitty.Meow(command.OperationId); } if (transitionResult.ShouldPublishEvents()) { if (batch.State == CashoutsBatchState.FillingUp && cashout.IndexInBatch == 0) { publisher.PublishEvent ( new BatchFillingStartedEvent { BatchId = batch.BatchId } ); } else if (batch.State == CashoutsBatchState.Filled) { publisher.PublishEvent ( new BatchFilledEvent { BatchId = batch.BatchId } ); } _chaosKitty.Meow(command.OperationId); publisher.PublishEvent ( new BatchedCashoutStartedEvent { BatchId = batch.BatchId, OperationId = command.OperationId, BlockchainType = command.BlockchainType, BlockchainAssetId = command.BlockchainAssetId, AssetId = command.AssetId, HotWalletAddress = command.HotWalletAddress, ToAddress = command.ToAddress, Amount = command.Amount, ClientId = command.ClientId } ); _chaosKitty.Meow(command.OperationId); } return(CommandHandlingResult.Ok()); }
public async Task Expire_Batch_If_Batch_Is_Have_To_Be_Expired_With_Batch_Saving_And_Event_Publishing_Failures() { // == Step 1 - Batch saving failure == // Arrange 1 _batch.AddCashout(new BatchedCashoutValueType(Guid.NewGuid(), Guid.NewGuid(), "Destination", 100, 0, DateTime.UtcNow)); var batchEntity = CashoutsBatchEntity.FromDomain(_batch); batchEntity.StartMoment = DateTime.UtcNow - _batch.AgeThreshold - TimeSpan.FromSeconds(1); _batch = batchEntity.ToDomain(); _batchRepositoryMock .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId))) .Throws <InvalidOperationException>(); // Act/Assert 1 await Assert.ThrowsAsync <InvalidOperationException>(() => _handler.Handle ( new WaitForBatchExpirationCommand { BatchId = _batch.BatchId }, _eventsPublisherMock.Object )); // Assert 1 Assert.Equal(CashoutsBatchState.Expired, _batch.State); _batchRepositoryMock.Verify(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId)), Times.Once); _eventsPublisherMock.Verify(x => x.PublishEvent(It.IsAny <BatchExpiredEvent>()), Times.Never); _eventsPublisherMock.VerifyNoOtherCalls(); // == Step 2 - Event publishing failure == _batchRepositoryMock.Invocations.Clear(); _eventsPublisherMock.Invocations.Clear(); _batchRepositoryMock .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId))) .Returns(Task.CompletedTask); _eventsPublisherMock .Setup(x => x.PublishEvent(It.Is <BatchExpiredEvent>(p => p.BatchId == _batch.BatchId))) .Throws <InvalidOperationException>(); // Act/Assert 2 await Assert.ThrowsAsync <InvalidOperationException>(() => _handler.Handle ( new WaitForBatchExpirationCommand { BatchId = _batch.BatchId }, _eventsPublisherMock.Object )); // Assert 2 Assert.Equal(CashoutsBatchState.Expired, _batch.State); _batchRepositoryMock.Verify(x => x.SaveAsync(It.IsAny <CashoutsBatchAggregate>()), Times.Never); _eventsPublisherMock.Verify(x => x.PublishEvent(It.Is <BatchExpiredEvent>(p => p.BatchId == _batch.BatchId)), Times.Once); _eventsPublisherMock.VerifyNoOtherCalls(); // == Step 3 - Success == // Arrange 3 _batchRepositoryMock.Invocations.Clear(); _eventsPublisherMock.Invocations.Clear(); _eventsPublisherMock .Setup(x => x.PublishEvent(It.Is <BatchExpiredEvent>(p => p.BatchId == _batch.BatchId))) .Callback(() => { }); // Act 3 var handlingResult = await _handler.Handle ( new WaitForBatchExpirationCommand { BatchId = _batch.BatchId }, _eventsPublisherMock.Object ); // Assert 3 Assert.False(handlingResult.Retry); Assert.Equal(CashoutsBatchState.Expired, _batch.State); _batchRepositoryMock.Verify(x => x.SaveAsync(It.IsAny <CashoutsBatchAggregate>()), Times.Never); _eventsPublisherMock.Verify(x => x.PublishEvent(It.Is <BatchExpiredEvent>(p => p.BatchId == _batch.BatchId)), Times.Once); _eventsPublisherMock.VerifyNoOtherCalls(); }
public StartCashoutCommandsHandlerTests() { var logFactory = LogFactory.Create().AddUnbufferedConsole(); _eventsPublisherMock = new Mock <IEventPublisher>(); _batchRepositoryMock = new Mock <ICashoutsBatchRepository>(); _closedBatchedCashoutsRepositoryMock = new Mock <IClosedBatchedCashoutRepository>(); var activeCashoutsBatchIdRepositoryMock = new Mock <IActiveCashoutsBatchIdRepository>(); var assetsServiceMock = new Mock <IAssetsServiceWithCache>(); var walletsClient = new Mock <IBlockchainWalletsClient>(); _cqrsSettings = new CqrsSettings { RabbitConnectionString = "fake-connection-string", RetryDelay = TimeSpan.FromSeconds(30) }; _countTreshold = 10; var ageThreshold = TimeSpan.FromMinutes(10); var blockchainConfigurationProvider = new BlockchainConfigurationsProvider ( logFactory, new Dictionary <string, BlockchainConfiguration> { { "Bitcoin", new BlockchainConfiguration ( "HotWallet", false, new CashoutsAggregationConfiguration ( ageThreshold, _countTreshold )) } } ); _handler = new AcceptCashoutCommandsHandler ( new SilentChaosKitty(), _batchRepositoryMock.Object, _closedBatchedCashoutsRepositoryMock.Object, activeCashoutsBatchIdRepositoryMock.Object, blockchainConfigurationProvider, walletsClient.Object, _cqrsSettings, false ); var activeCashoutsBatchId = ActiveCashoutBatchId.Create(CashoutsBatchAggregate.GetNextId()); _batch = CashoutsBatchAggregate.Start ( activeCashoutsBatchId.BatchId, "Bitcoin", "LykkeBTC", "BTC", "HotWallet", 10, TimeSpan.FromMinutes(10) ); var asset = new Asset { Id = "LykkeBTC", BlockchainIntegrationLayerId = "Bitcoin", BlockchainIntegrationLayerAssetId = "BTC" }; _batchRepositoryMock .Setup ( x => x.GetOrAddAsync ( It.Is <Guid>(p => p == _batch.BatchId), It.IsAny <Func <CashoutsBatchAggregate> >() ) ) .ReturnsAsync(() => _batch); _closedBatchedCashoutsRepositoryMock .Setup(x => x.IsCashoutClosedAsync(It.Is <Guid>(p => p == _batch.BatchId))) .ReturnsAsync(false); activeCashoutsBatchIdRepositoryMock .Setup ( x => x.GetActiveOrNextBatchId ( It.Is <string>(p => p == "Bitcoin"), It.Is <string>(p => p == "BTC"), It.Is <string>(p => p == "HotWallet"), It.Is <Func <Guid> >(p => p == CashoutsBatchAggregate.GetNextId) ) ) .ReturnsAsync(() => activeCashoutsBatchId); assetsServiceMock .Setup ( x => x.TryGetAssetAsync ( It.Is <string>(p => p == "LykkeBTC"), It.IsAny <CancellationToken>() ) ) .ReturnsAsync(() => asset); walletsClient .Setup ( x => x.TryGetClientIdAsync ( It.Is <string>(p => p == "Bitcoin"), It.IsAny <string>() ) ) .ReturnsAsync((Guid?)null); }
public async Task SaveAsync(CashoutsBatchAggregate aggregate) { var entity = CashoutsBatchEntity.FromDomain(aggregate); await _storage.ReplaceAsync(entity); }