public async Task Read_ValidQrCodeData_CreatesTicketScanRequest()
        {
            // Arrange
            var itemCount = new Random().Next(1, 10);
            var tickets   = new DigitalTicket[itemCount];

            for (int i = 0; i < itemCount; i++)
            {
                tickets[i] = new DigitalTicket
                {
                    Seat = new Seat {
                        Number = i + 1, Letter = 'D'
                    },
                    Secret  = "plaintext",
                    NameKey = new byte[32],
                    NameIV  = new byte[16]
                };
            }

            var validQrCodeData = JsonConvert.SerializeObject(tickets);

            // Act
            await _qrCodeValidator.Validate(validQrCodeData);

            // Assert
            _mediator.Verify(callTo => callTo.Send(
                                 It.Is <TicketScanRequest>(request => request.Tickets.Count() == tickets.Length),
                                 It.IsAny <CancellationToken>()),
                             Times.Once);
        }
        public async Task Read_ValidQrCodeData_OnValidQrCodeInvoked()
        {
            var eventInvoked = false;
            var tickets      = new DigitalTicket[]
            {
                new DigitalTicket
                {
                    Seat = new Seat {
                        Number = 2, Letter = 'D'
                    },
                    Secret  = "plaintext",
                    NameKey = new byte[32],
                    NameIV  = new byte[16]
                }
            };
            var validQrCodeData = JsonConvert.SerializeObject(tickets);

            _qrCodeValidator.OnValidQrCode += (s, e) => { eventInvoked = true; };

            // Act
            await _qrCodeValidator.Validate(validQrCodeData);

            // Assert
            Assert.That(eventInvoked, Is.True);

            _qrCodeValidator.OnValidQrCode -= (s, e) => { eventInvoked = true; };
        }
        public async Task Handle_CheckTicketThrowsException_PublishesTicketScanResultNotificationNullResult()
        {
            var ticket = new DigitalTicket()
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                }
            };
            var ticketScanRequest = new TicketScanRequest(ticket);

            var ticketTransaction = new Ticket
            {
                Address = new Address(5, 5, 4, 3, 5),
                Price   = 2490000000,
                Seat    = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret             = new byte[16],
                CustomerIdentifier = new byte[16]
            };

            var ticketTransactionReceipts = new Receipt <object, Ticket>[]
            {
                new Receipt <object, Ticket>
                {
                    BlockHash = "vZxae80111cab8c7e8f932289fdda9a2",
                    Logs      = new LogDto <Ticket>[]
                    {
                        new LogDto <Ticket>
                        {
                            Log = ticketTransaction
                        }
                    }
                }
            };

            _smartContractService.Setup(callTo => callTo.FetchReceiptsAsync <Ticket>()).Returns(Task.FromResult(ticketTransactionReceipts));
            _smartContractService.Setup(callTo => callTo.FetchReceiptsAsync <Show>()).Returns(Task.FromResult(_showReceipts));
            _blockStoreService.Setup(callTo => callTo.GetBlockDataAsync("ks9I72n9Hjj9azM2l9D3Palq2jfBjkb9")).Returns(Task.FromResult(new BlockDto {
                Height = 1
            }));
            _blockStoreService.Setup(callTo => callTo.GetBlockDataAsync("vZxae80111cab8c7e8f932289fdda9a2")).Returns(Task.FromResult(new BlockDto {
                Height = 2
            }));
            _ticketChecker.Setup(callTo => callTo.CheckTicket(ticket, ticketTransaction)).Throws <Exception>();

            // Act
            try
            {
                await _ticketScanRequestHandler.Handle(ticketScanRequest, default);
            }
            catch (Exception)
            {
            }

            // Assert
            _mediator.Verify(callTo => callTo.Publish(It.Is <TicketScanResultNotification>(notification => notification.Result == null),
                                                      It.IsAny <CancellationToken>()),
                             Times.Once);
        }
