Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ package uk.gov.hmrc.formpproxy.cis.controllers

import play.api.Logging
import play.api.libs.json.{JsError, JsValue, Json}
import play.api.mvc.{Action, AnyContent, ControllerComponents}
import play.api.mvc.{Action, AnyContent, ControllerComponents, Result}
import uk.gov.hmrc.formpproxy.actions.AuthAction
import uk.gov.hmrc.formpproxy.cis.services.VerificationService
import uk.gov.hmrc.play.bootstrap.backend.controller.BackendController
import uk.gov.hmrc.formpproxy.cis.models.requests._

import javax.inject.Inject
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal

class VerificationController @Inject() (
authorise: AuthAction,
Expand Down Expand Up @@ -57,24 +58,6 @@ class VerificationController @Inject() (
}
}

def getSubmissionWithVerificationBatch: Action[JsValue] =
Action(parse.json).async { implicit request =>
request.body
.validate[GetSubmissionWithVerificationBatchRequest]
.fold(
errs =>
Future.successful(BadRequest(Json.obj("message" -> "Invalid payload", "errors" -> JsError.toJson(errs)))),
req =>
service
.getSubmissionWithVerificationBatch(req)
.map(res => Ok(Json.toJson(res)))
.recover { case t =>
logger.error("[getSubmissionWithVerificationBatch] failed", t)
InternalServerError(Json.obj("message" -> "Unexpected error"))
}
)
}

def createVerificationBatchAndVerifications(): Action[JsValue] =
authorise(parse.json).async { implicit request =>
request.body
Expand Down Expand Up @@ -164,6 +147,53 @@ class VerificationController @Inject() (
}
)
}
def getSubmissionWithVerificationBatchByRefs(
instanceId: String,
verificationBatchResourceRef: Long
): Action[AnyContent] =
authorise.async { implicit request =>
handleGetSubmissionWithVerificationBatch(
GetSubmissionWithVerificationBatchRequest(
instanceId = instanceId,
verificationBatchResourceRef = verificationBatchResourceRef
)
)
}

def getSubmissionWithVerificationBatch: Action[JsValue] =
Action(parse.json).async { implicit request =>
request.body
.validate[GetSubmissionWithVerificationBatchRequest]
.fold(
errors =>
Future.successful(
BadRequest(
Json.obj(
"message" -> "Invalid payload",
"errors" -> JsError.toJson(errors)
)
)
),
handleGetSubmissionWithVerificationBatch
)
}

private def handleGetSubmissionWithVerificationBatch(
request: GetSubmissionWithVerificationBatchRequest
): Future[Result] =
service
.getSubmissionWithVerificationBatch(request)
.map(response => Ok(Json.toJson(response)))
.recover { case NonFatal(exception) =>
logger.error(
s"[VerificationController][getSubmissionWithVerificationBatch] failed for " +
s"instanceId=${request.instanceId}, " +
s"verificationBatchResourceRef=${request.verificationBatchResourceRef}",
exception
)

InternalServerError(Json.obj("message" -> "Unexpected error"))
}

def getSubmittedVerifications(): Action[JsValue] =
authorise(parse.json).async { implicit request =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2586,8 +2586,6 @@ class SdltFormpRepository @Inject() (@NamedDatabase("sdlt") db: Database)(implic

cs.execute()

val submissionId = cs.getLong(4)

CreateSubmissionReturn(success = true)
} finally cs.close()
}
Expand Down Expand Up @@ -2696,7 +2694,6 @@ class SdltFormpRepository @Inject() (@NamedDatabase("sdlt") db: Database)(implic

cs.execute()

val errorDetailId = cs.getLong(5)
CreateSubmissionErrorDetailReturn(success = true)
} finally cs.close()
}
Expand Down
1 change: 1 addition & 0 deletions conf/app.routes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Route style diverges from convention.

All other GET routes in this controller use path parameters (e.g. /cis/verification-batch/newest/:instanceId).
The new endpoint uses query parameters (?instanceId=...&verificationBatchResourceRef=...).

@abhinavgupta-hmrc abhinavgupta-hmrc Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Route style updated. Review comment addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ POST /cis/amend-monthly-return/create uk.gov.hmrc.
POST /cis/verification/submission/create uk.gov.hmrc.formpproxy.cis.controllers.VerificationController.createSubmissionAndUpdateVerifications()
POST /cis/verification/submitted-verifications uk.gov.hmrc.formpproxy.cis.controllers.VerificationController.getSubmittedVerifications()
POST /cis/verification/submission/update uk.gov.hmrc.formpproxy.cis.controllers.VerificationController.updateVerificationSubmission()
GET /cis/verification/submission-batch/:instanceId/:verificationBatchResourceRef uk.gov.hmrc.formpproxy.cis.controllers.VerificationController.getSubmissionWithVerificationBatchByRefs(instanceId: String, verificationBatchResourceRef: Long)

