From 4e0694d61961169434281a246dcdbb0ac50e42b2 Mon Sep 17 00:00:00 2001 From: dhinethweliwitegoda <220175817+dhinethweliwitegoda@users.noreply.github.com> Date: Mon, 3 Nov 2025 13:42:03 +0000 Subject: [PATCH] NGR-3177 Pro-tem reference scheme --- .../utils/UniqueIdGenerator.scala | 50 ++++++++++ .../util/UniqueIdGeneratorSpec.scala | 93 +++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 app/uk/gov/hmrc/ngrraldfrontend/utils/UniqueIdGenerator.scala create mode 100644 test/uk/gov/hmrc/ngrraldfrontend/util/UniqueIdGeneratorSpec.scala diff --git a/app/uk/gov/hmrc/ngrraldfrontend/utils/UniqueIdGenerator.scala b/app/uk/gov/hmrc/ngrraldfrontend/utils/UniqueIdGenerator.scala new file mode 100644 index 00000000..ddd9d25e --- /dev/null +++ b/app/uk/gov/hmrc/ngrraldfrontend/utils/UniqueIdGenerator.scala @@ -0,0 +1,50 @@ +/* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.ngrraldfrontend.utils + +import java.security.SecureRandom + +object UniqueIdGenerator { + + val allowedChars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" + private val generator = new SecureRandom() + private val referenceLength = 12 + private val groupSize = 4 + + def generateId: String = { + val raw = (1 to referenceLength) + .map(_ => allowedChars(generator.nextInt(allowedChars.length))) + .mkString + + format(raw) + } + + def validateId(id: String): Either[Error, String] = { + val sanitised = id.replaceAll("\\s", "").replaceAll("-", "").toUpperCase + + if (sanitised.length != referenceLength || !sanitised.forall(allowedChars.contains(_))) + Left(new Error("Invalid reference")) + else + Right(format(sanitised)) + } + + def format(raw: String): String = + raw.grouped(groupSize).mkString("-") + + def parse(formatted: String): String = + formatted.replaceAll("-", "").toUpperCase +} diff --git a/test/uk/gov/hmrc/ngrraldfrontend/util/UniqueIdGeneratorSpec.scala b/test/uk/gov/hmrc/ngrraldfrontend/util/UniqueIdGeneratorSpec.scala new file mode 100644 index 00000000..b6e54b79 --- /dev/null +++ b/test/uk/gov/hmrc/ngrraldfrontend/util/UniqueIdGeneratorSpec.scala @@ -0,0 +1,93 @@ +/* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.ngrraldfrontend.util + +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.must.Matchers +import uk.gov.hmrc.ngrraldfrontend.utils.UniqueIdGenerator + +class UniqueIdGeneratorSpec extends AnyFreeSpec with Matchers { + + private val allowedChars = UniqueIdGenerator.allowedChars + + "UniqueIdGenerator" - { + + "generate a 12-char ID with 2 hyphens in correct format" in { + val id = UniqueIdGenerator.generateId + id.length mustBe 14 + id.count(_ == '-') mustBe 2 + + val compactId = UniqueIdGenerator.parse(id) + compactId.length mustBe 12 + compactId.forall(allowedChars.contains(_)) mustBe true + val formatted = UniqueIdGenerator.format(compactId) + formatted mustBe id + } + + "validate good IDs" in { + val validIds = List( + "fdfd-fdfd-dfdf", + "VDJ4-5NSG-8RHW", + "BDJ6867MLMNE", + "nvjf5245bsmv" + ) + + validIds.foreach { id => + withClue(s"Expected '$id' to be valid: ") { + UniqueIdGenerator.validateId(id).isRight mustBe true + } + } + } + + "invalidate bad IDs" in { + val invalidIds = List( + "0FDE-DFD1-DGJ1", + "0efkdkfvncma", + "hello", + "&fdh-9adf-4jnf", + "ABCD-EFGH-IJKLM", + "ABCD-EFGH-IJ1M", + "ABCD-EFGH-IJOM" + ) + + invalidIds.foreach { id => + withClue(s"Expected '$id' to be invalid: ") { + UniqueIdGenerator.validateId(id).isLeft mustBe true + } + } + } + + "format raw reference correctly" in { + val raw = "7GQX2MZKJH9B" + val formatted = UniqueIdGenerator.format(raw) + formatted mustBe "7GQX-2MZK-JH9B" + } + + "parse formatted reference back to raw" in { + val formatted = "7GQX-2MZK-JH9B" + val raw = UniqueIdGenerator.parse(formatted) + raw mustBe "7GQX2MZKJH9B" + } + + "round-trip format and parse should preserve original raw reference" in { + val raw = "N8V3W5Y2X4ZT" + val formatted = UniqueIdGenerator.format(raw) + val parsed = UniqueIdGenerator.parse(formatted) + parsed mustBe raw + } + } +}