Skip to content

Commit

Permalink
Merge pull request #14459 from transcom/MAIN-B-21505
Browse files Browse the repository at this point in the history
MAIN-B-21505
  • Loading branch information
deandreJones authored Dec 30, 2024
2 parents c6ae1bf + 8624ada commit 6fb9a16
Show file tree
Hide file tree
Showing 68 changed files with 3,717 additions and 62 deletions.
1 change: 1 addition & 0 deletions migrations/app/migrations_manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,7 @@
20241119163933_set_inactive_NSRA15_oconus_rate_areas.up.sql
20241120221040_change_port_location_fk_to_correct_table.up.sql
20241122155416_total_dependents_calculation.up.sql
20241122220314_create_port_and_port_location_test_data.up.sql
20241126222026_add_sort_column_to_re_service_items.up.sql
20241127133504_add_indexes_speed_up_counseling_offices.up.sql
20241202163059_create_test_sequence_dev_env.up.sql
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--Blow away existing port data as it is not valid
DELETE FROM ports;
DELETE FROM port_locations;

INSERT INTO ports (id,port_code,port_type,port_name,created_at,updated_at) VALUES
('b393e767-0703-43c6-b1e9-12541dc3f15b'::uuid,'PDX','A','PORTLAND INTL','2024-11-22 14:52:24.739738','2024-11-22 14:52:24.739738'),
('48569958-2889-41e5-8101-82c56ec48430'::uuid,'SEA','A','SEATTLE TACOMA INTL','2024-11-22 14:52:24.739738','2024-11-22 14:52:24.739738'),
('aa33487c-0637-4c1a-b260-d12ed1dbcc39'::uuid,'TCM','A','MCCHORD FLD','2024-11-22 14:52:24.739738','2024-11-22 14:52:24.739738'),
('5269b1ec-3478-414c-bdb1-c8db12ccd0b5'::uuid,'4G1','S','ASTORIA, OREGON','2024-11-22 15:09:55.189787','2024-11-22 15:09:55.189787'),
('30dc0c1f-c2e2-4bf1-b9fd-5c24e21034d8'::uuid,'4H6','S','PORTLAND, OREGON','2024-11-22 15:09:55.189787','2024-11-22 15:09:55.189787'),
('54a32fc7-c2a9-4ba6-b8dd-99a710730fda'::uuid,'4K1','S','COOS BAY, OREGON','2024-11-22 15:09:55.189787','2024-11-22 15:09:55.189787'),
('581da305-3a67-4311-b7d5-1fec19b221f9'::uuid,'4B2','S','ANACORTES, NORTH WEST WASHINGTON','2024-11-22 15:09:55.189787','2024-11-22 15:09:55.189787'),
('b8dc9c80-bd6d-4a2d-8bd3-f025ddef95fc'::uuid,'4C1','S','PORT ANGELES, WHIDBEY ISLAND','2024-11-22 15:09:55.189787','2024-11-22 15:09:55.189787'),
('5355255c-3c9c-4d86-af2a-4b63004dee24'::uuid,'4DL','S','SEATTLE SDDC TERMINAL, PUGET SOUND','2024-11-22 15:09:55.189787','2024-11-22 15:09:55.189787'),
('a9c24841-64e0-4e12-a872-ee7fb73a6300'::uuid,'4EA','S','TACOMA NAVAL STATION, WASHINGTON, PUGET SOUND','2024-11-22 15:09:55.189787','2024-11-22 15:09:55.189787'),
('e4ba8867-bf70-43c7-b143-0f719ed0121f'::uuid,'2902','B','NEWPORT, OR','2024-10-17 13:50:08.046274','2024-10-17 13:50:08.046274'),
('ae0667e0-b497-4dd3-bfb4-a20f457104d1'::uuid,'2904','B','PORTLAND, OR','2024-10-17 13:50:08.046274','2024-10-17 13:50:08.046274'),
('e21ce23a-c261-4b54-a711-6dee965bcfe9'::uuid,'3001','B','SEATTLE, WA','2024-10-17 13:50:08.046274','2024-10-17 13:50:08.046274'),
('27ce681f-6dd6-45fd-b3ab-9a5b459b814b'::uuid,'3002','B','TACOMA, WA','2024-10-17 13:50:08.046274','2024-10-17 13:50:08.046274');

