コード例 #1
0
        public async Task <IActionResult> Post([FromBody] RequestApiViewModel viewModel)
        {
            if (!ModelState.IsValid)
            {
                return(BadRequest());
            }

            //we can only accept the requests with the status of "new" from getasmokealarm
            if (viewModel.Status != "new")
            {
                return(BadRequest());
            }

            //TODO mgmccarthy: valiate the list of regions here for v1 launch that will allow us to determine if we can service the request or not
            //this will be in the incoming field based on GASA's "assigned_rc_region" which is mapped to RequestApiViewModel.ProviderData on the incoming json request

            //if we get here, the incoming request is already in our database with a matching ProviderId ("serial" field for getasmokealarm) and the request was sent with a status of "new"
            if (await mediator.SendAsync(new RequestExistsByProviderIdQuery {
                ProviderRequestId = viewModel.ProviderRequestId
            }))
            {
                return(BadRequest());
            }

            //this returns control to the caller immediately so the client is not left locked while we figure out if we can service the request
            backgroundjobClient.Enqueue <IProcessApiRequests>(x => x.Process(viewModel));

            //https://httpstatuses.com/202
            return(StatusCode(202));
        }
コード例 #2
0
        public async Task <IActionResult> Post([FromBody] RequestApiViewModel viewModel)
        {
            if (!ModelState.IsValid)
            {
                return(BadRequest());
            }

            //we can only accept the requests with the status of "new" from getasmokealarm
            if (viewModel.Status != "new")
            {
                return(BadRequest());
            }

            //if we get here, the incoming request is already in our database with a matching ProviderId ("serial" field for getasmokealarm) and the request was sent with a status of "new"
            if (await mediator.SendAsync(new RequestExistsByProviderIdQuery {
                ProviderRequestId = viewModel.ProviderRequestId
            }))
            {
                return(BadRequest());
            }

            //this returns control to the caller immediately so the client is not left locked while we figure out if we can service the request
            backgroundjobClient.Enqueue <IProcessApiRequests>(x => x.Process(viewModel));

            //https://httpstatuses.com/202
            return(StatusCode(202));
        }
コード例 #3
0
        public async Task PostSendsAddApiRequestCommandWithCorrectViewModel()
        {
            var viewModel = new RequestApiViewModel {
                Status = "new"
            };
            var mediator = new Mock <IMediator>();

            var sut = new RequestApiController(mediator.Object);
            await sut.Post(viewModel);

            mediator.Verify(x => x.SendAsync(It.Is <AddApiRequestCommand>(y => y.ViewModel == viewModel)), Times.Once);
        }
コード例 #4
0
        public void InvokeISendRequestStatusToGetASmokeAlarmWithAnAcceptanceOfTrue_WhenApprovedRegionsAreDisabled()
        {
            var viewModel = new RequestApiViewModel {
                ProviderRequestId = "1"
            };
            var backgroundJobClient = new Mock <IBackgroundJobClient>();

            var sut = new ProcessApiRequests(Context, Mock.Of <IGeocodeService>(),
                                             Options.Create(new ApprovedRegionsSettings()), backgroundJobClient.Object);

            sut.Process(viewModel);

            backgroundJobClient.Verify(x => x.Create(It.Is <Job>(job => (bool)job.Args[2]), It.IsAny <EnqueuedState>()));
        }
コード例 #5
0
        public async Task <IActionResult> Post([FromBody] RequestApiViewModel viewModel)
        {
            //TODO mgmccarthy: I'm making a guess that field validations will return a BadRequest result instead of a 202. Testing it with Postman, the model binding to enforce field validation worked
            //Anything that could potentially take longer then simple field validation (aka, region validation) will be moved further down the pipelines to be reported back to getasmokealarm's API
            //waiting to hear back from the getasmokealarm folks if they take specific actions from the ack from our endpoint.

            if (!ModelState.IsValid)
            {
                return(BadRequest());
            }

            //we only accept the status of "new" from RC integration, the rest we ignore
            if (viewModel.Status != "new")
            {
                return(BadRequest());
            }

            //if we get here, the incoming request has mistakenly been labeled with the "new" status code
            if (mediator.Send(new RequestExistsByProviderIdQuery {
                ProviderRequestId = viewModel.ProviderRequestId
            }))
            {
                return(BadRequest());
            }

            //TODO mgmccarthy: region specific verification (this COULD be moved further down the pipeline to have the request status reported back to getasmokealarm via their API)

            //TODO mgmccarthy: waiting to hear back from getasmokealarm what data they would expect back on the ack, if they only require the 202 back and we can invoke their API downstream from this to report back whether or not we're going to accept this request.
            await mediator.SendAsync(new AddApiRequestCommand { ViewModel = viewModel });

            //https://httpstatuses.com/202
            return(StatusCode(202));

            //for reporting errors back for the BadRequests, we should stick to Google's Json style guid for errors:
            //https://google.github.io/styleguide/jsoncstyleguide.xml?showone=error#Reserved_Property_Names_in_the_error_object
            //here's an example for field validation
            //{
            //    "error":
            //    {
            //        "code": 400
            //        "message": "field validation failed",
            //        "errors": [
            //            { "ProvierId":"empty or null"},
            //            { "Name":"empty or null"},
            //            { "Email":"not valid email address"}
            //        ]
            //    }
            //}
        }