GET /cis/batchpoll-submissions uk.gov.hmrc.formpproxy.cis.controllers.BatchPollController.getBatchPollSubmissions()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1099,26 +1099,90 @@ class VerificationControllerSpec extends SpecBase {
verifyNoMoreInteractions(mockService)
}

"returns 400 BadRequest with error payload when JSON is invalid" in {
val s = setup
import s.*
"GET /cis/verification/submission-batch/:instanceId/:verificationBatchResourceRef (getSubmissionWithVerificationBatchByRefs)" - {

val badJson = Json.obj("wrong" -> "value")
"returns 200 OK with JSON body when service succeeds" in {
val s = setup
import s.*

val req = FakeRequest(POST, url)
.withHeaders(CONTENT_TYPE -> JSON)
.withBody(badJson)
val requestModel =
GetSubmissionWithVerificationBatchRequest(
instanceId = "abc-123",
verificationBatchResourceRef = 77L
)

val result = controller.getSubmittedVerifications().apply(req)
val responseModel =
GetSubmissionWithVerificationBatchResponse(
scheme = None,
subcontractors = Seq.empty,
verifications = Seq.empty,
verificationBatch = None,
submission = None
)

status(result) mustBe BAD_REQUEST
contentType(result) mustBe Some(JSON)
when(mockService.getSubmissionWithVerificationBatch(eqTo(requestModel)))
.thenReturn(Future.successful(responseModel))

val body = contentAsJson(result)
(body \ "message").as[String] mustBe "Invalid payload"
(body \ "errors").isDefined mustBe true
val req =
FakeRequest(
GET,
s"/cis/verification/submission-batch/${requestModel.instanceId}/${requestModel.verificationBatchResourceRef}"
)

verifyNoInteractions(mockService)
val result =
controller
.getSubmissionWithVerificationBatchByRefs(
requestModel.instanceId,
requestModel.verificationBatchResourceRef
)
.apply(req)

status(result) mustBe OK
contentType(result) mustBe Some(JSON)
contentAsJson(result) mustBe Json.toJson(responseModel)

verify(mockService)
.getSubmissionWithVerificationBatch(eqTo(requestModel))

verifyNoMoreInteractions(mockService)
}

"returns 500 InternalServerError with error body when service fails" in {
val s = setup
import s.*

val requestModel =
GetSubmissionWithVerificationBatchRequest(
instanceId = "abc-123",
verificationBatchResourceRef = 77L
)

when(mockService.getSubmissionWithVerificationBatch(eqTo(requestModel)))
.thenReturn(Future.failed(new RuntimeException("boom")))

val req =
FakeRequest(
GET,
s"/cis/verification/submission-batch/${requestModel.instanceId}/${requestModel.verificationBatchResourceRef}"
)

val result =
controller
.getSubmissionWithVerificationBatchByRefs(
requestModel.instanceId,
requestModel.verificationBatchResourceRef
)
.apply(req)

status(result) mustBe INTERNAL_SERVER_ERROR
contentType(result) mustBe Some(JSON)
contentAsJson(result) mustBe Json.obj("message" -> "Unexpected error")

verify(mockService)
.getSubmissionWithVerificationBatch(eqTo(requestModel))

verifyNoMoreInteractions(mockService)
}
}

"returns 500 InternalServerError with error body when service fails" in {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ import play.api.db.Database
import uk.gov.hmrc.formpproxy.base.SpecBase
import uk.gov.hmrc.formpproxy.cis.models.*
import uk.gov.hmrc.formpproxy.cis.models.requests.*
import uk.gov.hmrc.formpproxy.cis.repositories.CisStoredProcedures.CallDeleteSubcontractor
import uk.gov.hmrc.formpproxy.cis.models.response.*
import uk.gov.hmrc.formpproxy.cis.repositories.CisStoredProcedures.CallDeleteSubcontractor
import uk.gov.hmrc.formpproxy.shared.utils.CallableStatementUtils.*
import uk.gov.hmrc.formpproxy.cis.models.response.GetSubcontractorResponse

import java.sql.*
import java.time.{Instant, LocalDateTime}
Expand Down Expand Up @@ -3407,7 +3406,6 @@ final class CisFormpRepositorySpec extends SpecBase {
verify(csUpdateV2).close()
}
}

"getBatchPollSubmissions" - {
"register both cursors, execute the proc, and assemble the response" in {
val db = mock[Database]
Expand Down Expand Up @@ -3751,6 +3749,67 @@ final class CisFormpRepositorySpec extends SpecBase {
verify(csUpdateSubmission).execute()
}

"getSubmissionWithVerificationBatch" - {

"call Get_Verif_Batch_Submission and return response using existing helper mapping" in {
val db = mock[Database]
val conn = mock[Connection]
val cs = mock[CallableStatement]

val submissionRs = mock[ResultSet]
val verificationBatchRs = mock[ResultSet]
val verificationsRs = mock[ResultSet]
val subcontractorsRs = mock[ResultSet]
val schemeRs = mock[ResultSet]

val request = GetSubmissionWithVerificationBatchRequest(
instanceId = "abc-123",
verificationBatchResourceRef = 77L
)

when(db.withConnection(anyArg[Connection => Any])).thenAnswer { inv =>
val f = inv.getArgument(0, classOf[Connection => Any])
f(conn)
}

when(conn.prepareCall("{ call SUBMISSION_PROCS.Get_Verif_Batch_Submission(?, ?, ?, ?, ?, ?, ?) }"))
.thenReturn(cs)

when(cs.getObject(3, classOf[ResultSet])).thenReturn(submissionRs)
when(cs.getObject(4, classOf[ResultSet])).thenReturn(verificationBatchRs)
when(cs.getObject(5, classOf[ResultSet])).thenReturn(verificationsRs)
when(cs.getObject(6, classOf[ResultSet])).thenReturn(subcontractorsRs)
when(cs.getObject(7, classOf[ResultSet])).thenReturn(schemeRs)

when(submissionRs.next()).thenReturn(false)
when(verificationBatchRs.next()).thenReturn(false)
when(verificationsRs.next()).thenReturn(false)
when(subcontractorsRs.next()).thenReturn(false)
when(schemeRs.next()).thenReturn(false)

val repo = new CisFormpRepository(db)

val result =
repo.getSubmissionWithVerificationBatch(request).futureValue

result.scheme mustBe None
result.subcontractors mustBe Seq.empty
result.verifications mustBe Seq.empty
result.verificationBatch mustBe None
result.submission mustBe None

verify(conn).prepareCall("{ call SUBMISSION_PROCS.Get_Verif_Batch_Submission(?, ?, ?, ?, ?, ?, ?) }")
verify(cs).setString(1, request.instanceId)
verify(cs).setLong(2, request.verificationBatchResourceRef)
verify(cs).registerOutParameter(3, OracleTypes.CURSOR)
verify(cs).registerOutParameter(4, OracleTypes.CURSOR)
verify(cs).registerOutParameter(5, OracleTypes.CURSOR)
verify(cs).registerOutParameter(6, OracleTypes.CURSOR)
verify(cs).registerOutParameter(7, OracleTypes.CURSOR)
verify(cs).execute()
}
}

"throws when scheme is missing" in {
val db = mock[Database]
val conn = mock[Connection]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,56 @@ class VerificationServiceSpec extends SpecBase {
}
}

"VerificationService#getSubmissionWithVerificationBatch" - {

"return successful response from repo" in {
val c = Ctx()
import c.*

val request = GetSubmissionWithVerificationBatchRequest(
instanceId = "abc-123",
verificationBatchResourceRef = 77L
)

val response = GetSubmissionWithVerificationBatchResponse(
scheme = None,
subcontractors = Seq.empty,
verifications = Seq.empty,
verificationBatch = None,
submission = None
)

when(repo.getSubmissionWithVerificationBatch(eqTo(request)))
.thenReturn(Future.successful(response))

service.getSubmissionWithVerificationBatch(request).futureValue mustBe response

verify(repo).getSubmissionWithVerificationBatch(eqTo(request))
verifyNoMoreInteractions(repo)
}

"propagates failure from repo" in {
val c = Ctx()
import c.*

val request = GetSubmissionWithVerificationBatchRequest(
instanceId = "abc-123",
verificationBatchResourceRef = 77L
)

val boom = new RuntimeException("boom")

when(repo.getSubmissionWithVerificationBatch(eqTo(request)))
.thenReturn(Future.failed(boom))

val ex = service.getSubmissionWithVerificationBatch(request).failed.futureValue

ex mustBe boom

verify(repo).getSubmissionWithVerificationBatch(eqTo(request))
verifyNoMoreInteractions(repo)
}
}
"VerificationService#getSubmittedVerifications" - {

"delegates to repository" in {
Expand Down