Skip to content
Open
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
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ val common = library("common")
pekkoSlf4j,
pekkoSerializationJackson,
pekkoActorTyped,
supportInternationalisation
) ++ jackson,
)

Expand Down
30 changes: 30 additions & 0 deletions common/app/controllers/EmailSignupController.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package controllers

import com.gu.i18n.{CountryGroup, Country}
import com.typesafe.scalalogging.LazyLogging
import common.EmailSubsciptionMetrics._
import common.{GuLogging, ImplicitControllerExecutionContext, LinkTo}
Expand All @@ -12,6 +13,7 @@ import conf.switches.Switches.{
}
import model.Cached.{RevalidatableResult, WithoutRevalidationResult}
import model._
import net.liftweb.json.JObject
import play.api.data.Forms._
import play.api.data._
import play.api.data.format.Formats._
Expand Down Expand Up @@ -60,8 +62,30 @@ class EmailFormService(wsClient: WSClient, emailEmbedAgent: NewsletterSignupAgen
extends LazyLogging
with RemoteAddress {

private def getCountryAndRegion(
countryCode: String,
)(implicit request: Request[AnyContent]): (String, Option[String]) = {
val registrationLocation: String = CountryGroup.byFastlyCountryCode(countryCode).map(_.name).getOrElse("Other")
val registrationLocationState: Option[String] =
for {
countryCode <- Option(countryCode).filter(Set("US", "AU").contains)
country <- CountryGroup.countryByCode(countryCode)
stateCode <- request.headers.get("X-GU-GeoIP-Region")
stateName <- country.statesByCode.get(stateCode)
} yield stateName
(registrationLocation, registrationLocationState)
}

def submit(form: EmailForm)(implicit request: Request[AnyContent]): Future[WSResponse] = {
val consentMailerUrl = serviceUrl(form, emailEmbedAgent)
val countryCode = request.headers.get("X-GU-GeoLocation") match {
case Some(country) =>
country.replace("country:", "")
case None => "row"
}

val (registrationLocation, registrationLocationState) = getCountryAndRegion(countryCode)

val consentMailerPayload = JsObject(
Json
.obj(
Expand All @@ -70,6 +94,8 @@ class EmailFormService(wsClient: WSClient, emailEmbedAgent: NewsletterSignupAgen
"set-consents" -> form.marketing.filter(_ == true).map(_ => List("similar_guardian_products")),
"unset-consents" -> form.marketing.filter(_ == false).map(_ => List("similar_guardian_products")),
"browser-id" -> form.browserId,
"registrationLocation" -> registrationLocation,
"registrationLocationState" -> registrationLocationState,
)
.fields,
)
Expand All @@ -87,6 +113,8 @@ class EmailFormService(wsClient: WSClient, emailEmbedAgent: NewsletterSignupAgen
}

def submitWithMany(form: EmailFormManyNewsletters)(implicit request: Request[AnyContent]): Future[WSResponse] = {
val countryCode = request.headers.get("X-GU-GeoLocation").getOrElse("country:row").replace("country:", "")
val (registrationLocation, registrationLocationState) = getCountryAndRegion(countryCode)
val consentMailerPayload = JsObject(
Json
.obj(
Expand All @@ -96,6 +124,8 @@ class EmailFormService(wsClient: WSClient, emailEmbedAgent: NewsletterSignupAgen
"ref" -> form.ref,
"set-consents" -> form.marketing.filter(_ == true).map(_ => List("similar_guardian_products")),
"unset-consents" -> form.marketing.filter(_ == false).map(_ => List("similar_guardian_products")),
"registrationLocation" -> registrationLocation,
"registrationLocationState" -> registrationLocationState,
)
.fields,
)
Expand Down
234 changes: 234 additions & 0 deletions common/test/controllers/EmailFormServiceTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package controllers

import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers._
import org.mockito.Mockito._
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.scalatestplus.mockito.MockitoSugar
import play.api.libs.json._
import play.api.libs.ws._
import play.api.mvc.AnyContentAsEmpty
import play.api.test.FakeRequest
import services.newsletters.NewsletterSignupAgent
import test.WithTestExecutionContext

import scala.concurrent.Future

class EmailFormServiceTest
extends AnyWordSpec
with Matchers
with MockitoSugar
with ScalaFutures
with WithTestExecutionContext {

trait Fixture {
val wsClient: WSClient = mock[WSClient]
val wsRequest: WSRequest = mock[WSRequest]
val wsResponse: WSResponse = mock[WSResponse]
val newsletterSignupAgent: NewsletterSignupAgent = mock[NewsletterSignupAgent]

val service = new EmailFormService(wsClient, newsletterSignupAgent)

val singleNewsletterBaseForm: EmailForm = EmailForm(
email = "test@example.com",
listName = Some("the-long-read"),
marketing = None,
referrer = None,
ref = None,
refViewId = None,
browserId = None,
campaignCode = None,
googleRecaptchaResponse = None,
name = None,
)

val multipleNewslettersBaseForm: EmailFormManyNewsletters = EmailFormManyNewsletters(
email = "test@example.com",
listNames = Seq("the-long-read", "morning-briefing"),
marketing = None,
referrer = None,
ref = None,
refViewId = None,
campaignCode = None,
googleRecaptchaResponse = None,
name = None,
)

when(newsletterSignupAgent.getV2NewsletterByName(any[String])) thenReturn Left("test")
when(wsClient.url(any[String])) thenReturn wsRequest
when(wsRequest.withQueryStringParameters(any())) thenReturn wsRequest
when(wsRequest.addHttpHeaders(any())) thenReturn wsRequest
when(wsRequest.post(any[JsValue])(any[BodyWritable[JsValue]])) thenReturn Future.successful(wsResponse)
when(wsResponse.status) thenReturn 200
}

def capturePostedBody(wsRequest: WSRequest): JsObject = {
val captor: ArgumentCaptor[JsValue] = ArgumentCaptor.forClass(classOf[JsValue])
verify(wsRequest).post(captor.capture())(any())
captor.getValue.as[JsObject]
}

def registrationLocation(body: JsObject): String =
(body \ "registrationLocation").get.as[String]

def registrationLocationState(body: JsObject): JsValue =
(body \ "registrationLocationState").get

"EmailFormService.submit" when {

"getting registrationLocation from X-GU-GeoLocation header" should {

"use the country group name for a known country code" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] =
FakeRequest().withHeaders("X-GU-GeoLocation" -> "country:GB")
service.submit(singleNewsletterBaseForm).futureValue
registrationLocation(capturePostedBody(wsRequest)) shouldBe "United Kingdom"
}

"use 'Other' for an unrecognised country code" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] =
FakeRequest().withHeaders("X-GU-GeoLocation" -> "country:XX")
service.submit(singleNewsletterBaseForm).futureValue
registrationLocation(capturePostedBody(wsRequest)) shouldBe "Other"
}

"use 'Other' when the X-GU-GeoLocation header isn't present" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest()
service.submit(singleNewsletterBaseForm).futureValue
registrationLocation(capturePostedBody(wsRequest)) shouldBe "Other"
}
}

"getting registrationLocationState from X-GU-GeoIP-Region header" should {

"get US state name from state code" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest().withHeaders(
"X-GU-GeoLocation" -> "country:US",
"X-GU-GeoIP-Region" -> "CA",
)
service.submit(singleNewsletterBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsString("California")
}

"get AU state name from state code" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest().withHeaders(
"X-GU-GeoLocation" -> "country:AU",
"X-GU-GeoIP-Region" -> "NSW",
)
service.submit(singleNewsletterBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsString("New South Wales")
}

"not be there (None) when the country is US but no state header is present" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] =
FakeRequest().withHeaders("X-GU-GeoLocation" -> "country:US")
service.submit(singleNewsletterBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull
}

"not be there (None) when the country is US but the state code is unrecognised" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest().withHeaders(
"X-GU-GeoLocation" -> "country:US",
"X-GU-GeoIP-Region" -> "ZZ",
)
service.submit(singleNewsletterBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull
}

"not be there (None) for a non-US/AU country even when a region header is present" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest().withHeaders(
"X-GU-GeoLocation" -> "country:GB",
"X-GU-GeoIP-Region" -> "ENG",
)
service.submit(singleNewsletterBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull
}

"not be there (None) when the X-GU-GeoLocation header is missing" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest()
service.submit(singleNewsletterBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull
}
}
}

"EmailFormService.submitWithMany" when {

"getting registrationLocation from X-GU-GeoLocation header" should {

"use the country group name for a known country code" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] =
FakeRequest().withHeaders("X-GU-GeoLocation" -> "country:GB")
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocation(capturePostedBody(wsRequest)) shouldBe "United Kingdom"
}

"use 'Other' for an unrecognised country code" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] =
FakeRequest().withHeaders("X-GU-GeoLocation" -> "country:XX")
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocation(capturePostedBody(wsRequest)) shouldBe "Other"
}

