示例#1
0
        public async Task CalculateTaxAsync_WhenValidPostalCode_ShouldUseCorrectTaxCalculator(TaxType taxType, decimal annualIncome, string postalCode)
        {
            // Arrange
            var loggerMock = Substitute.For <ILogger <TaxCalculatorController> >();
            var factory    = TestHelper.GetTaxRateCalculatorFactorySubstitude(annualIncome);
            var taxQueryServiceSubstitude = Substitute.For <ITaxQueryService>();

            taxQueryServiceSubstitude.GetTaxCalculationTypeByPostalCodeAsync(Arg.Any <string>()).Returns(Task.FromResult(taxType));
            var taxCommandServiceSubstitude = Substitute.For <ITaxCommandService>();
            var controller = new TaxCalculatorController(loggerMock, factory, taxQueryServiceSubstitude, taxCommandServiceSubstitude);
            var request    = new TaxCalculationRequest
            {
                PostalCode   = postalCode,
                AnnualIncome = annualIncome,
            };

            // Act
            var response = await controller.CalculateTaxAsync(request);

            // Assert
            var okResult = response as OkObjectResult;

            factory.Received(1)(Arg.Is <TaxType>(value => value == taxType));
            okResult.ShouldNotBeNull();
            okResult.StatusCode.ShouldBe(200);
            okResult.Value.ShouldBeOfType <ApiResponse <TaxCalculationResponse> >();

            var okResultObj = okResult.Value as ApiResponse <TaxCalculationResponse>;

            okResultObj.Payload.TaxAmountPayable.ShouldBe(annualIncome);
        }
示例#2
0
        public async Task <OperationResult <TaxCalculationResponse> > CalculateTax(TaxCalculationRequest request)
        {
            OperationResult <TaxCalculationResponse> validatorResult = _validationRuleEngine.Validate(request);

            if (validatorResult.HasErrors)
            {
                return(validatorResult);
            }

            var calculationTypeMapping = await _calculationMappingRepository.GetByPostalCodeAsync(request.PostalCode).ConfigureAwait(false);

            var taxCalculator = _taxCalculatorFactory.GetCalculator(calculationTypeMapping.CalculationType);
            var taxYear       = await _taxYearRepository.GetTaxYearAsync(_clock.GetCurrentDateTime()).ConfigureAwait(false);

            var taxAmountResult = await taxCalculator.CalculateTaxAsync(taxYear, request.AnnualIncome).ConfigureAwait(false);

            if (taxAmountResult.HasErrors)
            {
                return(taxAmountResult.MapErrors <TaxCalculationResponse>());
            }

            var response = new TaxCalculationResponse
            {
                TaxYear         = taxYear.ToString(),
                CalculationType = calculationTypeMapping.CalculationType,
                TaxAmount       = taxAmountResult.Response
            };

            await SaveCalculation(request, response, taxYear);

            return(new OperationResult <TaxCalculationResponse>(response));
        }
示例#3
0
        public async Task CalculateTax_Should_Return_ValidationRuleEngine_Errors()
        {
            //Arrange
            var calculationRequest         = new TaxCalculationRequest();
            var validationRuleEngineResult = new OperationResult <TaxCalculationResponse>();

            const string expectedErrorKey1     = "1232";
            const string expectedErrorMessage1 = "This is an error";

            validationRuleEngineResult.AddErrorMessage(expectedErrorKey1, expectedErrorMessage1);

            const string expectedErrorKey2     = "error2";
            const string expectedErrorMessage2 = "This is an error number2";

            validationRuleEngineResult.AddErrorMessage(expectedErrorKey2, expectedErrorMessage2);

            const int expectedErrorCount = 2;

            _validationRuleEngine.Setup(s => s.Validate(calculationRequest))
            .Returns(validationRuleEngineResult);

            //Act
            var operationResult = await _calculatorManager.CalculateTax(calculationRequest);

            //Assert
            Assert.IsTrue(operationResult.HasErrors);

            var actualErrors = operationResult.GetErrorMessages();

            Assert.AreEqual(expectedErrorCount, actualErrors.Count);

            Assert.IsTrue(actualErrors.Any(e => e.Key == expectedErrorKey1 && e.Value.Contains(expectedErrorMessage1)));
            Assert.IsTrue(actualErrors.Any(e => e.Key == expectedErrorKey2 && e.Value.Contains(expectedErrorMessage2)));
        }
