diff --git a/pkg/gen/primeapi/configure_mymove.go b/pkg/gen/primeapi/configure_mymove.go index c538a478d02..6def1f8afbc 100644 --- a/pkg/gen/primeapi/configure_mymove.go +++ b/pkg/gen/primeapi/configure_mymove.go @@ -11,6 +11,7 @@ import ( "github.com/go-openapi/runtime/middleware" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations" + "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/addresses" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/move_task_order" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_service_item" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_shipment" @@ -100,6 +101,11 @@ func configureAPI(api *primeoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation move_task_order.DownloadMoveOrder has not yet been implemented") }) } + if api.AddressesGetLocationByZipCityStateHandler == nil { + api.AddressesGetLocationByZipCityStateHandler = addresses.GetLocationByZipCityStateHandlerFunc(func(params addresses.GetLocationByZipCityStateParams) middleware.Responder { + return middleware.NotImplemented("operation addresses.GetLocationByZipCityState has not yet been implemented") + }) + } if api.MoveTaskOrderGetMoveTaskOrderHandler == nil { api.MoveTaskOrderGetMoveTaskOrderHandler = move_task_order.GetMoveTaskOrderHandlerFunc(func(params move_task_order.GetMoveTaskOrderParams) middleware.Responder { return middleware.NotImplemented("operation move_task_order.GetMoveTaskOrder has not yet been implemented") diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index e5f49170cbf..6b80d8d47a1 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -36,6 +36,44 @@ func init() { }, "basePath": "/prime/v1", "paths": { + "/addresses/zip-city-lookup/{search}": { + "get": { + "description": "Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code.", + "tags": [ + "addresses" + ], + "summary": "Returns city, state, postal code, and county associated with the specified full/partial postal code or city state string", + "operationId": "getLocationByZipCityState", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "the requested list of city, state, county, and postal code matches", + "schema": { + "$ref": "#/definitions/VLocations" + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/move-task-orders/{moveID}": { "get": { "description": "### Functionality\nThis endpoint gets an individual MoveTaskOrder by ID.\n\nIt will provide information about the Customer and any associated MTOShipments, MTOServiceItems and PaymentRequests.\n", @@ -4714,6 +4752,151 @@ func init() { } } }, + "VLocation": { + "description": "A postal code, city, and state lookup", + "type": "object", + "properties": { + "city": { + "type": "string", + "title": "City", + "example": "Anytown" + }, + "county": { + "type": "string", + "title": "County", + "x-nullable": true, + "example": "LOS ANGELES" + }, + "postalCode": { + "type": "string", + "format": "zip", + "title": "ZIP", + "pattern": "^(\\d{5}?)$", + "example": "90210" + }, + "state": { + "type": "string", + "title": "State", + "enum": [ + "AL", + "AK", + "AR", + "AZ", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "GA", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MI", + "MN", + "MO", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VT", + "WA", + "WI", + "WV", + "WY" + ], + "x-display-value": { + "AK": "AK", + "AL": "AL", + "AR": "AR", + "AZ": "AZ", + "CA": "CA", + "CO": "CO", + "CT": "CT", + "DC": "DC", + "DE": "DE", + "FL": "FL", + "GA": "GA", + "HI": "HI", + "IA": "IA", + "ID": "ID", + "IL": "IL", + "IN": "IN", + "KS": "KS", + "KY": "KY", + "LA": "LA", + "MA": "MA", + "MD": "MD", + "ME": "ME", + "MI": "MI", + "MN": "MN", + "MO": "MO", + "MS": "MS", + "MT": "MT", + "NC": "NC", + "ND": "ND", + "NE": "NE", + "NH": "NH", + "NJ": "NJ", + "NM": "NM", + "NV": "NV", + "NY": "NY", + "OH": "OH", + "OK": "OK", + "OR": "OR", + "PA": "PA", + "RI": "RI", + "SC": "SC", + "SD": "SD", + "TN": "TN", + "TX": "TX", + "UT": "UT", + "VA": "VA", + "VT": "VT", + "WA": "WA", + "WI": "WI", + "WV": "WV", + "WY": "WY" + } + }, + "usPostRegionCitiesID": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + } + } + }, + "VLocations": { + "type": "array", + "items": { + "$ref": "#/definitions/VLocation" + } + }, "ValidationError": { "allOf": [ { @@ -4848,6 +5031,56 @@ func init() { }, "basePath": "/prime/v1", "paths": { + "/addresses/zip-city-lookup/{search}": { + "get": { + "description": "Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code.", + "tags": [ + "addresses" + ], + "summary": "Returns city, state, postal code, and county associated with the specified full/partial postal code or city state string", + "operationId": "getLocationByZipCityState", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "the requested list of city, state, county, and postal code matches", + "schema": { + "$ref": "#/definitions/VLocations" + } + }, + "400": { + "description": "The request payload is invalid.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "403": { + "description": "The request was denied.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "404": { + "description": "The requested resource wasn't found.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "500": { + "description": "A server error occurred.", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/move-task-orders/{moveID}": { "get": { "description": "### Functionality\nThis endpoint gets an individual MoveTaskOrder by ID.\n\nIt will provide information about the Customer and any associated MTOShipments, MTOServiceItems and PaymentRequests.\n", @@ -9903,6 +10136,151 @@ func init() { } } }, + "VLocation": { + "description": "A postal code, city, and state lookup", + "type": "object", + "properties": { + "city": { + "type": "string", + "title": "City", + "example": "Anytown" + }, + "county": { + "type": "string", + "title": "County", + "x-nullable": true, + "example": "LOS ANGELES" + }, + "postalCode": { + "type": "string", + "format": "zip", + "title": "ZIP", + "pattern": "^(\\d{5}?)$", + "example": "90210" + }, + "state": { + "type": "string", + "title": "State", + "enum": [ + "AL", + "AK", + "AR", + "AZ", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "GA", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MI", + "MN", + "MO", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VT", + "WA", + "WI", + "WV", + "WY" + ], + "x-display-value": { + "AK": "AK", + "AL": "AL", + "AR": "AR", + "AZ": "AZ", + "CA": "CA", + "CO": "CO", + "CT": "CT", + "DC": "DC", + "DE": "DE", + "FL": "FL", + "GA": "GA", + "HI": "HI", + "IA": "IA", + "ID": "ID", + "IL": "IL", + "IN": "IN", + "KS": "KS", + "KY": "KY", + "LA": "LA", + "MA": "MA", + "MD": "MD", + "ME": "ME", + "MI": "MI", + "MN": "MN", + "MO": "MO", + "MS": "MS", + "MT": "MT", + "NC": "NC", + "ND": "ND", + "NE": "NE", + "NH": "NH", + "NJ": "NJ", + "NM": "NM", + "NV": "NV", + "NY": "NY", + "OH": "OH", + "OK": "OK", + "OR": "OR", + "PA": "PA", + "RI": "RI", + "SC": "SC", + "SD": "SD", + "TN": "TN", + "TX": "TX", + "UT": "UT", + "VA": "VA", + "VT": "VT", + "WA": "WA", + "WI": "WI", + "WV": "WV", + "WY": "WY" + } + }, + "usPostRegionCitiesID": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + } + } + }, + "VLocations": { + "type": "array", + "items": { + "$ref": "#/definitions/VLocation" + } + }, "ValidationError": { "allOf": [ { diff --git a/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state.go b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state.go new file mode 100644 index 00000000000..d202a9066f8 --- /dev/null +++ b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetLocationByZipCityStateHandlerFunc turns a function with the right signature into a get location by zip city state handler +type GetLocationByZipCityStateHandlerFunc func(GetLocationByZipCityStateParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetLocationByZipCityStateHandlerFunc) Handle(params GetLocationByZipCityStateParams) middleware.Responder { + return fn(params) +} + +// GetLocationByZipCityStateHandler interface for that can handle valid get location by zip city state params +type GetLocationByZipCityStateHandler interface { + Handle(GetLocationByZipCityStateParams) middleware.Responder +} + +// NewGetLocationByZipCityState creates a new http.Handler for the get location by zip city state operation +func NewGetLocationByZipCityState(ctx *middleware.Context, handler GetLocationByZipCityStateHandler) *GetLocationByZipCityState { + return &GetLocationByZipCityState{Context: ctx, Handler: handler} +} + +/* + GetLocationByZipCityState swagger:route GET /addresses/zip-city-lookup/{search} addresses getLocationByZipCityState + +Returns city, state, postal code, and county associated with the specified full/partial postal code or city state string + +Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code. +*/ +type GetLocationByZipCityState struct { + Context *middleware.Context + Handler GetLocationByZipCityStateHandler +} + +func (o *GetLocationByZipCityState) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetLocationByZipCityStateParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_parameters.go b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_parameters.go new file mode 100644 index 00000000000..0e8106fb581 --- /dev/null +++ b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_parameters.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" +) + +// NewGetLocationByZipCityStateParams creates a new GetLocationByZipCityStateParams object +// +// There are no default values defined in the spec. +func NewGetLocationByZipCityStateParams() GetLocationByZipCityStateParams { + + return GetLocationByZipCityStateParams{} +} + +// GetLocationByZipCityStateParams contains all the bound params for the get location by zip city state operation +// typically these are obtained from a http.Request +// +// swagger:parameters getLocationByZipCityState +type GetLocationByZipCityStateParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + Search string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetLocationByZipCityStateParams() beforehand. +func (o *GetLocationByZipCityStateParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rSearch, rhkSearch, _ := route.Params.GetOK("search") + if err := o.bindSearch(rSearch, rhkSearch, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindSearch binds and validates parameter Search from path. +func (o *GetLocationByZipCityStateParams) bindSearch(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.Search = raw + + return nil +} diff --git a/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_responses.go b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_responses.go new file mode 100644 index 00000000000..96eca32d7a9 --- /dev/null +++ b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_responses.go @@ -0,0 +1,242 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/primemessages" +) + +// GetLocationByZipCityStateOKCode is the HTTP code returned for type GetLocationByZipCityStateOK +const GetLocationByZipCityStateOKCode int = 200 + +/* +GetLocationByZipCityStateOK the requested list of city, state, county, and postal code matches + +swagger:response getLocationByZipCityStateOK +*/ +type GetLocationByZipCityStateOK struct { + + /* + In: Body + */ + Payload primemessages.VLocations `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateOK creates GetLocationByZipCityStateOK with default headers values +func NewGetLocationByZipCityStateOK() *GetLocationByZipCityStateOK { + + return &GetLocationByZipCityStateOK{} +} + +// WithPayload adds the payload to the get location by zip city state o k response +func (o *GetLocationByZipCityStateOK) WithPayload(payload primemessages.VLocations) *GetLocationByZipCityStateOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state o k response +func (o *GetLocationByZipCityStateOK) SetPayload(payload primemessages.VLocations) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = primemessages.VLocations{} + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetLocationByZipCityStateBadRequestCode is the HTTP code returned for type GetLocationByZipCityStateBadRequest +const GetLocationByZipCityStateBadRequestCode int = 400 + +/* +GetLocationByZipCityStateBadRequest The request payload is invalid. + +swagger:response getLocationByZipCityStateBadRequest +*/ +type GetLocationByZipCityStateBadRequest struct { + + /* + In: Body + */ + Payload *primemessages.ClientError `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateBadRequest creates GetLocationByZipCityStateBadRequest with default headers values +func NewGetLocationByZipCityStateBadRequest() *GetLocationByZipCityStateBadRequest { + + return &GetLocationByZipCityStateBadRequest{} +} + +// WithPayload adds the payload to the get location by zip city state bad request response +func (o *GetLocationByZipCityStateBadRequest) WithPayload(payload *primemessages.ClientError) *GetLocationByZipCityStateBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state bad request response +func (o *GetLocationByZipCityStateBadRequest) SetPayload(payload *primemessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetLocationByZipCityStateForbiddenCode is the HTTP code returned for type GetLocationByZipCityStateForbidden +const GetLocationByZipCityStateForbiddenCode int = 403 + +/* +GetLocationByZipCityStateForbidden The request was denied. + +swagger:response getLocationByZipCityStateForbidden +*/ +type GetLocationByZipCityStateForbidden struct { + + /* + In: Body + */ + Payload *primemessages.ClientError `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateForbidden creates GetLocationByZipCityStateForbidden with default headers values +func NewGetLocationByZipCityStateForbidden() *GetLocationByZipCityStateForbidden { + + return &GetLocationByZipCityStateForbidden{} +} + +// WithPayload adds the payload to the get location by zip city state forbidden response +func (o *GetLocationByZipCityStateForbidden) WithPayload(payload *primemessages.ClientError) *GetLocationByZipCityStateForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state forbidden response +func (o *GetLocationByZipCityStateForbidden) SetPayload(payload *primemessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetLocationByZipCityStateNotFoundCode is the HTTP code returned for type GetLocationByZipCityStateNotFound +const GetLocationByZipCityStateNotFoundCode int = 404 + +/* +GetLocationByZipCityStateNotFound The requested resource wasn't found. + +swagger:response getLocationByZipCityStateNotFound +*/ +type GetLocationByZipCityStateNotFound struct { + + /* + In: Body + */ + Payload *primemessages.ClientError `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateNotFound creates GetLocationByZipCityStateNotFound with default headers values +func NewGetLocationByZipCityStateNotFound() *GetLocationByZipCityStateNotFound { + + return &GetLocationByZipCityStateNotFound{} +} + +// WithPayload adds the payload to the get location by zip city state not found response +func (o *GetLocationByZipCityStateNotFound) WithPayload(payload *primemessages.ClientError) *GetLocationByZipCityStateNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state not found response +func (o *GetLocationByZipCityStateNotFound) SetPayload(payload *primemessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetLocationByZipCityStateInternalServerErrorCode is the HTTP code returned for type GetLocationByZipCityStateInternalServerError +const GetLocationByZipCityStateInternalServerErrorCode int = 500 + +/* +GetLocationByZipCityStateInternalServerError A server error occurred. + +swagger:response getLocationByZipCityStateInternalServerError +*/ +type GetLocationByZipCityStateInternalServerError struct { + + /* + In: Body + */ + Payload *primemessages.Error `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateInternalServerError creates GetLocationByZipCityStateInternalServerError with default headers values +func NewGetLocationByZipCityStateInternalServerError() *GetLocationByZipCityStateInternalServerError { + + return &GetLocationByZipCityStateInternalServerError{} +} + +// WithPayload adds the payload to the get location by zip city state internal server error response +func (o *GetLocationByZipCityStateInternalServerError) WithPayload(payload *primemessages.Error) *GetLocationByZipCityStateInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state internal server error response +func (o *GetLocationByZipCityStateInternalServerError) SetPayload(payload *primemessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_urlbuilder.go b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_urlbuilder.go new file mode 100644 index 00000000000..1ea3bc879de --- /dev/null +++ b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_urlbuilder.go @@ -0,0 +1,99 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// GetLocationByZipCityStateURL generates an URL for the get location by zip city state operation +type GetLocationByZipCityStateURL struct { + Search string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetLocationByZipCityStateURL) WithBasePath(bp string) *GetLocationByZipCityStateURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetLocationByZipCityStateURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetLocationByZipCityStateURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/addresses/zip-city-lookup/{search}" + + search := o.Search + if search != "" { + _path = strings.Replace(_path, "{search}", search, -1) + } else { + return nil, errors.New("search is required on GetLocationByZipCityStateURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/prime/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetLocationByZipCityStateURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetLocationByZipCityStateURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetLocationByZipCityStateURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetLocationByZipCityStateURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetLocationByZipCityStateURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetLocationByZipCityStateURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/primeapi/primeoperations/mymove_api.go b/pkg/gen/primeapi/primeoperations/mymove_api.go index b9e44b2190c..6ded41a6c0d 100644 --- a/pkg/gen/primeapi/primeoperations/mymove_api.go +++ b/pkg/gen/primeapi/primeoperations/mymove_api.go @@ -19,6 +19,7 @@ import ( "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" + "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/addresses" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/move_task_order" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_service_item" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_shipment" @@ -79,6 +80,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { MoveTaskOrderDownloadMoveOrderHandler: move_task_order.DownloadMoveOrderHandlerFunc(func(params move_task_order.DownloadMoveOrderParams) middleware.Responder { return middleware.NotImplemented("operation move_task_order.DownloadMoveOrder has not yet been implemented") }), + AddressesGetLocationByZipCityStateHandler: addresses.GetLocationByZipCityStateHandlerFunc(func(params addresses.GetLocationByZipCityStateParams) middleware.Responder { + return middleware.NotImplemented("operation addresses.GetLocationByZipCityState has not yet been implemented") + }), MoveTaskOrderGetMoveTaskOrderHandler: move_task_order.GetMoveTaskOrderHandlerFunc(func(params move_task_order.GetMoveTaskOrderParams) middleware.Responder { return middleware.NotImplemented("operation move_task_order.GetMoveTaskOrder has not yet been implemented") }), @@ -177,6 +181,8 @@ type MymoveAPI struct { MtoShipmentDeleteMTOShipmentHandler mto_shipment.DeleteMTOShipmentHandler // MoveTaskOrderDownloadMoveOrderHandler sets the operation handler for the download move order operation MoveTaskOrderDownloadMoveOrderHandler move_task_order.DownloadMoveOrderHandler + // AddressesGetLocationByZipCityStateHandler sets the operation handler for the get location by zip city state operation + AddressesGetLocationByZipCityStateHandler addresses.GetLocationByZipCityStateHandler // MoveTaskOrderGetMoveTaskOrderHandler sets the operation handler for the get move task order operation MoveTaskOrderGetMoveTaskOrderHandler move_task_order.GetMoveTaskOrderHandler // MoveTaskOrderListMovesHandler sets the operation handler for the list moves operation @@ -310,6 +316,9 @@ func (o *MymoveAPI) Validate() error { if o.MoveTaskOrderDownloadMoveOrderHandler == nil { unregistered = append(unregistered, "move_task_order.DownloadMoveOrderHandler") } + if o.AddressesGetLocationByZipCityStateHandler == nil { + unregistered = append(unregistered, "addresses.GetLocationByZipCityStateHandler") + } if o.MoveTaskOrderGetMoveTaskOrderHandler == nil { unregistered = append(unregistered, "move_task_order.GetMoveTaskOrderHandler") } @@ -475,6 +484,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/addresses/zip-city-lookup/{search}"] = addresses.NewGetLocationByZipCityState(o.context, o.AddressesGetLocationByZipCityStateHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/move-task-orders/{moveID}"] = move_task_order.NewGetMoveTaskOrder(o.context, o.MoveTaskOrderGetMoveTaskOrderHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/primeclient/addresses/addresses_client.go b/pkg/gen/primeclient/addresses/addresses_client.go new file mode 100644 index 00000000000..64fddbf9f02 --- /dev/null +++ b/pkg/gen/primeclient/addresses/addresses_client.go @@ -0,0 +1,81 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" +) + +// New creates a new addresses API client. +func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService { + return &Client{transport: transport, formats: formats} +} + +/* +Client for addresses API +*/ +type Client struct { + transport runtime.ClientTransport + formats strfmt.Registry +} + +// ClientOption is the option for Client methods +type ClientOption func(*runtime.ClientOperation) + +// ClientService is the interface for Client methods +type ClientService interface { + GetLocationByZipCityState(params *GetLocationByZipCityStateParams, opts ...ClientOption) (*GetLocationByZipCityStateOK, error) + + SetTransport(transport runtime.ClientTransport) +} + +/* +GetLocationByZipCityState returns city state postal code and county associated with the specified full partial postal code or city state string + +Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code. +*/ +func (a *Client) GetLocationByZipCityState(params *GetLocationByZipCityStateParams, opts ...ClientOption) (*GetLocationByZipCityStateOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewGetLocationByZipCityStateParams() + } + op := &runtime.ClientOperation{ + ID: "getLocationByZipCityState", + Method: "GET", + PathPattern: "/addresses/zip-city-lookup/{search}", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetLocationByZipCityStateReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*GetLocationByZipCityStateOK) + if ok { + return success, nil + } + // unexpected success response + // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue + msg := fmt.Sprintf("unexpected success response for getLocationByZipCityState: API contract not enforced by server. Client expected to get an error, but got: %T", result) + panic(msg) +} + +// SetTransport changes the transport on the client +func (a *Client) SetTransport(transport runtime.ClientTransport) { + a.transport = transport +} diff --git a/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_parameters.go b/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_parameters.go new file mode 100644 index 00000000000..494619925b4 --- /dev/null +++ b/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_parameters.go @@ -0,0 +1,148 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGetLocationByZipCityStateParams creates a new GetLocationByZipCityStateParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGetLocationByZipCityStateParams() *GetLocationByZipCityStateParams { + return &GetLocationByZipCityStateParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetLocationByZipCityStateParamsWithTimeout creates a new GetLocationByZipCityStateParams object +// with the ability to set a timeout on a request. +func NewGetLocationByZipCityStateParamsWithTimeout(timeout time.Duration) *GetLocationByZipCityStateParams { + return &GetLocationByZipCityStateParams{ + timeout: timeout, + } +} + +// NewGetLocationByZipCityStateParamsWithContext creates a new GetLocationByZipCityStateParams object +// with the ability to set a context for a request. +func NewGetLocationByZipCityStateParamsWithContext(ctx context.Context) *GetLocationByZipCityStateParams { + return &GetLocationByZipCityStateParams{ + Context: ctx, + } +} + +// NewGetLocationByZipCityStateParamsWithHTTPClient creates a new GetLocationByZipCityStateParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetLocationByZipCityStateParamsWithHTTPClient(client *http.Client) *GetLocationByZipCityStateParams { + return &GetLocationByZipCityStateParams{ + HTTPClient: client, + } +} + +/* +GetLocationByZipCityStateParams contains all the parameters to send to the API endpoint + + for the get location by zip city state operation. + + Typically these are written to a http.Request. +*/ +type GetLocationByZipCityStateParams struct { + + // Search. + Search string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get location by zip city state params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetLocationByZipCityStateParams) WithDefaults() *GetLocationByZipCityStateParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get location by zip city state params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetLocationByZipCityStateParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) WithTimeout(timeout time.Duration) *GetLocationByZipCityStateParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) WithContext(ctx context.Context) *GetLocationByZipCityStateParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) WithHTTPClient(client *http.Client) *GetLocationByZipCityStateParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithSearch adds the search to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) WithSearch(search string) *GetLocationByZipCityStateParams { + o.SetSearch(search) + return o +} + +// SetSearch adds the search to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) SetSearch(search string) { + o.Search = search +} + +// WriteToRequest writes these params to a swagger request +func (o *GetLocationByZipCityStateParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // path param search + if err := r.SetPathParam("search", o.Search); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_responses.go b/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_responses.go new file mode 100644 index 00000000000..a077d9cc5d5 --- /dev/null +++ b/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_responses.go @@ -0,0 +1,397 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/transcom/mymove/pkg/gen/primemessages" +) + +// GetLocationByZipCityStateReader is a Reader for the GetLocationByZipCityState structure. +type GetLocationByZipCityStateReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetLocationByZipCityStateReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewGetLocationByZipCityStateOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewGetLocationByZipCityStateBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 403: + result := NewGetLocationByZipCityStateForbidden() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 404: + result := NewGetLocationByZipCityStateNotFound() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 500: + result := NewGetLocationByZipCityStateInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + return nil, runtime.NewAPIError("[GET /addresses/zip-city-lookup/{search}] getLocationByZipCityState", response, response.Code()) + } +} + +// NewGetLocationByZipCityStateOK creates a GetLocationByZipCityStateOK with default headers values +func NewGetLocationByZipCityStateOK() *GetLocationByZipCityStateOK { + return &GetLocationByZipCityStateOK{} +} + +/* +GetLocationByZipCityStateOK describes a response with status code 200, with default header values. + +the requested list of city, state, county, and postal code matches +*/ +type GetLocationByZipCityStateOK struct { + Payload primemessages.VLocations +} + +// IsSuccess returns true when this get location by zip city state o k response has a 2xx status code +func (o *GetLocationByZipCityStateOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get location by zip city state o k response has a 3xx status code +func (o *GetLocationByZipCityStateOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state o k response has a 4xx status code +func (o *GetLocationByZipCityStateOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get location by zip city state o k response has a 5xx status code +func (o *GetLocationByZipCityStateOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get location by zip city state o k response a status code equal to that given +func (o *GetLocationByZipCityStateOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the get location by zip city state o k response +func (o *GetLocationByZipCityStateOK) Code() int { + return 200 +} + +func (o *GetLocationByZipCityStateOK) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateOK %+v", 200, o.Payload) +} + +func (o *GetLocationByZipCityStateOK) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateOK %+v", 200, o.Payload) +} + +func (o *GetLocationByZipCityStateOK) GetPayload() primemessages.VLocations { + return o.Payload +} + +func (o *GetLocationByZipCityStateOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetLocationByZipCityStateBadRequest creates a GetLocationByZipCityStateBadRequest with default headers values +func NewGetLocationByZipCityStateBadRequest() *GetLocationByZipCityStateBadRequest { + return &GetLocationByZipCityStateBadRequest{} +} + +/* +GetLocationByZipCityStateBadRequest describes a response with status code 400, with default header values. + +The request payload is invalid. +*/ +type GetLocationByZipCityStateBadRequest struct { + Payload *primemessages.ClientError +} + +// IsSuccess returns true when this get location by zip city state bad request response has a 2xx status code +func (o *GetLocationByZipCityStateBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get location by zip city state bad request response has a 3xx status code +func (o *GetLocationByZipCityStateBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state bad request response has a 4xx status code +func (o *GetLocationByZipCityStateBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this get location by zip city state bad request response has a 5xx status code +func (o *GetLocationByZipCityStateBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this get location by zip city state bad request response a status code equal to that given +func (o *GetLocationByZipCityStateBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the get location by zip city state bad request response +func (o *GetLocationByZipCityStateBadRequest) Code() int { + return 400 +} + +func (o *GetLocationByZipCityStateBadRequest) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateBadRequest %+v", 400, o.Payload) +} + +func (o *GetLocationByZipCityStateBadRequest) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateBadRequest %+v", 400, o.Payload) +} + +func (o *GetLocationByZipCityStateBadRequest) GetPayload() *primemessages.ClientError { + return o.Payload +} + +func (o *GetLocationByZipCityStateBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(primemessages.ClientError) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetLocationByZipCityStateForbidden creates a GetLocationByZipCityStateForbidden with default headers values +func NewGetLocationByZipCityStateForbidden() *GetLocationByZipCityStateForbidden { + return &GetLocationByZipCityStateForbidden{} +} + +/* +GetLocationByZipCityStateForbidden describes a response with status code 403, with default header values. + +The request was denied. +*/ +type GetLocationByZipCityStateForbidden struct { + Payload *primemessages.ClientError +} + +// IsSuccess returns true when this get location by zip city state forbidden response has a 2xx status code +func (o *GetLocationByZipCityStateForbidden) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get location by zip city state forbidden response has a 3xx status code +func (o *GetLocationByZipCityStateForbidden) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state forbidden response has a 4xx status code +func (o *GetLocationByZipCityStateForbidden) IsClientError() bool { + return true +} + +// IsServerError returns true when this get location by zip city state forbidden response has a 5xx status code +func (o *GetLocationByZipCityStateForbidden) IsServerError() bool { + return false +} + +// IsCode returns true when this get location by zip city state forbidden response a status code equal to that given +func (o *GetLocationByZipCityStateForbidden) IsCode(code int) bool { + return code == 403 +} + +// Code gets the status code for the get location by zip city state forbidden response +func (o *GetLocationByZipCityStateForbidden) Code() int { + return 403 +} + +func (o *GetLocationByZipCityStateForbidden) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateForbidden %+v", 403, o.Payload) +} + +func (o *GetLocationByZipCityStateForbidden) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateForbidden %+v", 403, o.Payload) +} + +func (o *GetLocationByZipCityStateForbidden) GetPayload() *primemessages.ClientError { + return o.Payload +} + +func (o *GetLocationByZipCityStateForbidden) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(primemessages.ClientError) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetLocationByZipCityStateNotFound creates a GetLocationByZipCityStateNotFound with default headers values +func NewGetLocationByZipCityStateNotFound() *GetLocationByZipCityStateNotFound { + return &GetLocationByZipCityStateNotFound{} +} + +/* +GetLocationByZipCityStateNotFound describes a response with status code 404, with default header values. + +The requested resource wasn't found. +*/ +type GetLocationByZipCityStateNotFound struct { + Payload *primemessages.ClientError +} + +// IsSuccess returns true when this get location by zip city state not found response has a 2xx status code +func (o *GetLocationByZipCityStateNotFound) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get location by zip city state not found response has a 3xx status code +func (o *GetLocationByZipCityStateNotFound) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state not found response has a 4xx status code +func (o *GetLocationByZipCityStateNotFound) IsClientError() bool { + return true +} + +// IsServerError returns true when this get location by zip city state not found response has a 5xx status code +func (o *GetLocationByZipCityStateNotFound) IsServerError() bool { + return false +} + +// IsCode returns true when this get location by zip city state not found response a status code equal to that given +func (o *GetLocationByZipCityStateNotFound) IsCode(code int) bool { + return code == 404 +} + +// Code gets the status code for the get location by zip city state not found response +func (o *GetLocationByZipCityStateNotFound) Code() int { + return 404 +} + +func (o *GetLocationByZipCityStateNotFound) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateNotFound %+v", 404, o.Payload) +} + +func (o *GetLocationByZipCityStateNotFound) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateNotFound %+v", 404, o.Payload) +} + +func (o *GetLocationByZipCityStateNotFound) GetPayload() *primemessages.ClientError { + return o.Payload +} + +func (o *GetLocationByZipCityStateNotFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(primemessages.ClientError) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetLocationByZipCityStateInternalServerError creates a GetLocationByZipCityStateInternalServerError with default headers values +func NewGetLocationByZipCityStateInternalServerError() *GetLocationByZipCityStateInternalServerError { + return &GetLocationByZipCityStateInternalServerError{} +} + +/* +GetLocationByZipCityStateInternalServerError describes a response with status code 500, with default header values. + +A server error occurred. +*/ +type GetLocationByZipCityStateInternalServerError struct { + Payload *primemessages.Error +} + +// IsSuccess returns true when this get location by zip city state internal server error response has a 2xx status code +func (o *GetLocationByZipCityStateInternalServerError) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get location by zip city state internal server error response has a 3xx status code +func (o *GetLocationByZipCityStateInternalServerError) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state internal server error response has a 4xx status code +func (o *GetLocationByZipCityStateInternalServerError) IsClientError() bool { + return false +} + +// IsServerError returns true when this get location by zip city state internal server error response has a 5xx status code +func (o *GetLocationByZipCityStateInternalServerError) IsServerError() bool { + return true +} + +// IsCode returns true when this get location by zip city state internal server error response a status code equal to that given +func (o *GetLocationByZipCityStateInternalServerError) IsCode(code int) bool { + return code == 500 +} + +// Code gets the status code for the get location by zip city state internal server error response +func (o *GetLocationByZipCityStateInternalServerError) Code() int { + return 500 +} + +func (o *GetLocationByZipCityStateInternalServerError) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateInternalServerError %+v", 500, o.Payload) +} + +func (o *GetLocationByZipCityStateInternalServerError) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateInternalServerError %+v", 500, o.Payload) +} + +func (o *GetLocationByZipCityStateInternalServerError) GetPayload() *primemessages.Error { + return o.Payload +} + +func (o *GetLocationByZipCityStateInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(primemessages.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/pkg/gen/primeclient/mymove_client.go b/pkg/gen/primeclient/mymove_client.go index 5a6cf119393..5f38f83617d 100644 --- a/pkg/gen/primeclient/mymove_client.go +++ b/pkg/gen/primeclient/mymove_client.go @@ -10,6 +10,7 @@ import ( httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" + "github.com/transcom/mymove/pkg/gen/primeclient/addresses" "github.com/transcom/mymove/pkg/gen/primeclient/move_task_order" "github.com/transcom/mymove/pkg/gen/primeclient/mto_service_item" "github.com/transcom/mymove/pkg/gen/primeclient/mto_shipment" @@ -58,6 +59,7 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *Mymove { cli := new(Mymove) cli.Transport = transport + cli.Addresses = addresses.New(transport, formats) cli.MoveTaskOrder = move_task_order.New(transport, formats) cli.MtoServiceItem = mto_service_item.New(transport, formats) cli.MtoShipment = mto_shipment.New(transport, formats) @@ -106,6 +108,8 @@ func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig { // Mymove is a client for mymove type Mymove struct { + Addresses addresses.ClientService + MoveTaskOrder move_task_order.ClientService MtoServiceItem mto_service_item.ClientService @@ -120,6 +124,7 @@ type Mymove struct { // SetTransport changes the transport on the client and all its subresources func (c *Mymove) SetTransport(transport runtime.ClientTransport) { c.Transport = transport + c.Addresses.SetTransport(transport) c.MoveTaskOrder.SetTransport(transport) c.MtoServiceItem.SetTransport(transport) c.MtoShipment.SetTransport(transport) diff --git a/pkg/gen/primemessages/v_location.go b/pkg/gen/primemessages/v_location.go new file mode 100644 index 00000000000..77cd75ee6e3 --- /dev/null +++ b/pkg/gen/primemessages/v_location.go @@ -0,0 +1,302 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primemessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// VLocation A postal code, city, and state lookup +// +// swagger:model VLocation +type VLocation struct { + + // City + // Example: Anytown + City string `json:"city,omitempty"` + + // County + // Example: LOS ANGELES + County *string `json:"county,omitempty"` + + // ZIP + // Example: 90210 + // Pattern: ^(\d{5}?)$ + PostalCode string `json:"postalCode,omitempty"` + + // State + // Enum: [AL AK AR AZ CA CO CT DC DE FL GA HI IA ID IL IN KS KY LA MA MD ME MI MN MO MS MT NC ND NE NH NJ NM NV NY OH OK OR PA RI SC SD TN TX UT VA VT WA WI WV WY] + State string `json:"state,omitempty"` + + // us post region cities ID + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Format: uuid + UsPostRegionCitiesID strfmt.UUID `json:"usPostRegionCitiesID,omitempty"` +} + +// Validate validates this v location +func (m *VLocation) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validatePostalCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateState(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUsPostRegionCitiesID(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *VLocation) validatePostalCode(formats strfmt.Registry) error { + if swag.IsZero(m.PostalCode) { // not required + return nil + } + + if err := validate.Pattern("postalCode", "body", m.PostalCode, `^(\d{5}?)$`); err != nil { + return err + } + + return nil +} + +var vLocationTypeStatePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["AL","AK","AR","AZ","CA","CO","CT","DC","DE","FL","GA","HI","IA","ID","IL","IN","KS","KY","LA","MA","MD","ME","MI","MN","MO","MS","MT","NC","ND","NE","NH","NJ","NM","NV","NY","OH","OK","OR","PA","RI","SC","SD","TN","TX","UT","VA","VT","WA","WI","WV","WY"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + vLocationTypeStatePropEnum = append(vLocationTypeStatePropEnum, v) + } +} + +const ( + + // VLocationStateAL captures enum value "AL" + VLocationStateAL string = "AL" + + // VLocationStateAK captures enum value "AK" + VLocationStateAK string = "AK" + + // VLocationStateAR captures enum value "AR" + VLocationStateAR string = "AR" + + // VLocationStateAZ captures enum value "AZ" + VLocationStateAZ string = "AZ" + + // VLocationStateCA captures enum value "CA" + VLocationStateCA string = "CA" + + // VLocationStateCO captures enum value "CO" + VLocationStateCO string = "CO" + + // VLocationStateCT captures enum value "CT" + VLocationStateCT string = "CT" + + // VLocationStateDC captures enum value "DC" + VLocationStateDC string = "DC" + + // VLocationStateDE captures enum value "DE" + VLocationStateDE string = "DE" + + // VLocationStateFL captures enum value "FL" + VLocationStateFL string = "FL" + + // VLocationStateGA captures enum value "GA" + VLocationStateGA string = "GA" + + // VLocationStateHI captures enum value "HI" + VLocationStateHI string = "HI" + + // VLocationStateIA captures enum value "IA" + VLocationStateIA string = "IA" + + // VLocationStateID captures enum value "ID" + VLocationStateID string = "ID" + + // VLocationStateIL captures enum value "IL" + VLocationStateIL string = "IL" + + // VLocationStateIN captures enum value "IN" + VLocationStateIN string = "IN" + + // VLocationStateKS captures enum value "KS" + VLocationStateKS string = "KS" + + // VLocationStateKY captures enum value "KY" + VLocationStateKY string = "KY" + + // VLocationStateLA captures enum value "LA" + VLocationStateLA string = "LA" + + // VLocationStateMA captures enum value "MA" + VLocationStateMA string = "MA" + + // VLocationStateMD captures enum value "MD" + VLocationStateMD string = "MD" + + // VLocationStateME captures enum value "ME" + VLocationStateME string = "ME" + + // VLocationStateMI captures enum value "MI" + VLocationStateMI string = "MI" + + // VLocationStateMN captures enum value "MN" + VLocationStateMN string = "MN" + + // VLocationStateMO captures enum value "MO" + VLocationStateMO string = "MO" + + // VLocationStateMS captures enum value "MS" + VLocationStateMS string = "MS" + + // VLocationStateMT captures enum value "MT" + VLocationStateMT string = "MT" + + // VLocationStateNC captures enum value "NC" + VLocationStateNC string = "NC" + + // VLocationStateND captures enum value "ND" + VLocationStateND string = "ND" + + // VLocationStateNE captures enum value "NE" + VLocationStateNE string = "NE" + + // VLocationStateNH captures enum value "NH" + VLocationStateNH string = "NH" + + // VLocationStateNJ captures enum value "NJ" + VLocationStateNJ string = "NJ" + + // VLocationStateNM captures enum value "NM" + VLocationStateNM string = "NM" + + // VLocationStateNV captures enum value "NV" + VLocationStateNV string = "NV" + + // VLocationStateNY captures enum value "NY" + VLocationStateNY string = "NY" + + // VLocationStateOH captures enum value "OH" + VLocationStateOH string = "OH" + + // VLocationStateOK captures enum value "OK" + VLocationStateOK string = "OK" + + // VLocationStateOR captures enum value "OR" + VLocationStateOR string = "OR" + + // VLocationStatePA captures enum value "PA" + VLocationStatePA string = "PA" + + // VLocationStateRI captures enum value "RI" + VLocationStateRI string = "RI" + + // VLocationStateSC captures enum value "SC" + VLocationStateSC string = "SC" + + // VLocationStateSD captures enum value "SD" + VLocationStateSD string = "SD" + + // VLocationStateTN captures enum value "TN" + VLocationStateTN string = "TN" + + // VLocationStateTX captures enum value "TX" + VLocationStateTX string = "TX" + + // VLocationStateUT captures enum value "UT" + VLocationStateUT string = "UT" + + // VLocationStateVA captures enum value "VA" + VLocationStateVA string = "VA" + + // VLocationStateVT captures enum value "VT" + VLocationStateVT string = "VT" + + // VLocationStateWA captures enum value "WA" + VLocationStateWA string = "WA" + + // VLocationStateWI captures enum value "WI" + VLocationStateWI string = "WI" + + // VLocationStateWV captures enum value "WV" + VLocationStateWV string = "WV" + + // VLocationStateWY captures enum value "WY" + VLocationStateWY string = "WY" +) + +// prop value enum +func (m *VLocation) validateStateEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, vLocationTypeStatePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *VLocation) validateState(formats strfmt.Registry) error { + if swag.IsZero(m.State) { // not required + return nil + } + + // value enum + if err := m.validateStateEnum("state", "body", m.State); err != nil { + return err + } + + return nil +} + +func (m *VLocation) validateUsPostRegionCitiesID(formats strfmt.Registry) error { + if swag.IsZero(m.UsPostRegionCitiesID) { // not required + return nil + } + + if err := validate.FormatOf("usPostRegionCitiesID", "body", "uuid", m.UsPostRegionCitiesID.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this v location based on context it is used +func (m *VLocation) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *VLocation) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *VLocation) UnmarshalBinary(b []byte) error { + var res VLocation + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primemessages/v_locations.go b/pkg/gen/primemessages/v_locations.go new file mode 100644 index 00000000000..caa019fc057 --- /dev/null +++ b/pkg/gen/primemessages/v_locations.go @@ -0,0 +1,78 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primemessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// VLocations v locations +// +// swagger:model VLocations +type VLocations []*VLocation + +// Validate validates this v locations +func (m VLocations) Validate(formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + if swag.IsZero(m[i]) { // not required + continue + } + + if m[i] != nil { + if err := m[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// ContextValidate validate this v locations based on the context it is used +func (m VLocations) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + + if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + + if err := m[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/handlers/primeapi/addresses.go b/pkg/handlers/primeapi/addresses.go new file mode 100644 index 00000000000..55263799d93 --- /dev/null +++ b/pkg/handlers/primeapi/addresses.go @@ -0,0 +1,62 @@ +package primeapi + +import ( + "context" + + "github.com/go-openapi/runtime/middleware" + "go.uber.org/zap" + + "github.com/transcom/mymove/pkg/appcontext" + addressop "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/addresses" + "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/handlers/primeapi/payloads" + "github.com/transcom/mymove/pkg/services" +) + +type GetLocationByZipCityStateHandler struct { + handlers.HandlerConfig + services.VLocation +} + +func (h GetLocationByZipCityStateHandler) Handle(params addressop.GetLocationByZipCityStateParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + locationList, err := h.GetLocationsByZipCityState(appCtx, params.Search, statesToExclude) + if err != nil { + appCtx.Logger().Error("Error searching for Zip/City/State: ", zap.Error(err)) + return addressop.NewGetLocationByZipCityStateInternalServerError(), err + } + + returnPayload := payloads.VLocations(*locationList) + return addressop.NewGetLocationByZipCityStateOK().WithPayload(returnPayload), nil + }) +} diff --git a/pkg/handlers/primeapi/api.go b/pkg/handlers/primeapi/api.go index 4eab1923c9f..c2452f0583e 100644 --- a/pkg/handlers/primeapi/api.go +++ b/pkg/handlers/primeapi/api.go @@ -54,6 +54,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP uploadCreator := upload.NewUploadCreator(handlerConfig.FileStorer()) ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + vLocation := address.NewVLocation() userUploader, err := uploader.NewUserUploader(handlerConfig.FileStorer(), uploader.MaxCustomerUserUploadFileSizeLimit) if err != nil { @@ -111,9 +112,15 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP mtoserviceitem.NewServiceRequestDocumentUploadCreator(handlerConfig.FileStorer()), } + primeAPI.AddressesGetLocationByZipCityStateHandler = GetLocationByZipCityStateHandler{ + handlerConfig, + vLocation, + } + primeAPI.MtoShipmentUpdateShipmentDestinationAddressHandler = UpdateShipmentDestinationAddressHandler{ handlerConfig, shipmentaddressupdate.NewShipmentAddressUpdateRequester(handlerConfig.HHGPlanner(), addressCreator, moveRouter), + vLocation, } addressUpdater := address.NewAddressUpdater() @@ -158,6 +165,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP primeAPI.MtoShipmentUpdateMTOShipmentAddressHandler = UpdateMTOShipmentAddressHandler{ handlerConfig, mtoshipment.NewMTOShipmentAddressUpdater(handlerConfig.HHGPlanner(), addressCreator, addressUpdater), + vLocation, } primeAPI.MtoShipmentCreateMTOAgentHandler = CreateMTOAgentHandler{ diff --git a/pkg/handlers/primeapi/mto_shipment.go b/pkg/handlers/primeapi/mto_shipment.go index a93967aea89..0fad3b2ff99 100644 --- a/pkg/handlers/primeapi/mto_shipment.go +++ b/pkg/handlers/primeapi/mto_shipment.go @@ -1,6 +1,9 @@ package primeapi import ( + "context" + "fmt" + "github.com/go-openapi/runtime/middleware" "github.com/gofrs/uuid" "go.uber.org/zap" @@ -19,6 +22,7 @@ import ( type UpdateShipmentDestinationAddressHandler struct { handlers.HandlerConfig services.ShipmentAddressUpdateRequester + services.VLocation } // Handle creates the address update request for non-SIT @@ -32,6 +36,52 @@ func (h UpdateShipmentDestinationAddressHandler) Handle(params mtoshipmentops.Up eTag := params.IfMatch + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + addressSearch := addressUpdate.NewAddress.City + ", " + addressUpdate.NewAddress.State + " " + addressUpdate.NewAddress.PostalCode + + locationList, err := h.GetLocationsByZipCityState(appCtx, addressSearch, statesToExclude, true) + if err != nil { + serverError := apperror.NewInternalServerError("Error searching for address") + errStr := serverError.Error() // we do this because InternalServerError wants a *string + appCtx.Logger().Warn(serverError.Error()) + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewUpdateShipmentDestinationAddressInternalServerError().WithPayload(payload), serverError + } else if len(*locationList) == 0 { + unprocessableErr := apperror.NewUnprocessableEntityError( + fmt.Sprintf("primeapi.UpdateShipmentDestinationAddress: could not find the provided location: %s", addressSearch)) + appCtx.Logger().Warn(unprocessableErr.Error()) + payload := payloads.ValidationError(unprocessableErr.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewUpdateShipmentDestinationAddressUnprocessableEntity().WithPayload(payload), unprocessableErr + } + response, err := h.ShipmentAddressUpdateRequester.RequestShipmentDeliveryAddressUpdate(appCtx, shipmentID, addressUpdate.NewAddress, addressUpdate.ContractorRemarks, eTag) if err != nil { diff --git a/pkg/handlers/primeapi/mto_shipment_address.go b/pkg/handlers/primeapi/mto_shipment_address.go index 5f699f384c1..395fc89f11a 100644 --- a/pkg/handlers/primeapi/mto_shipment_address.go +++ b/pkg/handlers/primeapi/mto_shipment_address.go @@ -1,6 +1,9 @@ package primeapi import ( + "context" + "fmt" + "github.com/go-openapi/runtime/middleware" "github.com/gofrs/uuid" "go.uber.org/zap" @@ -19,6 +22,7 @@ import ( type UpdateMTOShipmentAddressHandler struct { handlers.HandlerConfig MTOShipmentAddressUpdater services.MTOShipmentAddressUpdater + services.VLocation } // Handle updates an address on a shipment @@ -60,6 +64,52 @@ func (h UpdateMTOShipmentAddressHandler) Handle(params mtoshipmentops.UpdateMTOS newAddress := payloads.AddressModel(payload) newAddress.ID = addressID + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + addressSearch := newAddress.City + ", " + newAddress.State + " " + newAddress.PostalCode + + locationList, err := h.GetLocationsByZipCityState(appCtx, addressSearch, statesToExclude, true) + if err != nil { + serverError := apperror.NewInternalServerError("Error searching for address") + errStr := serverError.Error() // we do this because InternalServerError wants a *string + appCtx.Logger().Warn(serverError.Error()) + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewUpdateMTOShipmentAddressInternalServerError().WithPayload(payload), serverError + } else if len(*locationList) == 0 { + unprocessableErr := apperror.NewUnprocessableEntityError( + fmt.Sprintf("primeapi.UpdateMTOShipmentAddress: could not find the provided location: %s", addressSearch)) + appCtx.Logger().Warn(unprocessableErr.Error()) + payload := payloads.ValidationError(unprocessableErr.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payload), unprocessableErr + } + // Call the service object updatedAddress, err := h.MTOShipmentAddressUpdater.UpdateMTOShipmentAddress(appCtx, newAddress, mtoShipmentID, eTag, true) diff --git a/pkg/handlers/primeapi/mto_shipment_address_test.go b/pkg/handlers/primeapi/mto_shipment_address_test.go index 71235074946..cca597695c7 100644 --- a/pkg/handlers/primeapi/mto_shipment_address_test.go +++ b/pkg/handlers/primeapi/mto_shipment_address_test.go @@ -15,7 +15,9 @@ import ( "github.com/transcom/mymove/pkg/handlers/primeapi/payloads" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/route/mocks" + "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/address" + servicemocks "github.com/transcom/mymove/pkg/services/mocks" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" ) @@ -43,24 +45,27 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { planner := &mocks.Planner{} addressCreator := address.NewAddressCreator() addressUpdater := address.NewAddressUpdater() + vLocationServices := address.NewVLocation() planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false, ).Return(400, nil) + // Create handler handler := UpdateMTOShipmentAddressHandler{ suite.HandlerConfig(), mtoshipment.NewMTOShipmentAddressUpdater(planner, addressCreator, addressUpdater), + vLocationServices, } return handler, availableMove } newAddress := models.Address{ StreetAddress1: "7 Q St", - City: "Framington", - State: "MA", + City: "Acmar", + State: "AL", PostalCode: "35004", } @@ -120,7 +125,7 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), City: "Alameda", State: "CA", - PostalCode: "35004", + PostalCode: "94502", } // Update with new address @@ -353,4 +358,208 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { response := handler.Handle(params) suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) }) + + suite.Run("Failure - Unprocessable when updating address with invalid data", func() { + // Testcase: address is updated on a shipment that's available to MTO with invalid address + // Expected: Failure response 422 + // Under Test: UpdateMTOShipmentAddress handler code and mtoShipmentAddressUpdater service object + handler, availableMove := setupTestData() + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + }, nil) + newAddress2 := models.Address{ + StreetAddress1: "7 Q St", + StreetAddress2: models.StringPointer("6622 Airport Way S #1430"), + StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), + City: "Bad City", + State: "CA", + PostalCode: "99999", // invalid postal code + } + + // Update with new address + payload := payloads.Address(&newAddress2) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(shipment.PickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.PickupAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) + }) + + suite.Run("Failure - Unprocessable with AK FF off and valid AK address", func() { + // Testcase: address is updated on a shipment that's available to MTO with AK address but FF off + // Expected: Failure response 422 + // Under Test: UpdateMTOShipmentAddress handler code and mtoShipmentAddressUpdater service object + handler, availableMove := setupTestData() + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + }, nil) + newAddress2 := models.Address{ + StreetAddress1: "7 Q St", + StreetAddress2: models.StringPointer("6622 Airport Way S #1430"), + StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), + City: "JUNEAU", + State: "AK", + PostalCode: "99801", + } + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &servicemocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + + // Update with new address + payload := payloads.Address(&newAddress2) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(shipment.PickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.PickupAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) + }) + + suite.Run("Failure - Unprocessable with HI FF off and valid HI address", func() { + // Testcase: address is updated on a shipment that's available to MTO with HI address but FF off + // Expected: Failure response 422 + // Under Test: UpdateMTOShipmentAddress handler code and mtoShipmentAddressUpdater service object + handler, availableMove := setupTestData() + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + }, nil) + newAddress2 := models.Address{ + StreetAddress1: "7 Q St", + StreetAddress2: models.StringPointer("6622 Airport Way S #1430"), + StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), + City: "HONOLULU", + State: "HI", + PostalCode: "96835", + } + + // setting the HI flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &servicemocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + + // Update with new address + payload := payloads.Address(&newAddress2) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(shipment.PickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.PickupAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) + }) + + suite.Run("Failure - Internal Error mock GetLocationsByZipCityState return error", func() { + // Testcase: address is updated on a shipment that's available to MTO with invalid address + // Expected: Failure response 422 + // Under Test: UpdateMTOShipmentAddress handler code and mtoShipmentAddressUpdater service object + handler, availableMove := setupTestData() + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + }, nil) + newAddress2 := models.Address{ + StreetAddress1: "7 Q St", + StreetAddress2: models.StringPointer("6622 Airport Way S #1430"), + StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + } + + // Update with new address + payload := payloads.Address(&newAddress2) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(shipment.PickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.PickupAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + expectedError := models.ErrFetchNotFound + vLocationFetcher := &servicemocks.VLocation{} + vLocationFetcher.On("GetLocationsByZipCityState", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil, expectedError).Once() + + handler.VLocation = vLocationFetcher + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressInternalServerError{}, response) + }) } diff --git a/pkg/handlers/primeapi/mto_shipment_test.go b/pkg/handlers/primeapi/mto_shipment_test.go index 1a9b23790ac..be1af9713d3 100644 --- a/pkg/handlers/primeapi/mto_shipment_test.go +++ b/pkg/handlers/primeapi/mto_shipment_test.go @@ -36,6 +36,7 @@ import ( func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { req := httptest.NewRequest("POST", "/mto-shipments/{mtoShipmentID}/shipment-address-updates", nil) + vLocationServices := address.NewVLocation() makeSubtestData := func() mtoshipmentops.UpdateShipmentDestinationAddressParams { contractorRemark := "This is a contractor remark" @@ -57,12 +58,130 @@ func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { return params } + + suite.Run("POST failure - 500 Internal Server GetLocationsByZipCityState returns error", func() { + subtestData := makeSubtestData() + mockCreator := mocks.ShipmentAddressUpdateRequester{} + + expectedError := models.ErrFetchNotFound + vLocationFetcher := &mocks.VLocation{} + vLocationFetcher.On("GetLocationsByZipCityState", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil, expectedError).Once() + + handler := UpdateShipmentDestinationAddressHandler{ + HandlerConfig: suite.HandlerConfig(), + ShipmentAddressUpdateRequester: &mockCreator, + VLocation: vLocationFetcher, + } + + response := handler.Handle(subtestData) + suite.IsType(&mtoshipmentops.UpdateShipmentDestinationAddressInternalServerError{}, response) + }) + + suite.Run("POST failure - 422 Unprocessable Entity Error Invalid Address", func() { + subtestData := makeSubtestData() + mockCreator := mocks.ShipmentAddressUpdateRequester{} + vLocationServices := address.NewVLocation() + handler := UpdateShipmentDestinationAddressHandler{ + suite.HandlerConfig(), + &mockCreator, + vLocationServices, + } + + subtestData.Body.NewAddress.City = handlers.FmtString("Bad City") + // Validate incoming payload + suite.NoError(subtestData.Body.Validate(strfmt.Default)) + + response := handler.Handle(subtestData) + suite.IsType(&mtoshipmentops.UpdateShipmentDestinationAddressUnprocessableEntity{}, response) + }) + + suite.Run("POST failure - 422 Unprocessable Entity Error Valid AK Address FF off", func() { + subtestData := makeSubtestData() + mockCreator := mocks.ShipmentAddressUpdateRequester{} + vLocationServices := address.NewVLocation() + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler := UpdateShipmentDestinationAddressHandler{ + handlerConfig, + &mockCreator, + vLocationServices, + } + + subtestData.Body.NewAddress.City = handlers.FmtString("JUNEAU") + subtestData.Body.NewAddress.State = handlers.FmtString("AK") + subtestData.Body.NewAddress.PostalCode = handlers.FmtString("99801") + // Validate incoming payload + suite.NoError(subtestData.Body.Validate(strfmt.Default)) + + response := handler.Handle(subtestData) + suite.IsType(&mtoshipmentops.UpdateShipmentDestinationAddressUnprocessableEntity{}, response) + }) + + suite.Run("POST failure - 422 Unprocessable Entity Error Valid HI Address FF off", func() { + subtestData := makeSubtestData() + mockCreator := mocks.ShipmentAddressUpdateRequester{} + vLocationServices := address.NewVLocation() + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler := UpdateShipmentDestinationAddressHandler{ + handlerConfig, + &mockCreator, + vLocationServices, + } + + subtestData.Body.NewAddress.City = handlers.FmtString("HONOLULU") + subtestData.Body.NewAddress.State = handlers.FmtString("HI") + subtestData.Body.NewAddress.PostalCode = handlers.FmtString("96835") + // Validate incoming payload + suite.NoError(subtestData.Body.Validate(strfmt.Default)) + + response := handler.Handle(subtestData) + suite.IsType(&mtoshipmentops.UpdateShipmentDestinationAddressUnprocessableEntity{}, response) + }) + suite.Run("POST failure - 422 Unprocessable Entity Error", func() { subtestData := makeSubtestData() mockCreator := mocks.ShipmentAddressUpdateRequester{} handler := UpdateShipmentDestinationAddressHandler{ suite.HandlerConfig(), &mockCreator, + vLocationServices, } // InvalidInputError should generate an UnprocessableEntity response error // Need verrs incorporated to satisfy swagger validation @@ -95,6 +214,7 @@ func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { handler := UpdateShipmentDestinationAddressHandler{ suite.HandlerConfig(), &mockCreator, + vLocationServices, } // NewConflictError should generate a RequestConflict response error err := apperror.NewConflictError(uuid.Nil, "unable to create ShipmentAddressUpdate") @@ -125,6 +245,7 @@ func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { handler := UpdateShipmentDestinationAddressHandler{ suite.HandlerConfig(), &mockCreator, + vLocationServices, } // NewNotFoundError should generate a RequestNotFound response error err := apperror.NewNotFoundError(uuid.Nil, "unable to create ShipmentAddressUpdate") @@ -155,6 +276,7 @@ func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { handler := UpdateShipmentDestinationAddressHandler{ suite.HandlerConfig(), &mockCreator, + vLocationServices, } // NewQueryError should generate an InternalServerError response error err := apperror.NewQueryError("", nil, "unable to reach database") diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index 5a675099271..abe1b64a920 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -1145,3 +1145,31 @@ func GetCustomerContact(customerContacts models.MTOServiceItemCustomerContacts, return models.MTOServiceItemCustomerContact{} } + +// VLocation payload +func VLocation(vLocation *models.VLocation) *primemessages.VLocation { + if vLocation == nil { + return nil + } + if *vLocation == (models.VLocation{}) { + return nil + } + + return &primemessages.VLocation{ + City: vLocation.CityName, + State: vLocation.StateName, + PostalCode: vLocation.UsprZipID, + County: &vLocation.UsprcCountyNm, + UsPostRegionCitiesID: *handlers.FmtUUID(*vLocation.UsPostRegionCitiesID), + } +} + +// VLocations payload +func VLocations(vLocations models.VLocations) primemessages.VLocations { + payload := make(primemessages.VLocations, len(vLocations)) + for i, vLocation := range vLocations { + copyOfVLocation := vLocation + payload[i] = VLocation(©OfVLocation) + } + return payload +} diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index e54c61bd5fb..e94d78b063a 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -1244,3 +1244,30 @@ func (suite *PayloadsSuite) TestMTOServiceItemsPODFSC() { suite.Equal(portLocation.Port.PortCode, internationalFuelSurchargeItem.PortCode) suite.Equal(podfscServiceItem.ReService.Code.String(), internationalFuelSurchargeItem.ReServiceCode) } + +func (suite *PayloadsSuite) TestVLocation() { + suite.Run("correctly maps VLocation with all fields populated", func() { + city := "LOS ANGELES" + state := "CA" + postalCode := "90210" + county := "LOS ANGELES" + usPostRegionCityID := uuid.Must(uuid.NewV4()) + + vLocation := &models.VLocation{ + CityName: city, + StateName: state, + UsprZipID: postalCode, + UsprcCountyNm: county, + UsPostRegionCitiesID: &usPostRegionCityID, + } + + payload := VLocation(vLocation) + + suite.IsType(payload, &primemessages.VLocation{}) + suite.Equal(handlers.FmtUUID(usPostRegionCityID), &payload.UsPostRegionCitiesID, "Expected UsPostRegionCitiesID to match") + suite.Equal(city, payload.City, "Expected City to match") + suite.Equal(state, payload.State, "Expected State to match") + suite.Equal(postalCode, payload.PostalCode, "Expected PostalCode to match") + suite.Equal(county, *(payload.County), "Expected County to match") + }) +} diff --git a/pkg/handlers/primeapi/payloads/payload_to_model.go b/pkg/handlers/primeapi/payloads/payload_to_model.go index 77d0db1e1f6..5f2bfa9bada 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model.go @@ -233,7 +233,7 @@ func PPMShipmentModelFromCreate(ppmShipment *primemessages.CreatePPMShipment) *m StreetAddress1: "Deprecated Endpoint Prime V2", StreetAddress2: models.StringPointer("Endpoint no longer supported"), StreetAddress3: models.StringPointer("Update address field to appropriate values"), - City: "DEPV2", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } @@ -904,3 +904,19 @@ func validateReasonOriginSIT(m primemessages.MTOServiceItemOriginSIT) *validate. } return verrs } + +func VLocationModel(vLocation *primemessages.VLocation) *models.VLocation { + if vLocation == nil { + return nil + } + + usPostRegionCitiesID := uuid.FromStringOrNil(vLocation.UsPostRegionCitiesID.String()) + + return &models.VLocation{ + CityName: vLocation.City, + StateName: vLocation.State, + UsprZipID: vLocation.PostalCode, + UsprcCountyNm: *vLocation.County, + UsPostRegionCitiesID: &usPostRegionCitiesID, + } +} diff --git a/pkg/handlers/primeapi/payloads/payload_to_model_test.go b/pkg/handlers/primeapi/payloads/payload_to_model_test.go index d45071aa7fa..2f18cec241d 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model_test.go @@ -878,3 +878,28 @@ func (suite *PayloadsSuite) TestMTOShipmentModelFromCreate_WithOptionalFields() suite.NotNil(result.DestinationAddress) suite.Equal("456 Main St", result.DestinationAddress.StreetAddress1) } + +func (suite *PayloadsSuite) TestVLocationModel() { + city := "LOS ANGELES" + state := "CA" + postalCode := "90210" + county := "LOS ANGELES" + usPostRegionCityId := uuid.Must(uuid.NewV4()) + + vLocation := &primemessages.VLocation{ + City: city, + State: state, + PostalCode: postalCode, + County: &county, + UsPostRegionCitiesID: strfmt.UUID(usPostRegionCityId.String()), + } + + payload := VLocationModel(vLocation) + + suite.IsType(payload, &models.VLocation{}) + suite.Equal(usPostRegionCityId.String(), payload.UsPostRegionCitiesID.String(), "Expected UsPostRegionCitiesID to match") + suite.Equal(city, payload.CityName, "Expected City to match") + suite.Equal(state, payload.StateName, "Expected State to match") + suite.Equal(postalCode, payload.UsprZipID, "Expected PostalCode to match") + suite.Equal(county, payload.UsprcCountyNm, "Expected County to match") +} diff --git a/pkg/handlers/primeapiv2/api.go b/pkg/handlers/primeapiv2/api.go index 44e8ca916ef..ad9709f5b51 100644 --- a/pkg/handlers/primeapiv2/api.go +++ b/pkg/handlers/primeapiv2/api.go @@ -33,6 +33,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove queryBuilder := query.NewQueryBuilder() moveRouter := move.NewMoveRouter() waf := entitlements.NewWeightAllotmentFetcher() + vLocation := address.NewVLocation() primeSpec, err := loads.Analyzed(primev2api.SwaggerJSON, "") if err != nil { @@ -83,6 +84,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove handlerConfig, shipmentCreator, movetaskorder.NewMoveTaskOrderChecker(), + vLocation, } paymentRequestRecalculator := paymentrequest.NewPaymentRequestRecalculator( paymentrequest.NewPaymentRequestCreator( @@ -113,6 +115,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove primeAPIV2.MtoShipmentUpdateMTOShipmentHandler = UpdateMTOShipmentHandler{ handlerConfig, shipmentUpdater, + vLocation, handlerConfig.DTODPlanner(), } diff --git a/pkg/handlers/primeapiv2/mto_shipment.go b/pkg/handlers/primeapiv2/mto_shipment.go index d4a5e5012da..9484d6a03d8 100644 --- a/pkg/handlers/primeapiv2/mto_shipment.go +++ b/pkg/handlers/primeapiv2/mto_shipment.go @@ -1,6 +1,7 @@ package primeapiv2 import ( + "context" "fmt" "github.com/go-openapi/runtime/middleware" @@ -27,6 +28,7 @@ type CreateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentCreator mtoAvailabilityChecker services.MoveTaskOrderChecker + services.VLocation } // Handle creates the mto shipment @@ -84,6 +86,37 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment isUBFeatureOn = flag.Match } + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + // Return an error if UB shipment is sent while the feature flag is turned off. if !isUBFeatureOn && (*params.Body.ShipmentType == primev2messages.MTOShipmentTypeUNACCOMPANIEDBAGGAGE) { return mtoshipmentops.NewCreateMTOShipmentUnprocessableEntity().WithPayload(payloads.ValidationError( @@ -129,6 +162,45 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment mtoAvailableToPrime, err := h.mtoAvailabilityChecker.MTOAvailableToPrime(appCtx, moveTaskOrderID) if mtoAvailableToPrime { + // check each address prior to creating the shipment to ensure only valid addresses are being used to create the shipment + var addresses []models.Address + + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + if mtoShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PickupAddress) + } + + if mtoShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.DestinationAddress) + } + } else { + if mtoShipment.PPMShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.PickupAddress) + } + + if mtoShipment.PPMShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.DestinationAddress) + } + } + + for _, address := range addresses { + addressSearch := address.City + ", " + address.State + " " + address.PostalCode + err := checkValidAddress(h.VLocation, appCtx, statesToExclude, addressSearch) + + if err != nil { + appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) + switch e := err.(type) { + case apperror.UnprocessableEntityError: + payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewCreateMTOShipmentUnprocessableEntity().WithPayload(payload), err + default: + errStr := e.Error() // we do this because InternalServerError wants a *string + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewCreateMTOShipmentInternalServerError().WithPayload(payload), e + } + } + } + mtoShipment, err = h.ShipmentCreator.CreateShipment(appCtx, mtoShipment) } else if err == nil { appCtx.Logger().Error("primeapiv2.CreateMTOShipmentHandler error - MTO is not available to Prime") @@ -161,6 +233,7 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment payloads.InternalServerError(nil, h.GetTraceIDFromRequest(params.HTTPRequest))), err } } + returnPayload := payloads.MTOShipment(mtoShipment) return mtoshipmentops.NewCreateMTOShipmentOK().WithPayload(returnPayload), nil }) @@ -170,6 +243,7 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment type UpdateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentUpdater + services.VLocation planner route.Planner } @@ -202,8 +276,92 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment // Validate further prime restrictions on model mtoShipment.ShipmentType = dbShipment.ShipmentType - appCtx.Logger().Info("primeapi.UpdateMTOShipmentHandler info", zap.String("pointOfContact", params.Body.PointOfContact)) + + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + // check each address prior to updating the shipment to ensure only valid addresses are being used + var addresses []models.Address + + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + if mtoShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PickupAddress) + } + + if mtoShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryPickupAddress) + } + + if mtoShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.DestinationAddress) + } + + if mtoShipment.SecondaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryDeliveryAddress) + } + } else { + if mtoShipment.PPMShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.PickupAddress) + } + + if mtoShipment.PPMShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryPickupAddress) + } + + if mtoShipment.PPMShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.DestinationAddress) + } + + if mtoShipment.PPMShipment.SecondaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryDestinationAddress) + } + } + + for _, address := range addresses { + addressSearch := address.City + ", " + address.State + " " + address.PostalCode + err := checkValidAddress(h.VLocation, appCtx, statesToExclude, addressSearch) + + if err != nil { + appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) + switch e := err.(type) { + case apperror.UnprocessableEntityError: + payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewUpdateMTOShipmentUnprocessableEntity().WithPayload(payload), e + default: + errStr := e.Error() // we do this because InternalServerError wants a *string + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewUpdateMTOShipmentInternalServerError().WithPayload(payload), e + } + } + } + mtoShipment, err = h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "prime-v2") if err != nil { appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) @@ -229,3 +387,18 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment return mtoshipmentops.NewUpdateMTOShipmentOK().WithPayload(mtoShipmentPayload), nil }) } + +func checkValidAddress(vLocation services.VLocation, appCtx appcontext.AppContext, statesToExclude []string, addressSearch string) error { + locationList, err := vLocation.GetLocationsByZipCityState(appCtx, addressSearch, statesToExclude, true) + + if err != nil { + serverError := apperror.NewInternalServerError("Error searching for address") + return serverError + } else if len(*locationList) == 0 { + unprocessableErr := apperror.NewUnprocessableEntityError( + fmt.Sprintf("primeapi.UpdateShipmentDestinationAddress: could not find the provided location: %s", addressSearch)) + return unprocessableErr + } + + return nil +} diff --git a/pkg/handlers/primeapiv2/mto_shipment_test.go b/pkg/handlers/primeapiv2/mto_shipment_test.go index 83134e3b522..73f799f0270 100644 --- a/pkg/handlers/primeapiv2/mto_shipment_test.go +++ b/pkg/handlers/primeapiv2/mto_shipment_test.go @@ -54,6 +54,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, false, ).Return(400, nil) + vLocationServices := address.NewVLocation() setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { mockCreator := &mocks.SignedCertificationCreator{} @@ -142,6 +143,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { handlerConfig, shipmentCreator, mtoChecker, + vLocationServices, } // Make stubbed addresses just to collect address data for payload @@ -442,6 +444,47 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.Equal(handlers.InternalServerErrMessage, *errResponse.Payload.Title, "Payload title is wrong") }) + suite.Run("POST failure - 500 GetLocationsByZipCityState", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Failure GetLocationsByZipCityState returns internal server error + handler, move := setupTestData(false) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev2messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev2messages.NewMTOShipmentType(primev2messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev2messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev2messages.Address }{destinationAddress}, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + expectedError := models.ErrFetchNotFound + vLocationFetcher := &mocks.VLocation{} + vLocationFetcher.On("GetLocationsByZipCityState", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil, expectedError).Once() + + handler.VLocation = vLocationFetcher + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentInternalServerError{}, response) + }) + suite.Run("POST failure - 422 -- Bad agent IDs set on shipment", func() { // Under Test: CreateMTOShipmentHandler // Setup: Create a shipment with an agent that doesn't really exist, handler should return unprocessable entity diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model.go b/pkg/handlers/primeapiv2/payloads/payload_to_model.go index b57e5ca541b..2b2f5e420c8 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model.go @@ -276,7 +276,7 @@ func PPMShipmentModelFromCreate(ppmShipment *primev2messages.CreatePPMShipment) StreetAddress1: "Deprecated Endpoint Prime V1", StreetAddress2: models.StringPointer("Endpoint no longer supported"), StreetAddress3: models.StringPointer("Update address field to appropriate values"), - City: "DEPV1", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } diff --git a/pkg/handlers/primeapiv3/api.go b/pkg/handlers/primeapiv3/api.go index e46f49c8ad1..a8bdf1c6e0a 100644 --- a/pkg/handlers/primeapiv3/api.go +++ b/pkg/handlers/primeapiv3/api.go @@ -32,6 +32,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove fetcher := fetch.NewFetcher(builder) queryBuilder := query.NewQueryBuilder() moveRouter := move.NewMoveRouter() + vLocation := address.NewVLocation() waf := entitlements.NewWeightAllotmentFetcher() primeSpec, err := loads.Analyzed(primev3api.SwaggerJSON, "") @@ -73,6 +74,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove handlerConfig, shipmentCreator, movetaskorder.NewMoveTaskOrderChecker(), + vLocation, } paymentRequestRecalculator := paymentrequest.NewPaymentRequestRecalculator( paymentrequest.NewPaymentRequestCreator( @@ -115,6 +117,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove handlerConfig, shipmentUpdater, handlerConfig.DTODPlanner(), + vLocation, } return primeAPIV3 diff --git a/pkg/handlers/primeapiv3/mto_shipment.go b/pkg/handlers/primeapiv3/mto_shipment.go index d2f6221ac9f..8c893412669 100644 --- a/pkg/handlers/primeapiv3/mto_shipment.go +++ b/pkg/handlers/primeapiv3/mto_shipment.go @@ -1,6 +1,7 @@ package primeapiv3 import ( + "context" "fmt" "github.com/go-openapi/runtime/middleware" @@ -27,6 +28,7 @@ type CreateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentCreator mtoAvailabilityChecker services.MoveTaskOrderChecker + services.VLocation } // Handle creates the mto shipment @@ -90,6 +92,35 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment "Unaccompanied baggage shipments can't be created unless the unaccompanied_baggage feature flag is enabled.", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), nil } + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + for _, mtoServiceItem := range params.Body.MtoServiceItems() { // restrict creation to a list if _, ok := CreateableServiceItemMap[mtoServiceItem.ModelType()]; !ok { @@ -129,6 +160,77 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment mtoAvailableToPrime, err := h.mtoAvailabilityChecker.MTOAvailableToPrime(appCtx, moveTaskOrderID) if mtoAvailableToPrime { + // check each address prior to creating the shipment to ensure only valid addresses are being used to create the shipment + var addresses []models.Address + + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + if mtoShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PickupAddress) + } + + if mtoShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.DestinationAddress) + } + + if mtoShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryPickupAddress) + } + + if mtoShipment.TertiaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.TertiaryPickupAddress) + } + + if mtoShipment.SecondaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryDeliveryAddress) + } + + if mtoShipment.TertiaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.TertiaryDeliveryAddress) + } + } else { + if mtoShipment.PPMShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.PickupAddress) + } + + if mtoShipment.PPMShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.DestinationAddress) + } + + if mtoShipment.PPMShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryPickupAddress) + } + + if mtoShipment.PPMShipment.TertiaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.TertiaryPickupAddress) + } + + if mtoShipment.PPMShipment.SecondaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryDestinationAddress) + } + + if mtoShipment.PPMShipment.TertiaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.TertiaryDestinationAddress) + } + } + + for _, address := range addresses { + addressSearch := address.City + ", " + address.State + " " + address.PostalCode + err := checkValidAddress(h.VLocation, appCtx, statesToExclude, addressSearch) + + if err != nil { + appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) + switch e := err.(type) { + case apperror.UnprocessableEntityError: + payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewCreateMTOShipmentUnprocessableEntity().WithPayload(payload), err + default: + errStr := e.Error() // we do this because InternalServerError wants a *string + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewCreateMTOShipmentInternalServerError().WithPayload(payload), e + } + } + } + mtoShipment, err = h.ShipmentCreator.CreateShipment(appCtx, mtoShipment) } else if err == nil { appCtx.Logger().Error("primeapiv3.CreateMTOShipmentHandler error - MTO is not available to Prime") @@ -166,11 +268,27 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment }) } +func checkValidAddress(vLocation services.VLocation, appCtx appcontext.AppContext, statesToExclude []string, addressSearch string) error { + locationList, err := vLocation.GetLocationsByZipCityState(appCtx, addressSearch, statesToExclude, true) + + if err != nil { + serverError := apperror.NewInternalServerError("Error searching for address") + return serverError + } else if len(*locationList) == 0 { + unprocessableErr := apperror.NewUnprocessableEntityError( + fmt.Sprintf("primeapi.UpdateShipmentDestinationAddress: could not find the provided location: %s", addressSearch)) + return unprocessableErr + } + + return nil +} + // UpdateMTOShipmentHandler is the handler to update MTO shipments type UpdateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentUpdater planner route.Planner + services.VLocation } // Handle handler that updates a mto shipment @@ -205,8 +323,108 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment // Validate further prime restrictions on model mtoShipment.ShipmentType = dbShipment.ShipmentType - appCtx.Logger().Info("primeapi.UpdateMTOShipmentHandler info", zap.String("pointOfContact", params.Body.PointOfContact)) + + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + // check each address prior to updating the shipment to ensure only valid addresses are being used + var addresses []models.Address + + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + if mtoShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PickupAddress) + } + + if mtoShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryPickupAddress) + } + + if mtoShipment.TertiaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.TertiaryPickupAddress) + } + + if mtoShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.DestinationAddress) + } + + if mtoShipment.SecondaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryDeliveryAddress) + } + + if mtoShipment.TertiaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.TertiaryDeliveryAddress) + } + } else { + if mtoShipment.PPMShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.PickupAddress) + } + + if mtoShipment.PPMShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryPickupAddress) + } + + if mtoShipment.PPMShipment.TertiaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.TertiaryPickupAddress) + } + + if mtoShipment.PPMShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.DestinationAddress) + } + + if mtoShipment.PPMShipment.SecondaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryDestinationAddress) + } + + if mtoShipment.PPMShipment.TertiaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.TertiaryDestinationAddress) + } + } + + for _, address := range addresses { + addressSearch := address.City + ", " + address.State + " " + address.PostalCode + err := checkValidAddress(h.VLocation, appCtx, statesToExclude, addressSearch) + + if err != nil { + appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) + switch e := err.(type) { + case apperror.UnprocessableEntityError: + payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewUpdateMTOShipmentUnprocessableEntity().WithPayload(payload), e + default: + errStr := e.Error() // we do this because InternalServerError wants a *string + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewUpdateMTOShipmentInternalServerError().WithPayload(payload), e + } + } + } + mtoShipment, err = h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "prime-v3") if err != nil { appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) diff --git a/pkg/handlers/primeapiv3/mto_shipment_test.go b/pkg/handlers/primeapiv3/mto_shipment_test.go index 085d9eab254..0082f97688d 100644 --- a/pkg/handlers/primeapiv3/mto_shipment_test.go +++ b/pkg/handlers/primeapiv3/mto_shipment_test.go @@ -61,6 +61,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, false, ).Return(400, nil) + vLocationServices := address.NewVLocation() setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { mockCreator := &mocks.SignedCertificationCreator{} @@ -114,8 +115,68 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mtoShipmentUpdater := mtoshipment.NewPrimeMTOShipmentUpdater(builder, fetcher, planner, moveRouter, moveWeights, suite.TestNotificationSender(), paymentRequestShipmentRecalculator, addressUpdater, addressCreator) shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) - setupTestData := func(boatFeatureFlag bool, ubFeatureFlag bool) (CreateMTOShipmentHandler, models.Move) { + setupAddresses := func() { + // Make stubbed addresses just to collect address data for payload + newAddress := factory.BuildAddress(nil, []factory.Customization{ + { + Model: models.Address{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + pickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + secondaryPickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryPickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + newAddress = factory.BuildAddress(nil, nil, []factory.Trait{factory.GetTraitAddress2}) + destinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + secondaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + } + setupTestData := func(boatFeatureFlag bool, ubFeatureFlag bool) (CreateMTOShipmentHandler, models.Move) { + vLocationServices := address.NewVLocation() move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) handlerConfig := suite.HandlerConfig() expectedFeatureFlag := services.FeatureFlag{ @@ -197,67 +258,26 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { handlerConfig, shipmentCreator, mtoChecker, + vLocationServices, } - // Make stubbed addresses just to collect address data for payload - newAddress := factory.BuildAddress(nil, []factory.Customization{ - { - Model: models.Address{ - ID: uuid.Must(uuid.NewV4()), - }, - }, - }, nil) - pickupAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - secondaryPickupAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - tertiaryPickupAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - newAddress = factory.BuildAddress(nil, nil, []factory.Trait{factory.GetTraitAddress2}) - destinationAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - secondaryDestinationAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - tertiaryDestinationAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } + setupAddresses() return handler, move + } + + setupTestDataWithoutFF := func() (CreateMTOShipmentHandler, models.Move) { + vLocationServices := address.NewVLocation() + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + handler := CreateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentCreator, + mtoChecker, + vLocationServices, + } + + setupAddresses() + return handler, move } suite.Run("Successful POST - Integration Test", func() { @@ -363,20 +383,20 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { address1 := models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } address2 := models.Address{ StreetAddress1: "some address", - City: "city", + City: "Scott Afb", State: "IL", PostalCode: "62225", } address3 := models.Address{ StreetAddress1: "some address", - City: "city", + City: "Suffolk", State: "VA", PostalCode: "23435", } @@ -552,6 +572,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.HandlerConfig(), shipmentUpdater, planner, + vLocationServices, } patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", createdPPM.ShipmentID.String()), nil) @@ -712,13 +733,13 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { address1 := models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } addressWithEmptyStreet1 := models.Address{ StreetAddress1: "", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } @@ -834,6 +855,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.HandlerConfig(), shipmentUpdater, planner, + vLocationServices, } patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", createdPPM.ShipmentID.String()), nil) @@ -856,7 +878,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { // as empty on the server side. // ************************************************************************************* ppmDestinationAddressOptionalStreet1ContainingWhitespaces := primev3messages.PPMDestinationAddress{ - City: models.StringPointer("SomeCity"), + City: models.StringPointer("Beverly Hills"), Country: models.StringPointer("US"), PostalCode: models.StringPointer("90210"), State: models.StringPointer("CA"), @@ -1554,6 +1576,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.HandlerConfig(), shipmentUpdater, planner, + vLocationServices, } now := time.Now() @@ -1561,7 +1584,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1570,7 +1593,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1630,6 +1653,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.HandlerConfig(), shipmentUpdater, planner, + vLocationServices, } move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{}, nil) @@ -1679,6 +1703,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.HandlerConfig(), shipmentUpdater, planner, + vLocationServices, } now := time.Now() @@ -1686,7 +1711,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1695,7 +1720,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1704,7 +1729,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1713,7 +1738,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1760,6 +1785,1063 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { response := patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, response) }) + + suite.Run("PATCH failure - Invalid address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an invalid zip + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + tertiaryAddress := GetTestAddress() + tertiaryAddress.PostalCode = handlers.FmtString("99999") + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{tertiaryAddress}, + } + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentUnprocessableEntity{}, errResponse) + }) + + suite.Run("PATCH failure - Internal Server error GetLocationsByZipCityState", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Mock location to return an error + // Expected: 500 Response returned + handler, move := setupTestData(false, true) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + expectedError := models.ErrFetchNotFound + vLocationFetcher := &mocks.VLocation{} + vLocationFetcher.On("GetLocationsByZipCityState", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil, expectedError).Once() + handler.VLocation = vLocationFetcher + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentInternalServerError{}, response) + }) + + suite.Run("PATCH success - valid AK address FF is on", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an valid AK address but turn FF on + // Expected: 200 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + alaskaAddress := primev3messages.Address{ + City: handlers.FmtString("Juneau"), + PostalCode: handlers.FmtString("99801"), + State: handlers.FmtString("AK"), + StreetAddress1: handlers.FmtString("Some AK street"), + } + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{alaskaAddress}, + } + + // setting the AK flag to true + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: true, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + patchHandler.HandlerConfig = handlerConfig + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, errResponse) + }) + + suite.Run("PATCH success - valid HI address FF is on", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an valid HI address but turn FF on + // Expected: 200 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + hawaiiAddress := primev3messages.Address{ + City: handlers.FmtString("HONOLULU"), + PostalCode: handlers.FmtString("96835"), + State: handlers.FmtString("HI"), + StreetAddress1: handlers.FmtString("Some HI street"), + } + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{hawaiiAddress}, + } + + // setting the HI flag to true + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: true, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + patchHandler.HandlerConfig = handlerConfig + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, errResponse) + }) + + suite.Run("PATCH failure - valid AK address FF is off", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an valid AK address but turn FF off + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + alaskaAddress := primev3messages.Address{ + City: handlers.FmtString("Juneau"), + PostalCode: handlers.FmtString("99801"), + State: handlers.FmtString("AK"), + StreetAddress1: handlers.FmtString("Some AK street"), + } + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{alaskaAddress}, + } + + // setting the AK flag to false + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + patchHandler.HandlerConfig = handlerConfig + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentUnprocessableEntity{}, errResponse) + }) + + suite.Run("PATCH failure - valid HI address FF is off", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an valid HI address but turn FF off + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + hawaiiAddress := primev3messages.Address{ + City: handlers.FmtString("HONOLULU"), + PostalCode: handlers.FmtString("HI"), + State: handlers.FmtString("96835"), + StreetAddress1: handlers.FmtString("Some HI street"), + } + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{hawaiiAddress}, + } + + // setting the HI flag to false + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + patchHandler.HandlerConfig = handlerConfig + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentUnprocessableEntity{}, errResponse) + }) + + suite.Run("POST failure - 422 - Invalid address", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Failure, invalid address + handler, move := setupTestDataWithoutFF() + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // set bad data for address so the validation fails + params.Body.PickupAddress.City = handlers.FmtString("Bad City") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + }) + + suite.Run("POST failure - 422 - Doesn't return results for valid AK address if FF returns false", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Failure, valid AK address but AK FF off, no results + handler, move := setupTestDataWithoutFF() + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlag", + mock.Anything, // context.Context + mock.Anything, // *zap.Logger + mock.AnythingOfType("string"), // entityID (userID) + mock.AnythingOfType("string"), // key + mock.Anything, // flagContext (map[string]string) + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + params.Body.PickupAddress.City = handlers.FmtString("JUNEAU") + params.Body.PickupAddress.State = handlers.FmtString("AK") + params.Body.PickupAddress.PostalCode = handlers.FmtString("99801") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + }) + + suite.Run("POST failure - 422 - Doesn't return results for valid HI address if FF returns false", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Failure, valid HI address but HI FF off, no results + handler, move := setupTestDataWithoutFF() + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // setting the HI flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlag", + mock.Anything, // context.Context + mock.Anything, // *zap.Logger + mock.AnythingOfType("string"), // entityID (userID) + mock.AnythingOfType("string"), // key + mock.Anything, // flagContext (map[string]string) + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + params.Body.PickupAddress.City = handlers.FmtString("HONOLULU") + params.Body.PickupAddress.State = handlers.FmtString("HI") + params.Body.PickupAddress.PostalCode = handlers.FmtString("96835") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + }) + + suite.Run("POST success - 200 - valid AK address if FF ON", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Success, valid AK address AK FF ON + handler, move := setupTestData(false, true) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: true, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlag", + mock.Anything, // context.Context + mock.Anything, // *zap.Logger + mock.AnythingOfType("string"), // entityID (userID) + mock.AnythingOfType("string"), // key + mock.Anything, // flagContext (map[string]string) + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + params.Body.PickupAddress.City = handlers.FmtString("JUNEAU") + params.Body.PickupAddress.State = handlers.FmtString("AK") + params.Body.PickupAddress.PostalCode = handlers.FmtString("99801") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentOK{}, response) + }) + + suite.Run("POST success - 200 - valid HI address if FF ON", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Success, valid HI address HI FF ON + handler, move := setupTestData(false, true) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // setting the HI flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: true, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlag", + mock.Anything, // context.Context + mock.Anything, // *zap.Logger + mock.AnythingOfType("string"), // entityID (userID) + mock.AnythingOfType("string"), // key + mock.Anything, // flagContext (map[string]string) + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + params.Body.PickupAddress.City = handlers.FmtString("HONOLULU") + params.Body.PickupAddress.State = handlers.FmtString("HI") + params.Body.PickupAddress.PostalCode = handlers.FmtString("96835") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentOK{}, response) + }) + + suite.Run("Failure POST - 422 - Invalid address (PPM)", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create a PPM shipment on an available move + // Expected: Failure, returns an invalid address error + handler, move := setupTestDataWithoutFF() + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + counselorRemarks := "Some counselor remarks" + expectedDepartureDate := time.Now().AddDate(0, 0, 10) + sitExpected := true + sitLocation := primev3messages.SITLocationTypeDESTINATION + sitEstimatedWeight := unit.Pound(1500) + sitEstimatedEntryDate := expectedDepartureDate.AddDate(0, 0, 5) + sitEstimatedDepartureDate := sitEstimatedEntryDate.AddDate(0, 0, 20) + estimatedWeight := unit.Pound(3200) + hasProGear := true + proGearWeight := unit.Pound(400) + spouseProGearWeight := unit.Pound(250) + estimatedIncentive := 123456 + sitEstimatedCost := 67500 + + address1 := models.Address{ + StreetAddress1: "some address", + City: "Bad City", + State: "CA", + PostalCode: "90210", + } + + expectedPickupAddress := address1 + pickupAddress = primev3messages.Address{ + City: &expectedPickupAddress.City, + PostalCode: &expectedPickupAddress.PostalCode, + State: &expectedPickupAddress.State, + StreetAddress1: &expectedPickupAddress.StreetAddress1, + StreetAddress2: expectedPickupAddress.StreetAddress2, + StreetAddress3: expectedPickupAddress.StreetAddress3, + } + + expectedDestinationAddress := address1 + destinationAddress = primev3messages.Address{ + City: &expectedDestinationAddress.City, + PostalCode: &expectedDestinationAddress.PostalCode, + State: &expectedDestinationAddress.State, + StreetAddress1: &expectedDestinationAddress.StreetAddress1, + StreetAddress2: expectedDestinationAddress.StreetAddress2, + StreetAddress3: expectedDestinationAddress.StreetAddress3, + } + ppmDestinationAddress = primev3messages.PPMDestinationAddress{ + City: &expectedDestinationAddress.City, + PostalCode: &expectedDestinationAddress.PostalCode, + State: &expectedDestinationAddress.State, + StreetAddress1: &expectedDestinationAddress.StreetAddress1, + StreetAddress2: expectedDestinationAddress.StreetAddress2, + StreetAddress3: expectedDestinationAddress.StreetAddress3, + } + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypePPM), + CounselorRemarks: &counselorRemarks, + PpmShipment: &primev3messages.CreatePPMShipment{ + ExpectedDepartureDate: handlers.FmtDate(expectedDepartureDate), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{ppmDestinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + SitExpected: &sitExpected, + SitLocation: &sitLocation, + SitEstimatedWeight: handlers.FmtPoundPtr(&sitEstimatedWeight), + SitEstimatedEntryDate: handlers.FmtDate(sitEstimatedEntryDate), + SitEstimatedDepartureDate: handlers.FmtDate(sitEstimatedDepartureDate), + EstimatedWeight: handlers.FmtPoundPtr(&estimatedWeight), + HasProGear: &hasProGear, + ProGearWeight: handlers.FmtPoundPtr(&proGearWeight), + SpouseProGearWeight: handlers.FmtPoundPtr(&spouseProGearWeight), + }, + }, + } + + ppmEstimator.On("EstimateIncentiveWithDefaultChecks", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + }) } func GetTestAddress() primev3messages.Address { newAddress := factory.BuildAddress(nil, []factory.Customization{ diff --git a/pkg/services/address.go b/pkg/services/address.go index a1b25f17448..4537083bad3 100644 --- a/pkg/services/address.go +++ b/pkg/services/address.go @@ -15,5 +15,5 @@ type AddressUpdater interface { //go:generate mockery --name VLocation type VLocation interface { - GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string) (*models.VLocations, error) + GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string, exactMatch ...bool) (*models.VLocations, error) } diff --git a/pkg/services/address/address_lookup.go b/pkg/services/address/address_lookup.go index a258ab29dfb..1c12c4ed277 100644 --- a/pkg/services/address/address_lookup.go +++ b/pkg/services/address/address_lookup.go @@ -6,6 +6,7 @@ import ( "regexp" "strings" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" "github.com/pkg/errors" @@ -22,8 +23,14 @@ func NewVLocation() services.VLocation { return &vLocation{} } -func (o vLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string) (*models.VLocations, error) { - locationList, err := FindLocationsByZipCity(appCtx, search, exclusionStateFilters) +func (o vLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string, exactMatch ...bool) (*models.VLocations, error) { + exact := false + + if len(exactMatch) > 0 { + exact = true + } + + locationList, err := FindLocationsByZipCity(appCtx, search, exclusionStateFilters, exact) if err != nil { switch err { @@ -42,7 +49,7 @@ func (o vLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, sear // to determine when the state and postal code need to be parsed from the search string // If there is only one result and no comma and the search string is all numbers we then search // using the entered postal code rather than city name -func FindLocationsByZipCity(appCtx appcontext.AppContext, search string, exclusionStateFilters []string) (models.VLocations, error) { +func FindLocationsByZipCity(appCtx appcontext.AppContext, search string, exclusionStateFilters []string, exactMatch bool) (models.VLocations, error) { var locationList []models.VLocation searchSlice := strings.Split(search, ",") city := "" @@ -67,8 +74,14 @@ func FindLocationsByZipCity(appCtx appcontext.AppContext, search string, exclusi } sqlQuery := `SELECT vl.city_name, vl.state, vl.usprc_county_nm, vl.uspr_zip_id, vl.uprc_id - FROM v_locations vl where vl.uspr_zip_id like ? AND - vl.city_name like upper(?) AND vl.state like upper(?)` + FROM v_locations vl where vl.uspr_zip_id like ? AND + vl.city_name like upper(?) AND vl.state like upper(?)` + + if exactMatch { + sqlQuery = `SELECT vl.city_name, vl.state, vl.usprc_county_nm, vl.uspr_zip_id, vl.uprc_id + FROM v_locations vl where vl.uspr_zip_id = ? AND + vl.city_name = upper(?) AND vl.state = upper(?)` + } // apply filter to exclude specific states if provided for _, value := range exclusionStateFilters { @@ -76,8 +89,15 @@ func FindLocationsByZipCity(appCtx appcontext.AppContext, search string, exclusi } sqlQuery += ` limit 30` + var query *pop.Query + + // we only want to add an extra % to the strings if we are using the LIKE in the query + if exactMatch { + query = appCtx.DB().RawQuery(sqlQuery, postalCode, city, state) + } else { + query = appCtx.DB().RawQuery(sqlQuery, fmt.Sprintf("%s%%", postalCode), fmt.Sprintf("%s%%", city), fmt.Sprintf("%s%%", state)) + } - query := appCtx.DB().RawQuery(sqlQuery, fmt.Sprintf("%s%%", postalCode), fmt.Sprintf("%s%%", city), fmt.Sprintf("%s%%", state)) if err := query.All(&locationList); err != nil { if errors.Cause(err).Error() != models.RecordNotFoundErrorString { return locationList, err diff --git a/pkg/services/mocks/VLocation.go b/pkg/services/mocks/VLocation.go index 162924e8464..7c932ff7910 100644 --- a/pkg/services/mocks/VLocation.go +++ b/pkg/services/mocks/VLocation.go @@ -14,9 +14,16 @@ type VLocation struct { mock.Mock } -// GetLocationsByZipCityState provides a mock function with given fields: appCtx, search, exclusionStateFilters -func (_m *VLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string) (*models.VLocations, error) { - ret := _m.Called(appCtx, search, exclusionStateFilters) +// GetLocationsByZipCityState provides a mock function with given fields: appCtx, search, exclusionStateFilters, exactMatch +func (_m *VLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string, exactMatch ...bool) (*models.VLocations, error) { + _va := make([]interface{}, len(exactMatch)) + for _i := range exactMatch { + _va[_i] = exactMatch[_i] + } + var _ca []interface{} + _ca = append(_ca, appCtx, search, exclusionStateFilters) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) if len(ret) == 0 { panic("no return value specified for GetLocationsByZipCityState") @@ -24,19 +31,19 @@ func (_m *VLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, se var r0 *models.VLocations var r1 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []string) (*models.VLocations, error)); ok { - return rf(appCtx, search, exclusionStateFilters) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []string, ...bool) (*models.VLocations, error)); ok { + return rf(appCtx, search, exclusionStateFilters, exactMatch...) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []string) *models.VLocations); ok { - r0 = rf(appCtx, search, exclusionStateFilters) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []string, ...bool) *models.VLocations); ok { + r0 = rf(appCtx, search, exclusionStateFilters, exactMatch...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.VLocations) } } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, []string) error); ok { - r1 = rf(appCtx, search, exclusionStateFilters) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, []string, ...bool) error); ok { + r1 = rf(appCtx, search, exclusionStateFilters, exactMatch...) } else { r1 = ret.Error(1) } diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester.go b/pkg/services/shipment_address_update/shipment_address_update_requester.go index 0cb8ba3c123..8f4b75973e6 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -281,6 +281,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap if eTag != etag.GenerateEtag(shipment.UpdatedAt) { return nil, apperror.NewPreconditionFailedError(shipmentID, nil) } + isInternationalShipment := shipment.MarketCode == models.MarketCodeInternational shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipment) diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index 4b9fb350f75..a26a330da88 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -1451,6 +1451,31 @@ paths: $ref: '#/responses/UnprocessableEntity' '500': $ref: '#/responses/ServerError' + /addresses/zip-city-lookup/{search}: + get: + summary: Returns city, state, postal code, and county associated with the specified full/partial postal code or city state string + description: Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code. + operationId: getLocationByZipCityState + tags: + - addresses + parameters: + - in: path + name: search + type: string + required: true + responses: + '200': + description: the requested list of city, state, county, and postal code matches + schema: + $ref: "#/definitions/VLocations" + '400': + $ref: '#/responses/InvalidRequest' + '403': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '500': + $ref: '#/responses/ServerError' definitions: Amendments: description: > @@ -2243,6 +2268,10 @@ definitions: type: string x-nullable: true x-omitempty: false + VLocations: + type: array + items: + $ref: "definitions/VLocation.yaml" responses: InvalidRequest: description: The request payload is invalid. diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 4d38e4b662f..bb6f1579545 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -1848,6 +1848,36 @@ paths: $ref: '#/responses/UnprocessableEntity' '500': $ref: '#/responses/ServerError' + /addresses/zip-city-lookup/{search}: + get: + summary: >- + Returns city, state, postal code, and county associated with the + specified full/partial postal code or city state string + description: >- + Find by API using full/partial postal code or city name that returns an + us_post_region_cities json object containing city, state, county and + postal code. + operationId: getLocationByZipCityState + tags: + - addresses + parameters: + - in: path + name: search + type: string + required: true + responses: + '200': + description: the requested list of city, state, county, and postal code matches + schema: + $ref: '#/definitions/VLocations' + '400': + $ref: '#/responses/InvalidRequest' + '403': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '500': + $ref: '#/responses/ServerError' definitions: Amendments: description: | @@ -3244,6 +3274,10 @@ definitions: type: string x-nullable: true x-omitempty: false + VLocations: + type: array + items: + $ref: '#/definitions/VLocation' ClientError: type: object properties: @@ -4948,6 +4982,136 @@ definitions: type: string required: - invalidFields + VLocation: + description: A postal code, city, and state lookup + type: object + properties: + city: + type: string + example: Anytown + title: City + state: + title: State + type: string + x-display-value: + AL: AL + AK: AK + AR: AR + AZ: AZ + CA: CA + CO: CO + CT: CT + DC: DC + DE: DE + FL: FL + GA: GA + HI: HI + IA: IA + ID: ID + IL: IL + IN: IN + KS: KS + KY: KY + LA: LA + MA: MA + MD: MD + ME: ME + MI: MI + MN: MN + MO: MO + MS: MS + MT: MT + NC: NC + ND: ND + NE: NE + NH: NH + NJ: NJ + NM: NM + NV: NV + NY: NY + OH: OH + OK: OK + OR: OR + PA: PA + RI: RI + SC: SC + SD: SD + TN: TN + TX: TX + UT: UT + VA: VA + VT: VT + WA: WA + WI: WI + WV: WV + WY: WY + enum: + - AL + - AK + - AR + - AZ + - CA + - CO + - CT + - DC + - DE + - FL + - GA + - HI + - IA + - ID + - IL + - IN + - KS + - KY + - LA + - MA + - MD + - ME + - MI + - MN + - MO + - MS + - MT + - NC + - ND + - NE + - NH + - NJ + - NM + - NV + - NY + - OH + - OK + - OR + - PA + - RI + - SC + - SD + - TN + - TX + - UT + - VA + - VT + - WA + - WI + - WV + - WY + postalCode: + type: string + format: zip + title: ZIP + example: '90210' + pattern: ^(\d{5}?)$ + county: + type: string + title: County + x-nullable: true + example: LOS ANGELES + usPostRegionCitiesID: + type: string + format: uuid + example: c56a4180-65aa-42ec-a945-5fd21dec0538 ReServiceCode: type: string description: >