Skip to content

Commit

Permalink
Add new page for M- repeat applications
Browse files Browse the repository at this point in the history
  • Loading branch information
hawx committed Nov 26, 2024
1 parent 9cb3c81 commit c54082a
Show file tree
Hide file tree
Showing 22 changed files with 492 additions and 55 deletions.
24 changes: 24 additions & 0 deletions cypress/e2e/donor/cost-of-repeat-application.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
describe('Cost of repeat application', () => {
beforeEach(() => {
cy.visit('/fixtures?redirect=/cost-of-repeat-application');
});

it('can be submitted', () => {
cy.checkA11yApp();

cy.contains('label', 'no fee').click();
cy.contains('button', 'Save and continue').click();

cy.url().should('contain', '/what-happens-next-repeat-application-no-fee');
});

it('errors when unselected', () => {
cy.contains('button', 'Save and continue').click();

cy.get('.govuk-error-summary').within(() => {
cy.contains('Select which fee you are eligible to pay');
});

cy.contains('.govuk-error-message', 'Select which fee you are eligible to pay');
});
});
2 changes: 1 addition & 1 deletion cypress/e2e/donor/previous-application-number.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('Previous application number', () => {
cy.get('#f-previous-application-number').type('MABC');
cy.contains('button', 'Save and continue').click();

cy.url().should('contain', '/evidence-successfully-uploaded');
cy.url().should('contain', '/cost-of-repeat-application');
});

