public void CreateAndSplitBillAsync_WhenCreateNewGroupIsNotValid_ShouldThrow()
        {
            //Arrange
            var sutBuilder = new BillServiceSutBuilder();

            var createNewBill = new CreateNewBill();
            var billService   = sutBuilder.CreateSut();

            //Act & Assert
            var exception = Assert.ThrowsAsync <ValidationException>(async() => await billService.CreateAndSplitBillAsync(createNewBill));
        }
        public async Task CreateAndSplitBillAsync_WhenGroupHasMembers_ShouldCreateBillWithEqualLoans(decimal total, int userCount)
        {
            //Arrange
            var sutBuilder = new BillServiceSutBuilder();
            var group      = sutBuilder.CreateGroupWithUsers("Test group", userCount);
            var loaner     = group.Users.First();

            A.CallTo(() => sutBuilder.GroupRepository.GetByIdAsync(group.Id))
            .Returns(group);

            A.CallTo(() => sutBuilder.BillRepository.AddAsync(A <Bill> ._))
            .ReturnsLazily((Bill bill) => bill);

            var createNewBill = sutBuilder.CreateNewBill("Test bill", total, group.Id, loaner.Id);
            var billService   = sutBuilder.CreateSut();

            //Act
            var resultBill = await billService.CreateAndSplitBillAsync(createNewBill);

            //Assert
            A.CallTo(() => sutBuilder.BillRepository.AddAsync(A <Bill> ._))
            .MustHaveHappenedOnceExactly();

            resultBill.Total.ShouldBe(total);
            resultBill.Name.ShouldBe(createNewBill.Name);
            resultBill.Loaner.ShouldBe(loaner);
            resultBill.Loans.Count.ShouldBe(group.Users.Count);

            var billLoanees = resultBill.Loans.Select(loan => loan.Loanee);

            billLoanees.ShouldBe(group.Users);

            var loanTotal = resultBill.Loans.Sum(loan => loan.Amount);

            loanTotal.ShouldBe(total);

            //Explanation of ToZero: https://docs.microsoft.com/en-us/dotnet/api/system.midpointrounding?view=netcore-3.1#directed-rounding
            var expectedAmount = Math.Round(total / userCount, 2, MidpointRounding.ToZero);

            resultBill.Loans.ForEach((loan) =>
            {
                // Allow error of 1 cent
                loan.Amount.ShouldBeInRange(expectedAmount, expectedAmount + 0.01M);
            });
        }
        public async Task GetBillsAsyncAndGetFilteredBillsAsync_BillsExistInSpecifiedFilterCriteria_ShouldReturnBillList()
        {
            //Arrange
            var sutBuilder = new BillServiceSutBuilder();
            var group      = sutBuilder.CreateGroupWithUsers("Test group", 10);
            var loaner     = group.Users.First();
            var bill       = new Bill()
            {
                Name           = "Test bill",
                Total          = 100,
                LoanerId       = loaner.Id,
                Loaner         = loaner,
                GroupContextId = group.Id,
                GroupContext   = group,
            };
            var billList = new List <Bill>
            {
                bill
            };
            var billService = sutBuilder.CreateSut();
            var startTime   = DateTime.Now.AddTicks(-1000);
            var endTime     = DateTime.Now.AddTicks(1000);

            var filterInfo = new BillFilterInfo()
            {
                GroupId   = group.Id,
                StartTime = startTime,
                EndTime   = endTime,
            };
            var filter = new BillDbFilter(filterInfo);

            A.CallTo(() => sutBuilder.BillDbFilterFactory.Create(filterInfo))
            .Returns(filter);

            A.CallTo(() => sutBuilder.BillRepository.GetAllAsync(filter))
            .Returns(billList);

            //Act
            IList <Bill> result = await billService.GetBillsAsync(group.Id, startTime, endTime);

            //Assert
            result.ShouldBe(billList);
        }
        public void CreateAndSplitBillAsync_WhenGroupDoesNotExist_ShouldThrow()
        {
            //Arrange
            var sutBuilder = new BillServiceSutBuilder();
            var group      = sutBuilder.CreateGroupWithUsers("Test group", 0);
            var loaner     = sutBuilder.CreateUser();

            A.CallTo(() => sutBuilder.GroupRepository.GetByIdAsync(group.Id))
            .Returns <Group?>(null);

            A.CallTo(() => sutBuilder.BillRepository.AddAsync(A <Bill> ._))
            .ReturnsLazily((Bill bill) => bill);

            var createNewBill = sutBuilder.CreateNewBill("Test bill", 100, group.Id, loaner.Id);
            var billService   = sutBuilder.CreateSut();

            //Act & Assert
            var exception = Assert.ThrowsAsync <NotFoundException>(async() => await billService.CreateAndSplitBillAsync(createNewBill));

            exception.Message.ShouldBe($"Group with id {group.Id} does not exist.");
        }
        public void CreateAndSplitBillAsync_WhenBillPayeeIsNotInGroup_ShouldThrow(decimal total, int userCount)
        {
            //Arrange
            var sutBuilder = new BillServiceSutBuilder();
            var group      = sutBuilder.CreateGroupWithUsers("Test group", userCount);
            var loaner     = sutBuilder.CreateUser();

            A.CallTo(() => sutBuilder.GroupRepository.GetByIdAsync(group.Id))
            .Returns(group);

            A.CallTo(() => sutBuilder.BillRepository.AddAsync(A <Bill> ._))
            .ReturnsLazily((Bill bill) => bill);

            var createNewBill = sutBuilder.CreateNewBill("Test bill", total, group.Id, loaner.Id);
            var billService   = sutBuilder.CreateSut();

            //Act & Aseert
            var exception = Assert.ThrowsAsync <NotFoundException>(async() => await billService.CreateAndSplitBillAsync(createNewBill));

            exception.Message.ShouldBe($"Payee with id {loaner.Id} does not exist in group.");
        }