示例#4
0
        public async Task <TaxJarCalculateResponse> Calculate(TaxCalculationRequest request)
        {
            OrderModel mapped = mapper.Map <OrderModel>(request);

            //NOTE: Returning mocked data so clientId isn't used right now
            ClientModel  client  = clientService.GetClient(mapped.ClientId);
            ProductModel product = productService.GetProduct(request.ProductId);

            var lineItems = new List <Models.LineItem>
            {
                mapper.Map <Models.LineItem>(product)
            };

            mapped.LineItems   = lineItems;
            mapped.FromCity    = client.City;
            mapped.FromCountry = client.Country;
            mapped.FromState   = client.State;
            mapped.FromStreet  = client.Street;
            mapped.FromZip     = client.Zip;

            TaxJarCalculateResponse response = null;

            response = await taxRepository.Post <OrderModel, TaxJarCalculateResponse>(mapped);

            //TODO: Add mapping to TaxCalculationResposne, but for sake of time, return object from TaxJar. I would like to flatten this response out eventually.
            return(response);
        }
示例#5
0
        public IActionResult ProcessCalculation([FromForm] TaxCalculationRequest request)
        {
            var result = _apiFacade.CalculateTax(new CalculationRequest()
            {
                //user id gets set via the auth token (if i get that far)
                PostalCode    = request.PostalCode,
                TaxableAmount = request.TaxableAmount,
                UserId        = UserId
            });

            ViewBag.Message = string.IsNullOrWhiteSpace(result.ErrorMessage)? $"The assessed tax is {result.AssessedAmount.ToString("##0.00")}":result.ErrorMessage;

            return(View("Calculation"));
        }
示例#6
0
        private async Task SaveCalculation(TaxCalculationRequest request, TaxCalculationResponse response, TaxYear taxYear)
        {
            var calculation = new TaxCalculation
            {
                AnnualIncome    = request.AnnualIncome,
                TaxAmount       = response.TaxAmount,
                CalculationType = response.CalculationType,
                CreatedBy       = request.RequestedBy,
                CreationDate    = _clock.GetCurrentDateTime(),
                PostalCode      = request.PostalCode,
                TaxYear         = taxYear
            };

            await _calculationRepository.AddAsync(calculation).ConfigureAwait(false);
        }
示例#7
0
        public async Task <IActionResult> Post([FromBody] TaxCalculationRequest request)
        {
            try
            {
                if (request == null)
                {
                    return(BadRequest("Invalid Request"));
                }

                //TODO: Recommended to get clientId from Auth token/Identity unless an 'agent' is selecting clients in the app
                if (string.IsNullOrEmpty(request.ClientId))
                {
                    return(BadRequest("ClientId is Required"));
                }

                if (string.IsNullOrEmpty(request.ToCountry))
                {
                    return(BadRequest("Ship to Country is Required"));
                }

                if (string.IsNullOrEmpty(request.ToState))
                {
                    return(BadRequest("Ship to State is Required"));
                }

                if (request.Shipping == 0)
                {
                    return(BadRequest("Total amount to Ship is Required"));
                }

                if (request.ToCountry == "US" || request.ToCountry == "CA")
                {
                    if (string.IsNullOrEmpty(request.ToZip))
                    {
                        return(BadRequest("Zip is required if US or CA is the Country"));
                    }
                }

                TaxJarCalculateResponse calculation = await taxService.Calculate(request);

                return(Ok(JsonConvert.SerializeObject(calculation)));
            }
            catch (Exception ex)
            {
                return(BadRequest());
            }
        }
