Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

B 21036 enforce dodid and emplid main #13674

Merged
merged 20 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4367faf
add yup validation for edipi and emplid
cameroncaci Sep 5, 2024
2887cc3
enforce requirement on backend and some cleanup
cameroncaci Sep 6, 2024
3ee8661
adjust yup error
cameroncaci Sep 6, 2024
a9ed198
change capitalization
cameroncaci Sep 6, 2024
2d459eb
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 10, 2024
055776a
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 11, 2024
073ca03
Merge remote-tracking branch 'origin/MAIN-B-21038' into b-21036-enfor…
cameroncaci Sep 13, 2024
1570575
Merge branch 'b-21036-enforce-dodid-and-emplid-main' of github.com:tr…
cameroncaci Sep 13, 2024
daa946e
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 19, 2024
5c4d48f
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 19, 2024
3eaba66
Merge remote-tracking branch 'origin/main' into b-21036-enforce-dodid…
cameroncaci Sep 19, 2024
9ae3f96
catch up main to int test
cameroncaci Sep 19, 2024
51c6f9f
server gen
cameroncaci Sep 19, 2024
60344bc
prettier
cameroncaci Sep 19, 2024
52ce658
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 19, 2024
c852584
Revert "server gen"
cameroncaci Sep 20, 2024
2d63281
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 20, 2024
3d66dcb
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 20, 2024
35bc758
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 20, 2024
55942b7
Merge branch 'main' into b-21036-enforce-dodid-and-emplid-main
cameroncaci Sep 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
28 changes: 22 additions & 6 deletions src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,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(/[0-9]{10}/, 'Enter a 10-digit DOD ID number'),
emplid: Yup.string()
.notRequired()
.matches(/[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(/[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(/[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 Expand Up @@ -262,17 +278,17 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
label="DoD ID number"
name="edipi"
id="edipi"
labelHint="Optional"
maxLength="10"
data-testid="edipi"
cameroncaci marked this conversation as resolved.
Show resolved Hide resolved
isDisabled={isSafetyMove}
/>
{showEmplid && (
<TextField
label="EMPLID"
name="emplid"
id="emplid"
data-testid="emplid"
maxLength="7"
labelHint="Optional"
inputMode="numeric"
pattern="[0-9]{7}"
isDisabled={isSafetyMove}
Expand Down
54 changes: 54 additions & 0 deletions src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,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('edipi'), 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 @@ -282,6 +284,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('edipi'), fakePayload.edipi);
cameroncaci marked this conversation as resolved.
Show resolved Hide resolved

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('emplid'), '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 @@ -350,6 +403,7 @@ describe('CreateCustomerForm', () => {
expect(saveBtn).toBeInTheDocument();

await user.selectOptions(getByLabelText('Branch of service'), [fakePayload.affiliation]);
await userEvent.type(getByTestId('edipi'), 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 @@ -4475,8 +4475,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 @@ -4666,8 +4666,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
Loading