Example #4
0
        public void CheckTicket_ProvidedSecretHashDoesNotMatchActualHash_ReturnsResultDoesNotOwnTicket()
        {
            // Arrange
            var secret = new byte[16] {
                203, 92, 1, 93, 84, 38, 27, 94, 190, 10, 199, 232, 28, 2, 34, 83
            };
            var scannedTicket = new DigitalTicket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret = "f09aIm3-hH9379c"
            };

            var actualTicket = new Ticket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret = secret
            };

            // Act
            var result = _ticketChecker.CheckTicket(scannedTicket, actualTicket);

            // Assert
            Assert.That(result.OwnsTicket, Is.False);
        }
        public async Task Handle_EvenMatchingTicketTransactions_PublishesInvalidTicketScanResultNotification()
        {
            // Arrange
            var ticketCount = new Random().Next(1, 3) * 2;
            var ticket      = new DigitalTicket()
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                }
            };
            var ticketScanRequest = new TicketScanRequest(ticket);

            var ticketTransactionReceipts = new Receipt <object, Ticket> [ticketCount];

            for (int i = 0; i < ticketCount; i++)
            {
                ticketTransactionReceipts[i] = new Receipt <object, Ticket>
                {
                    BlockHash = "vZxae80111cab8c7e8f932289fdda9a2",
                    Logs      = new LogDto <Ticket>[]
                    {
                        new LogDto <Ticket>
                        {
                            Log = new Ticket
                            {
                                Address = new Address(5, 5, 4, 3, 5),
                                Price   = 2490000000,
                                Seat    = new Seat {
                                    Number = 1, Letter = 'A'
                                },
                                Secret             = i % 2 == 0 ? new byte[16] : null,
                                CustomerIdentifier = i % 2 == 0 ? new byte[16] : null
                            }
                        }
                    }
                };
            }

            _smartContractService.Setup(callTo => callTo.FetchReceiptsAsync <Ticket>()).Returns(Task.FromResult(ticketTransactionReceipts));
            _smartContractService.Setup(callTo => callTo.FetchReceiptsAsync <Show>()).Returns(Task.FromResult(_showReceipts));
            _blockStoreService.Setup(callTo => callTo.GetBlockDataAsync("ks9I72n9Hjj9azM2l9D3Palq2jfBjkb9")).Returns(Task.FromResult(new BlockDto {
                Height = 1
            }));
            _blockStoreService.Setup(callTo => callTo.GetBlockDataAsync("vZxae80111cab8c7e8f932289fdda9a2")).Returns(Task.FromResult(new BlockDto {
                Height = 2
            }));

            // Act
            await _ticketScanRequestHandler.Handle(ticketScanRequest, default);

            // Assert
            _mediator.Verify(callTo => callTo.Publish(It.Is <TicketScanResultNotification>(notification => !notification.Result.OwnsTicket),
                                                      It.IsAny <CancellationToken>()),
                             Times.Once);
        }