コード例 #6
0
        public async Task AddRequest()
        {
            var requestId = Guid.NewGuid();
            var dateAdded = DateTime.UtcNow;

            var viewModel = new RequestApiViewModel
            {
                ProviderRequestId = "ProviderRequestId",
                Name         = "Name",
                Address      = "Address",
                City         = "City",
                State        = "state",
                Zip          = "zip",
                Phone        = "phone",
                Email        = "email",
                Latitude     = 10,
                Longitude    = 10,
                Status       = "new",
                ProviderData = "ProviderData"
            };

            var message = new AddApiRequestCommand {
                ViewModel = viewModel
            };

            var sut = new AddApiRequestCommandHandler(Context, Mock.Of <IGeocoder>(), Mock.Of <IMediator>())
            {
                NewRequestId   = () => requestId,
                DateTimeUtcNow = () => dateAdded
            };
            await sut.Handle(message);

            var request = Context.Requests.Single(x => x.RequestId == requestId);

            Assert.Equal(request.RequestId, requestId);
            Assert.Equal(request.ProviderId, viewModel.ProviderRequestId);
            Assert.Equal(request.ProviderData, viewModel.ProviderData);
            Assert.Equal(request.Address, viewModel.Address);
            Assert.Equal(request.City, viewModel.City);
            Assert.Equal(request.DateAdded, dateAdded);
            Assert.Equal(request.Email, viewModel.Email);
            Assert.Equal(request.Name, viewModel.Name);
            Assert.Equal(request.State, viewModel.State);
            Assert.Equal(request.Zip, viewModel.Zip);
            Assert.Equal(request.Status, RequestStatus.Unassigned);
            Assert.Equal(request.Source, RequestSource.Api);
            Assert.Equal(request.Latitude, viewModel.Latitude);
            Assert.Equal(request.Longitude, viewModel.Longitude);
        }
コード例 #7
0
        public async Task PostEnqueuesProcessApiRequestsJobWithCorrectViewModel()
        {
            var viewModel = new RequestApiViewModel {
                Status = "new"
            };
            var mediator            = new Mock <IMediator>();
            var backgroundJobClient = new Mock <IBackgroundJobClient>();

            var sut = new RequestApiController(mediator.Object, backgroundJobClient.Object);
            await sut.Post(viewModel);

            backgroundJobClient.Verify(x => x.Create(It.Is <Job>(job =>
                                                                 job.Method.Name == nameof(ProcessApiRequests.Process) &&
                                                                 job.Args[0] == viewModel), It.IsAny <EnqueuedState>()), Times.Once);
        }
コード例 #8
0
        public void InvokeIGeocoderWithTheCorrectParameters()
        {
            var requestId = Guid.NewGuid();
            var geoCoder  = new Mock <IGeocodeService>();
            var viewModel = new RequestApiViewModel {
                Address = "address", City = "city", State = "state", Zip = "zip"
            };
            var sut = new ProcessApiRequests(Context, Mock.Of <IMediator>(), geoCoder.Object, Options.Create(new ApprovedRegionsSettings()))
            {
                NewRequestId = () => requestId
            };

            sut.Process(viewModel);

            geoCoder.Verify(x => x.GetCoordinatesFromAddress(viewModel.Address, viewModel.City, viewModel.State, viewModel.Zip, string.Empty), Times.Once);
        }
コード例 #9
0
        public void InvokeISendRequestStatusToGetASmokeAlarmWithTheCorrectProviderRequestIdAndStatus()
        {
            var viewModel = new RequestApiViewModel {
                ProviderRequestId = "1"
            };
            var backgroundJobClient = new Mock <IBackgroundJobClient>();

            var sut = new ProcessApiRequests(Context, Mock.Of <IGeocodeService>(),
                                             Options.Create(new ApprovedRegionsSettings()), backgroundJobClient.Object);

            sut.Process(viewModel);

            backgroundJobClient.Verify(x => x.Create(It.Is <Job>(job => job.Method.Name == nameof(ISendRequestStatusToGetASmokeAlarm.Send) &&
                                                                 job.Args[0].ToString() == viewModel.ProviderRequestId &&
                                                                 job.Args[1].ToString() == GasaStatus.New), It.IsAny <EnqueuedState>()));
        }
