From c94d018107329667f025d3cd60eb203e36a547ab Mon Sep 17 00:00:00 2001 From: SandeepUmredkar <1963092+SandeepUmredkar@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:00:35 +0000 Subject: [PATCH 1/2] DDCNL-11592 --- .../AddressSubmissionControllerHelper.scala | 5 +- app/models/dto/AddressDto.scala | 67 ++++++----- app/services/AddressMovedService.scala | 6 + ...ddressSubmissionControllerHelperSpec.scala | 107 ++++++------------ test/models/dto/AddressDtoSpec.scala | 41 ++++++- test/services/AddressMovedServiceSpec.scala | 11 +- 6 files changed, 125 insertions(+), 112 deletions(-) diff --git a/app/controllers/controllershelpers/AddressSubmissionControllerHelper.scala b/app/controllers/controllershelpers/AddressSubmissionControllerHelper.scala index a331f9e95..6794b2973 100644 --- a/app/controllers/controllershelpers/AddressSubmissionControllerHelper.scala +++ b/app/controllers/controllershelpers/AddressSubmissionControllerHelper.scala @@ -68,7 +68,10 @@ class AddressSubmissionControllerHelper @Inject() ( def isStartDateError(error: UpstreamErrorResponse): Boolean = error.statusCode == 400 && error.message.toLowerCase().contains("start date") - val p85enabled: Boolean = !isUk(journeyData.submittedInternationalAddressChoiceDto) + val p85enabled: Boolean = journeyData.submittedInternationalAddressChoiceDto match { + case Some(choice) => !isUk(Some(choice)) + case None => true + } val startDateErrorResponse: Result = BadRequest( diff --git a/app/models/dto/AddressDto.scala b/app/models/dto/AddressDto.scala index bf10af276..b492a1885 100644 --- a/app/models/dto/AddressDto.scala +++ b/app/models/dto/AddressDto.scala @@ -41,35 +41,44 @@ case class AddressDto( def toAddress(`type`: String, startDate: LocalDate): Address = { val List(newLine2, newLine3, newline4OrTown, newline5OrCounty) = List(line2, line3, line4OrTown, line5OrCounty).flatten.map(Some(_)).padTo(4, None) - postcode match { - case Some(postcode) => - Address( - Some(line1), - newLine2, - newLine3, - newline4OrTown, - newline5OrCounty, - Some(formatMandatoryPostCode(postcode)), - None, - Some(startDate), - None, - Some(`type`), - isRls = false - ) - case None => - Address( - Some(line1), - newLine2, - newLine3, - newline4OrTown, - newline5OrCounty, - None, - country, - Some(startDate), - None, - Some(`type`), - isRls = false - ) + + val normalisedPostcode: Option[String] = + postcode.map(_.trim).filter(_.nonEmpty) + + val isInternational: Boolean = + country.exists(_.trim.nonEmpty) + + if (isInternational) { + // INTERNATIONAL ADDRESS: + // Citizen-details does NOT allow postcode unless country is UK/IoM + Address( + Some(line1), + newLine2, + newLine3, + newline4OrTown, + newline5OrCounty, + None, + country, + Some(startDate), + None, + Some(`type`), + isRls = false + ) + } else { + // UK ADDRESS: + Address( + Some(line1), + newLine2, + newLine3, + newline4OrTown, + newline5OrCounty, + normalisedPostcode.map(formatMandatoryPostCode), + None, + Some(startDate), + None, + Some(`type`), + isRls = false + ) } } diff --git a/app/services/AddressMovedService.scala b/app/services/AddressMovedService.scala index 9c8a08763..ac423c84d 100644 --- a/app/services/AddressMovedService.scala +++ b/app/services/AddressMovedService.scala @@ -22,11 +22,14 @@ import models.addresslookup.Country import models.{AddressChanged, AnyOtherMove, MovedFromScotland, MovedToScotland} import uk.gov.hmrc.http.HeaderCarrier import play.api.Logging +import util.PertaxValidators.PostcodeRegex import scala.concurrent.{ExecutionContext, Future} class AddressMovedService @Inject() (addressLookupService: AddressLookupConnector) extends Logging { + private def isValidUkPostcode(p: String): Boolean = PostcodeRegex.pattern.matcher(p.trim.toUpperCase).matches() + def moved(originalPostcode: String, newPostcode: String, p85Enabled: Boolean)(implicit hc: HeaderCarrier, ec: ExecutionContext @@ -39,6 +42,9 @@ class AddressMovedService @Inject() (addressLookupService: AddressLookupConnecto logger.error("New postcode is empty when checking for address move.") Future.successful(AnyOtherMove) case ("", _) => Future.successful(AnyOtherMove) + case (originalPostcode, newPostcode) + if !isValidUkPostcode(originalPostcode) || !isValidUkPostcode(newPostcode) => + Future.successful(AnyOtherMove) case (originalPostcode, newPostcode) => (for { fromResponse <- addressLookupService.lookup(originalPostcode) diff --git a/test/controllers/controllershelpers/AddressSubmissionControllerHelperSpec.scala b/test/controllers/controllershelpers/AddressSubmissionControllerHelperSpec.scala index 0a8804fc3..284ae4335 100644 --- a/test/controllers/controllershelpers/AddressSubmissionControllerHelperSpec.scala +++ b/test/controllers/controllershelpers/AddressSubmissionControllerHelperSpec.scala @@ -26,20 +26,19 @@ import models.dto.{AddressDto, DateDto} import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.{reset, times, verify, when} -import play.api +import play.api.http.Status.{BAD_REQUEST, INTERNAL_SERVER_ERROR, OK} import play.api.i18n.{Lang, Messages, MessagesImpl, MessagesProvider} +import play.api.mvc.Request import play.api.test.Helpers.{contentAsString, defaultAwaitTimeout, status} import services.{AddressMovedService, CitizenDetailsService} import testUtils.BaseSpec -import play.api.http.Status.{BAD_REQUEST, INTERNAL_SERVER_ERROR, OK} -import play.api.mvc.Request +import testUtils.Fixtures.buildPersonDetails import testUtils.UserRequestFixture.buildUserRequest import uk.gov.hmrc.http.UpstreamErrorResponse import uk.gov.hmrc.play.audit.http.connector.{AuditConnector, AuditResult} import uk.gov.hmrc.play.audit.model.DataEvent import uk.gov.hmrc.play.language.LanguageUtils import views.html.personaldetails.{CannotUpdateAddressEarlyDateView, UpdateAddressConfirmationView} -import testUtils.Fixtures.buildPersonDetails import java.time.LocalDate import scala.concurrent.ExecutionContext.Implicits.global @@ -89,21 +88,15 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { "Calling updateCitizenDetailsAddress" must { "update address" when { + "address is residential" in { when(mockCitizenDetailsService.updateAddress(any(), any(), any(), any())(any(), any(), any())) .thenReturn(EitherT.rightT[Future, UpstreamErrorResponse](true)) - when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn( - Future.successful(AuditResult.Success) - ) - when(mockEditAddressLockRepository.insert(any(), any())).thenReturn( - Future.successful(true) - ) - when(mockAddressMovedService.moved(any(), any(), any())(any(), any())).thenReturn( - Future.successful(AnyOtherMove) - ) - when(mockAddressMovedService.toMessageKey(any())).thenReturn( - Some("label.mockAddressMovedService") - ) + when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn(Future.successful(AuditResult.Success)) + when(mockEditAddressLockRepository.insert(any(), any())).thenReturn(Future.successful(true)) + when(mockAddressMovedService.moved(any(), any(), any())(any(), any())) + .thenReturn(Future.successful(AnyOtherMove)) + when(mockAddressMovedService.toMessageKey(any())).thenReturn(Some("label.mockAddressMovedService")) val addressJourneyData = AddressJourneyData( None, @@ -112,7 +105,7 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { None, None, None, - None, + Some(models.dto.InternationalAddressChoiceDto.Scotland), None ) @@ -143,18 +136,11 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { "address is postal" in { when(mockCitizenDetailsService.updateAddress(any(), any(), any(), any())(any(), any(), any())) .thenReturn(EitherT.rightT[Future, UpstreamErrorResponse](true)) - when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn( - Future.successful(AuditResult.Success) - ) - when(mockEditAddressLockRepository.insert(any(), any())).thenReturn( - Future.successful(true) - ) - when(mockAddressMovedService.moved(any(), any(), any())(any(), any())).thenReturn( - Future.successful(AnyOtherMove) - ) - when(mockAddressMovedService.toMessageKey(any())).thenReturn( - Some("label.mockAddressMovedService") - ) + when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn(Future.successful(AuditResult.Success)) + when(mockEditAddressLockRepository.insert(any(), any())).thenReturn(Future.successful(true)) + when(mockAddressMovedService.moved(any(), any(), any())(any(), any())) + .thenReturn(Future.successful(AnyOtherMove)) + when(mockAddressMovedService.toMessageKey(any())).thenReturn(Some("label.mockAddressMovedService")) val addressJourneyData = AddressJourneyData( None, @@ -163,7 +149,7 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { None, None, None, - None, + Some(models.dto.InternationalAddressChoiceDto.Scotland), None ) @@ -194,18 +180,11 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { "address is international" in { when(mockCitizenDetailsService.updateAddress(any(), any(), any(), any())(any(), any(), any())) .thenReturn(EitherT.rightT[Future, UpstreamErrorResponse](true)) - when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn( - Future.successful(AuditResult.Success) - ) - when(mockEditAddressLockRepository.insert(any(), any())).thenReturn( - Future.successful(true) - ) - when(mockAddressMovedService.moved(any(), any(), any())(any(), any())).thenReturn( - Future.successful(AnyOtherMove) - ) - when(mockAddressMovedService.toMessageKey(any())).thenReturn( - Some("label.mockAddressMovedService") - ) + when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn(Future.successful(AuditResult.Success)) + when(mockEditAddressLockRepository.insert(any(), any())).thenReturn(Future.successful(true)) + when(mockAddressMovedService.moved(any(), any(), any())(any(), any())) + .thenReturn(Future.successful(AnyOtherMove)) + when(mockAddressMovedService.toMessageKey(any())).thenReturn(Some("label.mockAddressMovedService")) val addressJourneyData = AddressJourneyData( None, @@ -254,18 +233,10 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { ) ) ) - when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn( - Future.successful(AuditResult.Success) - ) - when(mockEditAddressLockRepository.insert(any(), any())).thenReturn( - Future.successful(true) - ) - when(mockAddressMovedService.moved(any(), any(), any())(any(), any())).thenReturn( - Future.successful(AnyOtherMove) - ) - when(mockAddressMovedService.toMessageKey(any())).thenReturn( - Some("label.mockAddressMovedService") - ) + when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn(Future.successful(AuditResult.Success)) + when(mockEditAddressLockRepository.insert(any(), any())).thenReturn(Future.successful(true)) + when(mockAddressMovedService.moved(any(), any(), any())(any(), any())).thenReturn(Future.successful(AnyOtherMove)) + when(mockAddressMovedService.toMessageKey(any())).thenReturn(Some("label.mockAddressMovedService")) val addressJourneyData = AddressJourneyData( None, @@ -312,18 +283,10 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { ) ) ) - when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn( - Future.successful(AuditResult.Success) - ) - when(mockEditAddressLockRepository.insert(any(), any())).thenReturn( - Future.successful(true) - ) - when(mockAddressMovedService.moved(any(), any(), any())(any(), any())).thenReturn( - Future.successful(AnyOtherMove) - ) - when(mockAddressMovedService.toMessageKey(any())).thenReturn( - Some("label.mockAddressMovedService") - ) + when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn(Future.successful(AuditResult.Success)) + when(mockEditAddressLockRepository.insert(any(), any())).thenReturn(Future.successful(true)) + when(mockAddressMovedService.moved(any(), any(), any())(any(), any())).thenReturn(Future.successful(AnyOtherMove)) + when(mockAddressMovedService.toMessageKey(any())).thenReturn(Some("label.mockAddressMovedService")) val addressJourneyData = AddressJourneyData( None, @@ -359,9 +322,7 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { "Calling handleAddressChangeAuditing" must { "audit when not modified" in { - when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn( - Future.successful(AuditResult.Success) - ) + when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn(Future.successful(AuditResult.Success)) val addressDto: AddressDto = AddressDto("AddressLine1", Some("AddressLine2"), None, None, None, Some("TestPostcode"), None, None, None) @@ -384,9 +345,7 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { } "audit when heavily modified" in { - when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn( - Future.successful(AuditResult.Success) - ) + when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn(Future.successful(AuditResult.Success)) val addressDto: AddressDto = AddressDto("AddressLine1", Some("AddressLine2"), None, None, None, Some("TestPostcode"), None, None, None) @@ -412,9 +371,7 @@ class AddressSubmissionControllerHelperSpec extends BaseSpec { } "audit when slightly modified" in { - when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn( - Future.successful(AuditResult.Success) - ) + when(mockAuditConnector.sendEvent(any())(any(), any())).thenReturn(Future.successful(AuditResult.Success)) val addressDto: AddressDto = AddressDto("AddressLine1", Some("AddressLine2"), None, None, None, Some("TestPostcode"), None, None, None) diff --git a/test/models/dto/AddressDtoSpec.scala b/test/models/dto/AddressDtoSpec.scala index 3bca09413..3f4641f93 100644 --- a/test/models/dto/AddressDtoSpec.scala +++ b/test/models/dto/AddressDtoSpec.scala @@ -753,7 +753,7 @@ class AddressDtoSpec extends BaseSpec { "Calling AddressDto.toAddress" must { - "return address with postcode and not country when postcode exists" in { + "return address with postcode and not country when postcode exists and country is empty" in { val addressDto = AddressDto( "Line 1", @@ -762,7 +762,7 @@ class AddressDtoSpec extends BaseSpec { Some("Line 4"), Some("Line 5"), Some("AA1 1AA"), - Some("UK"), + None, None, None ) @@ -784,6 +784,37 @@ class AddressDtoSpec extends BaseSpec { ) } + "return address with country and drop postcode when country exists even if postcode exists (international)" in { + val addressDto = + AddressDto( + "Line 1", + Some("Line 2"), + Some("Line 3"), + Some("Line 4"), + Some("Line 5"), + Some("75001"), + Some("France"), + None, + None + ) + val addressTye = "residential" + val startDate = LocalDate.of(2019, 1, 1) + + addressDto.toAddress(addressTye, startDate) mustBe Address( + Some("Line 1"), + Some("Line 2"), + Some("Line 3"), + Some("Line 4"), + Some("Line 5"), + None, + Some("France"), + Some(startDate), + None, + Some(addressTye), + isRls = false + ) + } + "return address with country when postcode does not exist" in { val addressDto = AddressDto("Line 1", Some("Line 2"), Some("Line 3"), None, None, None, Some("UK"), None, None) val addressTye = "residential" @@ -809,7 +840,7 @@ class AddressDtoSpec extends BaseSpec { "return formatted postcode when it contains 7 characters" in { val addressDto = - AddressDto("Line 1", Some("Line 2"), Some("Line 3"), None, None, Some("AA9A9AA"), Some("UK"), None, None) + AddressDto("Line 1", Some("Line 2"), Some("Line 3"), None, None, Some("AA9A9AA"), None, None, None) val addressTye = "residential" val startDate = LocalDate.of(2019, 1, 1) @@ -830,7 +861,7 @@ class AddressDtoSpec extends BaseSpec { "return formatted postcode when it contains 6 characters" in { val addressDto = - AddressDto("Line 1", Some("Line 2"), Some("Line 3"), None, None, Some("A9A9AA"), Some("UK"), None, None) + AddressDto("Line 1", Some("Line 2"), Some("Line 3"), None, None, Some("A9A9AA"), None, None, None) val addressTye = "residential" val startDate = LocalDate.of(2019, 1, 1) @@ -851,7 +882,7 @@ class AddressDtoSpec extends BaseSpec { "return formatted postcode when it contains 5 characters" in { val addressDto = - AddressDto("Line 1", Some("Line 2"), Some("Line 3"), None, None, Some("A99AA"), Some("UK"), None, None) + AddressDto("Line 1", Some("Line 2"), Some("Line 3"), None, None, Some("A99AA"), None, None, None) val addressTye = "residential" val startDate = LocalDate.of(2019, 1, 1) diff --git a/test/services/AddressMovedServiceSpec.scala b/test/services/AddressMovedServiceSpec.scala index d7abc4d65..5b59f7a72 100644 --- a/test/services/AddressMovedServiceSpec.scala +++ b/test/services/AddressMovedServiceSpec.scala @@ -34,7 +34,7 @@ class AddressMovedServiceSpec extends BaseSpec { val fromPostcode = "AA1 1AA" val toPostcode = "AA1 2AA" - val englandRecordSet: RecordSet = RecordSet( + val englandRecordSet: RecordSet = RecordSet( Seq( AddressRecord( "some id", @@ -43,6 +43,7 @@ class AddressMovedServiceSpec extends BaseSpec { ) ) ) + val scotlandRecordSet: RecordSet = RecordSet( Seq( AddressRecord( @@ -57,6 +58,7 @@ class AddressMovedServiceSpec extends BaseSpec { "moved" must { "be AnyOtherMove" when { + "the post code is the same" in { when(addressLookupService.lookup(fromPostcode)) .thenReturn( @@ -78,7 +80,6 @@ class AddressMovedServiceSpec extends BaseSpec { } "there are no addresses returned for the new address" in { - when(addressLookupService.lookup(fromPostcode)) .thenReturn( EitherT[Future, UpstreamErrorResponse, RecordSet]( @@ -107,6 +108,12 @@ class AddressMovedServiceSpec extends BaseSpec { service.moved("", "", false).futureValue mustBe AnyOtherMove } + "either postcode is not a valid UK postcode" in { + service.moved("75001", "AA1 1AA", false).futureValue mustBe AnyOtherMove + service.moved("AA1 1AA", "75001", false).futureValue mustBe AnyOtherMove + service.moved("75001", "75001", false).futureValue mustBe AnyOtherMove + } + List( TOO_MANY_REQUESTS, INTERNAL_SERVER_ERROR, From d30ca3d32ccda4ab8ef993243e14568484ce42f9 Mon Sep 17 00:00:00 2001 From: SandeepUmredkar <1963092+SandeepUmredkar@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:02:32 +0000 Subject: [PATCH 2/2] DDCNL-11592 --- app/models/dto/AddressDto.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/models/dto/AddressDto.scala b/app/models/dto/AddressDto.scala index b492a1885..44ed99e43 100644 --- a/app/models/dto/AddressDto.scala +++ b/app/models/dto/AddressDto.scala @@ -49,8 +49,6 @@ case class AddressDto( country.exists(_.trim.nonEmpty) if (isInternational) { - // INTERNATIONAL ADDRESS: - // Citizen-details does NOT allow postcode unless country is UK/IoM Address( Some(line1), newLine2, @@ -65,7 +63,6 @@ case class AddressDto( isRls = false ) } else { - // UK ADDRESS: Address( Some(line1), newLine2,