From a8cb75e13a36af1a90db33f793635e72a8bec678 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Tue, 24 Mar 2026 10:41:43 +0000 Subject: [PATCH 1/6] pass location and location state (If in US or AUS) through to the /consent-signup and /consent-email IDAPI endpoints --- build.sbt | 1 + .../controllers/EmailSignupController.scala | 22 ++- .../controllers/EmailFormServiceTest.scala | 145 ++++++++++++++++++ 3 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 common/test/controllers/EmailFormServiceTest.scala diff --git a/build.sbt b/build.sbt index 21e2cf51a34..59637274212 100644 --- a/build.sbt +++ b/build.sbt @@ -66,6 +66,7 @@ val common = library("common") pekkoSlf4j, pekkoSerializationJackson, pekkoActorTyped, + supportInternationalisation ) ++ jackson, ) diff --git a/common/app/controllers/EmailSignupController.scala b/common/app/controllers/EmailSignupController.scala index f1d0554bf02..fbf8e0c2bfb 100644 --- a/common/app/controllers/EmailSignupController.scala +++ b/common/app/controllers/EmailSignupController.scala @@ -1,17 +1,14 @@ package controllers +import com.gu.i18n.{CountryGroup, Country} import com.typesafe.scalalogging.LazyLogging import common.EmailSubsciptionMetrics._ import common.{GuLogging, ImplicitControllerExecutionContext, LinkTo} import conf.Configuration -import conf.switches.Switches.{ - EmailSignupRecaptcha, - ManyNewsletterVisibleRecaptcha, - NewslettersRemoveConfirmationStep, - ValidateEmailSignupRecaptchaTokens, -} +import conf.switches.Switches.{EmailSignupRecaptcha, ManyNewsletterVisibleRecaptcha, NewslettersRemoveConfirmationStep, ValidateEmailSignupRecaptchaTokens} 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._ @@ -62,6 +59,17 @@ class EmailFormService(wsClient: WSClient, emailEmbedAgent: NewsletterSignupAgen def submit(form: EmailForm)(implicit request: Request[AnyContent]): Future[WSResponse] = { val consentMailerUrl = serviceUrl(form, emailEmbedAgent) + val countryCode = request.headers.get("X-GU-GeoLocation").getOrElse("country:row").replace("country:", "") + val registrationLocation:String = CountryGroup.byFastlyCountryCode(countryCode).map(_.name).getOrElse("Other") + val registrationLocationState: Option[String] = countryCode match { + case "US" | "AU" => + for { + country <- CountryGroup.countryByCode(countryCode) + stateCode <- request.headers.get("X-GU-GeoIP-Region") + stateName <- country.statesByCode.get(stateCode) + } yield stateName + case _ => None + } val consentMailerPayload = JsObject( Json .obj( @@ -70,6 +78,7 @@ 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, + "privateFields" -> ("registrationLocation" -> registrationLocation, "registrationLocationState" -> registrationLocationState) ) .fields, ) @@ -98,6 +107,7 @@ class EmailFormService(wsClient: WSClient, emailEmbedAgent: NewsletterSignupAgen "unset-consents" -> form.marketing.filter(_ == false).map(_ => List("similar_guardian_products")), ) .fields, + ) val queryStringParameters = form.ref.map("ref" -> _).toList ++ diff --git a/common/test/controllers/EmailFormServiceTest.scala b/common/test/controllers/EmailFormServiceTest.scala new file mode 100644 index 00000000000..f78b2aad929 --- /dev/null +++ b/common/test/controllers/EmailFormServiceTest.scala @@ -0,0 +1,145 @@ +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 baseForm: 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, + ) + + 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] + } + + // privateFields serializes as [["registrationLocation", ImALocation], ["registrationLocationState", IamALocationState]] + def registrationLocation(body: JsObject): String = + (body \ "privateFields")(0)(1).as[String] + + def registrationLocationState(body: JsObject): JsValue = + (body \ "privateFields")(1)(1) + + "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(baseForm).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(baseForm).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(baseForm).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(baseForm).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(baseForm).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(baseForm).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(baseForm).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(baseForm).futureValue + registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull + } + + "not be there (None) for a country:row fallback (no geo header)" in new Fixture { + implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest() + service.submit(baseForm).futureValue + registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull + } + } + } +} From 93150c1448890eaff48619d5c5fdf338104c3886 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Tue, 24 Mar 2026 17:04:07 +0000 Subject: [PATCH 2/6] send the location and location state through with the multiple newsletter signup form as well --- .../controllers/EmailSignupController.scala | 32 ++++- .../controllers/EmailFormServiceTest.scala | 112 ++++++++++++++++-- 2 files changed, 128 insertions(+), 16 deletions(-) diff --git a/common/app/controllers/EmailSignupController.scala b/common/app/controllers/EmailSignupController.scala index fbf8e0c2bfb..1c29a117ebb 100644 --- a/common/app/controllers/EmailSignupController.scala +++ b/common/app/controllers/EmailSignupController.scala @@ -5,7 +5,12 @@ import com.typesafe.scalalogging.LazyLogging import common.EmailSubsciptionMetrics._ import common.{GuLogging, ImplicitControllerExecutionContext, LinkTo} import conf.Configuration -import conf.switches.Switches.{EmailSignupRecaptcha, ManyNewsletterVisibleRecaptcha, NewslettersRemoveConfirmationStep, ValidateEmailSignupRecaptchaTokens} +import conf.switches.Switches.{ + EmailSignupRecaptcha, + ManyNewsletterVisibleRecaptcha, + NewslettersRemoveConfirmationStep, + ValidateEmailSignupRecaptchaTokens, +} import model.Cached.{RevalidatableResult, WithoutRevalidationResult} import model._ import net.liftweb.json.JObject @@ -60,14 +65,14 @@ class EmailFormService(wsClient: WSClient, emailEmbedAgent: NewsletterSignupAgen def submit(form: EmailForm)(implicit request: Request[AnyContent]): Future[WSResponse] = { val consentMailerUrl = serviceUrl(form, emailEmbedAgent) val countryCode = request.headers.get("X-GU-GeoLocation").getOrElse("country:row").replace("country:", "") - val registrationLocation:String = CountryGroup.byFastlyCountryCode(countryCode).map(_.name).getOrElse("Other") + val registrationLocation: String = CountryGroup.byFastlyCountryCode(countryCode).map(_.name).getOrElse("Other") val registrationLocationState: Option[String] = countryCode match { case "US" | "AU" => for { country <- CountryGroup.countryByCode(countryCode) stateCode <- request.headers.get("X-GU-GeoIP-Region") stateName <- country.statesByCode.get(stateCode) - } yield stateName + } yield stateName case _ => None } val consentMailerPayload = JsObject( @@ -78,7 +83,10 @@ 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, - "privateFields" -> ("registrationLocation" -> registrationLocation, "registrationLocationState" -> registrationLocationState) + "privateFields" -> ( + "registrationLocation" -> registrationLocation, + "registrationLocationState" -> registrationLocationState, + ), ) .fields, ) @@ -96,6 +104,17 @@ 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: String = CountryGroup.byFastlyCountryCode(countryCode).map(_.name).getOrElse("Other") + val registrationLocationState: Option[String] = countryCode match { + case "US" | "AU" => + for { + country <- CountryGroup.countryByCode(countryCode) + stateCode <- request.headers.get("X-GU-GeoIP-Region") + stateName <- country.statesByCode.get(stateCode) + } yield stateName + case _ => None + } val consentMailerPayload = JsObject( Json .obj( @@ -105,9 +124,12 @@ 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")), + "privateFields" -> ( + "registrationLocation" -> registrationLocation, + "registrationLocationState" -> registrationLocationState, + ), ) .fields, - ) val queryStringParameters = form.ref.map("ref" -> _).toList ++ diff --git a/common/test/controllers/EmailFormServiceTest.scala b/common/test/controllers/EmailFormServiceTest.scala index f78b2aad929..058e4ce0ee7 100644 --- a/common/test/controllers/EmailFormServiceTest.scala +++ b/common/test/controllers/EmailFormServiceTest.scala @@ -31,7 +31,7 @@ class EmailFormServiceTest val service = new EmailFormService(wsClient, newsletterSignupAgent) - val baseForm: EmailForm = EmailForm( + val singleNewsletterBaseForm: EmailForm = EmailForm( email = "test@example.com", listName = Some("the-long-read"), marketing = None, @@ -44,6 +44,18 @@ class EmailFormServiceTest 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 @@ -72,20 +84,20 @@ class EmailFormServiceTest "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(baseForm).futureValue + 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(baseForm).futureValue + 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(baseForm).futureValue + service.submit(singleNewsletterBaseForm).futureValue registrationLocation(capturePostedBody(wsRequest)) shouldBe "Other" } } @@ -97,7 +109,7 @@ class EmailFormServiceTest "X-GU-GeoLocation" -> "country:US", "X-GU-GeoIP-Region" -> "CA", ) - service.submit(baseForm).futureValue + service.submit(singleNewsletterBaseForm).futureValue registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsString("California") } @@ -106,14 +118,14 @@ class EmailFormServiceTest "X-GU-GeoLocation" -> "country:AU", "X-GU-GeoIP-Region" -> "NSW", ) - service.submit(baseForm).futureValue + 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(baseForm).futureValue + service.submit(singleNewsletterBaseForm).futureValue registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull } @@ -122,7 +134,7 @@ class EmailFormServiceTest "X-GU-GeoLocation" -> "country:US", "X-GU-GeoIP-Region" -> "ZZ", ) - service.submit(baseForm).futureValue + service.submit(singleNewsletterBaseForm).futureValue registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull } @@ -131,13 +143,91 @@ class EmailFormServiceTest "X-GU-GeoLocation" -> "country:GB", "X-GU-GeoIP-Region" -> "ENG", ) - service.submit(baseForm).futureValue + 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) for a country:row fallback (no geo header)" in new Fixture { + "not be there (None) when the X-GU-GeoLocation header is missing" in new Fixture { implicit val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest() - service.submit(baseForm).futureValue + service.submitWithMany(multipleNewslettersBaseForm).futureValue registrationLocationState(capturePostedBody(wsRequest)) shouldBe JsNull } } From 6f787a950b8f16920552f9921f456ab81547d564 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Wed, 25 Mar 2026 22:28:01 +0000 Subject: [PATCH 3/6] refactor EmailSignupController; create new getCountryAndRegion function to get rid of duplication. Make the way we get the country code more readable --- .../controllers/EmailSignupController.scala | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/common/app/controllers/EmailSignupController.scala b/common/app/controllers/EmailSignupController.scala index 1c29a117ebb..d0c39257a19 100644 --- a/common/app/controllers/EmailSignupController.scala +++ b/common/app/controllers/EmailSignupController.scala @@ -62,19 +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").getOrElse("country:row").replace("country:", "") - val registrationLocation: String = CountryGroup.byFastlyCountryCode(countryCode).map(_.name).getOrElse("Other") - val registrationLocationState: Option[String] = countryCode match { - case "US" | "AU" => - for { - country <- CountryGroup.countryByCode(countryCode) - stateCode <- request.headers.get("X-GU-GeoIP-Region") - stateName <- country.statesByCode.get(stateCode) - } yield stateName - case _ => None + 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( @@ -105,16 +116,7 @@ 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: String = CountryGroup.byFastlyCountryCode(countryCode).map(_.name).getOrElse("Other") - val registrationLocationState: Option[String] = countryCode match { - case "US" | "AU" => - for { - country <- CountryGroup.countryByCode(countryCode) - stateCode <- request.headers.get("X-GU-GeoIP-Region") - stateName <- country.statesByCode.get(stateCode) - } yield stateName - case _ => None - } + val (registrationLocation, registrationLocationState) = getCountryAndRegion(countryCode) val consentMailerPayload = JsObject( Json .obj( From a714822587d233f84813128795f858473e9a2cad Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Wed, 25 Mar 2026 22:38:27 +0000 Subject: [PATCH 4/6] fix EmailFormServiceTest.scala formatting :s --- common/test/controllers/EmailFormServiceTest.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/test/controllers/EmailFormServiceTest.scala b/common/test/controllers/EmailFormServiceTest.scala index 058e4ce0ee7..705f9c7ad93 100644 --- a/common/test/controllers/EmailFormServiceTest.scala +++ b/common/test/controllers/EmailFormServiceTest.scala @@ -184,7 +184,7 @@ class EmailFormServiceTest "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-GeoLocation" -> "country:US", "X-GU-GeoIP-Region" -> "CA", ) service.submitWithMany(multipleNewslettersBaseForm).futureValue @@ -193,7 +193,7 @@ class EmailFormServiceTest "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-GeoLocation" -> "country:AU", "X-GU-GeoIP-Region" -> "NSW", ) service.submitWithMany(multipleNewslettersBaseForm).futureValue @@ -209,7 +209,7 @@ class EmailFormServiceTest "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-GeoLocation" -> "country:US", "X-GU-GeoIP-Region" -> "ZZ", ) service.submitWithMany(multipleNewslettersBaseForm).futureValue @@ -218,7 +218,7 @@ class EmailFormServiceTest "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-GeoLocation" -> "country:GB", "X-GU-GeoIP-Region" -> "ENG", ) service.submitWithMany(multipleNewslettersBaseForm).futureValue From 7e2cdbc38cd6a0e0fc506f9453ac6003e9f114d1 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Thu, 26 Mar 2026 09:56:15 +0000 Subject: [PATCH 5/6] change the shape of the request object for the /consent-signup and /consent-email IDAPI endpoints --- common/app/controllers/EmailSignupController.scala | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/common/app/controllers/EmailSignupController.scala b/common/app/controllers/EmailSignupController.scala index d0c39257a19..cfd6620f5c7 100644 --- a/common/app/controllers/EmailSignupController.scala +++ b/common/app/controllers/EmailSignupController.scala @@ -94,10 +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, - "privateFields" -> ( - "registrationLocation" -> registrationLocation, - "registrationLocationState" -> registrationLocationState, - ), + "registrationLocation" -> registrationLocation, + "registrationLocationState" -> registrationLocationState, ) .fields, ) @@ -126,10 +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")), - "privateFields" -> ( - "registrationLocation" -> registrationLocation, - "registrationLocationState" -> registrationLocationState, - ), + "registrationLocation" -> registrationLocation, + "registrationLocationState" -> registrationLocationState, ) .fields, ) From 335d72f5cfa2b9bb7e25ccc6f3ab9211bdfcc299 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Thu, 26 Mar 2026 14:53:00 +0000 Subject: [PATCH 6/6] update EmailFormServiceTest to remove align with code refactor to remove privateFields request body property/object --- common/test/controllers/EmailFormServiceTest.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/test/controllers/EmailFormServiceTest.scala b/common/test/controllers/EmailFormServiceTest.scala index 705f9c7ad93..087ab2e8d8c 100644 --- a/common/test/controllers/EmailFormServiceTest.scala +++ b/common/test/controllers/EmailFormServiceTest.scala @@ -70,12 +70,11 @@ class EmailFormServiceTest captor.getValue.as[JsObject] } - // privateFields serializes as [["registrationLocation", ImALocation], ["registrationLocationState", IamALocationState]] def registrationLocation(body: JsObject): String = - (body \ "privateFields")(0)(1).as[String] + (body \ "registrationLocation").get.as[String] def registrationLocationState(body: JsObject): JsValue = - (body \ "privateFields")(1)(1) + (body \ "registrationLocationState").get "EmailFormService.submit" when {