Example #6
0
        public TicketScanResult CheckTicket(DigitalTicket scannedTicket, Ticket actualTicket)
        {
            if (scannedTicket is null)
            {
                throw new ArgumentNullException(nameof(scannedTicket), "Cannot check null ticket");
            }

            if (!scannedTicket.Seat.Equals(actualTicket.Seat))
            {
                throw new ArgumentException(nameof(actualTicket), "Seats do not match");
            }

            byte[] scannedSecretHash;
            try
            {
                using var hasher  = Sha3.Sha3224();
                scannedSecretHash = hasher.ComputeHash(scannedTicket.Secret);
            }
            catch (ArgumentException e)
            {
                _logger.LogWarning(e.Message);
                return(null);
            }

            if (!actualTicket.Secret.SequenceEqual(scannedSecretHash))
            {
                return(new TicketScanResult(false, string.Empty));
            }

            if (actualTicket.CustomerIdentifier is null)
            {
                return(new TicketScanResult(true, string.Empty));
            }

            string plainTextCustomerIdentifier;

            try
            {
                using var aes = _cipherFactory.CreateCbcProvider();
                plainTextCustomerIdentifier = aes.Decrypt(actualTicket.CustomerIdentifier, scannedTicket.NameKey, scannedTicket.NameIV);
            }
            catch (CryptographicException e)
            {
                _logger.LogDebug(e.Message);
                return(new TicketScanResult(true, string.Empty));
            }
            catch (ArgumentException e)
            {
                _logger.LogWarning(e.Message);
                return(null);
            }

            return(new TicketScanResult(true, plainTextCustomerIdentifier));
        }
        public async Task Handle_CheckTicketThrowsArgumentException_LogsError()
        {
            var ticket = new DigitalTicket()
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                }
            };
            var ticketScanRequest = new TicketScanRequest(ticket);

            var ticketTransaction = new Ticket
            {
                Address = new Address(5, 5, 4, 3, 5),
                Price   = 2490000000,
                Seat    = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret             = new byte[16],
                CustomerIdentifier = new byte[16]
            };

            var ticketTransactionReceipts = new Receipt <object, Ticket>[]
            {
                new Receipt <object, Ticket>
                {
                    BlockHash = "vZxae80111cab8c7e8f932289fdda9a2",
                    Logs      = new LogDto <Ticket>[]
                    {
                        new LogDto <Ticket>
                        {
                            Log = ticketTransaction
                        }
                    }
                }
            };

            _smartContractService.Setup(callTo => callTo.FetchReceiptsAsync <Ticket>()).Returns(Task.FromResult(ticketTransactionReceipts));
            _smartContractService.Setup(callTo => callTo.FetchReceiptsAsync <Show>()).Returns(Task.FromResult(_showReceipts));
            _blockStoreService.Setup(callTo => callTo.GetBlockDataAsync("ks9I72n9Hjj9azM2l9D3Palq2jfBjkb9")).Returns(Task.FromResult(new BlockDto {
                Height = 1
            }));
            _blockStoreService.Setup(callTo => callTo.GetBlockDataAsync("vZxae80111cab8c7e8f932289fdda9a2")).Returns(Task.FromResult(new BlockDto {
                Height = 2
            }));
            _ticketChecker.Setup(callTo => callTo.CheckTicket(ticket, ticketTransaction)).Throws <ArgumentException>();

            // Act
            await _ticketScanRequestHandler.Handle(ticketScanRequest, default);

            // Assert
            _logger.VerifyLog(LogLevel.Error);
        }
Example #8
0
        public void CheckTicket_ProvidedSecretMatchesCustomerIdentifierDecrypted_ReturnsResultOwnsTicketNameMatchesDecryptedValue()
        {
            // Arrange
            var plainTextSecret = "f09aIm3-hH9379c";

            byte[] hashedSecret;
            using (var hasher = Sha3.Sha3224())
            {
                hashedSecret = hasher.ComputeHash(plainTextSecret);
            }

            var customerIdentifier = new byte[16] {
                33, 93, 23, 252, 24, 38, 43, 94, 224, 10, 12, 232, 28, 211, 64, 99
            };
            var name = "Benjamin Swift";

            var scannedTicket = new DigitalTicket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret = plainTextSecret
            };

            var actualTicket = new Ticket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret             = hashedSecret,
                CustomerIdentifier = customerIdentifier
            };

            _cbc.Setup(callTo => callTo.Decrypt(customerIdentifier, It.IsAny <byte[]>(), It.IsAny <byte[]>())).Returns(name);

            // Act
            var result = _ticketChecker.CheckTicket(scannedTicket, actualTicket);

            // Assert
            Assert.Multiple(() =>
            {
                Assert.That(result.OwnsTicket, Is.True, nameof(TicketScanResult.OwnsTicket));
                Assert.That(result.Name, Is.EqualTo(name), nameof(TicketScanResult.Name));
            });
        }
