Skip to content

Commit

Permalink
Merge pull request #2 from nationalarchives/TDRD-5_update_consignment…
Browse files Browse the repository at this point in the history
…_status

TDRD-5 - Update the 'DraftMetadata' consignment status
  • Loading branch information
vimleshtna authored Mar 5, 2024
2 parents 0aa9634 + dcc1cc0 commit 81abdbc
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import cats.implicits.catsSyntaxOptionId
import com.typesafe.scalalogging.Logger
import graphql.codegen.GetCustomMetadata.{customMetadata => cm}
import graphql.codegen.GetDisplayProperties.{displayProperties => dp}
import graphql.codegen.UpdateConsignmentStatus.{updateConsignmentStatus => ucs}
import graphql.codegen.types.ConsignmentStatusInput
import sttp.client3._
import uk.gov.nationalarchives.draftmetadatavalidator.ApplicationConfig.clientId
import uk.gov.nationalarchives.tdr.GraphQLClient
Expand All @@ -13,7 +15,12 @@ import uk.gov.nationalarchives.tdr.keycloak.{KeycloakUtils, TdrKeycloakDeploymen
import java.util.UUID
import scala.concurrent.{ExecutionContext, Future}

class GraphQlApi(keycloak: KeycloakUtils, customMetadataClient: GraphQLClient[cm.Data, cm.Variables], displayPropertiesClient: GraphQLClient[dp.Data, dp.Variables])(implicit
class GraphQlApi(
keycloak: KeycloakUtils,
customMetadataClient: GraphQLClient[cm.Data, cm.Variables],
updateConsignmentStatus: GraphQLClient[ucs.Data, ucs.Variables],
displayPropertiesClient: GraphQLClient[dp.Data, dp.Variables]
)(implicit
logger: Logger,
keycloakDeployment: TdrKeycloakDeployment,
backend: SttpBackend[Identity, Any]
Expand All @@ -31,17 +38,29 @@ class GraphQlApi(keycloak: KeycloakUtils, customMetadataClient: GraphQLClient[cm
data <- IO.fromOption(metadata.data)(new RuntimeException("No display properties definitions found"))
} yield data.displayProperties

def updateConsignmentStatus(consignmentId: UUID, clientSecret: String, statusType: String, statusValue: String)(implicit executionContext: ExecutionContext): IO[Option[Int]] =
for {
token <- keycloak.serviceAccountToken(clientId, clientSecret).toIO
metadata <- updateConsignmentStatus.getResult(token, ucs.document, ucs.Variables(ConsignmentStatusInput(consignmentId, statusType, statusValue.some)).some).toIO
data <- IO.fromOption(metadata.data)(new RuntimeException("Unable to update consignment status"))
} yield data.updateConsignmentStatus

implicit class FutureUtils[T](f: Future[T]) {
def toIO: IO[T] = IO.fromFuture(IO(f))
}
}

object GraphQlApi {
def apply(keycloak: KeycloakUtils, customMetadataClient: GraphQLClient[cm.Data, cm.Variables], displayPropertiesClient: GraphQLClient[dp.Data, dp.Variables])(implicit
def apply(
keycloak: KeycloakUtils,
customMetadataClient: GraphQLClient[cm.Data, cm.Variables],
updateConsignmentStatus: GraphQLClient[ucs.Data, ucs.Variables],
displayPropertiesClient: GraphQLClient[dp.Data, dp.Variables]
)(implicit
backend: SttpBackend[Identity, Any],
keycloakDeployment: TdrKeycloakDeployment
): GraphQlApi = {
val logger: Logger = Logger[GraphQlApi]
new GraphQlApi(keycloak, customMetadataClient, displayPropertiesClient)(logger, keycloakDeployment, backend)
new GraphQlApi(keycloak, customMetadataClient, updateConsignmentStatus, displayPropertiesClient)(logger, keycloakDeployment, backend)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import graphql.codegen.GetCustomMetadata.customMetadata.CustomMetadata
import graphql.codegen.GetCustomMetadata.{customMetadata => cm}
import graphql.codegen.GetDisplayProperties.displayProperties.DisplayProperties
import graphql.codegen.GetDisplayProperties.{displayProperties => dp}
import graphql.codegen.UpdateConsignmentStatus.{updateConsignmentStatus => ucs}
import io.circe.generic.auto._
import io.circe.parser.decode
import org.typelevel.log4cats.SelfAwareStructuredLogger
Expand Down Expand Up @@ -36,7 +37,8 @@ class Lambda {
val keycloakUtils = new KeycloakUtils()
val customMetadataClient = new GraphQLClient[cm.Data, cm.Variables](apiUrl)
val displayPropertiesClient = new GraphQLClient[dp.Data, dp.Variables](apiUrl)
val graphQlApi: GraphQlApi = GraphQlApi(keycloakUtils, customMetadataClient, displayPropertiesClient)
val updateConsignmentStatusClient = new GraphQLClient[ucs.Data, ucs.Variables](apiUrl)
val graphQlApi: GraphQlApi = GraphQlApi(keycloakUtils, customMetadataClient, updateConsignmentStatusClient, displayPropertiesClient)

def handleRequest(input: InputStream, output: OutputStream): Unit = {
val body: String = Source.fromInputStream(input).mkString
Expand All @@ -51,25 +53,33 @@ class Lambda {
}.unsafeRunSync()(cats.effect.unsafe.implicits.global)

private def validateMetadata(draftMetadata: DraftMetadata): IO[Boolean] = {
val clientSecret = getClientSecret(clientSecretPath, endpoint)
for {
customMetadata <- graphQlApi.getCustomMetadata(draftMetadata.consignmentId, getClientSecret(clientSecretPath, endpoint))
displayProperties <- graphQlApi.getDisplayProperties(draftMetadata.consignmentId, getClientSecret(clientSecretPath, endpoint))
customMetadata <- graphQlApi.getCustomMetadata(draftMetadata.consignmentId, clientSecret)
displayProperties <- graphQlApi.getDisplayProperties(draftMetadata.consignmentId, clientSecret)
metadataValidator = MetadataValidationUtils.createMetadataValidation(customMetadata)
} yield {
val csvHandler = new CSVHandler()
val filePath = getFilePath(draftMetadata)
val fileData = csvHandler.loadCSV(filePath, getMetadataNames(displayProperties, customMetadata))
val errors = metadataValidator.validateMetadata(fileData.fileRows)
if (errors.values.flatten.isEmpty) {
// This would be where the valid metadata would be saved to the DB
false
} else {
val updatedFileRows = fileData.fileRows.map(file => {
List(file.fileName) ++ file.metadata.map(_.value) ++ List(errors(file.fileName).map(p => s"${p.propertyName}: ${p.errorCode}").mkString(" | "))
})
csvHandler.writeCsv((fileData.header :+ "Error") :: updatedFileRows, filePath)
true
result <- {
val csvHandler = new CSVHandler()
val filePath = getFilePath(draftMetadata)
val fileData = csvHandler.loadCSV(filePath, getMetadataNames(displayProperties, customMetadata))
val errors = metadataValidator.validateMetadata(fileData.fileRows)
if (errors.values.exists(_.nonEmpty)) {
val updatedFileRows = fileData.fileRows.map(file => {
List(file.fileName) ++ file.metadata.map(_.value) ++ List(errors(file.fileName).map(p => s"${p.propertyName}: ${p.errorCode}").mkString(" | "))
})
csvHandler.writeCsv((fileData.header :+ "Error") :: updatedFileRows, filePath)
graphQlApi
.updateConsignmentStatus(draftMetadata.consignmentId, clientSecret, "DraftMetadata", "CompletedWithIssues")
.map(_ => true)
} else {
// This would be where the valid metadata would be saved to the DB
graphQlApi
.updateConsignmentStatus(draftMetadata.consignmentId, clientSecret, "DraftMetadata", "Completed")
.map(_ => false)
}
}
} yield {
result
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ class ExternalServicesSpec extends AnyFlatSpec with BeforeAndAfterEach with Befo
.withRequestBody(containing("displayProperties"))
.willReturn(okJson(fromResource(s"json/display_properties.json").mkString))
)

wiremockGraphqlServer.stubFor(
post(urlEqualTo(graphQlPath))
.withRequestBody(containing("updateConsignmentStatus"))
.willReturn(ok("""{"data": {"updateConsignmentStatus": 1}}""".stripMargin))
)
}

def authOkJson(): StubMapping = wiremockAuthServer.stubFor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.typesafe.scalalogging.Logger
import graphql.codegen.GetCustomMetadata.customMetadata.CustomMetadata
import graphql.codegen.GetCustomMetadata.{customMetadata => cm}
import graphql.codegen.GetDisplayProperties.{displayProperties => dp}
import graphql.codegen.UpdateConsignmentStatus.{updateConsignmentStatus => ucs}
import graphql.codegen.types.DataType
import graphql.codegen.types.DataType.{Boolean, Text}
import graphql.codegen.types.PropertyType.Defined
Expand Down Expand Up @@ -33,6 +34,7 @@ class GraphQlApiSpec extends AnyFlatSpec with MockitoSugar with Matchers with Ei
val consignmentId = UUID.randomUUID()
val customMetadataClient: GraphQLClient[cm.Data, cm.Variables] = mock[GraphQLClient[cm.Data, cm.Variables]]
val displayPropertiesClient: GraphQLClient[dp.Data, dp.Variables] = mock[GraphQLClient[dp.Data, dp.Variables]]
val updateConsignmentStatusClient = mock[GraphQLClient[ucs.Data, ucs.Variables]]
val keycloak = mock[KeycloakUtils]

val customMetadata: List[CustomMetadata] = List(
Expand All @@ -51,7 +53,7 @@ class GraphQlApiSpec extends AnyFlatSpec with MockitoSugar with Matchers with Ei
)

"getCustomMetadata" should "throw an exception when no custom metadata are found" in {
val api = new GraphQlApi(keycloak, customMetadataClient, displayPropertiesClient)
val api = new GraphQlApi(keycloak, customMetadataClient, updateConsignmentStatusClient, displayPropertiesClient)

doAnswer(() => Future(new BearerAccessToken("token")))
.when(keycloak)
Expand All @@ -68,7 +70,7 @@ class GraphQlApiSpec extends AnyFlatSpec with MockitoSugar with Matchers with Ei
}

"getCustomMetadata" should "return the custom metadata" in {
val api = new GraphQlApi(keycloak, customMetadataClient, displayPropertiesClient)
val api = new GraphQlApi(keycloak, customMetadataClient, updateConsignmentStatusClient, displayPropertiesClient)

doAnswer(() => Future(new BearerAccessToken("token")))
.when(keycloak)
Expand All @@ -84,7 +86,7 @@ class GraphQlApiSpec extends AnyFlatSpec with MockitoSugar with Matchers with Ei
}

"getDisplayProperties" should "throw an exception when no custom metadata are found" in {
val api = new GraphQlApi(keycloak, customMetadataClient, displayPropertiesClient)
val api = new GraphQlApi(keycloak, customMetadataClient, updateConsignmentStatusClient, displayPropertiesClient)

doAnswer(() => Future(new BearerAccessToken("token")))
.when(keycloak)
Expand All @@ -103,7 +105,7 @@ class GraphQlApiSpec extends AnyFlatSpec with MockitoSugar with Matchers with Ei
"getDisplayProperties" should "return the custom metadata" in {

val consignmentId = UUID.randomUUID()
val api = new GraphQlApi(keycloak, customMetadataClient, displayPropertiesClient)
val api = new GraphQlApi(keycloak, customMetadataClient, updateConsignmentStatusClient, displayPropertiesClient)

doAnswer(() => Future(new BearerAccessToken("token")))
.when(keycloak)
Expand All @@ -118,6 +120,41 @@ class GraphQlApiSpec extends AnyFlatSpec with MockitoSugar with Matchers with Ei
response should equal(displayProperties)
}

"updateConsignmentStatus" should "throw an exception when the api fails to update the consignment status" in {
val api = new GraphQlApi(keycloak, customMetadataClient, updateConsignmentStatusClient, displayPropertiesClient)

doAnswer(() => Future(new BearerAccessToken("token")))
.when(keycloak)
.serviceAccountToken[Identity](any[String], any[String])(any[SttpBackend[Identity, Any]], any[ClassTag[Identity[_]]], any[TdrKeycloakDeployment])

doAnswer(() => Future(GraphQlResponse[ucs.Data](None, Nil)))
.when(updateConsignmentStatusClient)
.getResult[Identity](any[BearerAccessToken], any[Document], any[Option[ucs.Variables]])(any[SttpBackend[Identity, Any]], any[ClassTag[Identity[_]]])

val exception = intercept[RuntimeException] {
api.updateConsignmentStatus(consignmentId, "secret", "status", "value").unsafeRunSync()
}
exception.getMessage should equal(s"Unable to update consignment status")
}

"updateConsignmentStatus" should "update the consignment status with status type and value" in {

val consignmentId = UUID.randomUUID()
val api = new GraphQlApi(keycloak, customMetadataClient, updateConsignmentStatusClient, displayPropertiesClient)

doAnswer(() => Future(new BearerAccessToken("token")))
.when(keycloak)
.serviceAccountToken[Identity](any[String], any[String])(any[SttpBackend[Identity, Any]], any[ClassTag[Identity[_]]], any[TdrKeycloakDeployment])

doAnswer(() => Future(GraphQlResponse[ucs.Data](Option(ucs.Data(Some(1))), Nil)))
.when(updateConsignmentStatusClient)
.getResult[Identity](any[BearerAccessToken], any[Document], any[Option[ucs.Variables]])(any[SttpBackend[Identity, Any]], any[ClassTag[Identity[_]]])

val response = api.updateConsignmentStatus(consignmentId, "secret", "status", "value").unsafeRunSync()

response should equal(Some(1))
}

def createCustomMetadata(name: String, fullName: String, exportOrdinal: Int, dataType: DataType = Text, allowExport: Boolean = true): CustomMetadata = CustomMetadata(
name,
None,
Expand Down

0 comments on commit 81abdbc

Please sign in to comment.