예제 #1
0
        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);
        }
예제 #2
0
 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
     });
 }
예제 #3
0
        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();
        }
예제 #5
0
 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));
 }
예제 #6
0
        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>()));
        }
예제 #7
0
        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());
        }
예제 #8
0
        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);
        }
예제 #10
0
        public async Task SaveAsync(CashoutsBatchAggregate aggregate)
        {
            var entity = CashoutsBatchEntity.FromDomain(aggregate);

            await _storage.ReplaceAsync(entity);
        }