Example #9
0
        public void CheckTicket_ProvidedSecretMatchesDecryptCustomerIdentifierThrowsArgumentException_ReturnsNull()
        {
            // Arrange
            var plainTextSecret = "f09aIm3-hH9379c";

            byte[] hashedSecret;
            using (var hasher = Sha3.Sha3224())
            {
                hashedSecret = hasher.ComputeHash(plainTextSecret);
            }

            var customerIdentifier = new byte[16] {
                33, 93, 23, 252, 24, 38, 43, 94, 224, 10, 12, 232, 28, 211, 64, 99
            };

            var scannedTicket = new DigitalTicket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret = plainTextSecret
            };

            var actualTicket = new Ticket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret             = hashedSecret,
                CustomerIdentifier = customerIdentifier
            };

            _cbc.Setup(callTo => callTo.Decrypt(customerIdentifier, It.IsAny <byte[]>(), It.IsAny <byte[]>())).Throws <ArgumentException>();

            // Act
            var result = _ticketChecker.CheckTicket(scannedTicket, actualTicket);

            // Assert
            Assert.That(result, Is.Null);
        }
        public async Task Handle_TicketsProvided_PublishesTicketScanStartedNotifications()
        {
            // Arrange
            var ticketCount = new Random().Next(0, 5);
            var tickets     = new DigitalTicket[ticketCount];

            for (int i = 0; i < ticketCount; i++)
            {
                tickets[i] = new DigitalTicket {
                    Seat = new Seat {
                        Number = i + 1, Letter = 'A'
                    }
                };
            }

            var ticketScanRequest = new TicketScanRequest(tickets);

            // Act
            await _ticketScanRequestHandler.Handle(ticketScanRequest, default);

            // Assert
            _mediator.Verify(callTo => callTo.Publish(It.IsAny <TicketScanStartedNotification>(), It.IsAny <CancellationToken>()), Times.Exactly(ticketCount));
        }
Example #11
0
        public void CheckTicket_SeatsDoNotMatch_ThrowsArgumentException()
        {
            // Arrange
            var scannedTicket = new DigitalTicket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                }
            };

            var actualTicket = new Ticket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'B'
                }
            };

            // Act
            var ticketCheckCall = new Action(() => _ticketChecker.CheckTicket(scannedTicket, actualTicket));

            // Assert
            Assert.That(ticketCheckCall, Throws.ArgumentException);
        }
Example #12
0
        public void CheckTicket_ProvidedSecretMatchesCustomerIdentifierNull_ReturnsResultOwnsTicketNameEmpty()
        {
            // Arrange
            var plainTextSecret = "f09aIm3-hH9379c";

            byte[] hashedSecret;
            using (var hasher = Sha3.Sha3224())
            {
                hashedSecret = hasher.ComputeHash(plainTextSecret);
            }

            var scannedTicket = new DigitalTicket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret = plainTextSecret
            };

            var actualTicket = new Ticket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret = hashedSecret
            };

            // Act
            var result = _ticketChecker.CheckTicket(scannedTicket, actualTicket);

            // Assert
            Assert.Multiple(() =>
            {
                Assert.That(result.OwnsTicket, Is.True, nameof(TicketScanResult.OwnsTicket));
                Assert.That(result.Name, Is.Empty, nameof(TicketScanResult.Name));
            });
        }
Example #13
0
        public void CheckTicket_DecryptNullSecret_LogsWarning()
        {
            // Arrange
            var scannedTicket = new DigitalTicket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                }
            };

            var actualTicket = new Ticket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret = null
            };

            // Act
            _ticketChecker.CheckTicket(scannedTicket, actualTicket);

            // Assert
            _logger.VerifyLog(LogLevel.Warning);
        }
Example #14
0
        public void CheckTicket_DecryptNullSecret_ReturnsNull()
        {
            // Arrange
            var scannedTicket = new DigitalTicket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                }
            };

            var actualTicket = new Ticket
            {
                Seat = new Seat {
                    Number = 1, Letter = 'A'
                },
                Secret = null
            };

            // Act
            var result = _ticketChecker.CheckTicket(scannedTicket, actualTicket);

            // Assert
            Assert.That(result, Is.Null);
        }