From 383b8ddb4806928284f5c82721e11950cfb41777 Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:27:42 +0000 Subject: [PATCH 01/14] DFI-1577 reduce number of lines --- src/test/resources/application.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/application.conf b/src/test/resources/application.conf index 2851484..ac03c0c 100644 --- a/src/test/resources/application.conf +++ b/src/test/resources/application.conf @@ -55,5 +55,5 @@ perftest { # "no-of-json-lines = 364" creates a 1MB ndjson file # set 'save-to-file = true' only when tests are running locally saveMonthlyReturnLocally { - no-of-json-lines = 5832 + no-of-json-lines = 2000 } From 924e55a8dc2de7ba367941ceb41f43994c92337d Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:15:09 +0000 Subject: [PATCH 02/14] # This is a combination of 19 commits. # This is the 1st commit message: DFI-1577 change simulation to use same zRef throughout all subsequent API calls # This is the commit message #2: DFI-1577 WIP # This is the commit message #3: DFI-1577 WIP # This is the commit message #4: DFI-1577 WIP # This is the commit message #5: DFI-1577 test setup refactored # This is the commit message #6: DFI-1577 descreased payload size # This is the commit message #7: DFI-1577 increased number of IsaAccount in single payload to 4000 lines # This is the commit message #8: DFI-1577 increased number of IsaAccount in single payload to 8000 # This is the commit message #9: DFI-1577 create one ndJson payload and reuse # This is the commit message #10: DFI-1577 test with 8000 IsaAccounts # This is the commit message #11: DFI-1577 added logging for memory used # This is the commit message #12: DFI-1577 reduce payload size # This is the commit message #13: DFI-1577 increase xXms & Xmx values and payload size # This is the commit message #14: DFI-1577 stream payload instead of holding payload in memory # This is the commit message #15: DFI-1577 increased payload to 16k ISA accounts # This is the commit message #16: DFI-1577 tidy up # This is the commit message #17: DFI-1577 increase concurrent users to 10 # This is the commit message #18: DFI-1577 increase concurrent users to 10 # This is the commit message #19: DFI-1577 increase concurrent users to 2 --- build.sbt | 7 +- src/test/resources/application.conf | 15 +-- src/test/resources/journeys.conf | 28 +----- .../MonthlyReturnsSubmissionRequests.scala | 19 +++- .../MonthlyReturnsSubmissionSimulation.scala | 96 +++++++++---------- .../disareturns/TestOnlyRequests.scala | 32 +++++++ .../disareturns/Util/DirectMemoryLogger.scala | 40 ++++++++ ...ata.scala => NdjsonPayloadGenerator.scala} | 60 ++++++------ .../Util/RandomDataGenerator.scala | 6 -- .../disareturns/constant/AppConfig.scala | 1 + .../models/TestDataSetupResult.scala | 9 +- .../disareturns/testSetup/AuthRequests.scala | 5 +- .../disareturns/testSetup/BaseRequests.scala | 71 +++++++++----- ...uests.scala => StubTestOnlyRequests.scala} | 2 +- 14 files changed, 236 insertions(+), 155 deletions(-) create mode 100644 src/test/scala/uk/gov/hmrc/perftests/disareturns/TestOnlyRequests.scala create mode 100644 src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/DirectMemoryLogger.scala rename src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/{MockMonthlyReturnData.scala => NdjsonPayloadGenerator.scala} (60%) rename src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/{ReportingWindowRequests.scala => StubTestOnlyRequests.scala} (93%) diff --git a/build.sbt b/build.sbt index d1329d2..981c4c9 100644 --- a/build.sbt +++ b/build.sbt @@ -1,3 +1,7 @@ +import sbt.Keys.javaOptions + +import scala.collection.Seq + lazy val root = (project in file(".")) .enablePlugins(GatlingPlugin) .settings( @@ -6,5 +10,6 @@ lazy val root = (project in file(".")) scalaVersion := "2.13.16", scalacOptions ++= Seq("-feature", "-language:implicitConversions", "-language:postfixOps"), Test / testOptions := Seq.empty, - libraryDependencies ++= Dependencies.test + libraryDependencies ++= Dependencies.test, + javaOptions ++= Seq("-Xms1024m", "-Xmx1024m") ) diff --git a/src/test/resources/application.conf b/src/test/resources/application.conf index ac03c0c..f055947 100644 --- a/src/test/resources/application.conf +++ b/src/test/resources/application.conf @@ -46,14 +46,7 @@ perftest { #requestPercentageFailureThreshold = 1 } - # if true, saves NDJSON to a txt file - # Each "no-of-json-lines" generates an NDJSON payload containing all 6 ISA account types. - # For example: - # no-of-json-lines = 1 → 6 JSON objects(6 NDJSON lines) - # no-of-json-lines = N → 6 x N JSON objects (N NDJSON lines) - # With the existing payload "no-of-json-lines = 5832" creates a 10MB ndjson file - # "no-of-json-lines = 364" creates a 1MB ndjson file - # set 'save-to-file = true' only when tests are running locally - saveMonthlyReturnLocally { - no-of-json-lines = 2000 - } +# numOfIsaAccountSets = 1 → 4 JSON objects(6 NDJSON lines) +monthlyReturnsTestPayload { + numOfIsaAccountSets = 4000 +} diff --git a/src/test/resources/journeys.conf b/src/test/resources/journeys.conf index fc8df28..ad4cc90 100644 --- a/src/test/resources/journeys.conf +++ b/src/test/resources/journeys.conf @@ -15,37 +15,19 @@ # Configure here your journeys. A journey is a sequence of requests at a certain load. journeys { - monthly-returns-submission-journey = { - description = "Monthly returns submission journey" - load = 10 - parts = [ - monthly-returns-submission-journey - ] - } - - monthly-returns-declaration-journey = { - description = "monthly returns declaration journey" - load = 50 + monthly-returns-journey = { + description = "monthly returns journey" + load = 2 parts = [ - monthly-returns-declaration-journey + monthly-returns-journey ] } - - monthly-reconciliation-report-summary-journey = { - description = "Monthly reconciliation report summary journey" - load = 50 - parts = [ - monthly-reconciliation-report-summary-journey - ] - } } # Default behaviour is to run all journeys. If that is not what you need you can specify the list of journeys to run journeysToRun = [ - monthly-returns-submission-journey, - monthly-returns-declaration-journey, - monthly-reconciliation-report-summary-journey + monthly-returns-journey ] # You can specify the same list of journeys via environment variables: diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionRequests.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionRequests.scala index 1f52027..a97f04b 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionRequests.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionRequests.scala @@ -19,15 +19,30 @@ package uk.gov.hmrc.perftests.disareturns import io.gatling.core.Predef._ import io.gatling.http.Predef._ import io.gatling.http.request.builder.HttpRequestBuilder -import uk.gov.hmrc.perftests.disareturns.Util.MockMonthlyReturnData.validNdjsonTestData +import uk.gov.hmrc.perftests.disareturns.Util.NdjsonPayloadGenerator import uk.gov.hmrc.perftests.disareturns.constant.AppConfig.{disaReturnsHost, disaReturnsRoute} import uk.gov.hmrc.perftests.disareturns.constant.Headers.headerWithClientIdAndBearerToken +import java.nio.file.{Files, Paths, StandardOpenOption} + object MonthlyReturnsSubmissionRequests { + + private val submissionPayloadFilePath: String = { + val path = "target/monthly-return-payload.ndjson" + val ndjson = NdjsonPayloadGenerator.generateNdjsonPayload() + val filePath = Paths.get(path) + Files.write(filePath, ndjson.getBytes("UTF-8"), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ) + path + } + val submitMonthlyReport: HttpRequestBuilder = http("Submit monthly report") .post(s"$disaReturnsHost$disaReturnsRoute#{isaManagerReference}/#{taxYear}/#{month}") .headers(headerWithClientIdAndBearerToken) - .body(StringBody(validNdjsonTestData())) + .body(RawFileBody(submissionPayloadFilePath)).asJson .check(status.is(204)) + } diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index 9e32d8a..cf39ad7 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -16,17 +16,20 @@ package uk.gov.hmrc.perftests.disareturns -import io.gatling.core.Predef.feed +import io.gatling.commons.validation.SuccessWrapper +import io.gatling.core.Predef.exec import io.gatling.core.structure.ChainBuilder import uk.gov.hmrc.performance.simulation.PerformanceTestRunner import uk.gov.hmrc.perftests.disareturns.MonthlyReconciliationReportRequests.{getReportingResultsSummary, submitReturnSummaryCallback} -import uk.gov.hmrc.perftests.disareturns.MonthlyReturnLoginRequest.getBearerToken import uk.gov.hmrc.perftests.disareturns.MonthlyReturnsDeclarationRequest.submitDeclaration import uk.gov.hmrc.perftests.disareturns.MonthlyReturnsSubmissionRequests.submitMonthlyReport -import uk.gov.hmrc.perftests.disareturns.Util.RandomDataGenerator.{generateRandomISAReference, getMonth, getTaxYear} +import uk.gov.hmrc.perftests.disareturns.TestOnlyRequests.openObligationStatus +import uk.gov.hmrc.perftests.disareturns.Util.DirectMemoryLogger +import uk.gov.hmrc.perftests.disareturns.Util.RandomDataGenerator.{getMonth, getTaxYear} import uk.gov.hmrc.perftests.disareturns.models.TestDataSetupResult import uk.gov.hmrc.perftests.disareturns.testSetup.BaseRequests +import java.util.concurrent.atomic.AtomicInteger import scala.concurrent.Await import scala.concurrent.duration.DurationInt @@ -36,63 +39,60 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base before { setupData = Await.result(testDataSetup(), 30.seconds) + + val scheduler = java.util.concurrent.Executors.newSingleThreadScheduledExecutor() + + scheduler.scheduleAtFixedRate( + new Runnable { + override def run(): Unit = DirectMemoryLogger.log() + }, + 0, + 5, + java.util.concurrent.TimeUnit.SECONDS + ) } after { testDataCleanUp(setupData) } - val bearerTokenFeeder: ChainBuilder = feed(Iterator.continually(Map("bearerToken" -> setupData.bearerToken))) - val clientIdFeeder: ChainBuilder = feed( - Iterator.continually(Map("clientId" -> setupData.clientIds(scala.util.Random.nextInt(setupData.clientIds.size)))) - ) + // Thread-safe counter for round-robin + val isaManagerCounter = new AtomicInteger(0) - def generateReportInformationForTheSubmission(): Iterator[Map[String, String]] = - Iterator.continually( - Map( - "isaManagerReference" -> generateRandomISAReference(1, 500), - "taxYear" -> getTaxYear, - "month" -> getMonth - ) - ) - - def generateReportInformationForTheDeclaration(): Iterator[Map[String, String]] = - Iterator.continually( - Map( - "isaManagerReference" -> generateRandomISAReference(501, 999), - "taxYear" -> getTaxYear, - "month" -> getMonth - ) - ) + val assignIsaManager: ChainBuilder = exec { session => +// if (setupData.isaManager.isEmpty) { +// session.failure("No IsaManagers available in setupData") +// } else { + // Get next index in round-robin, safely + val index = isaManagerCounter.getAndUpdate(i => (i + 1) % setupData.isaManager.size) + val im = setupData.isaManager(index) - setup( - "monthly-returns-submission-journey", - "Monthly returns submission journey" - ) withActions (feed( - generateReportInformationForTheSubmission() - ).actionBuilders ++ clientIdFeeder.actionBuilders: _*) withRequests ( - getBearerToken, - submitMonthlyReport - ) + session + .set("isaManagerReference", im.zRef) + .set("bearerToken", im.bearerToken) + .set("clientId", im.clientId) + .set("applicationId", im.applicationId) + .success +// } + } - setup( - "monthly-returns-declaration-journey", - "Monthly returns declaration journey" - ) withActions (feed( - generateReportInformationForTheDeclaration() - ).actionBuilders ++ clientIdFeeder.actionBuilders: _*) withRequests ( - getBearerToken, - submitDeclaration - ) + val setDates: ChainBuilder = exec { session => + session + .set("taxYear", getTaxYear) + .set("month", getMonth) + .success + } setup( - "monthly-reconciliation-report-summary-journey", - "Monthly reconciliation report summary journey" - ) withActions (feed( - generateReportInformationForTheSubmission() - ).actionBuilders: _*) withRequests ( - getBearerToken, + "monthly-returns-journey", + "Monthly returns journey" + ).withActions( + assignIsaManager.actionBuilders ++ setDates.actionBuilders: _* + ).withRequests( + openObligationStatus, + submitMonthlyReport, + submitDeclaration, submitReturnSummaryCallback, getReportingResultsSummary ) diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/TestOnlyRequests.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/TestOnlyRequests.scala new file mode 100644 index 0000000..8963a24 --- /dev/null +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/TestOnlyRequests.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2023 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.perftests.disareturns + +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import io.gatling.http.request.builder.HttpRequestBuilder +import uk.gov.hmrc.perftests.disareturns.constant.AppConfig.{disaReturnsStubHost, openObligationStatusPath} +import uk.gov.hmrc.perftests.disareturns.constant.Headers.headerWithClientIdAndBearerToken + +object TestOnlyRequests { + val openObligationStatus: HttpRequestBuilder = + http("Open Obligation Status") + .post(s"$disaReturnsStubHost$openObligationStatusPath#{isaManagerReference}") + .headers(headerWithClientIdAndBearerToken) + .body(StringBody("")) + .check(status.is(200)) +} diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/DirectMemoryLogger.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/DirectMemoryLogger.scala new file mode 100644 index 0000000..86b0d1e --- /dev/null +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/DirectMemoryLogger.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2026 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.perftests.disareturns.Util + +import java.lang.management.{BufferPoolMXBean, ManagementFactory} +import scala.jdk.CollectionConverters._ + +object DirectMemoryLogger { + + def log(): Unit = { + val bufferPools: Seq[BufferPoolMXBean] = + ManagementFactory.getPlatformMXBeans(classOf[BufferPoolMXBean]).asScala.toSeq + + bufferPools.foreach { pool => + if (pool.getName == "direct") { + val usedMb = pool.getMemoryUsed / 1024 / 1024 + val capMb = pool.getTotalCapacity / 1024 / 1024 + val count = pool.getCount + + println( + s"[DIRECT MEMORY] Used: ${usedMb}MB | Capacity: ${capMb}MB | Buffers: $count" + ) + } + } + } +} \ No newline at end of file diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/MockMonthlyReturnData.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/NdjsonPayloadGenerator.scala similarity index 60% rename from src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/MockMonthlyReturnData.scala rename to src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/NdjsonPayloadGenerator.scala index 49c700c..a251686 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/MockMonthlyReturnData.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/NdjsonPayloadGenerator.scala @@ -16,13 +16,16 @@ package uk.gov.hmrc.perftests.disareturns.Util -import com.typesafe.config.ConfigFactory +import com.typesafe.config.{Config, ConfigFactory} import play.api.libs.json.{JsValue, Json} import uk.gov.hmrc.perftests.disareturns.models.isaAccounts.{LifetimeISASubscriptionPayload, LiftimeISAClosurePayload, StandardISAClosurePayload, StandardISASubscriptionPayload} -object MockMonthlyReturnData extends NdjsonSupport { - private val config = ConfigFactory.load() - private val noOfJsons: Int = config.getInt("saveMonthlyReturnLocally.no-of-json-lines") +import java.nio.file.{Files, Paths, StandardOpenOption} + +object NdjsonPayloadGenerator extends NdjsonSupport { + private val config: Config = ConfigFactory.load() + private val numOfIsaAccountSets: Int = config.getInt("monthlyReturnsTestPayload.numOfIsaAccountSets") + def getLISASubscriptionPayload( nino: String, accountNumber: String @@ -107,39 +110,30 @@ object MockMonthlyReturnData extends NdjsonSupport { "VOID" ) - def validNdjsonTestData(): String = { - - def generatePayloadBlock(): Seq[JsValue] = { - val lisaSubscriptionPayload = - getLISASubscriptionPayload(RandomDataGenerator.generateNino(), RandomDataGenerator.generateAccountNumber()) - - val lisaClosurePayload = - getLISAClosurePayload(RandomDataGenerator.generateNino(), RandomDataGenerator.generateAccountNumber()) - - val sisaSubscriptionPayload = - getSISASubscriptionPayload(RandomDataGenerator.generateNino(), RandomDataGenerator.generateAccountNumber()) - - val sisaClosurePayload = - getSISAClosurePayload(RandomDataGenerator.generateNino(), RandomDataGenerator.generateAccountNumber()) + private val isaAccountBuilders: Seq[(String, String) => JsValue] = Seq( + (nino, acc) => Json.toJson(getLISASubscriptionPayload(nino, acc)), + (nino, acc) => Json.toJson(getLISAClosurePayload(nino, acc)), + (nino, acc) => Json.toJson(getSISASubscriptionPayload(nino, acc)), + (nino, acc) => Json.toJson(getSISAClosurePayload(nino, acc)) + ) - Seq( - Json.toJson(lisaSubscriptionPayload), - Json.toJson(lisaClosurePayload), - Json.toJson(sisaSubscriptionPayload), - Json.toJson(sisaClosurePayload) - ) + private def generateEachIsaAccountType(): Seq[JsValue] = + isaAccountBuilders.map { build => + val nino = RandomDataGenerator.generateNino() + val account = RandomDataGenerator.generateAccountNumber() + build(nino, account) } - val allPayloads: Seq[JsValue] = - Seq.fill(noOfJsons)(generatePayloadBlock()).flatten - - val ndjsonString = toNdjson(allPayloads) + def generateNdjsonPayload(): String = { + val allEvents: Seq[JsValue] = Seq.fill(numOfIsaAccountSets)(generateEachIsaAccountType()).flatten + toNdjson(allEvents) + } - /** Uncomment below code to save the ndjson fine in case for the testing purposes. 5832 * 4 creates a 10MB NDJson - * file, where 4 are the different types of subscription. - */ - /** val path = Paths.get("test-data.txt") Files.write(path, ndjsonString.getBytes(StandardCharsets.UTF_8))* */ - ndjsonString + def generateNdjsonFile(filePath: String): String = { + val ndjson = generateNdjsonPayload() + val path = Paths.get(filePath) + Files.write(path, ndjson.getBytes("UTF-8"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING) + filePath } } diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/RandomDataGenerator.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/RandomDataGenerator.scala index 7802d73..5ce6b99 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/RandomDataGenerator.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/Util/RandomDataGenerator.scala @@ -34,12 +34,6 @@ object RandomDataGenerator { f"STD$number%06d" } - def generateRandomISAReference(min: Int, max: Int): String = { - require(min <= max, "min must be <= max") - val num = scala.util.Random.nextInt(max - min + 1) + min - f"Z$num%04d" - } - def getMonth: String = { val dateFormatter = DateTimeFormatter.ofPattern("MMM") LocalDate.now().format(dateFormatter).toUpperCase diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/constant/AppConfig.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/constant/AppConfig.scala index 890ffc2..e3f0420 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/constant/AppConfig.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/constant/AppConfig.scala @@ -27,6 +27,7 @@ object AppConfig extends ServicesConfiguration { val ggSignInUrl = s"$authHost/government-gateway/session/login" val disaReturnsStubHost: String = baseUrlFor("disa-returns-stub") val reportingWindowPath: String = "/test-only/etmp/reporting-window-state" + val openObligationStatusPath: String = "/test-only/etmp/open-obligation-status/" val third_party_application_host: String = baseUrlFor("third-party-application") val ppns_host: String = baseUrlFor("push-pull-notification") val api_subscription_fields_host: String = baseUrlFor("api-subscription-fields") diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/models/TestDataSetupResult.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/models/TestDataSetupResult.scala index 55f7f8d..a509195 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/models/TestDataSetupResult.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/models/TestDataSetupResult.scala @@ -17,7 +17,12 @@ package uk.gov.hmrc.perftests.disareturns.models case class TestDataSetupResult( + isaManager: Seq[IsaManager] +) + +case class IsaManager( + zRef: String, bearerToken: String, - clientIds: List[String], - applicationIds: List[String] + clientId: String, + applicationId: String ) diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/AuthRequests.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/AuthRequests.scala index 08c81bc..1d67620 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/AuthRequests.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/AuthRequests.scala @@ -18,7 +18,6 @@ package uk.gov.hmrc.perftests.disareturns.testSetup import play.api.libs.ws.DefaultBodyWritables.writeableOf_String import play.api.libs.ws.ahc.StandaloneAhcWSClient -import uk.gov.hmrc.perftests.disareturns.Util.RandomDataGenerator.generateRandomISAReference import uk.gov.hmrc.perftests.disareturns.constant.AppConfig.ggSignInUrl import uk.gov.hmrc.perftests.disareturns.models.{SetupAssertions, SetupFailure} @@ -49,10 +48,10 @@ class AuthRequests(ws: StandaloneAhcWSClient)(implicit ec: ExecutionContext) ext | ] |}""".stripMargin - def getSubmissionBearerToken: Future[String] = { + def getSubmissionBearerToken(zRef: String): Future[String] = { val url = ggSignInUrl val bearerRegex = "Bearer\\s+\\S+".r - val payload = authRequestPayload.replaceAll("isaReference", generateRandomISAReference(1, 500)) + val payload = authRequestPayload.replaceAll("isaReference", zRef) ws.url(url) .addHttpHeaders( "Content-Type" -> "application/json" diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala index 77db89a..f3c4878 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala @@ -20,36 +20,53 @@ import akka.actor.ActorSystem import akka.stream.Materializer import org.scalatest.Assertions.cancel import play.api.libs.ws.ahc.StandaloneAhcWSClient -import uk.gov.hmrc.perftests.disareturns.models.TestDataSetupResult +import uk.gov.hmrc.perftests.disareturns.models.{IsaManager, TestDataSetupResult} import scala.concurrent.{ExecutionContext, Future} trait BaseRequests { - implicit val system: ActorSystem = ActorSystem("setup-system") - implicit val mat: Materializer = Materializer(system) - implicit val ec: ExecutionContext = system.dispatcher + implicit val system: ActorSystem = ActorSystem("setup-system") + implicit val mat: Materializer = Materializer(system) + implicit val ec: ExecutionContext = system.dispatcher val wsClient: StandaloneAhcWSClient = StandaloneAhcWSClient() - val authRequests = new AuthRequests(wsClient) - val thirdPartyApplicationRequests = new ThirdPartyApplicationRequests(wsClient) - val reportingWindowRequests = new ReportingWindowRequests(wsClient) - val noOfThirdPartyApplications = 10 + val authRequests = new AuthRequests(wsClient) + val thirdPartyApplicationRequests = new ThirdPartyApplicationRequests(wsClient) + val stubTestOnlyRequests = new StubTestOnlyRequests(wsClient) + val noOfThirdPartyApplications = 10 def testDataSetup(): Future[TestDataSetupResult] = { + + val zRefs: List[String] = + (0 until noOfThirdPartyApplications) + .map(i => { + require(i < 10000, "Exceeded max ZRef limit (Z9999)") + f"Z$i%04d" + }) + .toList + val setup = for { - bearerToken <- authRequests.getSubmissionBearerToken - _ <- reportingWindowRequests.setReportingWindowsOpen() - apps <- Future.traverse((1 to noOfThirdPartyApplications).toList) { _ => - for { - app <- thirdPartyApplicationRequests.createClientApplication(bearerToken) - _ <- thirdPartyApplicationRequests.createNotificationBox(app.clientId) - } yield app - } - _ <- thirdPartyApplicationRequests.createSubscriptionFields() - } yield TestDataSetupResult( - bearerToken = bearerToken, - clientIds = apps.map(_.clientId), - applicationIds = apps.map(_.applicationId) - ) + _ <- stubTestOnlyRequests.setReportingWindowsOpen() + + isaManagers <- Future.traverse(zRefs) { zRef => + for { + bearerToken <- authRequests.getSubmissionBearerToken(zRef) + + app <- thirdPartyApplicationRequests.createClientApplication(bearerToken) + + _ <- thirdPartyApplicationRequests.createNotificationBox(app.clientId) + + } yield IsaManager( + zRef = zRef, + bearerToken = bearerToken, + clientId = app.clientId, + applicationId = app.applicationId + ) + } + + _ <- thirdPartyApplicationRequests.createSubscriptionFields() + + } yield TestDataSetupResult(isaManager = isaManagers) + setup.recover { case e => cancel(s"Test has been aborted due to test setup failure: ${e.getMessage}") } @@ -57,12 +74,16 @@ trait BaseRequests { def testDataCleanUp(setupData: TestDataSetupResult): Future[Unit] = Future - .traverse(setupData.applicationIds)(appId => - thirdPartyApplicationRequests.deleteClientApplication(setupData.bearerToken, appId) - ) + .traverse(setupData.isaManager) { im => + thirdPartyApplicationRequests.deleteClientApplication( + im.bearerToken, + im.applicationId + ) + } .map(_ => ()) .andThen { case _ => wsClient.close() system.terminate() } + } diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/ReportingWindowRequests.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/StubTestOnlyRequests.scala similarity index 93% rename from src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/ReportingWindowRequests.scala rename to src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/StubTestOnlyRequests.scala index 0095015..b453fea 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/ReportingWindowRequests.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/StubTestOnlyRequests.scala @@ -27,7 +27,7 @@ import javax.inject.Singleton import scala.concurrent.{ExecutionContext, Future} @Singleton -class ReportingWindowRequests(ws: StandaloneAhcWSClient)(implicit ec: ExecutionContext) extends SetupAssertions { +class StubTestOnlyRequests(ws: StandaloneAhcWSClient)(implicit ec: ExecutionContext) extends SetupAssertions { val reportingWindowPayload: JsObject = Json.obj("reportingWindowOpen" -> true) def setReportingWindowsOpen(): Future[Unit] = From c5a4f658651f7fcbe953cc37285e6163bff0db19 Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:37:06 +0000 Subject: [PATCH 03/14] DFI-1577 increase concurrent users to 5 --- src/test/resources/journeys.conf | 2 +- .../MonthlyReturnsSubmissionSimulation.scala | 24 +++++++------------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/test/resources/journeys.conf b/src/test/resources/journeys.conf index ad4cc90..fa46566 100644 --- a/src/test/resources/journeys.conf +++ b/src/test/resources/journeys.conf @@ -17,7 +17,7 @@ journeys { monthly-returns-journey = { description = "monthly returns journey" - load = 2 + load = 5 parts = [ monthly-returns-journey ] diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index cf39ad7..9ab495f 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -57,24 +57,18 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base } - // Thread-safe counter for round-robin val isaManagerCounter = new AtomicInteger(0) val assignIsaManager: ChainBuilder = exec { session => -// if (setupData.isaManager.isEmpty) { -// session.failure("No IsaManagers available in setupData") -// } else { - // Get next index in round-robin, safely - val index = isaManagerCounter.getAndUpdate(i => (i + 1) % setupData.isaManager.size) - val im = setupData.isaManager(index) - - session - .set("isaManagerReference", im.zRef) - .set("bearerToken", im.bearerToken) - .set("clientId", im.clientId) - .set("applicationId", im.applicationId) - .success -// } + val index = isaManagerCounter.getAndUpdate(i => (i + 1) % setupData.isaManager.size) + val im = setupData.isaManager(index) + + session + .set("isaManagerReference", im.zRef) + .set("bearerToken", im.bearerToken) + .set("clientId", im.clientId) + .set("applicationId", im.applicationId) + .success } val setDates: ChainBuilder = exec { session => From 7b6df3474fde0ac1661ef925f755be04604d2652 Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:18:13 +0000 Subject: [PATCH 04/14] DFI-1577 each user is assigned one Zref --- src/test/resources/application.conf | 2 +- .../MonthlyReturnsSubmissionSimulation.scala | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/test/resources/application.conf b/src/test/resources/application.conf index f055947..fb28b78 100644 --- a/src/test/resources/application.conf +++ b/src/test/resources/application.conf @@ -46,7 +46,7 @@ perftest { #requestPercentageFailureThreshold = 1 } -# numOfIsaAccountSets = 1 → 4 JSON objects(6 NDJSON lines) +# numOfIsaAccountSets = 1 → 4 JSON objects(4 NDJSON lines) monthlyReturnsTestPayload { numOfIsaAccountSets = 4000 } diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index 9ab495f..cba0f3d 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -17,7 +17,7 @@ package uk.gov.hmrc.perftests.disareturns import io.gatling.commons.validation.SuccessWrapper -import io.gatling.core.Predef.exec +import io.gatling.core.Predef.{exec, feed} import io.gatling.core.structure.ChainBuilder import uk.gov.hmrc.performance.simulation.PerformanceTestRunner import uk.gov.hmrc.perftests.disareturns.MonthlyReconciliationReportRequests.{getReportingResultsSummary, submitReturnSummaryCallback} @@ -57,11 +57,9 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base } - val isaManagerCounter = new AtomicInteger(0) - - val assignIsaManager: ChainBuilder = exec { session => - val index = isaManagerCounter.getAndUpdate(i => (i + 1) % setupData.isaManager.size) - val im = setupData.isaManager(index) + val assignIsaManagerChain: ChainBuilder = exec { session => + val userIndex = session.userId.toInt % setupData.isaManager.size + val im = setupData.isaManager(userIndex) session .set("isaManagerReference", im.zRef) @@ -71,7 +69,7 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base .success } - val setDates: ChainBuilder = exec { session => + val setDatesChain: ChainBuilder = exec { session => session .set("taxYear", getTaxYear) .set("month", getMonth) @@ -82,7 +80,7 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base "monthly-returns-journey", "Monthly returns journey" ).withActions( - assignIsaManager.actionBuilders ++ setDates.actionBuilders: _* + assignIsaManagerChain.actionBuilders ++ setDatesChain.actionBuilders: _* ).withRequests( openObligationStatus, submitMonthlyReport, @@ -91,5 +89,8 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base getReportingResultsSummary ) + //TODO: Raising ticket to implement get report endpoint request + //TODO: Improvement - implement call to test support API to create report + runSimulation() } From 43272520599b9897eeb46d28897baf4df3025182 Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:41:16 +0000 Subject: [PATCH 05/14] DFI-1577 test --- .../MonthlyReturnsSubmissionSimulation.scala | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index cba0f3d..e987d83 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -29,7 +29,6 @@ import uk.gov.hmrc.perftests.disareturns.Util.RandomDataGenerator.{getMonth, get import uk.gov.hmrc.perftests.disareturns.models.TestDataSetupResult import uk.gov.hmrc.perftests.disareturns.testSetup.BaseRequests -import java.util.concurrent.atomic.AtomicInteger import scala.concurrent.Await import scala.concurrent.duration.DurationInt @@ -56,31 +55,26 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base testDataCleanUp(setupData) } - - val assignIsaManagerChain: ChainBuilder = exec { session => - val userIndex = session.userId.toInt % setupData.isaManager.size - val im = setupData.isaManager(userIndex) - - session - .set("isaManagerReference", im.zRef) - .set("bearerToken", im.bearerToken) - .set("clientId", im.clientId) - .set("applicationId", im.applicationId) - .success - } - - val setDatesChain: ChainBuilder = exec { session => - session - .set("taxYear", getTaxYear) - .set("month", getMonth) - .success - } + val isaManagerFeeder: Iterator[Map[String, Any]] = Iterator.continually( + setupData.isaManager.map { im => + Map( + "isaManagerReference" -> im.zRef, + "bearerToken" -> im.bearerToken, + "clientId" -> im.clientId, + "applicationId" -> im.applicationId, + "taxYear" -> getTaxYear, + "month" -> getMonth + ) + } + ).flatten + + val assignIsaManager: ChainBuilder = feed(isaManagerFeeder) setup( "monthly-returns-journey", "Monthly returns journey" ).withActions( - assignIsaManagerChain.actionBuilders ++ setDatesChain.actionBuilders: _* + assignIsaManager.actionBuilders: _* ).withRequests( openObligationStatus, submitMonthlyReport, From 16ac59aabd10d799d6d06aac22450522e4f5434f Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:01:14 +0000 Subject: [PATCH 06/14] DFI-1577 revert feeder --- src/test/resources/journeys.conf | 2 +- .../MonthlyReturnsSubmissionSimulation.scala | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/test/resources/journeys.conf b/src/test/resources/journeys.conf index fa46566..76299b5 100644 --- a/src/test/resources/journeys.conf +++ b/src/test/resources/journeys.conf @@ -17,7 +17,7 @@ journeys { monthly-returns-journey = { description = "monthly returns journey" - load = 5 + load = 4 parts = [ monthly-returns-journey ] diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index e987d83..992fef2 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -55,26 +55,28 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base testDataCleanUp(setupData) } - val isaManagerFeeder: Iterator[Map[String, Any]] = Iterator.continually( - setupData.isaManager.map { im => - Map( - "isaManagerReference" -> im.zRef, - "bearerToken" -> im.bearerToken, - "clientId" -> im.clientId, - "applicationId" -> im.applicationId, - "taxYear" -> getTaxYear, - "month" -> getMonth - ) - } - ).flatten - - val assignIsaManager: ChainBuilder = feed(isaManagerFeeder) + val isaManagerFeeder: ChainBuilder = + feed( + Iterator + .continually(setupData.isaManager) + .flatten + .map { im => + Map( + "isaManagerReference" -> im.zRef, + "bearerToken" -> im.bearerToken, + "clientId" -> im.clientId, + "applicationId" -> im.applicationId, + "taxYear" -> getTaxYear, + "month" -> getMonth + ) + } + ) setup( "monthly-returns-journey", "Monthly returns journey" ).withActions( - assignIsaManager.actionBuilders: _* + isaManagerFeeder.actionBuilders: _* ).withRequests( openObligationStatus, submitMonthlyReport, From c6cda05393777e173fcec37fb7e77081a27eeb26 Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:29:50 +0000 Subject: [PATCH 07/14] DFI-1577 updated feeder --- src/test/resources/journeys.conf | 2 +- .../MonthlyReturnsSubmissionSimulation.scala | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/test/resources/journeys.conf b/src/test/resources/journeys.conf index 76299b5..04300c4 100644 --- a/src/test/resources/journeys.conf +++ b/src/test/resources/journeys.conf @@ -17,7 +17,7 @@ journeys { monthly-returns-journey = { description = "monthly returns journey" - load = 4 + load = 10 parts = [ monthly-returns-journey ] diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index 992fef2..36f02bc 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -16,8 +16,7 @@ package uk.gov.hmrc.perftests.disareturns -import io.gatling.commons.validation.SuccessWrapper -import io.gatling.core.Predef.{exec, feed} +import io.gatling.core.Predef._ import io.gatling.core.structure.ChainBuilder import uk.gov.hmrc.performance.simulation.PerformanceTestRunner import uk.gov.hmrc.perftests.disareturns.MonthlyReconciliationReportRequests.{getReportingResultsSummary, submitReturnSummaryCallback} @@ -29,6 +28,7 @@ import uk.gov.hmrc.perftests.disareturns.Util.RandomDataGenerator.{getMonth, get import uk.gov.hmrc.perftests.disareturns.models.TestDataSetupResult import uk.gov.hmrc.perftests.disareturns.testSetup.BaseRequests +import java.util.concurrent.atomic.AtomicInteger import scala.concurrent.Await import scala.concurrent.duration.DurationInt @@ -55,28 +55,30 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base testDataCleanUp(setupData) } - val isaManagerFeeder: ChainBuilder = - feed( - Iterator - .continually(setupData.isaManager) - .flatten - .map { im => - Map( - "isaManagerReference" -> im.zRef, - "bearerToken" -> im.bearerToken, - "clientId" -> im.clientId, - "applicationId" -> im.applicationId, - "taxYear" -> getTaxYear, - "month" -> getMonth - ) - } - ) + val isaManagerCounter = new AtomicInteger(0) + + val assignIsaManager: ChainBuilder = exec { session => + val index = isaManagerCounter.getAndUpdate(i => (i + 1) % setupData.isaManager.size) + val im = setupData.isaManager(index) + + session + .set("isaManagerReference", im.zRef) + .set("bearerToken", im.bearerToken) + .set("clientId", im.clientId) + .set("applicationId", im.applicationId) + } + + val setDatesChain: ChainBuilder = exec { session => + session + .set("taxYear", getTaxYear) + .set("month", getMonth) + } setup( "monthly-returns-journey", "Monthly returns journey" ).withActions( - isaManagerFeeder.actionBuilders: _* + assignIsaManager.actionBuilders ++ setDatesChain.actionBuilders: _* ).withRequests( openObligationStatus, submitMonthlyReport, From 7634e2d0744395180c3329414d0f05be43d98b28 Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:54:31 +0000 Subject: [PATCH 08/14] DFI-1577 updated config --- src/test/resources/journeys.conf | 2 +- .../MonthlyReturnsSubmissionSimulation.scala | 46 ++++++++++++------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/test/resources/journeys.conf b/src/test/resources/journeys.conf index 04300c4..fa46566 100644 --- a/src/test/resources/journeys.conf +++ b/src/test/resources/journeys.conf @@ -17,7 +17,7 @@ journeys { monthly-returns-journey = { description = "monthly returns journey" - load = 10 + load = 5 parts = [ monthly-returns-journey ] diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index 36f02bc..b1b39d0 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -17,6 +17,7 @@ package uk.gov.hmrc.perftests.disareturns import io.gatling.core.Predef._ +import io.gatling.core.feeder.Feeder import io.gatling.core.structure.ChainBuilder import uk.gov.hmrc.performance.simulation.PerformanceTestRunner import uk.gov.hmrc.perftests.disareturns.MonthlyReconciliationReportRequests.{getReportingResultsSummary, submitReturnSummaryCallback} @@ -55,30 +56,41 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base testDataCleanUp(setupData) } - val isaManagerCounter = new AtomicInteger(0) - - val assignIsaManager: ChainBuilder = exec { session => - val index = isaManagerCounter.getAndUpdate(i => (i + 1) % setupData.isaManager.size) - val im = setupData.isaManager(index) + val isaManagerFeeder: ChainBuilder = + feed( + Iterator + .continually(setupData.isaManager) + .flatten + .map { im => + Map( + "isaManagerReference" -> im.zRef, + "bearerToken" -> im.bearerToken, + "clientId" -> im.clientId, + "applicationId" -> im.applicationId, + "taxYear" -> getTaxYear, + "month" -> getMonth + ) + } + ) - session - .set("isaManagerReference", im.zRef) - .set("bearerToken", im.bearerToken) - .set("clientId", im.clientId) - .set("applicationId", im.applicationId) - } + val isaManagerFeeder2: ChainBuilder = + exec { session => + val im = setupData.isaManager(session.userId.toInt % setupData.isaManager.size) - val setDatesChain: ChainBuilder = exec { session => - session - .set("taxYear", getTaxYear) - .set("month", getMonth) - } + session + .set("isaManagerReference", im.zRef) + .set("bearerToken", im.bearerToken) + .set("clientId", im.clientId) + .set("applicationId", im.applicationId) + .set("taxYear", getTaxYear) + .set("month", getMonth) + } setup( "monthly-returns-journey", "Monthly returns journey" ).withActions( - assignIsaManager.actionBuilders ++ setDatesChain.actionBuilders: _* + isaManagerFeeder2.actionBuilders: _* ).withRequests( openObligationStatus, submitMonthlyReport, From 10c842ba2c6d983c16a52531daaeca2b03843842 Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:03:38 +0000 Subject: [PATCH 09/14] DFI-1577 updated test data setup --- .../MonthlyReturnsSubmissionSimulation.scala | 42 ++++++------ ...ataSetupResult.scala => IsaManagers.scala} | 10 ++- .../disareturns/testSetup/BaseRequests.scala | 67 +++++++++++-------- 3 files changed, 68 insertions(+), 51 deletions(-) rename src/test/scala/uk/gov/hmrc/perftests/disareturns/models/{TestDataSetupResult.scala => IsaManagers.scala} (78%) diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index b1b39d0..31a731f 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -17,7 +17,6 @@ package uk.gov.hmrc.perftests.disareturns import io.gatling.core.Predef._ -import io.gatling.core.feeder.Feeder import io.gatling.core.structure.ChainBuilder import uk.gov.hmrc.performance.simulation.PerformanceTestRunner import uk.gov.hmrc.perftests.disareturns.MonthlyReconciliationReportRequests.{getReportingResultsSummary, submitReturnSummaryCallback} @@ -26,19 +25,20 @@ import uk.gov.hmrc.perftests.disareturns.MonthlyReturnsSubmissionRequests.submit import uk.gov.hmrc.perftests.disareturns.TestOnlyRequests.openObligationStatus import uk.gov.hmrc.perftests.disareturns.Util.DirectMemoryLogger import uk.gov.hmrc.perftests.disareturns.Util.RandomDataGenerator.{getMonth, getTaxYear} -import uk.gov.hmrc.perftests.disareturns.models.TestDataSetupResult +import uk.gov.hmrc.perftests.disareturns.models.{Applications, IsaManagers} import uk.gov.hmrc.perftests.disareturns.testSetup.BaseRequests -import java.util.concurrent.atomic.AtomicInteger import scala.concurrent.Await import scala.concurrent.duration.DurationInt class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with BaseRequests { - var setupData: TestDataSetupResult = _ + var setupIsaManagers: IsaManagers = _ + var setupIsaApplications: Applications = _ before { - setupData = Await.result(testDataSetup(), 30.seconds) + setupIsaManagers = Await.result(setupTestData(), 30.seconds) + setupIsaApplications = Await.result(setupApplications(), 30.seconds) val scheduler = java.util.concurrent.Executors.newSingleThreadScheduledExecutor() @@ -53,44 +53,44 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base } after { - testDataCleanUp(setupData) + testDataCleanUp(setupIsaApplications) } + + val appFeeder: ChainBuilder = + feed( + Iterator + .continually(setupIsaApplications.applications) + .flatten + .map { im => + Map( + "clientId" -> im.clientId, + "applicationId" -> im.applicationId) + } + ) + val isaManagerFeeder: ChainBuilder = feed( Iterator - .continually(setupData.isaManager) + .continually(setupIsaManagers.isaManager) .flatten .map { im => Map( "isaManagerReference" -> im.zRef, "bearerToken" -> im.bearerToken, - "clientId" -> im.clientId, - "applicationId" -> im.applicationId, "taxYear" -> getTaxYear, "month" -> getMonth ) } ) - val isaManagerFeeder2: ChainBuilder = - exec { session => - val im = setupData.isaManager(session.userId.toInt % setupData.isaManager.size) - session - .set("isaManagerReference", im.zRef) - .set("bearerToken", im.bearerToken) - .set("clientId", im.clientId) - .set("applicationId", im.applicationId) - .set("taxYear", getTaxYear) - .set("month", getMonth) - } setup( "monthly-returns-journey", "Monthly returns journey" ).withActions( - isaManagerFeeder2.actionBuilders: _* + isaManagerFeeder.actionBuilders ++ appFeeder.actionBuilders: _* ).withRequests( openObligationStatus, submitMonthlyReport, diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/models/TestDataSetupResult.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/models/IsaManagers.scala similarity index 78% rename from src/test/scala/uk/gov/hmrc/perftests/disareturns/models/TestDataSetupResult.scala rename to src/test/scala/uk/gov/hmrc/perftests/disareturns/models/IsaManagers.scala index a509195..7e6a5c0 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/models/TestDataSetupResult.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/models/IsaManagers.scala @@ -16,13 +16,17 @@ package uk.gov.hmrc.perftests.disareturns.models -case class TestDataSetupResult( +case class IsaManagers( isaManager: Seq[IsaManager] ) case class IsaManager( zRef: String, bearerToken: String, - clientId: String, - applicationId: String ) + +case class Applications(applications: Seq[Application]) + +case class Application(bearer: String, + clientId: String, + applicationId: String) diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala index f3c4878..ff4b08b 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala @@ -20,9 +20,10 @@ import akka.actor.ActorSystem import akka.stream.Materializer import org.scalatest.Assertions.cancel import play.api.libs.ws.ahc.StandaloneAhcWSClient -import uk.gov.hmrc.perftests.disareturns.models.{IsaManager, TestDataSetupResult} +import uk.gov.hmrc.perftests.disareturns.models.{Application, Applications, IsaManager, IsaManagers} import scala.concurrent.{ExecutionContext, Future} +import scala.util.control.NonFatal trait BaseRequests { implicit val system: ActorSystem = ActorSystem("setup-system") @@ -33,11 +34,12 @@ trait BaseRequests { val thirdPartyApplicationRequests = new ThirdPartyApplicationRequests(wsClient) val stubTestOnlyRequests = new StubTestOnlyRequests(wsClient) val noOfThirdPartyApplications = 10 + val noOfZReferences = 100 - def testDataSetup(): Future[TestDataSetupResult] = { + def setupTestData(): Future[IsaManagers] = { val zRefs: List[String] = - (0 until noOfThirdPartyApplications) + (0 until noOfZReferences) .map(i => { require(i < 10000, "Exceeded max ZRef limit (Z9999)") f"Z$i%04d" @@ -50,40 +52,51 @@ trait BaseRequests { isaManagers <- Future.traverse(zRefs) { zRef => for { bearerToken <- authRequests.getSubmissionBearerToken(zRef) - - app <- thirdPartyApplicationRequests.createClientApplication(bearerToken) - - _ <- thirdPartyApplicationRequests.createNotificationBox(app.clientId) - } yield IsaManager( zRef = zRef, - bearerToken = bearerToken, - clientId = app.clientId, - applicationId = app.applicationId + bearerToken = bearerToken ) } + } yield IsaManagers(isaManager = isaManagers) - _ <- thirdPartyApplicationRequests.createSubscriptionFields() + setup.recover { case e => + cancel(s"Test has been aborted due to test setup failure: ${e.getMessage}") + } + } - } yield TestDataSetupResult(isaManager = isaManagers) + def setupApplications(): Future[Applications] = { + + val setupFutures: Seq[Future[Application]] = (1 to noOfThirdPartyApplications).map { _ => + for { + bearerToken <- authRequests.getSubmissionBearerToken("Z1234") + app <- thirdPartyApplicationRequests.createClientApplication(bearerToken) + _ <- thirdPartyApplicationRequests.createNotificationBox(app.clientId) + _ <- thirdPartyApplicationRequests.createSubscriptionFields() + } yield Application( + bearer = bearerToken, + clientId = app.clientId, + applicationId = app.applicationId + ) + } - setup.recover { case e => + Future.sequence(setupFutures).map(Applications.apply).recover { case NonFatal(e) => cancel(s"Test has been aborted due to test setup failure: ${e.getMessage}") } } - def testDataCleanUp(setupData: TestDataSetupResult): Future[Unit] = - Future - .traverse(setupData.isaManager) { im => - thirdPartyApplicationRequests.deleteClientApplication( - im.bearerToken, - im.applicationId - ) - } - .map(_ => ()) - .andThen { case _ => - wsClient.close() - system.terminate() - } + def testDataCleanUp(applications: Applications): Future[Unit] = { + val cleanupFutures: Seq[Future[Unit]] = applications.applications.map { app => + thirdPartyApplicationRequests + .deleteClientApplication(app.bearer, app.applicationId) + .recover { case NonFatal(e) => + println(s"Warning: failed to delete application ${app.applicationId}: ${e.getMessage}") + () + } + } + Future.sequence(cleanupFutures).map(_ => ()).andThen { case _ => + wsClient.close() + system.terminate() + } + } } From 99c7fe61240426eeb785ec6d0195c2dc2b80ee2c Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Mon, 23 Feb 2026 07:07:20 +0000 Subject: [PATCH 10/14] DFI-1577 updated test config --- src/test/resources/journeys.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/journeys.conf b/src/test/resources/journeys.conf index fa46566..0794170 100644 --- a/src/test/resources/journeys.conf +++ b/src/test/resources/journeys.conf @@ -17,7 +17,7 @@ journeys { monthly-returns-journey = { description = "monthly returns journey" - load = 5 + load = 8 parts = [ monthly-returns-journey ] From 7d5361e254acd96740a123166b6c23fe78c99cfb Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Mon, 23 Feb 2026 07:34:37 +0000 Subject: [PATCH 11/14] DFI-1577 updated test config --- src/test/resources/journeys.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/journeys.conf b/src/test/resources/journeys.conf index 0794170..fa46566 100644 --- a/src/test/resources/journeys.conf +++ b/src/test/resources/journeys.conf @@ -17,7 +17,7 @@ journeys { monthly-returns-journey = { description = "monthly returns journey" - load = 8 + load = 5 parts = [ monthly-returns-journey ] From 7aad740ee0fae1e06ce86e376b718f2622bd4d4c Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:53:39 +0000 Subject: [PATCH 12/14] DFI-1577 separated journeys and reducued load --- src/test/resources/journeys.conf | 23 +++++++++++--- .../MonthlyReturnsSubmissionSimulation.scala | 31 ++++++++++++++----- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/test/resources/journeys.conf b/src/test/resources/journeys.conf index fa46566..9d09061 100644 --- a/src/test/resources/journeys.conf +++ b/src/test/resources/journeys.conf @@ -15,11 +15,25 @@ # Configure here your journeys. A journey is a sequence of requests at a certain load. journeys { - monthly-returns-journey = { - description = "monthly returns journey" + submit-monthly-returns = { + description = "Submit Monthly Returns Journey" + load = 2 + parts = [ + submit-monthly-returns + ] + }, + nps-report-summary-callback = { + description = "NPS Report Summary Callback Journey" + load = 2 + parts = [ + nps-report-summary-callback + ] + }, + nps-retrieve-monthly-summary-and-report = { + description = "NPS Retrieve Monthly Summary and Report Journey" load = 5 parts = [ - monthly-returns-journey + nps-retrieve-monthly-summary-and-report ] } } @@ -27,7 +41,8 @@ journeys { # Default behaviour is to run all journeys. If that is not what you need you can specify the list of journeys to run journeysToRun = [ - monthly-returns-journey + submit-monthly-returns, + nps-report-summary-callback ] # You can specify the same list of journeys via environment variables: diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index 31a731f..d1e0cd0 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -84,23 +84,40 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base } ) - - setup( - "monthly-returns-journey", - "Monthly returns journey" + "submit-monthly-returns", + "Submit Monthly Returns" ).withActions( isaManagerFeeder.actionBuilders ++ appFeeder.actionBuilders: _* ).withRequests( openObligationStatus, submitMonthlyReport, - submitDeclaration, + submitDeclaration + ) + + setup( + "nps-report-summary-callback", + "NPS Report Summary Callback" + ).withActions( + isaManagerFeeder.actionBuilders ++ appFeeder.actionBuilders: _* + ).withRequests( + submitReturnSummaryCallback + ) + + //TODO: Ticket raised - + //TODO: to implement request to test support API to setup reconciliation report + //TODO: and to implement request to get reconciliation report + //TODO: remove NPS callback as this is tested separately + setup( + "nps-retrieve-monthly-summary-and-report", + "NPS Retrieve Monthly Summary and Report" + ).withActions( + isaManagerFeeder.actionBuilders ++ appFeeder.actionBuilders: _* + ).withRequests( submitReturnSummaryCallback, getReportingResultsSummary ) - //TODO: Raising ticket to implement get report endpoint request - //TODO: Improvement - implement call to test support API to create report runSimulation() } From da1011c515178e38c70222c3e1df399f917f538b Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:20:17 +0000 Subject: [PATCH 13/14] DFI-1577 PR tidy up --- build.sbt | 3 +-- .../disareturns/MonthlyReturnsSubmissionSimulation.scala | 8 ++++---- .../perftests/disareturns/testSetup/BaseRequests.scala | 2 +- ...estOnlyRequests.scala => ReportingWindowRequest.scala} | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) rename src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/{StubTestOnlyRequests.scala => ReportingWindowRequest.scala} (93%) diff --git a/build.sbt b/build.sbt index 981c4c9..39324d3 100644 --- a/build.sbt +++ b/build.sbt @@ -10,6 +10,5 @@ lazy val root = (project in file(".")) scalaVersion := "2.13.16", scalacOptions ++= Seq("-feature", "-language:implicitConversions", "-language:postfixOps"), Test / testOptions := Seq.empty, - libraryDependencies ++= Dependencies.test, - javaOptions ++= Seq("-Xms1024m", "-Xmx1024m") + libraryDependencies ++= Dependencies.test ) diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala index d1e0cd0..ae93325 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/MonthlyReturnsSubmissionSimulation.scala @@ -104,10 +104,10 @@ class MonthlyReturnsSubmissionSimulation extends PerformanceTestRunner with Base submitReturnSummaryCallback ) - //TODO: Ticket raised - - //TODO: to implement request to test support API to setup reconciliation report - //TODO: and to implement request to get reconciliation report - //TODO: remove NPS callback as this is tested separately + //TODO: Ticket raised - DFI-1720 + // - to implement request to test support API to setup reconciliation report + // - to implement request to get reconciliation report + // - remove NPS callback as this is tested separately setup( "nps-retrieve-monthly-summary-and-report", "NPS Retrieve Monthly Summary and Report" diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala index ff4b08b..799d12c 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/BaseRequests.scala @@ -32,7 +32,7 @@ trait BaseRequests { val wsClient: StandaloneAhcWSClient = StandaloneAhcWSClient() val authRequests = new AuthRequests(wsClient) val thirdPartyApplicationRequests = new ThirdPartyApplicationRequests(wsClient) - val stubTestOnlyRequests = new StubTestOnlyRequests(wsClient) + val stubTestOnlyRequests = new ReportingWindowRequest(wsClient) val noOfThirdPartyApplications = 10 val noOfZReferences = 100 diff --git a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/StubTestOnlyRequests.scala b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/ReportingWindowRequest.scala similarity index 93% rename from src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/StubTestOnlyRequests.scala rename to src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/ReportingWindowRequest.scala index b453fea..7d13f1a 100644 --- a/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/StubTestOnlyRequests.scala +++ b/src/test/scala/uk/gov/hmrc/perftests/disareturns/testSetup/ReportingWindowRequest.scala @@ -27,7 +27,7 @@ import javax.inject.Singleton import scala.concurrent.{ExecutionContext, Future} @Singleton -class StubTestOnlyRequests(ws: StandaloneAhcWSClient)(implicit ec: ExecutionContext) extends SetupAssertions { +class ReportingWindowRequest(ws: StandaloneAhcWSClient)(implicit ec: ExecutionContext) extends SetupAssertions { val reportingWindowPayload: JsObject = Json.obj("reportingWindowOpen" -> true) def setReportingWindowsOpen(): Future[Unit] = From f4e0bc94a6b510bb9e81e184e6be49da8582933f Mon Sep 17 00:00:00 2001 From: "zach.hall" <29452017+zachhall1234@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:21:44 +0000 Subject: [PATCH 14/14] DFI-1577 PR tidy up --- build.sbt | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.sbt b/build.sbt index 39324d3..ce508f1 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,3 @@ -import sbt.Keys.javaOptions - import scala.collection.Seq lazy val root = (project in file("."))