示例#8
0
        public async Task CalculateTax_Should_Return_Calculation_Errors()
        {
            //Arrange
            var calculationRequest   = new TaxCalculationRequest();
            var taxCalculationResult = new OperationResult <decimal>();

            const string expectedErrorKey1     = "calcError1";
            const string expectedErrorMessage1 = "This is a calc error";

            taxCalculationResult.AddErrorMessage(expectedErrorKey1, expectedErrorMessage1);

            const string expectedErrorKey2     = "calcError2";
            const string expectedErrorMessage2 = "This is calc error number2";

            taxCalculationResult.AddErrorMessage(expectedErrorKey2, expectedErrorMessage2);

            const int expectedErrorCount = 2;

            _validationRuleEngine.Setup(s => s.Validate(calculationRequest))
            .Returns(new OperationResult <TaxCalculationResponse>());

            _calculationMappingRepository.Setup(f => f.GetByPostalCodeAsync(calculationRequest.PostalCode))
            .ReturnsAsync(new PostalCodeCalculationTypeMapping());

            _taxCalculatorFactory.Setup(f => f.GetCalculator(It.IsAny <TaxCalculationType>()))
            .Returns(_taxCalculator.Object);

            _taxYearRepository.Setup(t => t.GetTaxYearAsync(It.IsAny <DateTime>()))
            .ReturnsAsync(new TaxYear());

            _taxCalculator.Setup(t => t.CalculateTaxAsync(It.IsAny <TaxYear>(), calculationRequest.AnnualIncome))
            .ReturnsAsync(taxCalculationResult);

            //Act
            var operationResult = await _calculatorManager.CalculateTax(calculationRequest);

            //Assert
            Assert.IsTrue(operationResult.HasErrors);

            var actualErrors = operationResult.GetErrorMessages();

            Assert.AreEqual(expectedErrorCount, actualErrors.Count);

            Assert.IsTrue(actualErrors.Any(e => e.Key == expectedErrorKey1 && e.Value.Contains(expectedErrorMessage1)));
            Assert.IsTrue(actualErrors.Any(e => e.Key == expectedErrorKey2 && e.Value.Contains(expectedErrorMessage2)));
        }
示例#9
0
        public async Task Calculate_Should_Calculate()
        {
            //Arrange
            const decimal            annualIncome            = 250000;
            const string             postalCode              = "1234";
            const string             expectedTaxYear         = "Tax Year";
            const decimal            expectedTaxAmount       = 234.56M;
            const TaxCalculationType expectedCalculationType = TaxCalculationType.PROGRESSIVE_TAX;
            string expectedCalculationTypeDescription        = expectedCalculationType.GetDescription();

            TaxCalculationRequestModel model = new TaxCalculationRequestModel
            {
                AnnualIncome = annualIncome,
                PostalCode   = postalCode
            };

            TaxCalculationRequest actualManagerRequest = null;

            TaxCalculationResponse managerResponse = new TaxCalculationResponse
            {
                CalculationType = expectedCalculationType,
                TaxAmount       = expectedTaxAmount,
                TaxYear         = expectedTaxYear
            };

            _manager.Setup(r => r.CalculateTax(It.IsAny <TaxCalculationRequest>()))
            .Callback((TaxCalculationRequest r) => actualManagerRequest = r)
            .ReturnsAsync(() => new OperationResult <TaxCalculationResponse>(managerResponse));

            //Act
            var result = await _controller.Calculate(model);

            //Assert
            Assert.IsInstanceOf <OkObjectResult>(result.Result);
            TaxCalculationResponseModel
                responseMode = (TaxCalculationResponseModel)((OkObjectResult)result.Result).Value;

            Assert.AreEqual(expectedCalculationTypeDescription, responseMode.CalculationType);
            Assert.AreEqual(expectedTaxYear, responseMode.TaxYear);
            Assert.AreEqual(expectedTaxAmount, responseMode.TaxAmount);

            _manager.Verify(f => f.CalculateTax(It.IsAny <TaxCalculationRequest>()), Times.Once);
            Assert.AreEqual(annualIncome, actualManagerRequest.AnnualIncome);
            Assert.AreEqual(postalCode, actualManagerRequest.PostalCode);
        }
        public async Task <ActionResult <TaxCalculationResponse> > PostAsync([FromBody] TaxCalculationRequest request)
        {
            try
            {
                // retrieve the username from the JWT claims
                var requestedByUser = HttpContext?.User?.Identity?.Name;

                // execute main tax calculation logic
                var(statusCode, response) = await _taxCalculationService.CalculateAsync(request, requestedByUser);

                return(StatusCode((int)statusCode, response));
            }
            catch (Exception)
            {
                return(StatusCode(500, new TaxCalculationResponse
                {
                    Message = $"Error while processing tax calculation request for {nameof(request.PostalCode)} - {request.PostalCode ?? ("Unknown")} and {nameof(request.AnnualIncome)} - {request.AnnualIncome}."
                }));
            }
        }
