private static CommandHandlingResult StartRegularCashout( AcceptCashoutCommand command, IEventPublisher publisher) { publisher.PublishEvent(new CashoutStartedEvent { OperationId = command.OperationId, BlockchainType = command.BlockchainType, BlockchainAssetId = command.BlockchainAssetId, HotWalletAddress = command.HotWalletAddress, ToAddress = command.ToAddress, AssetId = command.AssetId, Amount = command.Amount, ClientId = command.ClientId }); return(CommandHandlingResult.Ok()); }
public async Task <CommandHandlingResult> Handle(AcceptCashoutCommand command, IEventPublisher publisher) { var blockchainConfiguration = _blockchainConfigurationProvider.GetConfiguration(command.BlockchainType); var recipientClientId = !_disableDirectCrossClientCashouts ? await _walletsClient.TryGetClientIdAsync(command.BlockchainType, command.ToAddress) : null; if (recipientClientId.HasValue) { return(StartCrossClientCashaout(command, publisher, recipientClientId.Value)); } if (blockchainConfiguration.SupportCashoutAggregation) { return(await StartBatchedCashoutAsync(command, publisher, blockchainConfiguration.CashoutsAggregation)); } return(StartRegularCashout(command, publisher)); }
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 BatchFillingStartedEvent_Published_On_Transient_Failure() { // == Step 1 - Failure == // Arrange 1 _batchRepositoryMock .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId))) .Throws <InvalidOperationException>(); // Act 1 var cashout1Id = Guid.NewGuid(); var clientId = Guid.NewGuid(); var command1 = new AcceptCashoutCommand { BlockchainType = "Bitcoin", BlockchainAssetId = "BTC", AssetId = "LykkeBTC", Amount = 50, ClientId = clientId, OperationId = cashout1Id, HotWalletAddress = "HotWallet", ToAddress = "Destination-first" }; await Assert.ThrowsAsync <InvalidOperationException>(() => _handler.Handle ( command1, _eventsPublisherMock.Object )); Assert.Equal(1, _batch.Cashouts.Count); Assert.Equal(cashout1Id, _batch.Cashouts.Single().CashoutId); Assert.Equal(0, _batch.Cashouts.Single().IndexInBatch); _eventsPublisherMock.VerifyNoOtherCalls(); // Assert 2 _batchRepositoryMock .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId))) .Returns(Task.CompletedTask); _batchRepositoryMock.Invocations.Clear(); var cashout2Id = Guid.NewGuid(); // Act 2 await _handler.Handle ( new AcceptCashoutCommand { BlockchainType = "Bitcoin", BlockchainAssetId = "BTC", AssetId = "LykkeBTC", Amount = 50, ClientId = Guid.NewGuid(), OperationId = cashout2Id, HotWalletAddress = "HotWallet", ToAddress = "Destination-2" }, _eventsPublisherMock.Object ); // Arrange 2 Assert.Equal(2, _batch.Cashouts.Count); Assert.Equal(cashout2Id, _batch.Cashouts.Single(p => p.CashoutId == cashout2Id).CashoutId); Assert.Equal(1, _batch.Cashouts.Single(p => p.CashoutId == cashout2Id).IndexInBatch); _eventsPublisherMock .Verify ( x => x.PublishEvent ( It.Is <BatchedCashoutStartedEvent>(p => p.OperationId == cashout2Id) ), Times.Once ); _eventsPublisherMock.VerifyNoOtherCalls(); // Act 3 await _handler.Handle ( command1, _eventsPublisherMock.Object ); // Arrange 3 _eventsPublisherMock .Verify ( x => x.PublishEvent ( It.Is <BatchFillingStartedEvent>(p => p.BatchId == _batch.BatchId) ), Times.Once ); _eventsPublisherMock .Verify ( x => x.PublishEvent ( It.Is <BatchedCashoutStartedEvent>(p => p.OperationId == cashout1Id) ), Times.Once ); _eventsPublisherMock.VerifyNoOtherCalls(); Assert.Equal(0, _batch.Cashouts.Single(p => p.CashoutId == cashout1Id).IndexInBatch); Assert.Equal(2, _batch.Cashouts.Count); }
public async Task Batch_Filling_Completed_With_Batch_Saving_Failure() { // == Step 1 - Failure == // Arrange 1 var clientId = Guid.NewGuid(); var cashoutId = Guid.NewGuid(); var command = new AcceptCashoutCommand { BlockchainType = "Bitcoin", BlockchainAssetId = "BTC", AssetId = "LykkeBTC", Amount = 50, ClientId = clientId, OperationId = cashoutId, HotWalletAddress = "HotWallet", ToAddress = "Destination-last" }; for (var i = 0; i < _countTreshold - 1; ++i) { _batch.AddCashout(new BatchedCashoutValueType(Guid.NewGuid(), Guid.NewGuid(), "Destination-{i}", 100 * i, i, DateTime.UtcNow)); } _batchRepositoryMock .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId))) .Throws <InvalidOperationException>(); // Act/Assert 1 await Assert.ThrowsAsync <InvalidOperationException>(() => _handler.Handle(command, _eventsPublisherMock.Object)); // Assert 1 _batchRepositoryMock.Verify(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId)), Times.Once); _eventsPublisherMock.VerifyNoOtherCalls(); // == Step 2 - Success == // Arrange 2 _batchRepositoryMock .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId))) .Callback(() => { }); _batchRepositoryMock.Invocations.Clear(); // Act 2 var handlingResult = await _handler.Handle(command, _eventsPublisherMock.Object); // Assert 2 Assert.False(handlingResult.Retry); Assert.Equal(CashoutsBatchState.Filled, _batch.State); Assert.Equal(_countTreshold, _batch.Cashouts.Count); Assert.Equal(cashoutId, _batch.Cashouts.Last().CashoutId); _batchRepositoryMock.Verify(x => x.SaveAsync(It.IsAny <CashoutsBatchAggregate>()), Times.Never); _eventsPublisherMock.Verify(x => x.PublishEvent(It.Is <BatchFilledEvent>(p => p.BatchId == _batch.BatchId)), Times.Once); _eventsPublisherMock .Verify ( x => x.PublishEvent ( It.Is <BatchedCashoutStartedEvent>(p => p.BatchId == _batch.BatchId && p.Amount == 50 && p.AssetId == "LykkeBTC" && p.BlockchainAssetId == "BTC" && p.BlockchainType == "Bitcoin" && p.ClientId == clientId && p.HotWalletAddress == "HotWallet" && p.OperationId == cashoutId && p.ToAddress == "Destination-last") ), Times.Once ); _eventsPublisherMock.VerifyNoOtherCalls(); }