diff --git a/cypress/e2e/supporter/dashboard.cy.js b/cypress/e2e/supporter/dashboard.cy.js index aa910118f9..ddd9ff914d 100644 --- a/cypress/e2e/supporter/dashboard.cy.js +++ b/cypress/e2e/supporter/dashboard.cy.js @@ -11,12 +11,7 @@ describe('Dashboard', () => { cy.contains('Property and affairs'); cy.contains('In progress'); - cy.contains('a', 'M-').click(); - cy.contains('Provide your details').click(); - cy.get('#f-first-names').type('2'); - cy.contains('button', 'Continue').click(); - cy.contains('a', 'Dashboard').click(); - cy.contains('Sam2 Smith'); + cy.contains('a', 'M-'); }); it('can start a new LPA', () => { diff --git a/cypress/e2e/supporter/view-lpa.cy.js b/cypress/e2e/supporter/view-lpa.cy.js new file mode 100644 index 0000000000..917a010fa9 --- /dev/null +++ b/cypress/e2e/supporter/view-lpa.cy.js @@ -0,0 +1,33 @@ +describe('View LPA', () => { + beforeEach(() => { + cy.visit('/fixtures/supporter?organisation=1&redirect=/dashboard&lpa=1'); + cy.checkA11yApp(); + }); + + it('can continue making an LPA', () => { + cy.contains('a', 'M-FAKE').click() + + cy.url().should('contain', '/view-lpa'); + cy.checkA11yApp(); + + cy.contains('h1', 'Property and affairs LPA') + cy.contains('div', 'M-FAKE') + + cy.contains('a', 'Donor access') + cy.contains('a', 'View LPA summary') + cy.contains('a', 'Go to task list').click() + + cy.url().should('contain', '/task-list'); + cy.checkA11yApp(); + + cy.contains('Provide your details').click() + + cy.url().should('contain', '/your-details'); + cy.checkA11yApp(); + + cy.get('#f-first-names').type('2'); + cy.contains('button', 'Continue').click(); + cy.contains('a', 'Dashboard').click(); + cy.contains('Sam2 Smith'); + }); +}) diff --git a/internal/page/paths.go b/internal/page/paths.go index 8ed571f0b4..6dac96ec64 100644 --- a/internal/page/paths.go +++ b/internal/page/paths.go @@ -258,6 +258,7 @@ type SupporterPaths struct { ManageTeamMembers SupporterPath OrganisationCreated SupporterPath OrganisationDetails SupporterPath + ViewLPA SupporterPath } type AppPaths struct { @@ -441,6 +442,7 @@ var Paths = AppPaths{ ManageTeamMembers: "/manage-organisation/manage-team-members", OrganisationCreated: "/organisation-or-company-created", OrganisationDetails: "/manage-organisation/organisation-details", + ViewLPA: "/view-lpa", }, HealthCheck: HealthCheckPaths{ diff --git a/internal/page/supporter/mock_DonorStore_test.go b/internal/page/supporter/mock_DonorStore_test.go index a108a938ff..858aa3fb1d 100644 --- a/internal/page/supporter/mock_DonorStore_test.go +++ b/internal/page/supporter/mock_DonorStore_test.go @@ -25,6 +25,64 @@ func (_m *mockDonorStore) EXPECT() *mockDonorStore_Expecter { return &mockDonorStore_Expecter{mock: &_m.Mock} } +// Get provides a mock function with given fields: ctx +func (_m *mockDonorStore) Get(ctx context.Context) (*actor.DonorProvidedDetails, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 *actor.DonorProvidedDetails + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*actor.DonorProvidedDetails, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *actor.DonorProvidedDetails); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*actor.DonorProvidedDetails) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockDonorStore_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type mockDonorStore_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +func (_e *mockDonorStore_Expecter) Get(ctx interface{}) *mockDonorStore_Get_Call { + return &mockDonorStore_Get_Call{Call: _e.mock.On("Get", ctx)} +} + +func (_c *mockDonorStore_Get_Call) Run(run func(ctx context.Context)) *mockDonorStore_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockDonorStore_Get_Call) Return(_a0 *actor.DonorProvidedDetails, _a1 error) *mockDonorStore_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockDonorStore_Get_Call) RunAndReturn(run func(context.Context) (*actor.DonorProvidedDetails, error)) *mockDonorStore_Get_Call { + _c.Call.Return(run) + return _c +} + // GetByKeys provides a mock function with given fields: ctx, keys func (_m *mockDonorStore) GetByKeys(ctx context.Context, keys []dynamo.Key) ([]actor.DonorProvidedDetails, error) { ret := _m.Called(ctx, keys) diff --git a/internal/page/supporter/mock_Template_test.go b/internal/page/supporter/mock_Template_test.go index 1650d213ff..fa1d16f749 100644 --- a/internal/page/supporter/mock_Template_test.go +++ b/internal/page/supporter/mock_Template_test.go @@ -21,9 +21,9 @@ func (_m *mockTemplate) EXPECT() *mockTemplate_Expecter { return &mockTemplate_Expecter{mock: &_m.Mock} } -// Execute provides a mock function with given fields: _a0, _a1 -func (_m *mockTemplate) Execute(_a0 io.Writer, _a1 interface{}) error { - ret := _m.Called(_a0, _a1) +// Execute provides a mock function with given fields: w, data +func (_m *mockTemplate) Execute(w io.Writer, data interface{}) error { + ret := _m.Called(w, data) if len(ret) == 0 { panic("no return value specified for Execute") @@ -31,7 +31,7 @@ func (_m *mockTemplate) Execute(_a0 io.Writer, _a1 interface{}) error { var r0 error if rf, ok := ret.Get(0).(func(io.Writer, interface{}) error); ok { - r0 = rf(_a0, _a1) + r0 = rf(w, data) } else { r0 = ret.Error(0) } @@ -45,13 +45,13 @@ type mockTemplate_Execute_Call struct { } // Execute is a helper method to define mock.On call -// - _a0 io.Writer -// - _a1 interface{} -func (_e *mockTemplate_Expecter) Execute(_a0 interface{}, _a1 interface{}) *mockTemplate_Execute_Call { - return &mockTemplate_Execute_Call{Call: _e.mock.On("Execute", _a0, _a1)} +// - w io.Writer +// - data interface{} +func (_e *mockTemplate_Expecter) Execute(w interface{}, data interface{}) *mockTemplate_Execute_Call { + return &mockTemplate_Execute_Call{Call: _e.mock.On("Execute", w, data)} } -func (_c *mockTemplate_Execute_Call) Run(run func(_a0 io.Writer, _a1 interface{})) *mockTemplate_Execute_Call { +func (_c *mockTemplate_Execute_Call) Run(run func(w io.Writer, data interface{})) *mockTemplate_Execute_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(io.Writer), args[1].(interface{})) }) diff --git a/internal/page/supporter/register.go b/internal/page/supporter/register.go index 54234b8903..f4a53b5b3b 100644 --- a/internal/page/supporter/register.go +++ b/internal/page/supporter/register.go @@ -43,6 +43,7 @@ type MemberStore interface { } type DonorStore interface { + Get(ctx context.Context) (*actor.DonorProvidedDetails, error) GetByKeys(ctx context.Context, keys []dynamo.Key) ([]actor.DonorProvidedDetails, error) } @@ -64,7 +65,7 @@ type NotifyClient interface { SendEmail(context context.Context, to string, email notify.Email) error } -type Template func(io.Writer, interface{}) error +type Template func(w io.Writer, data interface{}) error type Handler func(data page.AppData, w http.ResponseWriter, r *http.Request, organisation *actor.Organisation) error @@ -115,6 +116,8 @@ func Register( ConfirmDonorCanInteractOnline(tmpls.Get("confirm_donor_can_interact_online.gohtml"), organisationStore)) handleWithSupporter(paths.ContactOPGForPaperForms, None, Guidance(tmpls.Get("contact_opg_for_paper_forms.gohtml"))) + handleWithSupporter(paths.ViewLPA, CanGoBack, + ViewLPA(tmpls.Get("view_lpa.gohtml"), donorStore)) handleWithSupporter(paths.OrganisationDetails, RequireAdmin, Guidance(tmpls.Get("organisation_details.gohtml"))) diff --git a/internal/page/supporter/view_lpa.go b/internal/page/supporter/view_lpa.go new file mode 100644 index 0000000000..d02063cbf5 --- /dev/null +++ b/internal/page/supporter/view_lpa.go @@ -0,0 +1,45 @@ +package supporter + +import ( + "errors" + "net/http" + + "github.com/ministryofjustice/opg-go-common/template" + "github.com/ministryofjustice/opg-modernising-lpa/internal/actor" + "github.com/ministryofjustice/opg-modernising-lpa/internal/page" + "github.com/ministryofjustice/opg-modernising-lpa/internal/validation" +) + +type viewLPAData struct { + App page.AppData + Errors validation.List + Donor *actor.DonorProvidedDetails +} + +func ViewLPA(tmpl template.Template, donorStore DonorStore) Handler { + return func(appData page.AppData, w http.ResponseWriter, r *http.Request, organisation *actor.Organisation) error { + sessionData, err := page.SessionDataFromContext(r.Context()) + if err != nil { + return err + } + + lpaID := r.FormValue("id") + if lpaID == "" { + return errors.New("lpaID missing from query") + } + + sessionData.LpaID = lpaID + + ctx := page.ContextWithSessionData(r.Context(), sessionData) + + donor, err := donorStore.Get(ctx) + if err != nil { + return err + } + + return tmpl(w, &viewLPAData{ + App: appData, + Donor: donor, + }) + } +} diff --git a/internal/page/supporter/view_lpa_test.go b/internal/page/supporter/view_lpa_test.go new file mode 100644 index 0000000000..db075b3bdc --- /dev/null +++ b/internal/page/supporter/view_lpa_test.go @@ -0,0 +1,92 @@ +package supporter + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ministryofjustice/opg-modernising-lpa/internal/actor" + "github.com/ministryofjustice/opg-modernising-lpa/internal/page" + "github.com/stretchr/testify/assert" +) + +func TestGetViewLPA(t *testing.T) { + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(page.ContextWithSessionData(context.Background(), &page.SessionData{}), http.MethodGet, "/?id=lpa-id", nil) + + donor := &actor.DonorProvidedDetails{LpaID: "lpa-id"} + + donorStore := newMockDonorStore(t) + donorStore.EXPECT(). + Get(page.ContextWithSessionData(r.Context(), &page.SessionData{LpaID: "lpa-id"})). + Return(donor, nil) + + template := newMockTemplate(t) + template.EXPECT(). + Execute(w, &viewLPAData{ + App: testAppData, + Donor: donor, + }). + Return(nil) + + err := ViewLPA(template.Execute, donorStore)(testAppData, w, r, &actor.Organisation{}) + + assert.Nil(t, err) +} + +func TestGetViewLPAWithSessionMissing(t *testing.T) { + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(page.ContextWithSessionData(context.Background(), &page.SessionData{}), http.MethodGet, "/", nil) + + err := ViewLPA(nil, nil)(testAppData, w, r, &actor.Organisation{}) + + assert.Error(t, err) +} + +func TestGetViewLPAMissingLPAId(t *testing.T) { + w := httptest.NewRecorder() + r, _ := http.NewRequest(http.MethodGet, "/", nil) + + err := ViewLPA(nil, nil)(testAppData, w, r, &actor.Organisation{}) + + assert.Error(t, err) +} + +func TestGetViewLPAWithDonorStoreError(t *testing.T) { + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(page.ContextWithSessionData(context.Background(), &page.SessionData{}), http.MethodGet, "/?id=lpa-id", nil) + + donorStore := newMockDonorStore(t) + donorStore.EXPECT(). + Get(page.ContextWithSessionData(r.Context(), &page.SessionData{LpaID: "lpa-id"})). + Return(nil, expectedError) + + err := ViewLPA(nil, donorStore)(testAppData, w, r, &actor.Organisation{}) + + assert.Error(t, err) +} + +func TestGetViewLPAWhenTemplateError(t *testing.T) { + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(page.ContextWithSessionData(context.Background(), &page.SessionData{}), http.MethodGet, "/?id=lpa-id", nil) + + donor := &actor.DonorProvidedDetails{LpaID: "lpa-id"} + + donorStore := newMockDonorStore(t) + donorStore.EXPECT(). + Get(page.ContextWithSessionData(r.Context(), &page.SessionData{LpaID: "lpa-id"})). + Return(donor, nil) + + template := newMockTemplate(t) + template.EXPECT(). + Execute(w, &viewLPAData{ + App: testAppData, + Donor: donor, + }). + Return(expectedError) + + err := ViewLPA(template.Execute, donorStore)(testAppData, w, r, &actor.Organisation{}) + + assert.Error(t, err) +} diff --git a/lang/cy.json b/lang/cy.json index 4e6789c2b9..942abf9cc6 100644 --- a/lang/cy.json +++ b/lang/cy.json @@ -1125,5 +1125,9 @@ "enterYourName": "Welsh", "thisWillBeHowYourNameIsDisplayed": "Welsh", "accessSuspended": "Welsh", - "accessSuspendedContent": "Welsh {{.OrganisationName}}" + "accessSuspendedContent": "Welsh {{.OrganisationName}}", + "lpa": "Welsh", + "viewLPASummary": "Welsh", + "donorAccess": "Welsh", + "viewLPA": "Welsh" } diff --git a/lang/en.json b/lang/en.json index 28ad391be7..fcdf500e9c 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1057,5 +1057,9 @@ "enterYourName": "Enter your name", "thisWillBeHowYourNameIsDisplayed": "This will be how your name is displayed to others in your organisation.", "accessSuspended": "Access suspended", - "accessSuspendedContent": "
Your access to {{.OrganisationName}} has been temporarily suspended.
Contact an admin at your organisation to get access again.
" + "accessSuspendedContent": "Your access to {{.OrganisationName}} has been temporarily suspended.
Contact an admin at your organisation to get access again.
", + "lpa": "LPA", + "viewLPASummary": "View LPA summary", + "donorAccess": "Donor access", + "viewLPA": "View LPA" } diff --git a/web/template/supporter/dashboard.gohtml b/web/template/supporter/dashboard.gohtml index 56076f365c..99eb1c3c8b 100644 --- a/web/template/supporter/dashboard.gohtml +++ b/web/template/supporter/dashboard.gohtml @@ -32,7 +32,7 @@ {{ if gt (len .Pagination.Pages) 1 }}{{ tr .App "showing" }} {{ .Pagination.Start }} {{ tr .App "to" }} {{ .Pagination.End }} {{ tr .App "of" }} {{ .Pagination.Total }} {{ tr .App "lpas" }}
{{ end }} - +{{ .LpaUID }} | +{{ .LpaUID }} | {{ tr $.App .Type.String }} |
{{ if not .SignedAt.IsZero }}
diff --git a/web/template/supporter/view_lpa.gohtml b/web/template/supporter/view_lpa.gohtml
new file mode 100644
index 0000000000..40d4ddae6f
--- /dev/null
+++ b/web/template/supporter/view_lpa.gohtml
@@ -0,0 +1,24 @@
+{{ template "page" . }}
+
+{{ define "pageTitle" }}{{ trFormat .App "viewLPA" }}{{ end }}
+
+{{ define "main" }}
+
+
+{{ end }}
+
+
+ {{ tr .App .Donor.Type.String }} {{tr .App "lpa"}}+ +
+ {{ tr .App "referenceNumber" }} {{ .Donor.LpaUID }}
+
+
+
+
+ + |