示例#11
0
        public async Task Calculate_Should_Return_BadRequest_When_Manager_Returns_Errors()
        {
            //Arrange
            const decimal annualIncome         = 250000;
            const string  postalCode           = "1234";
            var           expectedErrorMessage = "my errors";
            var           errorKey             = "errorKey";

            TaxCalculationRequestModel model = new TaxCalculationRequestModel
            {
                AnnualIncome = annualIncome,
                PostalCode   = postalCode
            };

            TaxCalculationRequest actualManagerRequest = null;

            var managerResult = new OperationResult <TaxCalculationResponse>();

            managerResult.AddErrorMessage(errorKey, expectedErrorMessage);

            _manager.Setup(r => r.CalculateTax(It.IsAny <TaxCalculationRequest>()))
            .Callback((TaxCalculationRequest r) => actualManagerRequest = r)
            .ReturnsAsync(() => managerResult);

            //Act
            var result = await _controller.Calculate(model);

            //Assert
            Assert.IsInstanceOf <BadRequestObjectResult>(result.Result);
            var
                responseModel = (SerializableError)((BadRequestObjectResult)result.Result).Value;

            Assert.AreEqual(expectedErrorMessage, ((string[])responseModel[errorKey])[0]);

            _manager.Verify(f => f.CalculateTax(It.IsAny <TaxCalculationRequest>()), Times.Once);
            Assert.AreEqual(annualIncome, actualManagerRequest.AnnualIncome);
            Assert.AreEqual(postalCode, actualManagerRequest.PostalCode);
        }
        public async Task <IActionResult> CalculateTaxAsync([FromBody] TaxCalculationRequest taxCalculationRequest)
        {
            var taxType = await _taxQueryService.GetTaxCalculationTypeByPostalCodeAsync(taxCalculationRequest.PostalCode);

            var calculator       = _taxRateCalculatorFactory(taxType);
            var taxAmountPayable = await calculator.CalculateTaxAmountAsync(taxCalculationRequest.AnnualIncome);

            await _taxCommandService.CreateTaxCalculationHistoryAsync(new TaxCalculatorHistoryRequest
            {
                AnnualIncome    = taxCalculationRequest.AnnualIncome,
                PostalCode      = taxCalculationRequest.PostalCode,
                CalculatedTax   = taxAmountPayable,
                CalculationType = taxType
            });

            var response = new TaxCalculationResponse
            {
                TaxCalculationType = taxType,
                TaxAmountPayable   = taxAmountPayable
            };

            return(OkApiResponse(response));
        }
        /// <summary>
        /// Makes an HTTP request to the TaxCalculation API to calculate the tax ammount for a given postal code and annual income
        /// </summary>
        /// <param name="postalCode">Postal code</param>
        /// <param name="annualIncome">Total annual income</param>
        /// <returns>TaxCalculationResponse</returns>
        public async Task <TaxCalculationResponse> CalculateAsync(string postalCode, decimal annualIncome)
        {
            try
            {
                var endpointUri = new Uri($"{_apiGatewayUri}api/v1/calculateTax");
                var request     = new TaxCalculationRequest
                {
                    PostalCode   = postalCode,
                    AnnualIncome = annualIncome
                };

                using (var client = new GenericHttpClient <TaxCalculationResponse>(endpointUri))
                {
                    return(await client.PostAsync(request));
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error while trying to tax caluclation request to the PostalCodeTax API.");

                // rethrow to preserve stack details
                throw;
            }
        }
示例#14
0
        /// <summary>
        /// Main service method that facilitates the tax calculation request.
        /// </summary>
        /// <param name="request">TaxCalculationRequest object</param>
        /// <param name="requestBy">Username of person that requested the tax calculation</param>
        /// <returns>HTTP Status Code + TaxCalculationResponse</returns>
        public async Task <Tuple <HttpStatusCode, TaxCalculationResponse> > CalculateAsync(TaxCalculationRequest request, string requestedBy)
        {
            try
            {
                var postalCode = request.PostalCode;

                // first retrieve the tax type for the given postal Code
                var taxType = await _postalCodeTaxRepository.GetAsync(postalCode);

                // if unknown, it means it is a client error, thus respond with 400 - Bad Request
                if (taxType == TaxType.Unknown)
                {
                    return(new Tuple <HttpStatusCode, TaxCalculationResponse>(
                               HttpStatusCode.BadRequest,
                               new TaxCalculationResponse
                    {
                        Message = $"The {nameof(postalCode)}: {postalCode} that was provided is not a valid {nameof(postalCode)}"
                    }));
                }

                // let the factory method resolve the correct implementation of ITaxCalculation without exposing the creation logic of the factory to the client
                var taxCalculation = _taxCalculationResolver(taxType);
                var annualIncome   = request.AnnualIncome;

                // execute main tax calculation logic
                var taxAmount = await taxCalculation.CalculateAsyc(annualIncome);

                // use a builder to separate the construction of the TaxCalculationModel from its representation
                var model = new TaxCalculationModelBuilder()
                            .AddPostalCode(postalCode)
                            .AddAnnualIncome(annualIncome)
                            .AddTaxAmount(taxAmount)
                            .AddRequestedBy(requestedBy)
                            .Build();

                // then store the tax results to the database
                var persisted = await _taxCalculationRepository.SaveAsync(model);

                // use a builder to separate the construction of the complex Tuple result object from its representation
                var result = new ResultBuilder(persisted)
                             .AddTaxAmount(taxAmount)
                             .AddTaxType(taxType)
                             .Build();

                return(result);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Error while trying to calculate tax for postal code - {request?.PostalCode ?? ("Unknown")} and annual income - {request.AnnualIncome}.");

                // rethrow to preserve stack details
                throw;
            }
        }
示例#15
0
        public async Task CalculateTax_Should_Save_Successfully_Calculated_Tax()
        {
            //Arrange
            const string             expectedPostalCode      = "12234";
            const decimal            expectedAnnualIncome    = 23000M;
            const int                expectedTaxAmount       = 100;
            const TaxCalculationType expectedCalculationType = TaxCalculationType.PROGRESSIVE_TAX;
            DateTime expectedCreationDate = new DateTime(2020, 12, 16);
            Guid     expectedRequestedBy  = Guid.Parse("e64a32e6-f2a9-43d5-b87b-79c845955f93");

            var calculationRequest = new TaxCalculationRequest()
            {
                AnnualIncome = expectedAnnualIncome,
                PostalCode   = expectedPostalCode,
                RequestedBy  = expectedRequestedBy
            };

            TaxCalculation savedRecord = null;

            var taxYear = new TaxYear
            {
                FromDate = new DateTime(2020, 01, 01),
                ToDate   = new DateTime(2021, 01, 02),
                Name     = "Tax Year"
            };

            var taxCalculationResult = new OperationResult <decimal>
            {
                Response = expectedTaxAmount
            };

            _clock.Setup(c => c.GetCurrentDateTime()).Returns(expectedCreationDate);

            _validationRuleEngine.Setup(s => s.Validate(It.IsAny <TaxCalculationRequest>()))
            .Returns(new OperationResult <TaxCalculationResponse>());

            _calculationMappingRepository.Setup(f => f.GetByPostalCodeAsync(It.IsAny <string>()))
            .ReturnsAsync(new PostalCodeCalculationTypeMapping {
                CalculationType = expectedCalculationType
            });

            _taxCalculatorFactory.Setup(f => f.GetCalculator(It.IsAny <TaxCalculationType>()))
            .Returns(_taxCalculator.Object);

            _taxYearRepository.Setup(t => t.GetTaxYearAsync(It.IsAny <DateTime>()))
            .ReturnsAsync(taxYear);

            _taxCalculator.Setup(t => t.CalculateTaxAsync(It.IsAny <TaxYear>(), It.IsAny <decimal>()))
            .ReturnsAsync(taxCalculationResult);

            _calculationRepository.Setup(r => r.AddAsync(It.IsAny <TaxCalculation>()))
            .Callback((TaxCalculation c) => savedRecord = c);

            //Act
            var operationResult = await _calculatorManager.CalculateTax(calculationRequest);

            //Assert
            Assert.IsNotNull(operationResult?.Response);
            Assert.IsFalse(operationResult.HasErrors);

            _calculationRepository.Verify(r => r.AddAsync(It.IsAny <TaxCalculation>()), Times.Once);

            Assert.IsNotNull(savedRecord);
            Assert.AreEqual(expectedAnnualIncome, savedRecord.AnnualIncome);
            Assert.AreEqual(expectedTaxAmount, savedRecord.TaxAmount);
            Assert.AreEqual(expectedCalculationType, savedRecord.CalculationType);
            Assert.AreEqual(expectedRequestedBy, savedRecord.CreatedBy);
            Assert.AreEqual(expectedCreationDate, savedRecord.CreationDate);
            Assert.AreEqual(expectedPostalCode, savedRecord.PostalCode);
            Assert.AreEqual(taxYear, savedRecord.TaxYear);
        }
示例#16
0
        public async Task CalculateTax_Should_Successfully_Calculate_Tax()
        {
            //Arrange
            const string  postalCode   = "12234";
            const decimal incomeAmount = 23000M;
            var           requestDate  = new DateTime(2020, 12, 15);

            var calculationRequest = new TaxCalculationRequest()
            {
                AnnualIncome = incomeAmount,
                PostalCode   = postalCode
            };

            const int expectedTaxAmount = 100;
            const TaxCalculationType expectedCalculationType = TaxCalculationType.PROGRESSIVE_TAX;
            const string             expectedTaxYear         = "Tax Year (01-Jan-2020 - 02-Jan-2021)";

            var taxYear = new TaxYear
            {
                FromDate = new DateTime(2020, 01, 01),
                ToDate   = new DateTime(2021, 01, 02),
                Name     = "Tax Year"
            };

            var taxCalculationResult = new OperationResult <decimal>
            {
                Response = expectedTaxAmount
            };

            _clock.Setup(c => c.GetCurrentDateTime()).Returns(requestDate);

            _validationRuleEngine.Setup(s => s.Validate(It.IsAny <TaxCalculationRequest>()))
            .Returns(new OperationResult <TaxCalculationResponse>());

            _calculationMappingRepository.Setup(f => f.GetByPostalCodeAsync(It.IsAny <string>()))
            .ReturnsAsync(new PostalCodeCalculationTypeMapping {
                CalculationType = expectedCalculationType
            });

            _taxCalculatorFactory.Setup(f => f.GetCalculator(It.IsAny <TaxCalculationType>()))
            .Returns(_taxCalculator.Object);

            _taxYearRepository.Setup(t => t.GetTaxYearAsync(It.IsAny <DateTime>()))
            .ReturnsAsync(taxYear);

            _taxCalculator.Setup(t => t.CalculateTaxAsync(It.IsAny <TaxYear>(), It.IsAny <decimal>()))
            .ReturnsAsync(taxCalculationResult);

            //Act
            var operationResult = await _calculatorManager.CalculateTax(calculationRequest);

            //Assert
            Assert.IsNotNull(operationResult?.Response);
            Assert.IsFalse(operationResult.HasErrors);

            _validationRuleEngine.Verify(s => s.Validate(calculationRequest), Times.Once);
            _calculationMappingRepository.Verify(f => f.GetByPostalCodeAsync(postalCode),
                                                 Times.Once);
            _taxCalculatorFactory.Verify(f => f.GetCalculator(expectedCalculationType), Times.Once);

            _taxYearRepository.Verify(t => t.GetTaxYearAsync(requestDate), Times.Once);

            _taxCalculator.Verify(t => t.CalculateTaxAsync(taxYear, incomeAmount), Times.Once());

            Assert.AreEqual(expectedTaxAmount, operationResult.Response.TaxAmount);
            Assert.AreEqual(expectedCalculationType, operationResult.Response.CalculationType);
            Assert.AreEqual(expectedTaxYear, operationResult.Response.TaxYear);
        }