it('errors when unselected', () => {
Expand Down
12 changes: 9 additions & 3 deletions internal/donor/donordata/provided.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,13 @@ type Provided struct {
EvidenceDelivery pay.EvidenceDelivery
// PreviousApplicationNumber if the application is related to an existing application
PreviousApplicationNumber string
// PreviousFee is the fee previously paid for an LPA
// PreviousFee is the fee previously paid for an LPA, if applying for a repeat
// of an LPA with reference prefixed 7 or have selected HalfFee for
// CostOfRepeatApplication.
PreviousFee pay.PreviousFee
// CostOfRepeatApplication is the fee the donor believes they are eligible
// for, if applying for a repeat of an LPA with reference prefixed M.
CostOfRepeatApplication pay.CostOfRepeatApplication

HasSentApplicationUpdatedEvent bool `hash:"-"`
}
Expand Down Expand Up @@ -212,7 +217,8 @@ func (c toCheck) HashInclude(field string, _ any) (bool, error) {
"RegisteringWithCourtOfProtection",
"WantVoucher",
"Voucher",
"FailedVouchAttempts":
"FailedVouchAttempts",
"CostOfRepeatApplication":
return false, nil
}

Expand Down Expand Up @@ -405,7 +411,7 @@ func (p *Provided) Cost() int {
return 8200
}

return pay.Cost(p.FeeType, p.PreviousFee)
return pay.Cost(p.FeeType, p.PreviousFee, p.CostOfRepeatApplication)
}

func (p *Provided) FeeAmount() pay.AmountPence {
Expand Down
4 changes: 2 additions & 2 deletions internal/donor/donordata/provided_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ func TestGenerateHash(t *testing.T) {
}

// DO change this value to match the updates
const modified uint64 = 0x1270f2b79d9db4a5
const modified uint64 = 0x42eaf05fa4af6121

// DO NOT change these initial hash values. If a field has been added/removed
// you will need to handle the version gracefully by modifying
// (*Provided).HashInclude and adding another testcase for the new
// version.
testcases := map[uint8]uint64{
0: 0x71bd1fb1ede66b54,
0: 0xe929f7d694b60743,
}

for version, initial := range testcases {
Expand Down
53 changes: 53 additions & 0 deletions internal/donor/donorpage/cost_of_repeat_application.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package donorpage

import (
"net/http"

"github.com/ministryofjustice/opg-go-common/template"
"github.com/ministryofjustice/opg-modernising-lpa/internal/appcontext"
"github.com/ministryofjustice/opg-modernising-lpa/internal/donor"
"github.com/ministryofjustice/opg-modernising-lpa/internal/donor/donordata"
"github.com/ministryofjustice/opg-modernising-lpa/internal/form"
"github.com/ministryofjustice/opg-modernising-lpa/internal/pay"
"github.com/ministryofjustice/opg-modernising-lpa/internal/task"
"github.com/ministryofjustice/opg-modernising-lpa/internal/validation"
)

type costOfRepeatApplicationData struct {
App appcontext.Data
Errors validation.List
Form *form.SelectForm[pay.CostOfRepeatApplication, pay.CostOfRepeatApplicationOptions, *pay.CostOfRepeatApplication]
}

func CostOfRepeatApplication(tmpl template.Template, donorStore DonorStore) Handler {
return func(appData appcontext.Data, w http.ResponseWriter, r *http.Request, provided *donordata.Provided) error {
data := &costOfRepeatApplicationData{
App: appData,
Form: form.NewSelectForm(provided.CostOfRepeatApplication, pay.CostOfRepeatApplicationValues, "whichFeeYouAreEligibleToPay"),
}

if r.Method == http.MethodPost {
data.Form.Read(r)
data.Errors = data.Form.Validate()

if data.Errors.None() {
if provided.CostOfRepeatApplication != data.Form.Selected {
provided.CostOfRepeatApplication = data.Form.Selected
provided.Tasks.PayForLpa = task.PaymentStatePending

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

if provided.CostOfRepeatApplication.IsHalfFee() {
return donor.PathPreviousFee.Redirect(w, r, appData, provided)
}

return donor.PathWhatHappensNextRepeatApplicationNoFee.Redirect(w, r, appData, provided)
}
}

return tmpl(w, data)
}
}
165 changes: 165 additions & 0 deletions internal/donor/donorpage/cost_of_repeat_application_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package donorpage

import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/ministryofjustice/opg-modernising-lpa/internal/donor"
"github.com/ministryofjustice/opg-modernising-lpa/internal/donor/donordata"
"github.com/ministryofjustice/opg-modernising-lpa/internal/form"
"github.com/ministryofjustice/opg-modernising-lpa/internal/page"
"github.com/ministryofjustice/opg-modernising-lpa/internal/pay"
"github.com/ministryofjustice/opg-modernising-lpa/internal/task"
"github.com/ministryofjustice/opg-modernising-lpa/internal/validation"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestGetCostOfRepeatApplication(t *testing.T) {
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodGet, "/", nil)

template := newMockTemplate(t)
template.EXPECT().
Execute(w, &costOfRepeatApplicationData{
App: testAppData,
Form: form.NewEmptySelectForm[pay.CostOfRepeatApplication](pay.CostOfRepeatApplicationValues, "whichFeeYouAreEligibleToPay"),
}).
Return(nil)

err := CostOfRepeatApplication(template.Execute, nil)(testAppData, w, r, &donordata.Provided{})
resp := w.Result()

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

func TestGetCostOfRepeatApplicationFromStore(t *testing.T) {
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodGet, "/", nil)

template := newMockTemplate(t)
template.EXPECT().
Execute(w, &costOfRepeatApplicationData{
App: testAppData,
Form: form.NewSelectForm(pay.CostOfRepeatApplicationHalfFee, pay.CostOfRepeatApplicationValues, "whichFeeYouAreEligibleToPay"),
}).
Return(nil)

err := CostOfRepeatApplication(template.Execute, nil)(testAppData, w, r, &donordata.Provided{CostOfRepeatApplication: pay.CostOfRepeatApplicationHalfFee})
resp := w.Result()

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

func TestGetCostOfRepeatApplicationWhenTemplateErrors(t *testing.T) {
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodGet, "/", nil)

template := newMockTemplate(t)
template.EXPECT().
Execute(w, mock.Anything).
Return(expectedError)

err := CostOfRepeatApplication(template.Execute, nil)(testAppData, w, r, &donordata.Provided{})
resp := w.Result()

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

func TestPostCostOfRepeatApplication(t *testing.T) {
for cost, path := range map[pay.CostOfRepeatApplication]donor.Path{
pay.CostOfRepeatApplicationNoFee: donor.PathWhatHappensNextRepeatApplicationNoFee,
pay.CostOfRepeatApplicationHalfFee: donor.PathPreviousFee,
} {
t.Run(cost.String(), func(t *testing.T) {
form := url.Values{
form.FieldNames.Select: {cost.String()},
}

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

provided := &donordata.Provided{
LpaID: "lpa-id",
CostOfRepeatApplication: cost,
Tasks: donordata.Tasks{PayForLpa: task.PaymentStatePending},
}

donorStore := newMockDonorStore(t)
donorStore.EXPECT().
Put(r.Context(), provided).
Return(nil)

err := CostOfRepeatApplication(nil, donorStore)(testAppData, w, r, &donordata.Provided{LpaID: "lpa-id"})
resp := w.Result()

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

func TestPostCostOfRepeatApplicationWhenNotChanged(t *testing.T) {
form := url.Values{
form.FieldNames.Select: {pay.CostOfRepeatApplicationHalfFee.String()},
}

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

err := CostOfRepeatApplication(nil, nil)(testAppData, w, r, &donordata.Provided{
LpaID: "lpa-id",
CostOfRepeatApplication: pay.CostOfRepeatApplicationHalfFee,
})
resp := w.Result()

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

func TestPostCostOfRepeatApplicationWhenStoreErrors(t *testing.T) {
form := url.Values{
form.FieldNames.Select: {pay.CostOfRepeatApplicationHalfFee.String()},
}

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

donorStore := newMockDonorStore(t)
donorStore.EXPECT().
Put(r.Context(), mock.Anything).
Return(expectedError)

err := CostOfRepeatApplication(nil, donorStore)(testAppData, w, r, &donordata.Provided{})
assert.Equal(t, expectedError, err)
}

func TestPostCostOfRepeatApplicationWhenValidationErrors(t *testing.T) {
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(""))
r.Header.Add("Content-Type", page.FormUrlEncoded)

template := newMockTemplate(t)
template.EXPECT().
Execute(w, mock.MatchedBy(func(data *costOfRepeatApplicationData) bool {
return assert.Equal(t, validation.With(form.FieldNames.Select, validation.SelectError{Label: "whichFeeYouAreEligibleToPay"}), data.Errors)
})).
Return(nil)

err := CostOfRepeatApplication(template.Execute, nil)(testAppData, w, r, &donordata.Provided{})
resp := w.Result()

assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
2 changes: 1 addition & 1 deletion internal/donor/donorpage/previous_application_number.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func PreviousApplicationNumber(tmpl template.Template, donorStore DonorStore) Ha
if provided.PreviousApplicationNumber[0] == '7' {
return donor.PathPreviousFee.Redirect(w, r, appData, provided)
} else {
return donor.PathEvidenceSuccessfullyUploaded.Redirect(w, r, appData, provided)
return donor.PathCostOfRepeatApplication.Redirect(w, r, appData, provided)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestGetPreviousApplicationNumberWhenTemplateErrors(t *testing.T) {
func TestPostPreviousApplicationNumber(t *testing.T) {
testcases := map[string]donor.Path{
"7": donor.PathPreviousFee,
"M": donor.PathEvidenceSuccessfullyUploaded,
"M": donor.PathCostOfRepeatApplication,
}

for start, redirect := range testcases {
Expand Down
2 changes: 1 addition & 1 deletion internal/donor/donorpage/previous_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func PreviousFee(tmpl template.Template, payer Handler, donorStore DonorStore) H
}
}

if provided.PreviousFee.IsPreviousFeeFull() {
if provided.PreviousFee.IsFull() {
return payer(appData, w, r, provided)
}

Expand Down
6 changes: 5 additions & 1 deletion internal/donor/donorpage/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,12 @@ func Register(
AreYouApplyingForFeeDiscountOrExemption(tmpls.Get("are_you_applying_for_a_different_fee_type.gohtml"), payer, donorStore))
handleWithDonor(donor.PathWhichFeeTypeAreYouApplyingFor, page.CanGoBack,
WhichFeeTypeAreYouApplyingFor(tmpls.Get("which_fee_type_are_you_applying_for.gohtml"), donorStore))
handleWithDonor(donor.PathPreviousApplicationNumber, page.None,
handleWithDonor(donor.PathPreviousApplicationNumber, page.CanGoBack,
PreviousApplicationNumber(tmpls.Get("previous_application_number.gohtml"), donorStore))
handleWithDonor(donor.PathPreviousFee, page.CanGoBack,
PreviousFee(tmpls.Get("previous_fee.gohtml"), payer, donorStore))
handleWithDonor(donor.PathCostOfRepeatApplication, page.CanGoBack,
CostOfRepeatApplication(tmpls.Get("cost_of_repeat_application.gohtml"), donorStore))
handleWithDonor(donor.PathEvidenceRequired, page.CanGoBack,
Guidance(tmpls.Get("evidence_required.gohtml")))
handleWithDonor(donor.PathHowWouldYouLikeToSendEvidence, page.CanGoBack,
Expand All @@ -388,6 +390,8 @@ func Register(
Guidance(tmpls.Get("evidence_successfully_uploaded.gohtml")))
handleWithDonor(donor.PathWhatHappensNextPostEvidence, page.None,
Guidance(tmpls.Get("what_happens_next_post_evidence.gohtml")))
handleWithDonor(donor.PathWhatHappensNextRepeatApplicationNoFee, page.None,
Guidance(tmpls.Get("what_happens_next_repeat_application_no_fee.gohtml")))

handleWithDonor(donor.PathConfirmYourIdentity, page.CanGoBack,
ConfirmYourIdentity(tmpls.Get("prove_your_identity.gohtml"), donorStore))
Expand Down
2 changes: 2 additions & 0 deletions internal/donor/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
PathConfirmPersonAllowedToVouch = Path("/confirm-person-allowed-to-vouch")
PathConfirmYourCertificateProviderIsNotRelated = Path("/confirm-your-certificate-provider-is-not-related")
PathConfirmYourIdentity = Path("/confirm-your-identity")
PathCostOfRepeatApplication = Path("/cost-of-repeat-application")
PathDeleteThisLpa = Path("/delete-this-lpa")
PathDoYouWantReplacementAttorneys = Path("/do-you-want-replacement-attorneys")
PathDoYouWantToNotifyPeople = Path("/do-you-want-to-notify-people")
Expand Down Expand Up @@ -107,6 +108,7 @@ const (
PathWhatACertificateProviderDoes = Path("/what-a-certificate-provider-does")
PathWhatHappensNextPostEvidence = Path("/what-happens-next-post-evidence")
PathWhatHappensNextRegisteringWithCourtOfProtection = Path("/what-happens-next-registering-with-court-of-protection")
PathWhatHappensNextRepeatApplicationNoFee = Path("/what-happens-next-repeat-application-no-fee")
PathWhatYouCanDoNow = Path("/what-you-can-do-now")
PathWhatYouCanDoNowExpired = Path("/what-you-can-do-now-expired")
PathWhenCanTheLpaBeUsed = Path("/when-can-the-lpa-be-used")
Expand Down
Loading

0 comments on commit c54082a

Please sign in to comment.