Skip to content

Commit

Permalink
Merge pull request #13674 from transcom/b-21036-enforce-dodid-and-emp…
Browse files Browse the repository at this point in the history
…lid-main

B 21036 enforce dodid and emplid main
  • Loading branch information
cameroncaci authored Sep 20, 2024
2 parents da7bb17 + 55942b7 commit 57d1aa4
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 43 deletions.
10 changes: 6 additions & 4 deletions pkg/gen/ghcapi/embedded_spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 19 additions & 2 deletions pkg/gen/ghcmessages/create_customer_payload.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 22 additions & 29 deletions pkg/handlers/ghcapi/customer.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create
payload := params.Body
var err error
var serviceMembers []models.ServiceMember
var edipi *string
var dodidUniqueFeatureFlag bool

// evaluating feature flag to see if we need to check if the DODID exists already
Expand All @@ -177,30 +176,31 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create
}

if dodidUniqueFeatureFlag {
if payload.Edipi == nil || *payload.Edipi == "" {
edipi = nil
} else {
query := `SELECT service_members.edipi
query := `SELECT service_members.edipi
FROM service_members
WHERE service_members.edipi = $1`
err := appCtx.DB().RawQuery(query, payload.Edipi).All(&serviceMembers)
if err != nil {
errorMsg := apperror.NewBadDataError("error when checking for existing service member")
payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors())
return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg
} else if len(serviceMembers) > 0 {
errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service member with this DODID already exists. Please use a different DODID number.")
payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors())
return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg
}
err := appCtx.DB().RawQuery(query, payload.Edipi).All(&serviceMembers)
if err != nil {
errorMsg := apperror.NewBadDataError("error when checking for existing service member")
payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors())
return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg
} else if len(serviceMembers) > 0 {
errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service member with this DODID already exists. Please use a different DODID number.")
payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors())
return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg
}
}

if len(serviceMembers) == 0 {
edipi = params.Body.Edipi
// Endpoint specific EDIPI and EMPLID check
// The following validation currently is only intended for the customer creation
// conducted by an office user such as the Service Counselor
if payload.Affiliation != nil && *payload.Affiliation == ghcmessages.AffiliationCOASTGUARD {
// EMPLID cannot be null
if payload.Emplid == nil {
errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service members from the Coast Guard require an EMPLID for creation.")
payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors())
return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg
}
} else {
// If the feature flag is not enabled, we will just set the dodid and continue
edipi = params.Body.Edipi
}

var newServiceMember models.ServiceMember
Expand Down Expand Up @@ -250,18 +250,11 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create
residentialAddress := addressModelFromPayload(&payload.ResidentialAddress.Address)
backupMailingAddress := addressModelFromPayload(&payload.BackupMailingAddress.Address)

var emplid *string
if *payload.Emplid == "" {
emplid = nil
} else {
emplid = payload.Emplid
}

// Create a new serviceMember using the userID
newServiceMember = models.ServiceMember{
UserID: userID,
Edipi: edipi,
Emplid: emplid,
Edipi: &payload.Edipi,
Emplid: payload.Emplid,
Affiliation: (*models.ServiceMemberAffiliation)(payload.Affiliation),
FirstName: &payload.FirstName,
MiddleName: payload.MiddleName,
Expand Down
79 changes: 77 additions & 2 deletions pkg/handlers/ghcapi/customer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() {
FirstName: "First",
Telephone: handlers.FmtString("223-455-3399"),
Affiliation: &affiliation,
Edipi: handlers.FmtString(""),
Edipi: "",
Emplid: handlers.FmtString(""),
PersonalEmail: *handlers.FmtString("email@email.com"),
BackupContact: &ghcmessages.BackupContact{
Expand Down Expand Up @@ -260,7 +260,7 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() {
FirstName: "First",
Telephone: handlers.FmtString("223-455-3399"),
Affiliation: &affiliation,
Edipi: customer.Edipi,
Edipi: *customer.Edipi,
PersonalEmail: *handlers.FmtString("email@email.com"),
BackupContact: &ghcmessages.BackupContact{
Name: handlers.FmtString("New Backup Contact"),
Expand Down Expand Up @@ -298,6 +298,81 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() {
response := handler.Handle(params)
suite.Assertions.IsType(&customerops.CreateCustomerWithOktaOptionUnprocessableEntity{}, response)
})

suite.Run("Unable to create customer of affiliation Coast Guard with no EMPLID", func() {
// in order to call the endpoint, we need to be an authenticated office user that's a SC
officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO})
officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{
RoleType: roles.RoleTypeServicesCounselor,
})

// Build provider
provider, err := factory.BuildOktaProvider(officeProviderName)
suite.NoError(err)

mockAndActivateOktaEndpoints(provider)

residentialAddress := ghcmessages.Address{
StreetAddress1: handlers.FmtString("123 New Street"),
City: handlers.FmtString("Newcity"),
State: handlers.FmtString("MA"),
PostalCode: handlers.FmtString("02110"),
}

backupAddress := ghcmessages.Address{
StreetAddress1: handlers.FmtString("123 Backup Street"),
City: handlers.FmtString("Backupcity"),
State: handlers.FmtString("MA"),
PostalCode: handlers.FmtString("02115"),
}

affiliation := ghcmessages.AffiliationCOASTGUARD

body := &ghcmessages.CreateCustomerPayload{
LastName: "Last",
FirstName: "First",
Telephone: handlers.FmtString("223-455-3399"),
Affiliation: &affiliation,
Edipi: "1234567890",
PersonalEmail: *handlers.FmtString("email@email.com"),
BackupContact: &ghcmessages.BackupContact{
Name: handlers.FmtString("New Backup Contact"),
Phone: handlers.FmtString("445-345-1212"),
Email: handlers.FmtString("newbackup@mail.com"),
},
ResidentialAddress: struct {
ghcmessages.Address
}{
Address: residentialAddress,
},
BackupMailingAddress: struct {
ghcmessages.Address
}{
Address: backupAddress,
},
CreateOktaAccount: true,
// when CacUser is false, this indicates a non-CAC user so CacValidated is set to true
CacUser: false,
}

defer goth.ClearProviders()
goth.UseProviders(provider)

request := httptest.NewRequest("POST", "/customer", nil)
request = suite.AuthenticateOfficeRequest(request, officeUser)
params := customerops.CreateCustomerWithOktaOptionParams{
HTTPRequest: request,
Body: body,
}
handlerConfig := suite.HandlerConfig()
handler := CreateCustomerWithOktaOptionHandler{
handlerConfig,
}
response := handler.Handle(params)
suite.Assertions.IsType(&customerops.CreateCustomerWithOktaOptionUnprocessableEntity{}, response)
failedToCreateCustomerPayload := response.(*customerops.CreateCustomerWithOktaOptionUnprocessableEntity).Payload.ClientError.Detail
suite.Equal("ID: 00000000-0000-0000-0000-000000000000 is in a conflicting state Service members from the Coast Guard require an EMPLID for creation.", *failedToCreateCustomerPayload)
})
}

func (suite *HandlerSuite) TestSearchCustomersHandler() {
Expand Down
22 changes: 20 additions & 2 deletions src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,26 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {

const validationSchema = Yup.object().shape({
affiliation: Yup.mixed().oneOf(Object.keys(SERVICE_MEMBER_AGENCY_LABELS)).required('Required'),
edipi: Yup.string().matches(/^(SM[0-9]{8}|[0-9]{10})$/, 'Enter a 10-digit DOD ID number'),
emplid: Yup.string().matches(/^(SM[0-9]{5}|[0-9]{7})$/, 'Enter a 7-digit EMPLID number'),
// All branches require an EDIPI unless it is a safety move
// where a fake DoD ID may be used
edipi:
!isSafetyMove &&
Yup.string()
.matches(/^(SM[0-9]{8}|[0-9]{10})$/, 'Enter a 10-digit DoD ID number')
.required('Required'),
// Only the coast guard requires both EDIPI and EMPLID
// unless it is a safety move
emplid:
!isSafetyMove &&
showEmplid &&
Yup.string().when('affiliation', {
is: (affiliationValue) => affiliationValue === departmentIndicators.COAST_GUARD,
then: () =>
Yup.string()
.matches(/^(SM[0-9]{5}|[0-9]{7})$/, 'Enter a 7-digit EMPLID number')
.required(`EMPLID is required for the Coast Guard`),
otherwise: Yup.string().notRequired(),
}),
first_name: Yup.string().required('Required'),
middle_name: Yup.string(),
last_name: Yup.string().required('Required'),
Expand Down
55 changes: 55 additions & 0 deletions src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ describe('CreateCustomerForm', () => {

await user.type(getByLabelText('Best contact phone'), fakePayload.telephone);
await user.type(getByLabelText('Personal email'), fakePayload.personal_email);
await userEvent.type(getByTestId('edipiInput'), fakePayload.edipi);

await user.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1);
await user.type(getByTestId('res-add-city'), fakePayload.residential_address.city);
Expand Down Expand Up @@ -309,6 +310,8 @@ describe('CreateCustomerForm', () => {
await user.type(getByLabelText('Best contact phone'), fakePayload.telephone);
await user.type(getByLabelText('Personal email'), fakePayload.personal_email);

await userEvent.type(getByTestId('edipiInput'), fakePayload.edipi);

await userEvent.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1);
await userEvent.type(getByTestId('res-add-city'), fakePayload.residential_address.city);
await userEvent.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]);
Expand Down Expand Up @@ -342,6 +345,57 @@ describe('CreateCustomerForm', () => {
});
}, 10000);

it('validates emplid against a coast guard member', async () => {
createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse));

const { getByLabelText, getByTestId, getByRole } = render(
<MockProviders>
<CreateCustomerForm />
</MockProviders>,
);

const user = userEvent.setup();

const saveBtn = await screen.findByRole('button', { name: 'Save' });
expect(saveBtn).toBeInTheDocument();

await user.selectOptions(getByLabelText('Branch of service'), 'COAST_GUARD');

await user.type(getByLabelText('First name'), fakePayload.first_name);
await user.type(getByLabelText('Last name'), fakePayload.last_name);

await user.type(getByLabelText('Best contact phone'), fakePayload.telephone);
await user.type(getByLabelText('Personal email'), fakePayload.personal_email);

await userEvent.type(getByTestId('edipiInput'), fakePayload.edipi);

await userEvent.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1);
await userEvent.type(getByTestId('res-add-city'), fakePayload.residential_address.city);
await userEvent.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]);
await userEvent.type(getByTestId('res-add-zip'), fakePayload.residential_address.postalCode);

await userEvent.type(getByTestId('backup-add-street1'), fakePayload.backup_mailing_address.streetAddress1);
await userEvent.type(getByTestId('backup-add-city'), fakePayload.backup_mailing_address.city);
await userEvent.selectOptions(getByTestId('backup-add-state'), [fakePayload.backup_mailing_address.state]);
await userEvent.type(getByTestId('backup-add-zip'), fakePayload.backup_mailing_address.postalCode);

await userEvent.type(getByLabelText('Name'), fakePayload.backup_contact.name);
await userEvent.type(getByRole('textbox', { name: 'Email' }), fakePayload.backup_contact.email);
await userEvent.type(getByRole('textbox', { name: 'Phone' }), fakePayload.backup_contact.telephone);

await userEvent.type(getByTestId('create-okta-account-yes'), fakePayload.create_okta_account);

await userEvent.type(getByTestId('cac-user-no'), fakePayload.cac_user);

await waitFor(() => {
expect(saveBtn).toBeDisabled(); // EMPLID not set yet
});
await userEvent.type(getByTestId('emplidInput'), '1234567');
await waitFor(() => {
expect(saveBtn).toBeEnabled(); // EMPLID is set now, all validations true
});
}, 10000);

it('allows safety privileged users to pass safety move status to orders screen', async () => {
createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse));
isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true));
Expand Down Expand Up @@ -475,6 +529,7 @@ describe('CreateCustomerForm', () => {
expect(saveBtn).toBeInTheDocument();

await user.selectOptions(getByLabelText('Branch of service'), [fakePayload.affiliation]);
await userEvent.type(getByTestId('edipiInput'), fakePayload.edipi);

await user.type(getByLabelText('First name'), fakePayload.first_name);
await user.type(getByLabelText('Last name'), fakePayload.last_name);
Expand Down
5 changes: 3 additions & 2 deletions swagger-def/ghc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4546,8 +4546,9 @@ definitions:
$ref: 'definitions/Affiliation.yaml'
edipi:
type: string
example: John
x-nullable: true
example: '1234567890'
maxLength: 10
x-nullable: false
emplid:
type: string
example: '9485155'
Expand Down
5 changes: 3 additions & 2 deletions swagger/ghc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4738,8 +4738,9 @@ definitions:
$ref: '#/definitions/Affiliation'
edipi:
type: string
example: John
x-nullable: true
example: '1234567890'
maxLength: 10
x-nullable: false
emplid:
type: string
example: '9485155'
Expand Down

0 comments on commit 57d1aa4

Please sign in to comment.