Skip to content

Commit

Permalink
Merge pull request #1468 from ministryofjustice/MLPAB-1904-add-failed…
Browse files Browse the repository at this point in the history
…-vouch-guidance

MLPAB-1904: Add voucher guidance and send email on failed vouch
  • Loading branch information
acsauk authored Sep 9, 2024
2 parents d80498f + 7536bde commit 024d945
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 22 deletions.
26 changes: 26 additions & 0 deletions internal/notify/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,29 @@ func (s VoucherInviteEmail) emailID(isProduction bool) string {

return "9af150b5-d9cd-4702-bf97-d3e6bfe81eec"
}

type VoucherFirstFailedVouchAttempt struct {
Greeting string
VoucherFullName string
}

func (e VoucherFirstFailedVouchAttempt) emailID(isProduction bool) string {
if isProduction {
return "f21ee857-8c3e-43ee-adf2-2d9f1ff1a1a8"
}

return "584412e6-f235-4227-aff9-6cb56ba48e31"
}

type VoucherSecondFailedVouchAttempt struct {
Greeting string
VoucherFullName string
}

func (e VoucherSecondFailedVouchAttempt) emailID(isProduction bool) string {
if isProduction {
return "44ffb252-ce34-4164-baa5-8b21036625ac"
}

return "3af1b3c4-35ce-4a23-abd2-bd0d019985c2"
}
1 change: 1 addition & 0 deletions internal/voucher/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
const (
PathTaskList = Path("/task-list")
PathConfirmAllowedToVouch = Path("/confirm-allowed-to-vouch")
PathYouCannotVouchForDonor = Path("/you-cannot-vouch-for-donor")
PathConfirmYourName = Path("/confirm-your-name")
PathYourName = Path("/your-name")
PathVerifyDonorDetails = Path("/verify-donor-details")
Expand Down
35 changes: 32 additions & 3 deletions internal/voucher/voucherpage/confirm_allowed_to_vouch.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package voucherpage

import (
"errors"
"net/http"
"strings"

"github.com/ministryofjustice/opg-go-common/template"
"github.com/ministryofjustice/opg-modernising-lpa/internal/appcontext"
"github.com/ministryofjustice/opg-modernising-lpa/internal/form"
"github.com/ministryofjustice/opg-modernising-lpa/internal/lpastore/lpadata"
"github.com/ministryofjustice/opg-modernising-lpa/internal/notify"
"github.com/ministryofjustice/opg-modernising-lpa/internal/task"
"github.com/ministryofjustice/opg-modernising-lpa/internal/validation"
"github.com/ministryofjustice/opg-modernising-lpa/internal/voucher"
Expand All @@ -24,7 +24,7 @@ type confirmAllowedToVouchData struct {
MatchIdentity bool
}

func ConfirmAllowedToVouch(tmpl template.Template, lpaStoreResolvingService LpaStoreResolvingService, voucherStore VoucherStore) Handler {
func ConfirmAllowedToVouch(tmpl template.Template, lpaStoreResolvingService LpaStoreResolvingService, voucherStore VoucherStore, notifyClient NotifyClient, donorStore DonorStore) Handler {
return func(appData appcontext.Data, w http.ResponseWriter, r *http.Request, provided *voucherdata.Provided) error {
lpa, err := lpaStoreResolvingService.Get(r.Context())
if err != nil {
Expand All @@ -45,7 +45,36 @@ func ConfirmAllowedToVouch(tmpl template.Template, lpaStoreResolvingService LpaS

if data.Errors.None() {
if data.Form.YesNo.IsNo() {
return errors.New("// TODO there should be a page here but it hasn't been built yet")
donor, err := donorStore.GetAny(r.Context())
if err != nil {
return err
}

donor.FailedVouchAttempts++

var email notify.Email

if donor.FailedVouchAttempts > 1 {
email = notify.VoucherSecondFailedVouchAttempt{
Greeting: notifyClient.EmailGreeting(lpa),
VoucherFullName: provided.FullName(),
}
} else {
email = notify.VoucherFirstFailedVouchAttempt{
Greeting: notifyClient.EmailGreeting(lpa),
VoucherFullName: provided.FullName(),
}
}

if err := notifyClient.SendActorEmail(r.Context(), lpa.Donor.Email, lpa.LpaUID, email); err != nil {
return err
}

if err := donorStore.Put(r.Context(), donor); err != nil {
return err
}

return voucher.PathYouCannotVouchForDonor.Redirect(w, r, appData, appData.LpaID)
}

if provided.Tasks.ConfirmYourIdentity.IsInProgress() {
Expand Down
165 changes: 149 additions & 16 deletions internal/voucher/voucherpage/confirm_allowed_to_vouch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"

"github.com/ministryofjustice/opg-modernising-lpa/internal/donor/donordata"
"github.com/ministryofjustice/opg-modernising-lpa/internal/form"
"github.com/ministryofjustice/opg-modernising-lpa/internal/lpastore/lpadata"
"github.com/ministryofjustice/opg-modernising-lpa/internal/notify"
"github.com/ministryofjustice/opg-modernising-lpa/internal/page"
"github.com/ministryofjustice/opg-modernising-lpa/internal/task"
"github.com/ministryofjustice/opg-modernising-lpa/internal/voucher"
Expand Down Expand Up @@ -101,7 +104,7 @@ func TestGetConfirmAllowedToVouch(t *testing.T) {
Execute(w, tc.data).
Return(nil)

err := ConfirmAllowedToVouch(template.Execute, lpaStoreResolvingService, nil)(testAppData, w, r, tc.provided)
err := ConfirmAllowedToVouch(template.Execute, lpaStoreResolvingService, nil, nil, nil)(testAppData, w, r, tc.provided)
resp := w.Result()

assert.Nil(t, err)
Expand All @@ -121,7 +124,7 @@ func TestGetConfirmAllowedToVouchWhenLpaStoreResolvingServiceErrors(t *testing.T
Get(r.Context()).
Return(donor, expectedError)

err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, nil)(testAppData, w, r, nil)
err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, nil, nil, nil)(testAppData, w, r, nil)

assert.Equal(t, expectedError, err)
}
Expand All @@ -140,7 +143,7 @@ func TestGetConfirmAllowedToVouchWhenTemplateErrors(t *testing.T) {
Execute(w, mock.Anything).
Return(expectedError)

err := ConfirmAllowedToVouch(template.Execute, lpaStoreResolvingService, nil)(testAppData, w, r, &voucherdata.Provided{})
err := ConfirmAllowedToVouch(template.Execute, lpaStoreResolvingService, nil, nil, nil)(testAppData, w, r, &voucherdata.Provided{})

assert.Equal(t, expectedError, err)
}
Expand Down Expand Up @@ -171,7 +174,7 @@ func TestPostConfirmAllowedToVouch(t *testing.T) {
Put(r.Context(), &voucherdata.Provided{LpaID: "lpa-id", Tasks: tasks}).
Return(nil)

err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, voucherStore)(testAppData, w, r, &voucherdata.Provided{LpaID: "lpa-id", Tasks: voucherdata.Tasks{ConfirmYourIdentity: taskState}})
err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, voucherStore, nil, nil)(testAppData, w, r, &voucherdata.Provided{LpaID: "lpa-id", Tasks: voucherdata.Tasks{ConfirmYourIdentity: taskState}})
resp := w.Result()

assert.Nil(t, err)
Expand All @@ -182,21 +185,52 @@ func TestPostConfirmAllowedToVouch(t *testing.T) {
}

func TestPostConfirmAllowedToVouchWhenNo(t *testing.T) {
f := url.Values{
form.FieldNames.YesNo: {form.No.String()},
testcases := map[int]notify.Email{
0: notify.VoucherFirstFailedVouchAttempt{Greeting: "Email greeting", VoucherFullName: "a b"},
1: notify.VoucherSecondFailedVouchAttempt{Greeting: "Email greeting", VoucherFullName: "a b"},
}

w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
r.Header.Add("Content-Type", page.FormUrlEncoded)
for failedVouchAttempts, email := range testcases {
t.Run(strconv.Itoa(failedVouchAttempts), func(t *testing.T) {
f := url.Values{
form.FieldNames.YesNo: {form.No.String()},
}

lpaStoreResolvingService := newMockLpaStoreResolvingService(t)
lpaStoreResolvingService.EXPECT().
Get(r.Context()).
Return(&lpadata.Lpa{Donor: lpadata.Donor{LastName: "Smith"}}, nil)
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
r.Header.Add("Content-Type", page.FormUrlEncoded)

err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, nil)(testAppData, w, r, &voucherdata.Provided{LpaID: "lpa-id"})
assert.Error(t, err)
lpa := &lpadata.Lpa{Donor: lpadata.Donor{LastName: "Smith", Email: "[email protected]"}, LpaUID: "lpa-uid"}

lpaStoreResolvingService := newMockLpaStoreResolvingService(t)
lpaStoreResolvingService.EXPECT().
Get(r.Context()).
Return(lpa, nil)

notifyClient := newMockNotifyClient(t)
notifyClient.EXPECT().
EmailGreeting(lpa).
Return("Email greeting")
notifyClient.EXPECT().
SendActorEmail(r.Context(), "[email protected]", "lpa-uid", email).
Return(nil)

donorStore := newMockDonorStore(t)
donorStore.EXPECT().
GetAny(r.Context()).
Return(&donordata.Provided{FailedVouchAttempts: failedVouchAttempts}, nil)
donorStore.EXPECT().
Put(r.Context(), &donordata.Provided{FailedVouchAttempts: failedVouchAttempts + 1}).
Return(nil)

err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, nil, notifyClient, donorStore)(testAppData, w, r, &voucherdata.Provided{LpaID: "lpa-id", FirstNames: "a", LastName: "b"})
resp := w.Result()

assert.Nil(t, err)
assert.Equal(t, http.StatusFound, resp.StatusCode)
assert.Equal(t, voucher.PathYouCannotVouchForDonor.Format("lpa-id"), resp.Header.Get("Location"))
})
}
}

func TestPostConfirmAllowedToVouchWhenStoreErrors(t *testing.T) {
Expand All @@ -218,6 +252,105 @@ func TestPostConfirmAllowedToVouchWhenStoreErrors(t *testing.T) {
Put(r.Context(), mock.Anything).
Return(expectedError)

err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, voucherStore)(testAppData, w, r, &voucherdata.Provided{LpaID: "lpa-id"})
err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, voucherStore, nil, nil)(testAppData, w, r, &voucherdata.Provided{LpaID: "lpa-id"})
assert.Equal(t, expectedError, err)
}

func TestPostConfirmAllowedToVouchWhenNoWhenNotifyClientError(t *testing.T) {
f := url.Values{
form.FieldNames.YesNo: {form.No.String()},
}

w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
r.Header.Add("Content-Type", page.FormUrlEncoded)

lpaStoreResolvingService := newMockLpaStoreResolvingService(t)
lpaStoreResolvingService.EXPECT().
Get(mock.Anything).
Return(&lpadata.Lpa{}, nil)

notifyClient := newMockNotifyClient(t)
notifyClient.EXPECT().
EmailGreeting(mock.Anything).
Return("Email greeting")
notifyClient.EXPECT().
SendActorEmail(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(expectedError)

donorStore := newMockDonorStore(t)
donorStore.EXPECT().
GetAny(r.Context()).
Return(&donordata.Provided{}, nil)

err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, nil, notifyClient, donorStore)(testAppData, w, r, &voucherdata.Provided{LpaID: "lpa-id"})
resp := w.Result()

assert.Error(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}

func TestPostConfirmAllowedToVouchWhenNoWhenDonorStoreErrors(t *testing.T) {
testcases := map[string]struct {
donorStore func(t *testing.T) *mockDonorStore
notifyClient func(t *testing.T) *mockNotifyClient
}{
"GetAny": {
donorStore: func(t *testing.T) *mockDonorStore {
d := newMockDonorStore(t)
d.EXPECT().
GetAny(mock.Anything).
Return(&donordata.Provided{}, expectedError)
return d
},
notifyClient: func(t *testing.T) *mockNotifyClient {
return newMockNotifyClient(t)
},
},
"Put": {
donorStore: func(t *testing.T) *mockDonorStore {
d := newMockDonorStore(t)
d.EXPECT().
GetAny(mock.Anything).
Return(&donordata.Provided{}, nil)
d.EXPECT().
Put(mock.Anything, mock.Anything).
Return(expectedError)
return d
},
notifyClient: func(t *testing.T) *mockNotifyClient {
nc := newMockNotifyClient(t)
nc.EXPECT().
EmailGreeting(mock.Anything).
Return("Email greeting")
nc.EXPECT().
SendActorEmail(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(nil)
return nc
},
},
}

for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
f := url.Values{
form.FieldNames.YesNo: {form.No.String()},
}

w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
r.Header.Add("Content-Type", page.FormUrlEncoded)

lpaStoreResolvingService := newMockLpaStoreResolvingService(t)
lpaStoreResolvingService.EXPECT().
Get(mock.Anything).
Return(&lpadata.Lpa{}, nil)

err := ConfirmAllowedToVouch(nil, lpaStoreResolvingService, nil, tc.notifyClient(t), tc.donorStore(t))(testAppData, w, r, &voucherdata.Provided{LpaID: "lpa-id"})
resp := w.Result()

assert.Error(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
})
}
}
4 changes: 3 additions & 1 deletion internal/voucher/voucherpage/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ func Register(
handleVoucher(voucher.PathYourName, None,
YourName(tmpls.Get("your_name.gohtml"), lpaStoreResolvingService, voucherStore))
handleVoucher(voucher.PathConfirmAllowedToVouch, None,
ConfirmAllowedToVouch(tmpls.Get("confirm_allowed_to_vouch.gohtml"), lpaStoreResolvingService, voucherStore))
ConfirmAllowedToVouch(tmpls.Get("confirm_allowed_to_vouch.gohtml"), lpaStoreResolvingService, voucherStore, notifyClient, donorStore))
handleVoucher(voucher.PathYouCannotVouchForDonor, None,
Guidance(tmpls.Get("you_cannot_vouch_for_donor.gohtml"), lpaStoreResolvingService))

handleVoucher(voucher.PathVerifyDonorDetails, None,
VerifyDonorDetails(tmpls.Get("verify_donor_details.gohtml"), lpaStoreResolvingService, voucherStore, donorStore))
Expand Down
5 changes: 4 additions & 1 deletion lang/cy.json
Original file line number Diff line number Diff line change
Expand Up @@ -1373,5 +1373,8 @@
"tryVouchingAgainContent": "<h2 class=\"govuk-heading-m\">Welsh</h2><p class=\"govuk-body\">Welsh</p><details class=\"govuk-details\"><summary class=\"govuk-details__summary\"><span class=\"govuk-details__summary-text\">Welsh</span></summary><div class=\"govuk-details__text\">Welsh</div><div class=\"govuk-details__text\">Welsh</div></details>",
"iHaveSomeoneElseWhoCanVouch": "Welsh",
"thePersonYouAskedToVouchHasBeenUnableToContinue": "<div class=\"govuk-notification-banner__content\"><p class=\"govuk-notification-banner__heading\">Welsh</p></div>",
"thePersonYouAskedToVouchHasBeenUnableToContinueSecondAttempt": "<div class=\"govuk-notification-banner__content\"><p class=\"govuk-notification-banner__heading\">Welsh</p><p class=\"govuk-body\">Welsh</p></div>"
"thePersonYouAskedToVouchHasBeenUnableToContinueSecondAttempt": "<div class=\"govuk-notification-banner__content\"><p class=\"govuk-notification-banner__heading\">Welsh</p><p class=\"govuk-body\">Welsh</p></div>",
"youCannotVouch": "Welsh",
"youHaveToldUsYouCannotVouchForDonorName": "Welsh {{ .DonorFullName }}",
"voucherYouCannotVouchContent": "<p class=\"govuk-body\">Welsh {{ .DonorFullName }} Welsh</p><p class=\"govuk-body\">Welsh</p><p class=\"govuk-body\">Welsh</p>"
}
5 changes: 4 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1302,5 +1302,8 @@
"tryVouchingAgainContent": "<h2 class=\"govuk-heading-m\">Try vouching again</h2><p class=\"govuk-body\">As you have previously attempted vouching, you will only able to select this option one more time.</p><details class=\"govuk-details\"><summary class=\"govuk-details__summary\"><span class=\"govuk-details__summary-text\">About vouching attempts</span></summary><div class=\"govuk-details__text\">A vouching attempt is made once the person vouching for you enters the code you gave them.</div><div class=\"govuk-details__text\">If they cannot or do not complete the vouching process after entering the code, this counts as a failed attempt.</div></details>",
"iHaveSomeoneElseWhoCanVouch": "I have someone else who can vouch for me",
"thePersonYouAskedToVouchHasBeenUnableToContinue": "<div class=\"govuk-notification-banner__content\"><p class=\"govuk-notification-banner__heading\">The person you asked to vouch for you has been unable to continue</p></div>",
"thePersonYouAskedToVouchHasBeenUnableToContinueSecondAttempt": "<div class=\"govuk-notification-banner__content\"><p class=\"govuk-notification-banner__heading\">The person you asked to vouch for you has been unable to continue</p><p class=\"govuk-body\">You cannot ask another person to vouch for you as only 2 attempts can be made of having a someone vouch for your identity.</p></div>"
"thePersonYouAskedToVouchHasBeenUnableToContinueSecondAttempt": "<div class=\"govuk-notification-banner__content\"><p class=\"govuk-notification-banner__heading\">The person you asked to vouch for you has been unable to continue</p><p class=\"govuk-body\">You cannot ask another person to vouch for you as only 2 attempts can be made of having a someone vouch for your identity.</p></div>",
"youCannotVouch": "You cannot vouch",
"youHaveToldUsYouCannotVouchForDonorName": "You have told us that you cannot vouch for {{ .DonorFullName }}.",
"voucherYouCannotVouchContent": "<p class=\"govuk-body\">We have contacted {{ .DonorFullName }} to let them know.</p><p class=\"govuk-body\">You do not need to do anything else.</p><p class=\"govuk-body\">You can now close this browsing window.</p>"
}
13 changes: 13 additions & 0 deletions web/template/voucher/you_cannot_vouch_for_donor.gohtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{ template "page" . }}

{{ define "pageTitle" }}{{ tr .App "youCannotVouch" }}{{ end }}

{{ define "main" }}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
{{ template "notification-banner" (notificationBanner .App "important" (trFormatHtml .App "youHaveToldUsYouCannotVouchForDonorName" "DonorFullName" .Lpa.Donor.FullName) "heading" ) }}

{{ trFormatHtml .App "voucherYouCannotVouchContent" "DonorFullName" .Lpa.Donor.FullName }}
</div>
</div>
{{ end }}

0 comments on commit 024d945

Please sign in to comment.