INSERT INTO port_locations (id,port_id,cities_id,us_post_region_cities_id,country_id,is_active,created_at,updated_at) VALUES
('b6e94f5b-33c0-43f3-b960-7c7b2a4ee5fc'::uuid,'b393e767-0703-43c6-b1e9-12541dc3f15b'::uuid,'026f9d81-7715-424e-b368-bfc7a673aaa2'::uuid,'40bf95e4-72f1-4d75-8bc5-5842f7f26c94'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:04:41.941865','2024-11-22 15:04:41.941865'),
('43efe04d-0ca0-4571-9aa1-747193285d9e'::uuid,'48569958-2889-41e5-8101-82c56ec48430'::uuid,'836b9729-08b8-4d2c-b089-c4ea57425a23'::uuid,'aac7788f-0a0f-4397-9742-95394ce02346'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:04:41.941865','2024-11-22 15:04:41.941865'),
('13b2f85a-7370-468b-b3f8-e6689e2bcdf6'::uuid,'aa33487c-0637-4c1a-b260-d12ed1dbcc39'::uuid,'baaf6ab1-6142-4fb7-b753-d0a142c75baf'::uuid,'a70a6356-a318-4426-af02-dbff793fbbb0'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:04:41.941865','2024-11-22 15:04:41.941865'),
('a8f4471f-add6-4f5d-bbe0-48264ba24fda'::uuid,'5269b1ec-3478-414c-bdb1-c8db12ccd0b5'::uuid,'5065f129-c3fe-4533-a24c-408db08d0e28'::uuid,'5fd8306f-af71-4967-950a-4d415c18f415'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:17:22.131362','2024-11-22 15:17:22.131362'),
('be6cb946-4033-43dd-a356-3213b3ba7b53'::uuid,'30dc0c1f-c2e2-4bf1-b9fd-5c24e21034d8'::uuid,'026f9d81-7715-424e-b368-bfc7a673aaa2'::uuid,'40bf95e4-72f1-4d75-8bc5-5842f7f26c94'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:17:22.131362','2024-11-22 15:17:22.131362'),
('c5deb9d6-478b-46b4-ab61-da32fa575731'::uuid,'54a32fc7-c2a9-4ba6-b8dd-99a710730fda'::uuid,'9c7377d9-b175-4896-8acd-5a3fb9ecb48c'::uuid,'3b3d4a99-b6c2-4e01-8a10-7e4e780ecd92'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:17:22.131362','2024-11-22 15:17:22.131362'),
('25e2f255-fd9b-4c21-a513-63332b5c30b9'::uuid,'581da305-3a67-4311-b7d5-1fec19b221f9'::uuid,'9fa66dfd-73b0-4d5f-b637-cac6e0d24676'::uuid,'7f3d3fdb-1d0f-4061-af3c-d2f401737d06'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:17:22.131362','2024-11-22 15:17:22.131362'),
('adfe22d0-70ff-45c3-9b9e-08341f662377'::uuid,'b8dc9c80-bd6d-4a2d-8bd3-f025ddef95fc'::uuid,'999bcb30-edae-4b4d-8027-5e71cb8b015c'::uuid,'c4c4e9bf-631c-4ea0-9d03-bced40ab9189'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:17:22.131362','2024-11-22 15:17:22.131362'),
('8d74a7c2-addd-49d2-a945-697befa686a2'::uuid,'5355255c-3c9c-4d86-af2a-4b63004dee24'::uuid,'836b9729-08b8-4d2c-b089-c4ea57425a23'::uuid,'aac7788f-0a0f-4397-9742-95394ce02346'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:17:22.131362','2024-11-22 15:17:22.131362'),
('9e76c27d-fd4b-434e-9ffc-ef70abc88973'::uuid,'a9c24841-64e0-4e12-a872-ee7fb73a6300'::uuid,'baaf6ab1-6142-4fb7-b753-d0a142c75baf'::uuid,'a70a6356-a318-4426-af02-dbff793fbbb0'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:17:22.131362','2024-11-22 15:17:22.131362'),
('75038c6c-cd38-453d-a0c3-74e39908bb81'::uuid,'e4ba8867-bf70-43c7-b143-0f719ed0121f'::uuid,'fc264968-eaff-404a-8efe-24c35e965e2f'::uuid,'9dcbb419-f0c9-4c06-82f4-c43381a55b89'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:26:06.764703','2024-11-22 15:26:06.764703'),
('2a6b7d44-0b06-4c20-bad9-0bbe147c8e23'::uuid,'ae0667e0-b497-4dd3-bfb4-a20f457104d1'::uuid,'026f9d81-7715-424e-b368-bfc7a673aaa2'::uuid,'40bf95e4-72f1-4d75-8bc5-5842f7f26c94'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:26:06.764703','2024-11-22 15:26:06.764703'),
('10625607-6785-49f0-8a26-36039612743b'::uuid,'e21ce23a-c261-4b54-a711-6dee965bcfe9'::uuid,'836b9729-08b8-4d2c-b089-c4ea57425a23'::uuid,'aac7788f-0a0f-4397-9742-95394ce02346'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:26:06.764703','2024-11-22 15:26:06.764703'),
('a1a7ce07-94f2-45b3-b70a-59f1824778af'::uuid,'27ce681f-6dd6-45fd-b3ab-9a5b459b814b'::uuid,'baaf6ab1-6142-4fb7-b753-d0a142c75baf'::uuid,'a70a6356-a318-4426-af02-dbff793fbbb0'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,true,'2024-11-22 15:26:06.764703','2024-11-22 15:26:06.764703');
24 changes: 24 additions & 0 deletions pkg/apperror/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,30 @@ func (e *UnsupportedPostalCodeError) Error() string {
return fmt.Sprintf("Unsupported postal code (%s): %s", e.postalCode, e.reason)
}

// UnsupportedPortCode happens when we don't have the data to support a given postal code.
const UnsupportedPortCode ErrorCode = "UNSUPPORTED_PORT_CODE"

// UnsupportedPortCodeError is the custom error type (exported for type checking)
type UnsupportedPortCodeError struct {
baseError
portCode string
reason string
}

// NewUnsupportedPostalCodeError creates an error for when we don't have the data to support a given
// postal code.
func NewUnsupportedPortCodeError(portCode string, reason string) *UnsupportedPortCodeError {
return &UnsupportedPortCodeError{
baseError{UnsupportedPortCode},
portCode,
reason,
}
}

func (e *UnsupportedPortCodeError) Error() string {
return fmt.Sprintf("Unsupported port code (%s): %s", e.portCode, e.reason)
}

// InvalidInputError is returned when an update fails a validation rule
type InvalidInputError struct {
id uuid.UUID
Expand Down
130 changes: 130 additions & 0 deletions pkg/apperror/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package apperror

import (
"errors"
"fmt"
"testing"

validate "github.com/gobuffalo/validate/v3"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/suite"
)

type errorsSuite struct {
suite.Suite
}

func TestErrorSuite(t *testing.T) {
hs := &errorsSuite{}
suite.Run(t, hs)
}

func (suite *errorsSuite) TestContextError() {
suite.Run("ContextError shows error message", func() {
contextError := NewContextError("This is a context error message")
suite.Equal("ContextError: This is a context error message", contextError.Error())

})
}

func (suite *errorsSuite) TestPreconditionFailedError() {
suite.Run("PreconditionFailedError error function message", func() {
id := uuid.Must(uuid.NewV4())
err := errors.New("Precondition Failed Error")
preconditionFailedError := NewPreconditionFailedError(id, err)
suite.Equal("Precondition failed on update to object with ID: '"+id.String()+"'. The If-Match header value did not match the eTag for this record.", preconditionFailedError.Error())

})
}

func (suite *errorsSuite) TestNotFoundError() {
suite.Run("NotFoundError error function message", func() {
id := uuid.Must(uuid.NewV4())
errorMessage := "This is a Not Found Error"
notFoundError := NewNotFoundError(id, errorMessage)
suite.Equal("ID: "+id.String()+" not found "+errorMessage, notFoundError.Error())

})
}

func (suite *errorsSuite) TestUpdateError() {
suite.Run("UpdateError error function message", func() {
id := uuid.Must(uuid.NewV4())
errorMessage := "This is an Update Error"
updateError := NewUpdateError(id, errorMessage)
suite.Equal("Update Error "+errorMessage, updateError.Error())

})
}

func (suite *errorsSuite) TestPPMNotReadyForCloseoutError() {
suite.Run("PPMNotReadyForCloseoutError error function message", func() {
id := uuid.Must(uuid.NewV4())
errorMessage := "This is a PPM Not Ready For Closeout Error"
ppmNotReadyForCloseoutError := NewPPMNotReadyForCloseoutError(id, errorMessage)
suite.Equal("ID: "+id.String()+" - PPM Shipment is not ready for closeout. Customer must upload PPM documents. "+errorMessage, ppmNotReadyForCloseoutError.Error())

})
}

func (suite *errorsSuite) TestPPMNoWeightTicketsError() {
suite.Run("PPMNoWeightTicketsError error function message", func() {
id := uuid.Must(uuid.NewV4())
errorMessage := "This is a PPM No Weight Tickets Error"
pPMNoWeightTicketsError := NewPPMNoWeightTicketsError(id, errorMessage)
suite.Equal("ID: "+id.String()+" - PPM Shipment has no weight tickets assigned to it, can't calculate any weights. "+errorMessage, pPMNoWeightTicketsError.Error())

})
}

func (suite *errorsSuite) TestBadDataError() {
suite.Run("BadDataError error function message", func() {
errorMessage := "This is a Bad Data Error"
badDataError := NewBadDataError(errorMessage)
suite.Equal(fmt.Sprintf("Data received from requester is bad: %s: %s", badDataError.baseError.code, errorMessage), badDataError.Error())
})
}

func (suite *errorsSuite) TestUnsupportedPostalCodeError() {
suite.Run("UnsupportedPostalCodeError error function message", func() {
postalCode := "36022"
reason := "This postal code is not supported"
unsupportedPostalCodeError := NewUnsupportedPostalCodeError(postalCode, reason)
suite.Equal(fmt.Sprintf("Unsupported postal code (%s): %s", postalCode, reason), unsupportedPostalCodeError.Error())
})
}

func (suite *errorsSuite) TestUnsupportedPortCodeError() {
suite.Run("UnsupportedPortCodeError error function message", func() {
portCode := "ABC"
reason := "This port code is not legit"
unsupportedPortCode := NewUnsupportedPortCodeError(portCode, reason)
suite.Equal(fmt.Sprintf("Unsupported port code (%s): %s", portCode, reason), unsupportedPortCode.Error())
})
}

func (suite *errorsSuite) TestInvalidInputError() {
suite.Run("InvalidInputError error function returns the message when it's not empty", func() {
id := uuid.Must(uuid.NewV4())
err := errors.New("Invalid Input Error")
verrs := validate.NewErrors()
fieldWithErr := "field"
fieldErrorMsg := "Field error"
verrs.Add(fieldWithErr, fieldErrorMsg)
message := "Error messagefor an invalid input"
invalidInputError := NewInvalidInputError(id, err, verrs, message)
suite.Equal(message, invalidInputError.Error())
})

suite.Run("InvalidInputError error function with no message but validation errors returns correct message", func() {
id := uuid.Must(uuid.NewV4())
err := errors.New("Invalid Input Error")
verrs := validate.NewErrors()
fieldWithErr := "field"
fieldErrorMsg := "Field error"
verrs.Add(fieldWithErr, fieldErrorMsg)
message := ""
invalidInputError := NewInvalidInputError(id, err, verrs, message)
suite.Equal(fmt.Sprintf("Invalid input for ID: %s. %s", id.String(), verrs), invalidInputError.Error())
})
}
20 changes: 20 additions & 0 deletions pkg/factory/mto_service_item_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ func buildMTOServiceItemWithBuildType(db *pop.Connection, customs []Customizatio
reService = FetchReServiceByCode(db, models.ReServiceCode("DLH"))
}

var ptrPodLocation *models.PortLocation
if result := findValidCustomization(customs, PortLocations.PortOfDebarkation); result != nil {
podLocation := result.Model.(models.PortLocation)
if !result.LinkOnly {
podLocation = FetchPortLocation(db, customs, nil)
}
ptrPodLocation = &podLocation
}

var ptrPoeLocation *models.PortLocation
if result := findValidCustomization(customs, PortLocations.PortOfEmbarkation); result != nil {
poeLocation := result.Model.(models.PortLocation)
if !result.LinkOnly {
poeLocation = FetchPortLocation(db, customs, nil)
}
ptrPoeLocation = &poeLocation
}

requestedApprovalsRequestedStatus := false

var lockedPriceCents = unit.Cents(12303)
Expand All @@ -71,6 +89,8 @@ func buildMTOServiceItemWithBuildType(db *pop.Connection, customs []Customizatio
RequestedApprovalsRequestedStatus: &requestedApprovalsRequestedStatus,
CustomerExpense: isCustomerExpense,
LockedPriceCents: &lockedPriceCents,
POELocation: ptrPoeLocation,
PODLocation: ptrPodLocation,
}

// only set SITOriginHHGOriginalAddress if a customization is provided
Expand Down
46 changes: 46 additions & 0 deletions pkg/factory/mto_service_item_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,50 @@ func (suite *FactorySuite) TestBuildMTOServiceItem() {
suite.Equal(expectedCodes, reServiceCodes)
})

suite.Run("Port Locations not populated by default", func() {

serviceItem := BuildMTOServiceItem(suite.DB(), nil, nil)
suite.Nil(serviceItem.POELocation, nil)
suite.Nil(serviceItem.PODLocation, nil)
})

suite.Run("PODLocation populated for PortOfDebarkation type", func() {

portLocation := FetchPortLocation(suite.DB(), []Customization{
{
Model: models.Port{
PortCode: "PDX",
},
},
}, nil)
serviceItem := BuildMTOServiceItem(suite.DB(), []Customization{
{
Model: portLocation,
LinkOnly: true,
Type: &PortLocations.PortOfDebarkation,
},
}, nil)
suite.Equal(portLocation.Port.PortCode, serviceItem.PODLocation.Port.PortCode)
suite.Nil(serviceItem.POELocation)
})

suite.Run("POELocation populated for PortOfEmbarkation type", func() {

portLocation := FetchPortLocation(suite.DB(), []Customization{
{
Model: models.Port{
PortCode: "PDX",
},
},
}, nil)
serviceItem := BuildMTOServiceItem(suite.DB(), []Customization{
{
Model: portLocation,
LinkOnly: true,
Type: &PortLocations.PortOfEmbarkation,
},
}, nil)
suite.Nil(serviceItem.PODLocation)
suite.Equal(portLocation.Port.PortCode, serviceItem.POELocation.Port.PortCode)
})
}
41 changes: 41 additions & 0 deletions pkg/factory/port_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package factory

import (
"github.com/gobuffalo/pop/v6"

"github.com/transcom/mymove/pkg/models"
"github.com/transcom/mymove/pkg/testdatagen"
)

func FetchPort(db *pop.Connection, customs []Customization, traits []Trait) models.Port {
customs = setupCustomizations(customs, traits)

var cPort models.Port
if result := findValidCustomization(customs, Port); result != nil {
cPort = result.Model.(models.Port)
if result.LinkOnly {
return cPort
}
}

var port models.Port
if db != nil {

var err error
if cPort.PortCode != "" {
// Find the port based off the port code
err = db.Where("port_code = ?", cPort.PortCode).First(&port)
} else {
//No port code provided, so find the default port
err = db.Where("port_code = 'PDX'").First(&port)
}
if err == nil {
return port
}

}

// Overwrite values with those from customizations
testdatagen.MergeModels(&port, cPort)
return port
}
34 changes: 34 additions & 0 deletions pkg/factory/port_factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package factory

import "github.com/transcom/mymove/pkg/models"

func (suite *FactorySuite) TestFetchPort() {

const defaultPortType = models.PortTypeAir
const defaultPortCode = "PDX"
const defaultPortName = "PORTLAND INTL"

suite.Run("Successful fetch of default Port", func() {

defaultPortLocation := FetchPort(suite.DB(), nil, nil)

suite.Equal(defaultPortLocation.PortType, defaultPortType)
suite.Equal(defaultPortLocation.PortCode, defaultPortCode)
suite.Equal(defaultPortLocation.PortName, defaultPortName)
})

suite.Run("Successful fetch of Port using port code", func() {

customPort := FetchPortLocation(suite.DB(), []Customization{
{
Model: models.Port{
PortCode: "SEA",
},
},
}, nil)

suite.Equal("A", customPort.Port.PortType.String())
suite.Equal("SEA", customPort.Port.PortCode)
suite.Equal("SEATTLE TACOMA INTL", customPort.Port.PortName)
})
}
Loading

0 comments on commit 6fb9a16

Please sign in to comment.