"use 'Other' when the X-GU-GeoLocation header isn't present" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest()
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocation(capturePostedBody(wsRequest)) shouldBe "Other"
}
}

"getting registrationLocationState from X-GU-GeoIP-Region header" should {

"get US state name from state code" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest().withHeaders(
"X-GU-GeoLocation" -> "country:US",
"X-GU-GeoIP-Region" -> "CA",
)
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsString("California")
}

"get AU state name from state code" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest().withHeaders(
"X-GU-GeoLocation" -> "country:AU",
"X-GU-GeoIP-Region" -> "NSW",
)
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsString("New South Wales")
}

"not be there (None) when the country is US but no state header is present" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] =
FakeRequest().withHeaders("X-GU-GeoLocation" -> "country:US")
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull
}

"not be there (None) when the country is US but the state code is unrecognised" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest().withHeaders(
"X-GU-GeoLocation" -> "country:US",
"X-GU-GeoIP-Region" -> "ZZ",
)
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull
}

"not be there (None) for a non-US/AU country even when a region header is present" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest().withHeaders(
"X-GU-GeoLocation" -> "country:GB",
"X-GU-GeoIP-Region" -> "ENG",
)
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull
}

"not be there (None) when the X-GU-GeoLocation header is missing" in new Fixture {
implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest()
service.submitWithMany(multipleNewslettersBaseForm).futureValue
registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull
}
}
}
}
Loading