From ad8f936b6bf9b22382369226f41e97a745f1c6c5 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Fri, 6 Sep 2024 11:35:57 -0400 Subject: [PATCH 01/13] Added feature flags for Boat and Mobile Home in SC/TIO/TOO views, was misssing one for the "Add a new shipment" dropdown. --- src/pages/Office/MoveDetails/MoveDetails.jsx | 37 +++++++++++++---- .../ServicesCounselingMoveDetails.jsx | 41 +++++++++++++------ 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx index 1c448c575a7..5eb9805131d 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.jsx @@ -29,11 +29,12 @@ import LeftNavTag from 'components/LeftNavTag/LeftNavTag'; import Restricted from 'components/Restricted/Restricted'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; -import { SHIPMENT_OPTIONS_URL } from 'shared/constants'; +import { SHIPMENT_OPTIONS_URL, FEATURE_FLAG_KEYS } from 'shared/constants'; import { SIT_EXTENSION_STATUS } from 'constants/sitExtensions'; import { ORDERS_TYPE } from 'constants/orders'; import { permissionTypes } from 'constants/permissions'; import { objectIsMissingFieldWithCondition } from 'utils/displayFlags'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; import formattedCustomerName from 'utils/formattedCustomerName'; import { calculateEstimatedWeight } from 'hooks/custom'; @@ -64,6 +65,8 @@ const MoveDetails = ({ const [shipmentMissingRequiredInformation, setShipmentMissingRequiredInformation] = useState(false); const [alertMessage, setAlertMessage] = useState(null); const [alertType, setAlertType] = useState('success'); + const [enableBoat, setEnableBoat] = useState(false); + const [enableMobileHome, setEnableMobileHome] = useState(false); const navigate = useNavigate(); @@ -168,6 +171,14 @@ const MoveDetails = ({ navigate(addShipmentPath); }; + useEffect(() => { + const fetchData = async () => { + setEnableBoat(await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.BOAT)); + setEnableMobileHome(await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.MOBILE_HOME)); + }; + fetchData(); + }, []); + useEffect(() => { const shipmentCount = shipmentWithDestinationAddressChangeRequest?.length || 0; if (setShipmentsWithDeliveryAddressUpdateRequestedCount) @@ -306,6 +317,21 @@ const MoveDetails = ({ const hasDestinationAddressUpdate = shipmentWithDestinationAddressChangeRequest && shipmentWithDestinationAddressChangeRequest.length > 0; + const allowedShipmentOptions = () => { + return ( + <> + + + + + {enableBoat && } + {enableMobileHome && } + + ); + }; + return (
@@ -389,14 +415,7 @@ const MoveDetails = ({ - - - - - - + {allowedShipmentOptions()} )} diff --git a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx index b630bd71b68..a11571275f7 100644 --- a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx +++ b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx @@ -24,7 +24,7 @@ import ShipmentDisplay from 'components/Office/ShipmentDisplay/ShipmentDisplay'; import { SubmitMoveConfirmationModal } from 'components/Office/SubmitMoveConfirmationModal/SubmitMoveConfirmationModal'; import { useMoveDetailsQueries, useOrdersDocumentQueries } from 'hooks/queries'; import { updateMoveStatusServiceCounselingCompleted, updateFinancialFlag } from 'services/ghcApi'; -import { MOVE_STATUSES, SHIPMENT_OPTIONS_URL, SHIPMENT_OPTIONS } from 'shared/constants'; +import { MOVE_STATUSES, SHIPMENT_OPTIONS_URL, SHIPMENT_OPTIONS, FEATURE_FLAG_KEYS } from 'shared/constants'; import { ppmShipmentStatuses } from 'constants/shipments'; import shipmentCardsStyles from 'styles/shipmentCards.module.scss'; import LeftNav from 'components/LeftNav/LeftNav'; @@ -41,6 +41,7 @@ import NotificationScrollToTop from 'components/NotificationScrollToTop'; import { objectIsMissingFieldWithCondition } from 'utils/displayFlags'; import { ReviewButton } from 'components/form/IconButtons'; import { calculateWeightRequested } from 'hooks/custom'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCount, isMoveLocked }) => { const { moveCode } = useParams(); @@ -51,6 +52,8 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo const [isSubmitModalVisible, setIsSubmitModalVisible] = useState(false); const [isFinancialModalVisible, setIsFinancialModalVisible] = useState(false); const [shipmentConcernCount, setShipmentConcernCount] = useState(0); + const [enableBoat, setEnableBoat] = useState(false); + const [enableMobileHome, setEnableMobileHome] = useState(false); const { upload, amendedUpload } = useOrdersDocumentQueries(moveCode); const documentsForViewer = Object.values(upload || {}) .concat(Object.values(amendedUpload || {})) @@ -135,6 +138,14 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo checkProGearAllowances(); }); + useEffect(() => { + const fetchData = async () => { + setEnableBoat(await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.BOAT)); + setEnableMobileHome(await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.MOBILE_HOME)); + }; + fetchData(); + }, []); + // for now we are only showing dest type on retiree and separatee orders const isRetirementOrSeparation = order.order_type === ORDERS_TYPE.RETIREMENT || order.order_type === ORDERS_TYPE.SEPARATION; @@ -465,6 +476,21 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo const hasMissingOrdersRequiredInfo = Object.values(requiredOrdersInfo).some((value) => !value || value === ''); const hasAmendedOrders = ordersInfo.uploadedAmendedOrderID && !ordersInfo.amendedOrdersAcknowledgedAt; + const allowedShipmentOptions = () => { + return ( + <> + + + + + {enableBoat && } + {enableMobileHome && } + + ); + }; + return (
@@ -574,18 +600,7 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo - - - - - - + {allowedShipmentOptions()} ) } From 62281970304eb081c643d757ba536cfcae7c012d Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Thu, 12 Sep 2024 18:23:09 -0400 Subject: [PATCH 02/13] Initial additions to Playwright tests for Mobile Home shipments. --- pkg/testdatagen/scenario/shared.go | 213 ++++++++++++++++- pkg/testdatagen/testharness/dispatch.go | 3 + pkg/testdatagen/testharness/make_move.go | 43 ++++ .../servicesCounselingMobileHome.spec.js | 225 +++++++++++++++++- .../tests/utils/office/waitForOfficePage.js | 8 + playwright/tests/utils/testharness.js | 8 + 6 files changed, 493 insertions(+), 7 deletions(-) diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go index e794ccb104e..abea790b36c 100644 --- a/pkg/testdatagen/scenario/shared.go +++ b/pkg/testdatagen/scenario/shared.go @@ -1,6 +1,7 @@ package scenario import ( + "errors" "fmt" "log" "sort" @@ -10169,6 +10170,167 @@ func createMoveWithUniqueDestinationAddress(appCtx appcontext.AppContext) { }, nil) } +/* +Generic helper function that lets you create a move with any staus and with any shipment type +*/ +func CreateMoveWithMTOShipment(appCtx appcontext.AppContext, ordersType internalmessages.OrdersType, shipmentType models.MTOShipmentType, destinationType *models.DestinationType, locator string, moveStatus models.MoveStatus) models.Move { + if shipmentType == models.MTOShipmentTypeBoatHaulAway || shipmentType == models.MTOShipmentTypeBoatTowAway { // Add boat specific fields in relevant PR + log.Panic(fmt.Errorf("Unable to generate random integer for submitted move date"), zap.Error(errors.New("Not yet implemented"))) + } + + db := appCtx.DB() + submittedAt := time.Now() + hhgPermitted := internalmessages.OrdersTypeDetailHHGPERMITTED + ordersNumber := "8675309" + departmentIndicator := "ARMY" + tac := "E19A" + newDutyLocation := factory.FetchOrBuildCurrentDutyLocation(db) + newDutyLocation.Address.PostalCode = "52549" + orders := factory.BuildOrderWithoutDefaults(db, []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: true, + }, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + { + Model: newDutyLocation, + LinkOnly: true, + Type: &factory.DutyLocations.NewDutyLocation, + }, + { + Model: models.Order{ + OrdersType: ordersType, + OrdersTypeDetail: &hhgPermitted, + OrdersNumber: &ordersNumber, + DepartmentIndicator: &departmentIndicator, + TAC: &tac, + }, + }, + }, nil) + move := factory.BuildMove(db, []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Locator: locator, + Status: moveStatus, + SubmittedAt: &submittedAt, + }, + }, + }, nil) + requestedPickupDate := submittedAt.Add(60 * 24 * time.Hour) + requestedDeliveryDate := requestedPickupDate.Add(7 * 24 * time.Hour) + destinationAddress := factory.BuildAddress(db, nil, nil) + + if destinationType != nil { // Destination type is only used for retirement moves + retirementMTOShipment := factory.BuildMTOShipment(db, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: shipmentType, + Status: models.MTOShipmentStatusSubmitted, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + DestinationType: destinationType, + }, + }, + { + Model: destinationAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + if shipmentType == models.MTOShipmentTypeMobileHome { + factory.BuildMobileHomeShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MobileHome{ + Year: models.IntPointer(2000), + Make: models.StringPointer("Boat Make"), + Model: models.StringPointer("Boat Model"), + LengthInInches: models.IntPointer(300), + WidthInInches: models.IntPointer(108), + HeightInInches: models.IntPointer(72), + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: retirementMTOShipment, + LinkOnly: true, + }, + }, nil) + } + } + + requestedPickupDate = submittedAt.Add(30 * 24 * time.Hour) + requestedDeliveryDate = requestedPickupDate.Add(7 * 24 * time.Hour) + regularMTOShipment := factory.BuildMTOShipment(db, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: shipmentType, + Status: models.MTOShipmentStatusSubmitted, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + }, + }, + }, nil) + + if shipmentType == models.MTOShipmentTypeMobileHome { + factory.BuildMobileHomeShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MobileHome{ + Year: models.IntPointer(2000), + Make: models.StringPointer("Boat Make"), + Model: models.StringPointer("Boat Model"), + LengthInInches: models.IntPointer(300), + WidthInInches: models.IntPointer(108), + HeightInInches: models.IntPointer(72), + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: regularMTOShipment, + LinkOnly: true, + }, + }, nil) + } + + officeUser := factory.BuildOfficeUserWithRoles(db, nil, []roles.RoleType{roles.RoleTypeTOO}) + factory.BuildCustomerSupportRemark(db, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: officeUser, + LinkOnly: true, + }, + { + Model: models.CustomerSupportRemark{ + Content: "The customer mentioned that they need to provide some more complex instructions for pickup and drop off.", + }, + }, + }, nil) + + return move +} + /* Create Needs Service Counseling - pass in orders with all required information, shipment type, destination type, locator */ @@ -10219,7 +10381,7 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte requestedPickupDate := submittedAt.Add(60 * 24 * time.Hour) requestedDeliveryDate := requestedPickupDate.Add(7 * 24 * time.Hour) destinationAddress := factory.BuildAddress(db, nil, nil) - factory.BuildMTOShipment(db, []factory.Customization{ + retirementMTOShipment := factory.BuildMTOShipment(db, []factory.Customization{ { Model: move, LinkOnly: true, @@ -10240,9 +10402,32 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte }, }, nil) + if shipmentType == models.MTOShipmentTypeMobileHome { + factory.BuildMobileHomeShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MobileHome{ + Year: models.IntPointer(2000), + Make: models.StringPointer("Boat Make"), + Model: models.StringPointer("Boat Model"), + LengthInInches: models.IntPointer(300), + WidthInInches: models.IntPointer(108), + HeightInInches: models.IntPointer(72), + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: retirementMTOShipment, + LinkOnly: true, + }, + }, nil) + } + requestedPickupDate = submittedAt.Add(30 * 24 * time.Hour) requestedDeliveryDate = requestedPickupDate.Add(7 * 24 * time.Hour) - factory.BuildMTOShipment(db, []factory.Customization{ + regularMTOShipment := factory.BuildMTOShipment(db, []factory.Customization{ { Model: move, LinkOnly: true, @@ -10256,6 +10441,30 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte }, }, }, nil) + + if shipmentType == models.MTOShipmentTypeMobileHome { + factory.BuildMobileHomeShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MobileHome{ + Year: models.IntPointer(2000), + Make: models.StringPointer("Boat Make"), + Model: models.StringPointer("Boat Model"), + LengthInInches: models.IntPointer(300), + WidthInInches: models.IntPointer(108), + HeightInInches: models.IntPointer(72), + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: regularMTOShipment, + LinkOnly: true, + }, + }, nil) + } + officeUser := factory.BuildOfficeUserWithRoles(db, nil, []roles.RoleType{roles.RoleTypeTOO}) factory.BuildCustomerSupportRemark(db, []factory.Customization{ { diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index 8033ca02825..99d21b4240c 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -35,6 +35,9 @@ var actionDispatcher = map[string]actionFunc{ "HHGMoveWithNTSAndNeedsSC": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveWithNTSAndNeedsSC(appCtx) }, + "MobileHomeMoveNeedsSC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeMobileHomeMoveNeedsSC(appCtx) + }, "GoodTACAndLoaCombination": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeGoodTACAndLoaCombination(appCtx) }, diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index 23e8665a889..7354f74fe4b 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -3792,6 +3792,49 @@ func MakeHHGMoveNeedsSC(appCtx appcontext.AppContext) models.Move { return *newmove } +// MakeHHGMoveNeedsSC creates an fully ready move needing SC approval +func MakeMobileHomeMoveNeedsSC(appCtx appcontext.AppContext) models.Move { + locator := models.GenerateLocator() + move := scenario.CreateMoveWithMTOShipment(appCtx, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, models.MTOShipmentTypeMobileHome, nil, locator, models.MoveStatusNeedsServiceCounseling) + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, move.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + return *newmove +} + +func MakeMobileHomeMoveForTOO(appCtx appcontext.AppContext) models.Move { + hhg := models.MTOShipmentTypeHHG + hor := models.DestinationTypeHomeOfRecord + originDutyLocation := factory.FetchOrBuildCurrentDutyLocation(appCtx.DB()) + move := scenario.CreateMoveWithOptions(appCtx, testdatagen.Assertions{ + Order: models.Order{ + OriginDutyLocation: &originDutyLocation, + }, + MTOShipment: models.MTOShipment{ + ShipmentType: hhg, + DestinationType: &hor, + }, + Move: models.Move{ + Status: models.MoveStatusSUBMITTED, + }, + DutyLocation: models.DutyLocation{ + ProvidesServicesCounseling: false, + }, + }) + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, move.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + return *newmove +} + // MakeHHGMoveNeedsServicesCounselingUSMC creates an fully ready move as USMC needing SC approval func MakeHHGMoveNeedsServicesCounselingUSMC(appCtx appcontext.AppContext) models.Move { userUploader := newUserUploader(appCtx) diff --git a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js index 0588fe6cd0d..b0d603d29e8 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js @@ -1,16 +1,90 @@ // @ts-check import { test, expect } from './servicesCounselingTestFixture'; +const today = new Date(); +const pickupDate = today; +const pickupDateString = pickupDate.toLocaleDateString('en-US'); +const deliveryDate = new Date(new Date().setDate(today.getDate() + 14)); +const deliveryDateString = deliveryDate.toLocaleDateString('en-US'); + +const pickupAddress = { + Address1: '7 Q St', + City: 'Atco', + State: 'NJ', + ZIP: '08004', +}; + +const secondaryPickupAddress = { + ...pickupAddress, +}; +secondaryPickupAddress.Address1 = '8 Q St'; + +const addressToString = (address) => { + return `${address.Address1}, ${address.Address2 ? `${address.Address2}, ` : ''}${ + address.Address3 ? `${address.Address3}, ` : '' + }${address.City}, ${address.State} ${address.ZIP}`; +}; + +const deliveryAddress = { + Address1: '9 W 2nd Ave', + Address2: 'P.O. Box 456', + City: 'Hollywood', + State: 'MD', + ZIP: '20636', +}; + +const secondaryDeliveryAddress = { + Address1: '9 Q St', + City: 'Atco', + State: 'NJ', + ZIP: '08004', +}; + +const releasingAgent = { + firstName: 'Grace', + lastName: 'Griffin', + phone: '2025551234', + email: 'grace.griffin@example.com', +}; + +const receivingAgent = { + firstName: 'Leo', + lastName: 'Spacemen', + phone: '2025552345', + email: 'leo.spaceman@example.com', +}; + +const formatPhone = (phone) => { + return `${phone.slice(0, 3)}-${phone.slice(3, 6)}-${phone.slice(6)}`; +}; + +const agentToString = (agent) => { + return `${agent.firstName} ${agent.lastName}${formatPhone(agent.phone)}${agent.email}`; +}; + +const formatDate = (date) => { + const formattedDay = date.toLocaleDateString(undefined, { day: '2-digit' }); + const formattedMonth = date.toLocaleDateString(undefined, { + month: 'short', + }); + const formattedYear = date.toLocaleDateString(undefined, { + year: 'numeric', + }); + + return `${formattedDay} ${formattedMonth} ${formattedYear}`; +}; + test.describe('Services counselor user', () => { test.beforeEach(async ({ scPage }) => { - const move = await scPage.testHarness.buildHHGMoveWithNTSAndNeedsSC(); + const move = await scPage.testHarness.buildMobileHomeMoveNeedsSC(); await scPage.navigateToMove(move.locator); }); test('Services Counselor can create a mobile home shipment and view shipment card info', async ({ page, scPage }) => { - const deliveryDate = new Date().toLocaleDateString('en-US'); await page.getByTestId('dropdown').selectOption({ label: 'Mobile Home' }); + await expect(page.getByText('Mobile Home Information')).toBeVisible(); + await expect(page.getByRole('heading', { level: 1 })).toHaveText('Add shipment details'); await expect(page.getByTestId('tag')).toHaveText('Mobile Home'); @@ -18,17 +92,21 @@ test.describe('Services counselor user', () => { await page.getByLabel('Make').fill('make'); await page.getByLabel('Model').fill('model'); await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('lengthInches').fill('0'); await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('widthInches').fill('0'); await page.getByTestId('heightFeet').fill('22'); + await page.getByTestId('heightInches').fill('0'); - await page.locator('#requestedPickupDate').fill(deliveryDate); + await page.locator('#requestedPickupDate').fill(pickupDateString); await page.locator('#requestedPickupDate').blur(); await page.getByText('Use current address').click(); - await page.locator('#requestedDeliveryDate').fill('16 Mar 2022'); - await page.locator('#requestedDeliveryDate').blur(); await page.getByLabel('Counselor remarks').fill('Sample counselor remarks'); + await page.locator('#requestedDeliveryDate').fill(deliveryDateString); + await page.locator('#requestedDeliveryDate').blur(); + // Save the shipment await page.getByRole('button', { name: 'Save' }).click(); await scPage.waitForPage.moveDetails(); @@ -44,4 +122,141 @@ test.describe('Services counselor user', () => { await expect(page.getByText('Dimensions')).toBeVisible(); await expect(page.getByTestId('dimensions')).toHaveText("22' L x 22' W x 22' H"); }); + + test('Services Counselor can delete an existing Mobile Home shipment', async ({ page, scPage }) => { + // Testdata fixture creates 2 shipments, one with the "Destination Type" field populated, which is used only for retirement moves + await expect(page.getByText('Edit Shipment')).toHaveCount(1); + // Choose a shipment and store it's shipment ID + const editShipmentButton = await page.getByRole('button', { name: 'Edit Shipment' }); + process.stdout.write(await editShipmentButton.evaluate((el) => el.outerHTML)); + + const shipmentButtonTestID = await editShipmentButton.evaluate((e) => e.dataset.testid); + await editShipmentButton.click(); + await scPage.waitForLoading(); + await scPage.waitForPage.editMobileHomeShipment(); + + // Delete that shipment + await page.getByRole('button', { name: 'Delete shipment' }).click(); + await expect(page.getByTestId('modalCloseButton')).toBeVisible(); + await page.getByTestId('modal').getByRole('button', { name: 'Delete shipment' }).click(); + await scPage.waitForPage.moveDetails(); + + // Verify that there's only 1 shipment displayed now + await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); + + // Verify that the deleted shipment is not on the page + await expect(page.getByTestId(shipmentButtonTestID)).toHaveCount(0); + }); + + test('Services Counselor can edit an existing Mobile Home shipment', async ({ page, scPage }) => { + // Testdata fixture creates 2 shipments, one with the "Destination Type" field populated, which is used only for retirement moves + await expect(page.getByText('Edit Shipment')).toHaveCount(1); + + // Choose a shipment, store it's container, and click the edit button + const shipmentContainer = await page.getByTestId('ShipmentContainer'); + await shipmentContainer.getByRole('button').click(); + await scPage.waitForLoading(); + await scPage.waitForPage.editMobileHomeShipment(); + + // Fill in all of the form fields with new data + await page.getByLabel('Year').fill('2024'); + await page.getByLabel('Make').fill('Test Make'); + await page.getByLabel('Model').fill('Test Model'); + + await page.getByTestId('lengthFeet').fill('20'); + await page.getByTestId('lengthInches').fill('6'); + + await page.getByTestId('widthFeet').fill('15'); + await page.getByTestId('widthInches').fill('1'); + + await page.getByTestId('heightFeet').fill('10'); + await page.getByTestId('heightInches').fill('0'); + + await page.locator('#requestedPickupDate').fill(pickupDateString); + await page.locator('#requestedPickupDate').blur(); + await page.locator('#requestedDeliveryDate').fill(deliveryDateString); + await page.locator('#requestedDeliveryDate').blur(); + + // Update form (adding pickup and delivery address) + const pickupAddressGroup = await page.getByRole('group', { name: 'Pickup location' }); + await pickupAddressGroup.getByText('Yes').click(); + await pickupAddressGroup.getByLabel('Address 1').nth(0).fill(pickupAddress.Address1); + await pickupAddressGroup.getByLabel('Address 2').nth(0).clear(); + await pickupAddressGroup.getByLabel('Address 3').nth(0).clear(); + await pickupAddressGroup.getByLabel('City').nth(0).fill(pickupAddress.City); + await pickupAddressGroup.getByLabel('State').nth(0).selectOption({ label: pickupAddress.State }); + await pickupAddressGroup.getByLabel('ZIP').nth(0).fill(pickupAddress.ZIP); + + // Secondary pickup address + await pickupAddressGroup.getByText('Yes').click(); + await pickupAddressGroup.getByLabel('Address 1').nth(1).fill(secondaryPickupAddress.Address1); + await pickupAddressGroup.getByLabel('Address 2').nth(1).clear(); + await pickupAddressGroup.getByLabel('Address 3').nth(1).clear(); + await pickupAddressGroup.getByLabel('City').nth(1).fill(secondaryPickupAddress.City); + await pickupAddressGroup.getByLabel('State').nth(1).selectOption({ label: secondaryPickupAddress.State }); + await pickupAddressGroup.getByLabel('ZIP').nth(1).fill(secondaryPickupAddress.ZIP); + + // Releasing agent + await page.locator(`[name='pickup.agent.firstName']`).fill(releasingAgent.firstName); + await page.locator(`[name='pickup.agent.lastName']`).fill(releasingAgent.lastName); + await page.locator(`[name='pickup.agent.phone']`).fill(releasingAgent.phone); + await page.locator(`[name='pickup.agent.email']`).fill(releasingAgent.email); + + const deliveryAddressGroup = await page.getByRole('group', { name: 'Delivery location' }); + await deliveryAddressGroup.getByText('Yes').nth(0).click(); + await deliveryAddressGroup.getByLabel('Address 1').nth(0).fill(deliveryAddress.Address1); + await deliveryAddressGroup.getByLabel('Address 2').nth(0).fill(deliveryAddress.Address2); + await deliveryAddressGroup.getByLabel('Address 3').nth(0).clear(); + await deliveryAddressGroup.getByLabel('City').nth(0).fill(deliveryAddress.City); + await deliveryAddressGroup.getByLabel('State').nth(0).selectOption({ label: deliveryAddress.State }); + await deliveryAddressGroup.getByLabel('ZIP').nth(0).fill(deliveryAddress.ZIP); + + // Secondary delivery address + await deliveryAddressGroup.getByText('Yes').nth(1).click(); + await deliveryAddressGroup.getByLabel('Address 1').nth(1).fill(secondaryDeliveryAddress.Address1); + await deliveryAddressGroup.getByLabel('Address 2').nth(1).clear(); + await deliveryAddressGroup.getByLabel('Address 3').nth(1).clear(); + await deliveryAddressGroup.getByLabel('City').nth(1).fill(secondaryDeliveryAddress.City); + await deliveryAddressGroup.getByLabel('State').nth(1).selectOption({ label: secondaryDeliveryAddress.State }); + await deliveryAddressGroup.getByLabel('ZIP').nth(1).fill(secondaryDeliveryAddress.ZIP); + + // Receiving agent + await page.locator(`[name='delivery.agent.firstName']`).fill(receivingAgent.firstName); + await page.locator(`[name='delivery.agent.lastName']`).fill(receivingAgent.lastName); + await page.locator(`[name='delivery.agent.phone']`).fill(receivingAgent.phone); + await page.locator(`[name='delivery.agent.email']`).fill(receivingAgent.email); + + await page.getByLabel('Counselor remarks').fill('Sample counselor remarks'); + + // Submit edits + await page.getByTestId('submitForm').click(); + await scPage.waitForLoading(); + await expect(page.locator('.usa-alert__text')).toContainText('Your changes were saved.'); + + // Check that the data in the shipment card now matches what we just submitted + await shipmentContainer.locator('[data-prefix="fas"][data-icon="chevron-down"]').click(); + await expect(shipmentContainer.getByTestId('requestedPickupDate')).toHaveText(formatDate(pickupDate)); + await expect(shipmentContainer.getByTestId('pickupAddress')).toHaveText(addressToString(pickupAddress)); + await expect(shipmentContainer.getByTestId('secondaryPickupAddress')).toHaveText( + addressToString(secondaryPickupAddress), + ); + + await expect(shipmentContainer.getByTestId('RELEASING_AGENT')).toHaveText(agentToString(releasingAgent)); + + await expect(shipmentContainer.getByTestId('requestedDeliveryDate')).toHaveText(formatDate(deliveryDate)); + await expect(shipmentContainer.getByTestId('destinationAddress')).toHaveText(addressToString(deliveryAddress)); + await expect(shipmentContainer.getByTestId('secondaryDeliveryAddress')).toHaveText( + addressToString(secondaryDeliveryAddress), + ); + + await expect(shipmentContainer.getByTestId('RECEIVING_AGENT')).toHaveText(agentToString(receivingAgent)); + + await expect(shipmentContainer.getByTestId('year')).toHaveText('2024'); + await expect(shipmentContainer.getByTestId('make')).toHaveText('Test Make'); + await expect(shipmentContainer.getByTestId('model')).toHaveText('Test Model'); + + await expect(shipmentContainer.getByTestId('dimensions')).toHaveText(`20' 6" L x 15' 1" W x 10' H`); + + await expect(shipmentContainer.getByTestId('counselorRemarks')).toHaveText('Sample counselor remarks'); + }); }); diff --git a/playwright/tests/utils/office/waitForOfficePage.js b/playwright/tests/utils/office/waitForOfficePage.js index e3bf4b82f80..7a785949cdf 100644 --- a/playwright/tests/utils/office/waitForOfficePage.js +++ b/playwright/tests/utils/office/waitForOfficePage.js @@ -68,6 +68,14 @@ export class WaitForOfficePage extends WaitForPage { await base.expect(this.page.getByTestId('tag')).toHaveText('NTS-release'); } + /** + * @returns {Promise} + */ + async editMobileHomeShipment() { + await base.expect(this.page.getByRole('heading', { level: 1 })).toHaveText('Edit shipment details'); + await base.expect(this.page.getByTestId('tag')).toHaveText('Mobile Home'); + } + /** * @returns {Promise} */ diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index 1faf82f5ee5..1ece5698f7d 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -411,6 +411,14 @@ export class TestHarness { return this.buildDefault('HHGMoveWithNTSAndNeedsSC'); } + /** + * Use testharness to build Mobile move + * @returns {Promise} + */ + async buildMobileHomeMoveNeedsSC() { + return this.buildDefault('MobileHomeMoveNeedsSC'); + } + /** * Use testharness to build a good TAC and LOA combination, return the TAC * so that office users can input the TAC, and preview the LOA (If the From 882601a342df545b91dcae1fe837b1da9b10b2a7 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Fri, 13 Sep 2024 15:26:01 -0400 Subject: [PATCH 03/13] Fixed typo in factory and added unit tests for boat/mobile home shipment types. --- pkg/factory/boat_shipment_factory.go | 8 ++-- pkg/services/move/move_router_test.go | 66 ++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/pkg/factory/boat_shipment_factory.go b/pkg/factory/boat_shipment_factory.go index dacbd3f26a8..d08cfd0f8de 100644 --- a/pkg/factory/boat_shipment_factory.go +++ b/pkg/factory/boat_shipment_factory.go @@ -14,7 +14,7 @@ type boatBuildType byte const ( boatBuildStandard boatBuildType = iota boatBuildStandardTowAway - boatBuildStandardHualAway + boatBuildStandardhaulaway ) // buildBoatShipmentWithBuildType does the actual work @@ -56,7 +56,7 @@ func buildBoatShipmentWithBuildType(db *pop.Connection, customs []Customization, shipment.ShipmentType = models.MTOShipmentTypeBoatTowAway } - if buildType == boatBuildStandardHualAway { + if buildType == boatBuildStandardhaulaway { shipment.ShipmentType = models.MTOShipmentTypeBoatHaulAway } @@ -80,7 +80,7 @@ func buildBoatShipmentWithBuildType(db *pop.Connection, customs []Customization, boatShipment.IsRoadworthy = models.BoolPointer(true) } - if buildType == boatBuildStandardHualAway { + if buildType == boatBuildStandardhaulaway { boatShipment.Type = models.BoatShipmentTypeHaulAway boatShipment.HasTrailer = models.BoolPointer(false) boatShipment.IsRoadworthy = models.BoolPointer(false) @@ -106,7 +106,7 @@ func BuildBoatShipmentTowAway(db *pop.Connection, customs []Customization, trait return buildBoatShipmentWithBuildType(db, customs, traits, boatBuildStandardTowAway) } func BuildBoatShipmentHaulAway(db *pop.Connection, customs []Customization, traits []Trait) models.BoatShipment { - return buildBoatShipmentWithBuildType(db, customs, traits, boatBuildStandardHualAway) + return buildBoatShipmentWithBuildType(db, customs, traits, boatBuildStandardhaulaway) } // ------------------------ diff --git a/pkg/services/move/move_router_test.go b/pkg/services/move/move_router_test.go index cd6b9f6623e..945a65d6033 100644 --- a/pkg/services/move/move_router_test.go +++ b/pkg/services/move/move_router_test.go @@ -296,6 +296,32 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { }, }, nil) + boatMTOSHipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusDraft, + ShipmentType: models.MTOShipmentTypeBoatHaulAway, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + mobileHomeMTOShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusDraft, + ShipmentType: models.MTOShipmentTypeMobileHome, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ { Model: models.PPMShipment{ @@ -303,8 +329,23 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { }, }, }, nil) - move.MTOShipments = models.MTOShipments{shipment} + + boatShipment := factory.BuildBoatShipmentHaulAway(suite.DB(), []factory.Customization{ + { + Model: models.BoatShipment{}, + }, + }, nil) + + mobileHomeShipment := factory.BuildMobileHomeShipment(suite.DB(), []factory.Customization{ + { + Model: models.MobileHome{}, + }, + }, nil) + + move.MTOShipments = models.MTOShipments{shipment, boatMTOSHipment, mobileHomeMTOShipment} move.MTOShipments[0].PPMShipment = &ppmShipment + move.MTOShipments[0].BoatShipment = &boatShipment + move.MTOShipments[0].MobileHome = &mobileHomeShipment newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{ { @@ -397,6 +438,7 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { suite.Equal(models.MTOShipmentStatusSubmitted, move.MTOShipments[0].Status, "expected Submitted") suite.Equal(models.PPMShipmentStatusSubmitted, move.MTOShipments[0].PPMShipment.Status, "expected Submitted") }) + } func (suite *MoveServiceSuite) TestMoveCancellation() { @@ -745,6 +787,28 @@ func (suite *MoveServiceSuite) TestCompleteServiceCounseling() { suite.IsType(apperror.ConflictError{}, err) suite.Contains(err.Error(), "NTS-release shipment must include facility info") }) + + suite.Run("Boat Shipment - status changed to 'SERVICE_COUNSELING_COMPLETED'", func() { + move := factory.BuildStubbedMoveWithStatus(models.MoveStatusNeedsServiceCounseling) + boatShipment := factory.BuildBoatShipment(nil, nil, nil) + move.MTOShipments = models.MTOShipments{boatShipment.Shipment} + + err := moveRouter.CompleteServiceCounseling(suite.AppContextForTest(), &move) + + suite.NoError(err) + suite.Equal(models.MoveStatusServiceCounselingCompleted, move.Status) + }) + + suite.Run("Mobile Home Shipment - status changed to 'SERVICE_COUNSELING_COMPLETED'", func() { + move := factory.BuildStubbedMoveWithStatus(models.MoveStatusNeedsServiceCounseling) + mobileHomeShipment := factory.BuildMobileHomeShipment(nil, nil, nil) + move.MTOShipments = models.MTOShipments{mobileHomeShipment.Shipment} + + err := moveRouter.CompleteServiceCounseling(suite.AppContextForTest(), &move) + + suite.NoError(err) + suite.Equal(models.MoveStatusServiceCounselingCompleted, move.Status) + }) } func (suite *MoveServiceSuite) createServiceItem() (models.MTOServiceItem, models.Move) { From 5bb7d4fea81e5fa47151d6c3d3923b85061e1c9a Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Fri, 13 Sep 2024 20:38:32 +0000 Subject: [PATCH 04/13] Fixed typo again to have correct camelCase naming --- pkg/factory/boat_shipment_factory.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/factory/boat_shipment_factory.go b/pkg/factory/boat_shipment_factory.go index d08cfd0f8de..bf6fae5a275 100644 --- a/pkg/factory/boat_shipment_factory.go +++ b/pkg/factory/boat_shipment_factory.go @@ -14,7 +14,7 @@ type boatBuildType byte const ( boatBuildStandard boatBuildType = iota boatBuildStandardTowAway - boatBuildStandardhaulaway + boatBuildStandardHaulAway ) // buildBoatShipmentWithBuildType does the actual work @@ -56,7 +56,7 @@ func buildBoatShipmentWithBuildType(db *pop.Connection, customs []Customization, shipment.ShipmentType = models.MTOShipmentTypeBoatTowAway } - if buildType == boatBuildStandardhaulaway { + if buildType == boatBuildStandardHaulAway { shipment.ShipmentType = models.MTOShipmentTypeBoatHaulAway } @@ -80,7 +80,7 @@ func buildBoatShipmentWithBuildType(db *pop.Connection, customs []Customization, boatShipment.IsRoadworthy = models.BoolPointer(true) } - if buildType == boatBuildStandardhaulaway { + if buildType == boatBuildStandardHaulAway { boatShipment.Type = models.BoatShipmentTypeHaulAway boatShipment.HasTrailer = models.BoolPointer(false) boatShipment.IsRoadworthy = models.BoolPointer(false) @@ -106,7 +106,7 @@ func BuildBoatShipmentTowAway(db *pop.Connection, customs []Customization, trait return buildBoatShipmentWithBuildType(db, customs, traits, boatBuildStandardTowAway) } func BuildBoatShipmentHaulAway(db *pop.Connection, customs []Customization, traits []Trait) models.BoatShipment { - return buildBoatShipmentWithBuildType(db, customs, traits, boatBuildStandardhaulaway) + return buildBoatShipmentWithBuildType(db, customs, traits, boatBuildStandardHaulAway) } // ------------------------ From 78eb5df3b5ce5bf01ae877f03fb6c528ef49c64f Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Mon, 16 Sep 2024 14:30:02 +0000 Subject: [PATCH 05/13] Fixed duplicate instance of "Requested delivery" field that was present in Mobile Home Card component --- .../Office/DefinitionLists/MobileHomeShipmentInfoList.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.jsx b/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.jsx index 8854c983d75..b8630805eeb 100644 --- a/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.jsx @@ -315,7 +315,6 @@ const ShipmentInfoList = ({ {secondaryPickupAddressElement} {isTertiaryAddressEnabled ? tertiaryPickupAddressElement : null} {showElement(agentsElementFlags) && releasingAgentElement} - {showElement(requestedDeliveryDateElementFlags) && requestedDeliveryDateElement} {requestedDeliveryDateElement} {destinationAddressElement} {showElement(destinationTypeFlags) && displayDestinationType && destinationTypeElement} From 474950f61112ca867330c8abb455f60d99bad6a5 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Tue, 17 Sep 2024 19:00:00 +0000 Subject: [PATCH 06/13] Fix for mobile home creation playwright test. --- .../servicesCounselingMobileHome.spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js index b0d603d29e8..8160a7c35fc 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js @@ -113,14 +113,14 @@ test.describe('Services counselor user', () => { await expect(page.getByTestId('ShipmentContainer')).toHaveCount(2); - await expect(page.getByText('Mobile home year')).toBeVisible(); - await expect(page.getByTestId('year')).toHaveText('2022'); - await expect(page.getByText('Mobile home make')).toBeVisible(); - await expect(page.getByTestId('make')).toHaveText('make'); - await expect(page.getByText('Mobile home model')).toBeVisible(); - await expect(page.getByTestId('model')).toHaveText('model'); - await expect(page.getByText('Dimensions')).toBeVisible(); - await expect(page.getByTestId('dimensions')).toHaveText("22' L x 22' W x 22' H"); + await expect(page.getByText('Mobile home year').last()).toBeVisible(); + await expect(page.getByTestId('year').last()).toHaveText('2022'); + await expect(page.getByText('Mobile home make').last()).toBeVisible(); + await expect(page.getByTestId('make').last()).toHaveText('make'); + await expect(page.getByText('Mobile home model').last()).toBeVisible(); + await expect(page.getByTestId('model').last()).toHaveText('model'); + await expect(page.getByText('Dimensions').last()).toBeVisible(); + await expect(page.getByTestId('dimensions').last()).toHaveText("22' L x 22' W x 22' H"); }); test('Services Counselor can delete an existing Mobile Home shipment', async ({ page, scPage }) => { From e47b241f6ee6eb79ed4c6ad4ce88b168199f2790 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Tue, 17 Sep 2024 20:12:14 +0000 Subject: [PATCH 07/13] Changed unneeded selector in playwright test --- .../servicesCounselingMobileHome.spec.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js index 8160a7c35fc..0f7a6d1d695 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js @@ -124,13 +124,11 @@ test.describe('Services counselor user', () => { }); test('Services Counselor can delete an existing Mobile Home shipment', async ({ page, scPage }) => { - // Testdata fixture creates 2 shipments, one with the "Destination Type" field populated, which is used only for retirement moves await expect(page.getByText('Edit Shipment')).toHaveCount(1); // Choose a shipment and store it's shipment ID const editShipmentButton = await page.getByRole('button', { name: 'Edit Shipment' }); process.stdout.write(await editShipmentButton.evaluate((el) => el.outerHTML)); - const shipmentButtonTestID = await editShipmentButton.evaluate((e) => e.dataset.testid); await editShipmentButton.click(); await scPage.waitForLoading(); await scPage.waitForPage.editMobileHomeShipment(); @@ -141,11 +139,8 @@ test.describe('Services counselor user', () => { await page.getByTestId('modal').getByRole('button', { name: 'Delete shipment' }).click(); await scPage.waitForPage.moveDetails(); - // Verify that there's only 1 shipment displayed now - await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); - - // Verify that the deleted shipment is not on the page - await expect(page.getByTestId(shipmentButtonTestID)).toHaveCount(0); + // Verify that the shipment has been deleted + await expect(page.getByTestId('ShipmentContainer')).toHaveCount(0); }); test('Services Counselor can edit an existing Mobile Home shipment', async ({ page, scPage }) => { From 469ce49dc999954f3c934e14595de04d56853b0d Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Tue, 17 Sep 2024 20:12:55 +0000 Subject: [PATCH 08/13] Removed outdated comment. --- .../servicescounseling/servicesCounselingMobileHome.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js index 0f7a6d1d695..be161ca0594 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js @@ -144,7 +144,6 @@ test.describe('Services counselor user', () => { }); test('Services Counselor can edit an existing Mobile Home shipment', async ({ page, scPage }) => { - // Testdata fixture creates 2 shipments, one with the "Destination Type" field populated, which is used only for retirement moves await expect(page.getByText('Edit Shipment')).toHaveCount(1); // Choose a shipment, store it's container, and click the edit button From 6484e3876b147d2a560d614f1a10d39d3f60b7d4 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Thu, 19 Sep 2024 18:56:06 +0000 Subject: [PATCH 09/13] Addd test coverage for boats and mobile homes in move_router.go --- pkg/services/move/move_router.go | 27 +++- pkg/services/move/move_router_test.go | 225 ++++++++++++++++++++++---- 2 files changed, 217 insertions(+), 35 deletions(-) diff --git a/pkg/services/move/move_router.go b/pkg/services/move/move_router.go index 9b3ccc44d49..b1012865aa6 100644 --- a/pkg/services/move/move_router.go +++ b/pkg/services/move/move_router.go @@ -212,7 +212,13 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo move.MTOShipments[i].Status = models.MTOShipmentStatusSubmitted if verrs, err := appCtx.DB().ValidateAndUpdate(&move.MTOShipments[i]); verrs.HasAny() || err != nil { - msg := "failure saving shipment when routing move submission" + msg := "failure saving parent MTO shipment object for boat shipment when routing move submission" + appCtx.Logger().Error(msg, zap.Error(err)) + return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) + } + + if verrs, err := appCtx.DB().ValidateAndUpdate(move.MTOShipments[i].BoatShipment); verrs.HasAny() || err != nil { + msg := "failure saving boat shipment when routing move submission" appCtx.Logger().Error(msg, zap.Error(err)) return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) } @@ -222,19 +228,24 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo move.MTOShipments[i].Status = models.MTOShipmentStatusSubmitted if verrs, err := appCtx.DB().ValidateAndUpdate(&move.MTOShipments[i]); verrs.HasAny() || err != nil { - msg := "failure saving shipment when routing move submission" + msg := "failure saving parent MTO shipment object for mobile home shipment when routing move submission" + appCtx.Logger().Error(msg, zap.Error(err)) + return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) + } + + if verrs, err := appCtx.DB().ValidateAndUpdate(move.MTOShipments[i].MobileHome); verrs.HasAny() || err != nil { + msg := "failure saving mobile home shipment when routing move submission" appCtx.Logger().Error(msg, zap.Error(err)) return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) } } - } - if verrs, err := appCtx.DB().ValidateAndSave(move); verrs.HasAny() || err != nil { - msg := "failure saving move when routing move submission" - appCtx.Logger().Error(msg, zap.Error(err)) - return apperror.NewInvalidInputError(move.ID, err, verrs, msg) + if verrs, err := appCtx.DB().ValidateAndSave(move); verrs.HasAny() || err != nil { + msg := "failure saving move when routing move submission" + appCtx.Logger().Error(msg, zap.Error(err)) + return apperror.NewInvalidInputError(move.ID, err, verrs, msg) + } } - return nil } diff --git a/pkg/services/move/move_router_test.go b/pkg/services/move/move_router_test.go index 945a65d6033..f897d239329 100644 --- a/pkg/services/move/move_router_test.go +++ b/pkg/services/move/move_router_test.go @@ -296,32 +296,6 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { }, }, nil) - boatMTOSHipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusDraft, - ShipmentType: models.MTOShipmentTypeBoatHaulAway, - }, - }, - { - Model: move, - LinkOnly: true, - }, - }, nil) - - mobileHomeMTOShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusDraft, - ShipmentType: models.MTOShipmentTypeMobileHome, - }, - }, - { - Model: move, - LinkOnly: true, - }, - }, nil) - ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ { Model: models.PPMShipment{ @@ -342,7 +316,7 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { }, }, nil) - move.MTOShipments = models.MTOShipments{shipment, boatMTOSHipment, mobileHomeMTOShipment} + move.MTOShipments = models.MTOShipments{shipment} move.MTOShipments[0].PPMShipment = &ppmShipment move.MTOShipments[0].BoatShipment = &boatShipment move.MTOShipments[0].MobileHome = &mobileHomeShipment @@ -439,6 +413,203 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { suite.Equal(models.PPMShipmentStatusSubmitted, move.MTOShipments[0].PPMShipment.Status, "expected Submitted") }) + suite.Run("returns an error when a Mobile Home Shipment is not formatted correctly", func() { + // Under test: MoveRouter.Submit + // Set up: Submit a move without a mobile home shipment that has a field that will not pass validation + // Expected outcome: Error on DB().ValidateAndUpdate + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: true, + }, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + hhgShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusDraft, + ShipmentType: models.MTOShipmentTypeMobileHome, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + year := -10000 + mobileHomeShipment := factory.BuildMobileHomeShipment(suite.DB(), []factory.Customization{ + { + Model: models.MobileHome{}, + }, + }, nil) + mobileHomeShipment.Year = &year + move.MTOShipments = models.MTOShipments{hhgShipment} + move.MTOShipments[0].MobileHome = &mobileHomeShipment + newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification) + + suite.Error(err) + suite.Contains(err.Error(), "failure saving mobile home shipment when routing move submission") + suite.Equal(models.MoveStatusNeedsServiceCounseling, move.Status, "expected move to still be in NEEDS_SERVICE_COUNSELING status when routing has failed") + }) + + suite.Run("returns an error when a Mobile Home Shipment is not formatted correctly", func() { + // Under test: MoveRouter.Submit + // Set up: Submit a move without a mobile home shipment that has a field that will not pass validation + // Expected outcome: Error on DB().ValidateAndUpdate + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: true, + }, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + hhgShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusDraft, + ShipmentType: models.MTOShipmentTypeBoatHaulAway, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + year := -10000 + boatShipment := factory.BuildBoatShipment(suite.DB(), []factory.Customization{ + { + Model: models.BoatShipment{}, + }, + }, nil) + boatShipment.Year = &year + move.MTOShipments = models.MTOShipments{hhgShipment} + move.MTOShipments[0].BoatShipment = &boatShipment + newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification) + + suite.Error(err) + suite.Contains(err.Error(), "failure saving boat shipment when routing move submission") + suite.Equal(models.MoveStatusNeedsServiceCounseling, move.Status, "expected move to still be in NEEDS_SERVICE_COUNSELING status when routing has failed") + }) + + suite.Run("returns validation errors when a the parent MTO Shipment object for a Mobile Home Shipment is not formatted correctly", func() { + // Under test: MoveRouter.Submit + // Set up: Submit a move without a mobile home shipment that has a field that will not pass validation + // Expected outcome: Error on DB().ValidateAndUpdate + + sitDaysAllowance := -1 // Invalid value that should cause a validation error on MTOShipment + + expError := "failure saving parent MTO shipment object for mobile home shipment when routing move submission" + + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: true, + }, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + hhgShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusDraft, + ShipmentType: models.MTOShipmentTypeMobileHome, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + hhgShipment.SITDaysAllowance = &sitDaysAllowance + + mobileHomeShipment := factory.BuildMobileHomeShipment(suite.DB(), []factory.Customization{ + { + Model: models.MobileHome{}, + }, + }, nil) + move.MTOShipments = models.MTOShipments{hhgShipment} + move.MTOShipments[0].MobileHome = &mobileHomeShipment + newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification) + + suite.Error(err) + suite.Contains(err.Error(), expError) + suite.Equal(models.MoveStatusNeedsServiceCounseling, move.Status, "expected move to still be in NEEDS_SERVICE_COUNSELING status when routing has failed") + }) + + suite.Run("returns validation errors when a the parent MTO Shipment object for a Boat Shipment is not formatted correctly", func() { + // Under test: MoveRouter.Submit + // Set up: Submit a move without a mobile home shipment that has a field that will not pass validation + // Expected outcome: Error on DB().ValidateAndUpdate + + sitDaysAllowance := -1 // Invalid value that should cause a validation error on MTOShipment + + expError := "failure saving parent MTO shipment object for boat shipment when routing move submission" + + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: true, + }, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + hhgShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusDraft, + ShipmentType: models.MTOShipmentTypeBoatHaulAway, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + hhgShipment.SITDaysAllowance = &sitDaysAllowance + + boatShipment := factory.BuildBoatShipment(suite.DB(), []factory.Customization{ + { + Model: models.BoatShipment{}, + }, + }, nil) + move.MTOShipments = models.MTOShipments{hhgShipment} + move.MTOShipments[0].BoatShipment = &boatShipment + newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification) + + suite.Error(err) + suite.Contains(err.Error(), expError) + suite.Equal(models.MoveStatusNeedsServiceCounseling, move.Status, "expected move to still be in NEEDS_SERVICE_COUNSELING status when routing has failed") + }) } func (suite *MoveServiceSuite) TestMoveCancellation() { From dcb29f37fe8dca424ac36928abe708c7e8969e96 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Mon, 23 Sep 2024 15:06:20 +0000 Subject: [PATCH 10/13] Fixed duplicate if statement block and coverage for mobile home shipment --- pkg/services/move/move_router.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/pkg/services/move/move_router.go b/pkg/services/move/move_router.go index 1b69f2f5674..ece47044b49 100644 --- a/pkg/services/move/move_router.go +++ b/pkg/services/move/move_router.go @@ -219,26 +219,20 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) } - if verrs, err := appCtx.DB().ValidateAndUpdate(move.MTOShipments[i].BoatShipment); verrs.HasAny() || err != nil { - msg := "failure saving boat shipment when routing move submission" - appCtx.Logger().Error(msg, zap.Error(err)) - return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) - } - } - // update status for mobile home shipment - if move.MTOShipments[i].ShipmentType == models.MTOShipmentTypeMobileHome { - move.MTOShipments[i].Status = models.MTOShipmentStatusSubmitted - - if verrs, err := appCtx.DB().ValidateAndUpdate(&move.MTOShipments[i]); verrs.HasAny() || err != nil { - msg := "failure saving parent MTO shipment object for mobile home shipment when routing move submission" - appCtx.Logger().Error(msg, zap.Error(err)) - return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) + if move.MTOShipments[i].BoatShipment != nil { + if verrs, err := appCtx.DB().ValidateAndUpdate(move.MTOShipments[i].BoatShipment); verrs.HasAny() || err != nil { + msg := "failure saving boat shipment when routing move submission" + appCtx.Logger().Error(msg, zap.Error(err)) + return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) + } } - if verrs, err := appCtx.DB().ValidateAndUpdate(move.MTOShipments[i].MobileHome); verrs.HasAny() || err != nil { - msg := "failure saving mobile home shipment when routing move submission" - appCtx.Logger().Error(msg, zap.Error(err)) - return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) + if move.MTOShipments[i].MobileHome != nil { + if verrs, err := appCtx.DB().ValidateAndUpdate(move.MTOShipments[i].MobileHome); verrs.HasAny() || err != nil { + msg := "failure saving mobile home shipment when routing move submission" + appCtx.Logger().Error(msg, zap.Error(err)) + return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) + } } } From e8b9f149fc3cc4dfca8ac59c17fa8d3a4d347806 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Mon, 23 Sep 2024 15:31:46 +0000 Subject: [PATCH 11/13] Fixed test error message to accomodate new consolidated if-block --- pkg/services/move/move_router.go | 2 +- pkg/services/move/move_router_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/services/move/move_router.go b/pkg/services/move/move_router.go index ece47044b49..24b31450935 100644 --- a/pkg/services/move/move_router.go +++ b/pkg/services/move/move_router.go @@ -214,7 +214,7 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo move.MTOShipments[i].Status = models.MTOShipmentStatusSubmitted if verrs, err := appCtx.DB().ValidateAndUpdate(&move.MTOShipments[i]); verrs.HasAny() || err != nil { - msg := "failure saving parent MTO shipment object for boat shipment when routing move submission" + msg := "failure saving parent MTO shipment object for boat/mobile home shipment when routing move submission" appCtx.Logger().Error(msg, zap.Error(err)) return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg) } diff --git a/pkg/services/move/move_router_test.go b/pkg/services/move/move_router_test.go index f897d239329..29d2e0d2e51 100644 --- a/pkg/services/move/move_router_test.go +++ b/pkg/services/move/move_router_test.go @@ -514,7 +514,7 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { sitDaysAllowance := -1 // Invalid value that should cause a validation error on MTOShipment - expError := "failure saving parent MTO shipment object for mobile home shipment when routing move submission" + expError := "failure saving parent MTO shipment object for boat/mobile home shipment when routing move submission" move := factory.BuildMove(suite.DB(), []factory.Customization{ { @@ -566,7 +566,7 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { sitDaysAllowance := -1 // Invalid value that should cause a validation error on MTOShipment - expError := "failure saving parent MTO shipment object for boat shipment when routing move submission" + expError := "failure saving parent MTO shipment object for boat/mobile home shipment when routing move submission" move := factory.BuildMove(suite.DB(), []factory.Customization{ { From de174ad0fd33bb1e34f23eb43d2d7db07fa4c827 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Mon, 23 Sep 2024 16:37:34 +0000 Subject: [PATCH 12/13] Fixed mistake where move update was inside the mobile home/boat if-block --- pkg/services/move/move_router.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/services/move/move_router.go b/pkg/services/move/move_router.go index 24b31450935..e8facf242f3 100644 --- a/pkg/services/move/move_router.go +++ b/pkg/services/move/move_router.go @@ -235,12 +235,11 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo } } } - - if verrs, err := appCtx.DB().ValidateAndSave(move); verrs.HasAny() || err != nil { - msg := "failure saving move when routing move submission" - appCtx.Logger().Error(msg, zap.Error(err)) - return apperror.NewInvalidInputError(move.ID, err, verrs, msg) - } + } + if verrs, err := appCtx.DB().ValidateAndSave(move); verrs.HasAny() || err != nil { + msg := "failure saving move when routing move submission" + appCtx.Logger().Error(msg, zap.Error(err)) + return apperror.NewInvalidInputError(move.ID, err, verrs, msg) } return nil } From 360e3cfe14a4dcf5ee01882b2423c8f5565d8900 Mon Sep 17 00:00:00 2001 From: Brooklyn Welsh Date: Mon, 23 Sep 2024 17:16:50 +0000 Subject: [PATCH 13/13] Fix for TOO integration tests --- .../office/txo/tooFlowsMobileHome.spec.js | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/playwright/tests/office/txo/tooFlowsMobileHome.spec.js b/playwright/tests/office/txo/tooFlowsMobileHome.spec.js index daf0c47e549..a888c64e5e1 100644 --- a/playwright/tests/office/txo/tooFlowsMobileHome.spec.js +++ b/playwright/tests/office/txo/tooFlowsMobileHome.spec.js @@ -87,7 +87,7 @@ test.describe('TOO user', () => { let tooFlowPage; test.beforeEach(async ({ officePage }) => { - const move = await officePage.testHarness.buildHHGMoveWithServiceItemsAndPaymentRequestsAndFilesForTOO(); + const move = await officePage.testHarness.buildMobileHomeMoveNeedsSC(); await officePage.signInAsNewTOOUser(); tooFlowPage = new TooFlowPage(officePage, move); await tooFlowPage.waitForLoading(); @@ -119,14 +119,14 @@ test.describe('TOO user', () => { await expect(page.getByTestId('ShipmentContainer')).toHaveCount(2); - await expect(page.getByText('Mobile home year')).toBeVisible(); - await expect(page.getByTestId('year')).toHaveText('2022'); - await expect(page.getByText('Mobile home make')).toBeVisible(); - await expect(page.getByTestId('make')).toHaveText('make'); - await expect(page.getByText('Mobile home model')).toBeVisible(); - await expect(page.getByTestId('model')).toHaveText('model'); - await expect(page.getByText('Dimensions')).toBeVisible(); - await expect(page.getByTestId('dimensions')).toHaveText("22' L x 22' W x 22' H"); + await expect(page.getByText('Mobile home year').last()).toBeVisible(); + await expect(page.getByTestId('year').last()).toHaveText('2022'); + await expect(page.getByText('Mobile home make').last()).toBeVisible(); + await expect(page.getByTestId('make').last()).toHaveText('make'); + await expect(page.getByText('Mobile home model').last()).toBeVisible(); + await expect(page.getByTestId('model').last()).toHaveText('model'); + await expect(page.getByText('Dimensions').last()).toBeVisible(); + await expect(page.getByTestId('dimensions').last()).toHaveText("22' L x 22' W x 22' H"); }); test('Services Counselor can delete an existing Mobile Home shipment', async ({ page, officePage }) => { @@ -225,12 +225,9 @@ test.describe('TOO user', () => { await page.locator(`[name='delivery.agent.phone']`).fill(receivingAgent.phone); await page.locator(`[name='delivery.agent.email']`).fill(receivingAgent.email); - await page.getByLabel('Counselor remarks').fill('Sample counselor remarks'); - // Submit edits await page.getByTestId('submitForm').click(); await officePage.waitForLoading(); - await expect(page.locator('.usa-alert__text')).toContainText('Your changes were saved.'); // Check that the data in the shipment card now matches what we just submitted await shipmentContainer.locator('[data-prefix="fas"][data-icon="chevron-down"]').click(); @@ -255,7 +252,5 @@ test.describe('TOO user', () => { await expect(shipmentContainer.getByTestId('model')).toHaveText('Test Model'); await expect(shipmentContainer.getByTestId('dimensions')).toHaveText(`20' 6" L x 15' 1" W x 10' H`); - - await expect(shipmentContainer.getByTestId('counselorRemarks')).toHaveText('Sample counselor remarks'); }); });