コード例 #10
0
        public void InvokeISendRequestStatusToGetASmokeAlarmWithAnAcceptanceOfFalse_WhenApprovedRegionsAreEnabledAndRegionIsNotApproved()
        {
            var viewModel = new RequestApiViewModel {
                ProviderRequestId = "1", ProviderData = "region"
            };
            var backgroundJobClient = new Mock <IBackgroundJobClient>();

            var sut = new ProcessApiRequests(Context, Mock.Of <IGeocodeService>(),
                                             Options.Create(new ApprovedRegionsSettings {
                Enabled = true, Regions = new List <string> {
                    "UnapprovedRegion"
                }
            }), backgroundJobClient.Object);

            sut.Process(viewModel);

            backgroundJobClient.Verify(x => x.Create(It.Is <Job>(job => (bool)job.Args[2] == false), It.IsAny <EnqueuedState>()));
        }
コード例 #11
0
        public void Process(RequestApiViewModel viewModel)
        {
            //since this is Hangfire job code, it needs to be idempotent, this could be re-tried if there is a failure
            if (context.Requests.Any(x => x.ProviderRequestId == viewModel.ProviderRequestId))
            {
                return;
            }

            var requestIsFromApprovedRegion = RequestIsFromApprovedRegion(viewModel.ProviderData);

            if (requestIsFromApprovedRegion)
            {
                var request = new Request
                {
                    RequestId = NewRequestId(),
                    //TODO mgmccarthy: this is hard-coded for now to 1, which is HTBox Org's Id in dev b/c SampleDataGenerator always creates it first. We'll need something more robust when we go to production.
                    OrganizationId    = 1,
                    ProviderRequestId = viewModel.ProviderRequestId,
                    ProviderData      = viewModel.ProviderData,
                    Address           = viewModel.Address,
                    City       = viewModel.City,
                    DateAdded  = DateTimeUtcNow(),
                    Email      = viewModel.Email,
                    Name       = viewModel.Name,
                    Phone      = viewModel.Phone,
                    State      = viewModel.State,
                    PostalCode = viewModel.PostalCode,
                    Status     = RequestStatus.Unassigned,
                    Source     = RequestSource.Api
                };

                //this is a web service call
                var coordinates = geocoder.GetCoordinatesFromAddress(request.Address, request.City, request.State, request.PostalCode, string.Empty).Result;

                request.Latitude  = coordinates?.Latitude ?? 0;
                request.Longitude = coordinates?.Longitude ?? 0;

                context.Add(request);
                context.SaveChanges();
            }

            //acceptance is true if we can service the Request or false if can't service it
            backgroundJobClient.Enqueue <ISendRequestStatusToGetASmokeAlarm>(x => x.Send(viewModel.ProviderRequestId, GasaStatus.New, requestIsFromApprovedRegion));
        }
コード例 #12
0
        public void AddRequest()
        {
            var          requestId  = Guid.NewGuid();
            var          dateAdded  = DateTime.UtcNow;
            const string postalCode = "11111";

            var viewModel = new RequestApiViewModel
            {
                ProviderRequestId = "ProviderRequestId",
                Status            = "new",
                Name         = "Name",
                Address      = "Address",
                City         = "City",
                State        = "state",
                Zip          = postalCode,
                Phone        = "111-111-1111",
                Email        = "*****@*****.**",
                ProviderData = "ProviderData"
            };

            var sut = new ProcessApiRequests(Context, Mock.Of <IMediator>(), Mock.Of <IGeocodeService>(), Options.Create(new ApprovedRegionsSettings()))
            {
                NewRequestId   = () => requestId,
                DateTimeUtcNow = () => dateAdded
            };

            sut.Process(viewModel);

            var request = Context.Requests.Single(x => x.RequestId == requestId);

            Assert.Equal(request.RequestId, requestId);
            Assert.Equal(request.OrganizationId, 1);
            Assert.Equal(request.ProviderRequestId, viewModel.ProviderRequestId);
            Assert.Equal(request.ProviderData, viewModel.ProviderData);
            Assert.Equal(request.Address, viewModel.Address);
            Assert.Equal(request.City, viewModel.City);
            Assert.Equal(request.DateAdded, dateAdded);
            Assert.Equal(request.Email, viewModel.Email);
            Assert.Equal(request.Name, viewModel.Name);
            Assert.Equal(request.State, viewModel.State);
            Assert.Equal(request.Zip, viewModel.Zip);
            Assert.Equal(request.Status, RequestStatus.Unassigned);
            Assert.Equal(request.Source, RequestSource.Api);
        }
