diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/controllers/AdminDetailsController.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/controllers/AdminDetailsController.scala new file mode 100644 index 00000000..f84921bb --- /dev/null +++ b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/controllers/AdminDetailsController.scala @@ -0,0 +1,93 @@ +/* + * Copyright 2024 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.excisemovementcontrolsystemapi.controllers + +import play.api.libs.json.{Json, OFormat} +import play.api.mvc.{Action, AnyContent, ControllerComponents} +import uk.gov.hmrc.excisemovementcontrolsystemapi.repository.MovementRepository +import uk.gov.hmrc.excisemovementcontrolsystemapi.repository.model.Movement +import uk.gov.hmrc.internalauth.client.{BackendAuthComponents, IAAction, Predicate, Resource, ResourceLocation, ResourceType, Retrieval} +import uk.gov.hmrc.play.bootstrap.backend.controller.BackendController + +import java.time.Instant +import javax.inject.{Inject, Singleton} +import scala.concurrent.ExecutionContext + +@Singleton +class AdminDetailsController @Inject() ( + cc: ControllerComponents, + auth: BackendAuthComponents, + movementRepository: MovementRepository +)(implicit + ec: ExecutionContext +) extends BackendController(cc) { + private val permission = Predicate.Permission( + Resource(ResourceType("excise-movement-control-system-api"), ResourceLocation("admin/movement")), + IAAction("ADMIN") + ) + + private val authorised = auth.authorizedAction( + predicate = permission, + retrieval = Retrieval.EmptyRetrieval + ) + + def getMovementDetails(movementId: String): Action[AnyContent] = authorised.async { implicit request => + movementRepository.getMovementById(movementId).map { m => + m match { + case Some(movement) => Ok(Json.toJson(MovementDetails.createFromMovement(movement))) + case None => NotFound(s"No Movement Found with id: $movementId") + } + } + } +} + +case class MovementDetails( + id: String, + boxId: Option[String], + lrn: String, + consignorId: String, + consigneeId: Option[String], + arc: Option[String], + lastUpdated: Instant, + messageDetails: Seq[MessageDetails] +) + +object MovementDetails { + def createFromMovement(m: Movement): MovementDetails = + MovementDetails( + m._id, + m.boxId, + m.localReferenceNumber, + m.consignorId, + m.consigneeId, + m.administrativeReferenceCode, + m.lastUpdated, + MessageDetails.createFromMovement(m) + ) + + implicit lazy val format: OFormat[MovementDetails] = Json.format +} +case class MessageDetails(id: String, messageType: String, recipient: String, boxes: Set[String], createdOn: Instant) + +object MessageDetails { + def createFromMovement(movement: Movement): Seq[MessageDetails] = + movement.messages.map { m => + MessageDetails(m.messageId, m.messageType, m.recipient, m.boxesToNotify, m.createdOn) + } + implicit lazy val format: OFormat[MessageDetails] = Json.format + +} diff --git a/conf/app.routes b/conf/app.routes index 859166b6..44620c4d 100755 --- a/conf/app.routes +++ b/conf/app.routes @@ -29,4 +29,7 @@ GET /admin/miscoded-movements uk.gov.hmrc.excisemoveme GET /admin/miscoded-movements/build-work-list uk.gov.hmrc.excisemovementcontrolsystemapi.controllers.MiscodedMovementController.buildWorkList() POST /admin/miscoded-movements/resolve uk.gov.hmrc.excisemovementcontrolsystemapi.controllers.MiscodedMovementController.resolve() +GET /admin/movements/:movementId/details uk.gov.hmrc.excisemovementcontrolsystemapi.controllers.AdminDetailsController.getMovementDetails(movementId: String) + GET /admin/:ern/summary uk.gov.hmrc.excisemovementcontrolsystemapi.controllers.RemoveErnsAdminController.findAllReferencesToErn(ern: String) + diff --git a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/controllers/AdminDetailsControllerSpec.scala b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/controllers/AdminDetailsControllerSpec.scala new file mode 100644 index 00000000..218c8054 --- /dev/null +++ b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/controllers/AdminDetailsControllerSpec.scala @@ -0,0 +1,119 @@ +/* + * Copyright 2024 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.excisemovementcontrolsystemapi.controllers + +import org.mockito.ArgumentMatchers.any +import org.mockito.MockitoSugar.{reset, when} +import org.scalatest.BeforeAndAfterEach +import org.scalatestplus.mockito.MockitoSugar.mock +import org.scalatestplus.play.PlaySpec +import org.scalatestplus.play.guice.GuiceOneAppPerSuite +import play.api.Application +import play.api.inject.bind +import play.api.inject.guice.GuiceApplicationBuilder +import play.api.libs.json.Json +import play.api.mvc.Result +import play.api.test.Helpers._ +import play.api.test.{FakeRequest, Helpers} +import uk.gov.hmrc.excisemovementcontrolsystemapi.repository.model.{Message, Movement} +import uk.gov.hmrc.excisemovementcontrolsystemapi.repository.{BoxIdRepository, ErnRetrievalRepository, ErnSubmissionRepository, MovementRepository} +import uk.gov.hmrc.internalauth.client._ +import uk.gov.hmrc.internalauth.client.test.{BackendAuthComponentsStub, StubBehaviour} + +import java.time.Instant +import scala.concurrent.{ExecutionContext, Future} + +class AdminDetailsControllerSpec extends PlaySpec with GuiceOneAppPerSuite with BeforeAndAfterEach { + + private val mockStubBehaviour = mock[StubBehaviour] + private val mockMovementRepository = mock[MovementRepository] + + val backendAuthComponentsStub: BackendAuthComponents = + BackendAuthComponentsStub(mockStubBehaviour)(Helpers.stubControllerComponents(), ExecutionContext.global) + + override def fakeApplication(): Application = + new GuiceApplicationBuilder() + .overrides( + bind[BackendAuthComponents].toInstance(backendAuthComponentsStub), + bind[MovementRepository].toInstance(mockMovementRepository) + ) + .build() + + override def beforeEach(): Unit = { + super.beforeEach() + reset(mockMovementRepository, mockStubBehaviour) + } + + private val expectedPredicate = Predicate.Permission( + Resource(ResourceType("excise-movement-control-system-api"), ResourceLocation("admin/movement")), + IAAction("ADMIN") + ) + + "getMovementDetails" must { + "return OK with a json payload of movement details as expected for an existing id" in { + val testErn = "apples" + val timestamp = Instant.now() + + val movement = Movement( + "testId", + None, + "lrn", + testErn, + Some(testErn), + None, + timestamp, + Seq(Message("messageCode", "IE801", "message_id", testErn, Set.empty, timestamp)) + ) + + when(mockStubBehaviour.stubAuth(Some(expectedPredicate), Retrieval.EmptyRetrieval)) + .thenReturn(Future.successful(())) + + when(mockMovementRepository.getMovementById(any)) + .thenReturn(Future.successful(Some(movement))) + + val expectedResult = MovementDetails.createFromMovement(movement) + + val fakeRequest = + FakeRequest(routes.AdminDetailsController.getMovementDetails("testId")) + .withHeaders("Authorization" -> "Token some-token") + + val result: Future[Result] = route(app, fakeRequest).value + + contentAsJson(result) mustBe Json.toJson(expectedResult) + status(result) mustBe OK + } + + "return NotFound for the movementId" in { + + when(mockStubBehaviour.stubAuth(Some(expectedPredicate), Retrieval.EmptyRetrieval)) + .thenReturn(Future.successful(())) + + when(mockMovementRepository.getMovementById(any)) + .thenReturn(Future.successful(None)) + + val fakeRequest = + FakeRequest(routes.AdminDetailsController.getMovementDetails("testId")) + .withHeaders("Authorization" -> "Token some-token") + + val result: Future[Result] = route(app, fakeRequest).value + + contentAsString(result) mustBe s"No Movement Found with id: testId" + status(result) mustBe NOT_FOUND + } + } + +}