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

fix for (1) "whole site" inconsistent with form (2) user URL not usin… #68

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ func IsSiteDomainURL(urlString, siteDomain string) bool {
if siteDomain == "" {
siteDomain = cfg.SiteDomain
}
if !strings.HasPrefix(urlString, "http") {
// user may enter a URL without a scheme (e.g. `host/path`), so add it before parsing
urlString = "https://" + urlString
}
urlObject, err := url.ParseRequestURI(urlString)
if err != nil {
return false
Expand Down
2 changes: 1 addition & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestConfig(t *testing.T) {
So(isAllowedURL, ShouldBeTrue)
})
Convey("Then a sub-domain/host of the config's site domain is recognised", func() {
isAllowedURL := IsSiteDomainURL("https://anything.localhost", "")
isAllowedURL := IsSiteDomainURL("anything.localhost", "")
So(isAllowedURL, ShouldBeTrue)
})
Convey("Then a non-site domain URL is not recognised for config's site domain", func() {
Expand Down
10 changes: 4 additions & 6 deletions handlers/feedback.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func addFeedback(w http.ResponseWriter, req *http.Request, rend interfaces.Rende
}

if ff.URL == "" {
ff.URL = "Whole site"
ff.URL = mapper.WholeSite
}

if err := emailSender.Send(
Expand All @@ -111,7 +111,7 @@ func addFeedback(w http.ResponseWriter, req *http.Request, rend interfaces.Rende

returnTo := ff.URL

if returnTo == "Whole site" || returnTo == "" {
if returnTo == mapper.WholeSite || returnTo == "" {
returnTo = "https://www.ons.gov.uk"
}

Expand All @@ -133,7 +133,7 @@ func validateForm(ff *model.FeedbackForm, siteDomain string) (validationErrors [
}

ff.URL = strings.TrimSpace(ff.URL)
if ff.Type == "A specific page" {
if ff.Type == mapper.ASpecificPage {
if ff.URL == "" {
validationErrors = append(validationErrors, core.ErrorItem{
Description: core.Localisation{
Expand All @@ -156,9 +156,7 @@ func validateForm(ff *model.FeedbackForm, siteDomain string) (validationErrors [
ff.IsURLErr = true
}
}
}

if ff.Type != "A specific page" && ff.URL != "" {
} else if ff.Type != mapper.ASpecificPage && ff.URL != "" {
ff.URL = ""
}

Expand Down
27 changes: 14 additions & 13 deletions handlers/feedback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/ONSdigital/dp-frontend-feedback-controller/config"
"github.com/ONSdigital/dp-frontend-feedback-controller/email/emailtest"
"github.com/ONSdigital/dp-frontend-feedback-controller/interfaces/interfacestest"
"github.com/ONSdigital/dp-frontend-feedback-controller/mapper"
"github.com/ONSdigital/dp-frontend-feedback-controller/mocks"
"github.com/ONSdigital/dp-frontend-feedback-controller/model"
"github.com/ONSdigital/dp-renderer/v2/helper"
Expand Down Expand Up @@ -345,7 +346,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the form is valid",
given: &model.FeedbackForm{
Type: "Whole site",
Type: mapper.WholeSite,
Description: "Some text",
},
expectedDescription: "no validation errors are returned",
Expand All @@ -370,7 +371,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the a specific page/url type is chosen but the child input field is empty",
given: &model.FeedbackForm{
Type: "A specific page",
Type: mapper.ASpecificPage,
Description: "Some text",
},
expectedDescription: "a page/url validation error is returned",
Expand All @@ -387,7 +388,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the a specific page/url type is chosen and the url is invalid",
given: &model.FeedbackForm{
Type: "A specific page",
Type: mapper.ASpecificPage,
Description: "Some text",
URL: "not a url",
},
Expand All @@ -405,7 +406,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the a specific page/url type is chosen and the url is valid but not allowed",
given: &model.FeedbackForm{
Type: "A specific page",
Type: mapper.ASpecificPage,
Description: "Some text",
URL: "https://not-site-domain.com",
},
Expand All @@ -422,7 +423,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the a specific page/url type is chosen and the url is valid without a path",
given: &model.FeedbackForm{
Type: "A specific page",
Type: mapper.ASpecificPage,
Description: "Some text",
URL: "https://cy.ons.gov.uk",
},
Expand All @@ -432,17 +433,17 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the a specific page/url type is chosen and the url is valid and has a path",
given: &model.FeedbackForm{
Type: "A specific page",
Type: mapper.ASpecificPage,
Description: "Some text",
URL: "https://cy.ons.gov.uk/path",
},
expectedDescription: "no validation errors are returned",
expected: []coreModel.ErrorItem(nil),
},
{
givenDescription: "the a whole site type is chosen but the child input for a specific page is not empty",
givenDescription: "the whole-site type is chosen but the child input for a specific page is not empty",
given: &model.FeedbackForm{
Type: "Whole site",
Type: mapper.WholeSite,
Description: "Some text",
URL: "http://somewhere.com",
},
Expand All @@ -461,7 +462,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the form does not have any feedback",
given: &model.FeedbackForm{
Type: "Whole site",
Type: mapper.WholeSite,
},
expectedDescription: "a description validation error is returned",
expected: []coreModel.ErrorItem{
Expand All @@ -477,7 +478,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the feedback provided is whitespace",
given: &model.FeedbackForm{
Type: "Whole site",
Type: mapper.WholeSite,
Description: " ",
},
expectedDescription: "a description validation error is returned",
Expand All @@ -494,7 +495,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the email field has an invalid email address",
given: &model.FeedbackForm{
Type: "Whole site",
Type: mapper.WholeSite,
Description: "A description",
Email: "a.string",
},
Expand All @@ -512,7 +513,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "the email field has a valid email address",
given: &model.FeedbackForm{
Type: "Whole site",
Type: mapper.WholeSite,
Description: "A description",
Email: "[email protected]",
},
Expand All @@ -522,7 +523,7 @@ func TestValidateForm(t *testing.T) {
{
givenDescription: "multiple form validation errors",
given: &model.FeedbackForm{
Type: "A specific page",
Type: mapper.ASpecificPage,
URL: "",
Description: "",
Email: "not an email address",
Expand Down
30 changes: 19 additions & 11 deletions mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import (
core "github.com/ONSdigital/dp-renderer/v2/model"
)

const (
WholeSite = "The whole website"
ASpecificPage = "A specific page"
)

// CreateGetFeedback returns a mapped feedback page to the feedback model
func CreateGetFeedback(req *http.Request, basePage core.Page, validationErrors []core.ErrorItem, ff model.FeedbackForm, lang string) model.Feedback {
p := model.Feedback{
Expand Down Expand Up @@ -59,25 +64,25 @@ func CreateGetFeedback(req *http.Request, basePage core.Page, validationErrors [
{
Input: core.Input{
ID: "whole-site",
IsChecked: ff.Type == "The whole website",
IsChecked: ff.Type == WholeSite,
Label: core.Localisation{
LocaleKey: "FeedbackWholeWebsite",
Plural: 1,
},
Name: "type",
Value: "The whole website",
Value: WholeSite,
},
},
{
Input: core.Input{
ID: "specific-page",
IsChecked: ff.Type == "A specific page" || (ff.URL != "" && serviceDescription == ""),
IsChecked: ff.Type == ASpecificPage || (ff.URL != "" && serviceDescription == ""),
Label: core.Localisation{
LocaleKey: "FeedbackASpecificPage",
Plural: 1,
},
Name: "type",
Value: "A specific page",
Value: ASpecificPage,
},
OtherInput: core.Input{
Autocomplete: "url",
Expand Down Expand Up @@ -196,24 +201,27 @@ func CreateGetFeedback(req *http.Request, basePage core.Page, validationErrors [
return p
}

func CreateGetFeedbackThanks(req *http.Request, basePage core.Page, lang, referrer, wholeSite string) model.Feedback {
p := model.Feedback{
Page: basePage,
func CreateGetFeedbackThanks(req *http.Request, basePage core.Page, lang, referrer, wholeSiteURL string) model.Feedback {
if wholeSiteURL == "" {
wholeSiteURL = "https://www.ons.gov.uk"
}
if referrer == "" {
referrer = wholeSite
referrer = wholeSiteURL
}

p := model.Feedback{
Page: basePage,
PreviousURL: referrer,
}
p.Language = lang
p.Type = "feedback"
p.URI = req.URL.Path
p.Metadata.Title = helper.Localise("FeedbackThanks", lang, 1)
p.PreviousURL = referrer

// returnTo is rendered on page so needs XSS protection
returnTo := html.EscapeString(req.URL.Query().Get("returnTo"))
if returnTo == "Whole site" {
returnTo = wholeSite
if returnTo == WholeSite {
returnTo = wholeSiteURL
} else if returnTo == "" {
returnTo = referrer
} else if !config.IsSiteDomainURL(returnTo, "") {
Expand Down
35 changes: 24 additions & 11 deletions mapper/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mapper
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/ONSdigital/dp-frontend-feedback-controller/mocks"
Expand Down Expand Up @@ -105,8 +106,8 @@ func TestCreateGetFeedbackThanks(t *testing.T) {
bp := core.Page{}
url := "https://localhost/a/page/somewhere"
lang := "en"
wholeSite := "https://ons.gov.uk"
sut := CreateGetFeedbackThanks(req, bp, lang, url, wholeSite)
wholeSiteURL := "https://ons.gov.uk"
sut := CreateGetFeedbackThanks(req, bp, lang, url, wholeSiteURL)

Convey("Then it sets the page metadata", func() {
So(sut.Metadata.Title, ShouldEqual, "Thank you")
Expand All @@ -123,16 +124,28 @@ func TestCreateGetFeedbackThanks(t *testing.T) {
})
})

Convey("When the return to parameter is set", func() {
req := httptest.NewRequest(http.MethodGet, "/?returnTo=Whole%20site", nil)
bp := core.Page{}
url := "https://localhost/a/page/somewhere"
lang := "en"
wholeSite := "https://ons.gov.uk"
sut := CreateGetFeedbackThanks(req, bp, lang, url, wholeSite)
var (
referrer = "https://any.localhost/a/page/somewhere"
lang = "en"
wholeSiteURL = "https://cy.localhost"
encWholeSite = url.QueryEscape(WholeSite)
bp = core.Page{}
)

Convey("Then it sets the returnTo property", func() {
So(sut.ReturnTo, ShouldEqual, wholeSite)
Convey("When the returnTo parameter is set to whole-site and whole-site is explicit", func() {
req := httptest.NewRequest(http.MethodGet, "/?returnTo="+encWholeSite, nil)
sut := CreateGetFeedbackThanks(req, bp, lang, referrer, wholeSiteURL)

Convey("Then it sets the returnTo property to the whole-site", func() {
So(sut.ReturnTo, ShouldEqual, wholeSiteURL)
})
})
Convey("When the returnTo parameter is set to whole-site but whole-site is not explicit", func() {
req := httptest.NewRequest(http.MethodGet, "/?returnTo="+encWholeSite, nil)
sut := CreateGetFeedbackThanks(req, bp, lang, referrer, "")

Convey("Then it sets the returnTo property to the default whole-site", func() {
So(sut.ReturnTo, ShouldEqual, "https://www.ons.gov.uk")
})
})
})
Expand Down