コード例 #13
0
        public void PublishApiRequestAddedNotificationWithTheCorrectProviderRequestId()
        {
            const string providerRequestId = "ProviderRequestId";
            var          requestId         = Guid.NewGuid();
            var          viewModel         = new RequestApiViewModel {
                ProviderRequestId = providerRequestId
            };

            var mediator = new Mock <IMediator>();

            var sut = new ProcessApiRequests(Context, mediator.Object, Mock.Of <IGeocoder>())
            {
                NewRequestId = () => requestId
            };

            sut.Process(viewModel);

            mediator.Verify(x => x.Publish(It.Is <ApiRequestProcessedNotification>(y => y.ProviderRequestId == providerRequestId)), Times.Once);
        }
コード例 #14
0
        public void Process(RequestApiViewModel viewModel)
        {
            //since this is job code now, it needs to be idempotent, this could be re-tried by Hangire if it fails
            var requestExists = context.Requests.Any(x => x.ProviderRequestId == viewModel.ProviderRequestId);

            if (!requestExists)
            {
                var request = new Request
                {
                    RequestId = NewRequestId(),
                    //TODO mgmccarthy: this is hard-coded for now to 1, which is HTBox Org's Id in dev b/c SampleDataGenerator always creates it first. We'll need something more robust when we go to production.
                    OrganizationId    = 1,
                    ProviderRequestId = viewModel.ProviderRequestId,
                    ProviderData      = viewModel.ProviderData,
                    Address           = viewModel.Address,
                    City      = viewModel.City,
                    DateAdded = DateTimeUtcNow(),
                    Email     = viewModel.Email,
                    Name      = viewModel.Name,
                    Phone     = viewModel.Phone,
                    State     = viewModel.State,
                    Zip       = viewModel.Zip,
                    Status    = RequestStatus.Unassigned,
                    Source    = RequestSource.Api
                };


                //this is a web service call
                var address = geocoder.Geocode(viewModel.Address, viewModel.City, viewModel.State, viewModel.Zip, string.Empty).FirstOrDefault();

                request.Latitude  = address?.Coordinates.Latitude ?? 0;
                request.Longitude = address?.Coordinates.Longitude ?? 0;

                context.Add(request);
                context.SaveChanges();

                mediator.Publish(new ApiRequestProcessedNotification {
                    ProviderRequestId = viewModel.ProviderRequestId
                });
            }
        }
コード例 #15
0
        public async Task PostEnqueuesProcessApiRequestsJobWithCorrectViewModel()
        {
            var viewModel = new RequestApiViewModel {
                Status = "new"
            };

            var mediator = new Mock <IMediator>();

            mediator.Setup(x => x.SendAsync(It.IsAny <ValidatePhoneNumberRequestCommand>())).ReturnsAsync(new ValidatePhoneNumberResult {
                IsValid = true, PhoneNumberE164 = "0000"
            });

            var backgroundJobClient = new Mock <IBackgroundJobClient>();

            var sut = new RequestApiController(mediator.Object, backgroundJobClient.Object);
            await sut.Post(viewModel);

            backgroundJobClient.Verify(x => x.Create(It.Is <Job>(job =>
                                                                 job.Method.Name == nameof(ProcessApiRequests.Process) &&
                                                                 job.Args[0] == viewModel), It.IsAny <EnqueuedState>()), Times.Once);
        }
コード例 #16
0
        public void NotSaveRequestNotInvokeIGeocodeServiceAndNotNotEnqueueISendRequestStatusToGetASmokeAlarm_WhenRequestExists()
        {
            const string providerRequestId = "1";
            var          model             = new RequestApiViewModel {
                ProviderRequestId = providerRequestId
            };

            Context.Requests.Add(new Request {
                ProviderRequestId = providerRequestId
            });
            Context.SaveChanges();

            var geocodeService      = new Mock <IGeocodeService>();
            var backgroundJobClient = new Mock <IBackgroundJobClient>();

            var sut = new ProcessApiRequests(Context, geocodeService.Object, Mock.Of <IOptions <ApprovedRegionsSettings> >(), backgroundJobClient.Object);

            sut.Process(model);

            Assert.Equal(Context.Requests.Count(x => x.ProviderRequestId == providerRequestId), 1);
            geocodeService.Verify(x => x.GetCoordinatesFromAddress(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>()), Times.Never);
            backgroundJobClient.Verify(x => x.Create(It.Is <Job>(job => job.Method.Name == nameof(ISendRequestStatusToGetASmokeAlarm.Send)), It.IsAny <EnqueuedState>()), Times.Never);
        }