Skip to content

Commit

Permalink
[ESWE-1181] Employer Registrar - register creation
Browse files Browse the repository at this point in the history
* implement `registerCreation(employer)`
* check ID mapping before creation
  • Loading branch information
rickchoijd committed Jan 30, 2025
1 parent 5168863 commit b273291
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,23 @@ class RefDataMappingRepositoryShould : RepositoryTestCase() {
fun `return correct mapping, given reference data and data value`() {
val refData = "employer_status"
val dataValue = "GOLD"
val expectedExtId = 2L
val expectedExtId = 2

val mapping = refDataMappingRepository.findByDataRefDataAndDataValue(refData, dataValue)

assertThat(mapping).hasSize(1)
assertThat(mapping.first().data.externalId).isEqualTo(expectedExtId)
assertThat(mapping).isNotNull
assertThat(mapping!!.data.externalId).isEqualTo(expectedExtId)
}

@Test
fun `return correct mapping, given reference data and data external ID`() {
val refData = "employer_sector"
val dataExternalId = 6L
val dataExternalId = 6
val expectedDataValue = "CONSTRUCTION"

val mapping = refDataMappingRepository.findByDataRefDataAndDataExternalId(refData, dataExternalId)
assertThat(mapping).hasSize(1)
assertThat(mapping.first().data.value).isEqualTo(expectedDataValue)
assertThat(mapping).isNotNull
assertThat(mapping!!.data.value).isEqualTo(expectedDataValue)
}

@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ abstract class RepositoryTestCase {
private val refDataMappingsForTests: List<RefDataMapping>
get() = mapOf(
"employer_status" to mapOf(
"KEY_PARTNER" to 1L,
"KEY_PARTNER" to 1,
"GOLD" to 2,
"SILVER" to 3,
),
"employer_sector" to mapOf(
"ADMIN_SUPPORT" to 14L,
"ADMIN_SUPPORT" to 14,
"AGRICULTURE" to 1,
"ARTS_ENTERTAINMENT" to 18,
"CONSTRUCTION" to 6,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.application

import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.config.ConditionalOnIntegrationEnabled
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.domain.Employer
Expand All @@ -9,14 +10,22 @@ import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.domain.Emp
class EmployerRegistrar(
private val employerService: EmployerService,
) {
companion object {
private val log = LoggerFactory.getLogger(this::class.java)
}

fun registerCreation(employer: Employer) {
try {
val mnEmployer = employerService.run { create(convert(employer)) }
assert(mnEmployer.id != null) { "MN Employer ID is missing! employerId=${employer.id}, employerName=${employer.name}" }
employerService.createIdMapping(mnEmployer.id!!, employer.id)
} catch (ex: Exception) {
"Fail to register employer-creation; employerId=${employer.id}, employerName=${employer.name}".let { message ->
throw Exception(message, ex)
if (employerService.existsIdMappingById(employer.id)) {
log.warn("Employer with id={} already exists (with ID mapping), thus skipping", employer.id)
} else {
try {
val mnEmployer = employerService.run { create(convert(employer)) }
assert(mnEmployer.id != null) { "MN Employer ID is missing! employerId=${employer.id}, employerName=${employer.name}" }
employerService.createIdMapping(mnEmployer.id!!, employer.id)
} catch (throwable: Throwable) {
"Fail to register employer-creation; employerId=${employer.id}, employerName=${employer.name}".let { message ->
throw Exception(message, throwable)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ package uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.applicati
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.config.ConditionalOnIntegrationEnabled
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.domain.Employer
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.domain.EmployerExternalId
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.domain.EmployerExternalIdRepository
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.refdata.domain.RefData
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.refdata.domain.RefData.EmployerSector
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.refdata.domain.RefData.EmployerStatus
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.refdata.domain.RefDataMappingRepository
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.domain.JobsBoardApiClient
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.domain.MNJobBoardApiClient
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.infrastructure.CreatEmployerRequest
Expand All @@ -13,28 +19,39 @@ import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.infrastructur
class EmployerService(
private val jobsBoardApiClient: JobsBoardApiClient,
private val mnJobBoardApiClient: MNJobBoardApiClient,
private val employerExternalIdRepository: EmployerExternalIdRepository,
private val refDataMappingRepository: RefDataMappingRepository,
) {
fun retrieveById(id: String): Employer? = jobsBoardApiClient.getEmployer(id)

fun create(mnEmployer: MNEmployer): MNEmployer {
val request = CreatEmployerRequest.from(mnEmployer)
// TODO cater optional fields for employerStatus = KEY_PARTNER (sectorId==2)
return mnJobBoardApiClient.createEmployer(request)
}

fun convert(employer: Employer) = employer.run {
MNEmployer(
employerName = name,
employerBio = description,
// FIXME translate from employer.sector
sectorId = 1,
// FIXME translate from employer.status
partnerId = 1,
sectorId = translateId(EmployerSector, sector),
partnerId = translateId(EmployerStatus, status),
)
}

fun createIdMapping(mnId: Long, employerId: String) {
// TODO persist ID mapping
throw NotImplementedError("ID Mapping is not yet implemented")
fun existsIdMappingById(id: String): Boolean = retrieveExternalIdById(id) != null

fun createIdMapping(externalId: Long, id: String) {
if (!existsIdMappingById(id)) {
employerExternalIdRepository.save(EmployerExternalId(id, externalId))
} else {
throw Exception("Employer ID cannot be created! ID mapping already exists. ID pair: externalId=$externalId, id=$id")
}
}

private fun retrieveExternalIdById(id: String): Long? = employerExternalIdRepository.findByKeyId(id)?.key?.externalId

private fun translateId(refData: RefData, value: String) =
refDataMappingRepository.findByDataRefDataAndDataValue(refData.type, value)?.data?.externalId ?: run {
throw IllegalArgumentException("Reference data does not exist! refData=${refData.type}: value=$value")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package uk.gov.justice.digital.hmpps.jobsboardintegrationapi.refdata.domain

enum class RefData(val type: String) {
EmployerStatus("employer_status"),
EmployerSector("employer_sector"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.domain.ReadOn
data class RefDataMapping(
@EmbeddedId var data: RefDataMappingKey,
) {
constructor(refData: String, value: String, externalId: Long) : this(RefDataMappingKey(refData, value, externalId))
constructor(refData: String, value: String, externalId: Int) : this(RefDataMappingKey(refData, value, externalId))
constructor() : this(RefDataMappingKey())
}

@Embeddable
data class RefDataMappingKey(
@Column(name = "ref_data") val refData: String,
@Column(name = "value") val value: String,
@Column(name = "ext_id") val externalId: Long,
@Column(name = "ext_id") val externalId: Int,
) {
constructor() : this("", "", 0)
}
Expand All @@ -30,7 +30,7 @@ data class RefDataMappingKey(
interface RefDataMappingRepository : ReadOnlyRepository<RefDataMapping, RefDataMappingKey> {
fun findByDataRefData(refData: String): List<RefDataMapping>

fun findByDataRefDataAndDataValue(refData: String, dataValue: String): List<RefDataMapping>
fun findByDataRefDataAndDataValue(refData: String, dataValue: String): RefDataMapping?

fun findByDataRefDataAndDataExternalId(refData: String, dataExternalId: Long): List<RefDataMapping>
fun findByDataRefDataAndDataExternalId(refData: String, dataExternalId: Int): RefDataMapping?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.application

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.domain.Employer
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.domain.EmployerObjects.mnEmployer
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.employers.domain.EmployerObjects.sainsburys
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.application.UnitTestBase
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.infrastructure.MNEmployer
import kotlin.test.assertFailsWith

class EmployerRegistrarShould : UnitTestBase() {
@Mock
private lateinit var employerService: EmployerService

@InjectMocks
private lateinit var employerRegistrar: EmployerRegistrar

@Nested
@DisplayName("Given a valid employer to be registered")
inner class GivenAnEmployerToRegister {
private val employer = sainsburys
private val externalId = 1L
private val mnEmployer = employer.mnEmployer(externalId)
private val mnEmployerNoId = mnEmployer.copy(id = null)

@Test
fun `register a valid employer`() {
givenMNEmployerCreated(employer, mnEmployerNoId, mnEmployer)

employerRegistrar.registerCreation(employer)

verify(employerService).createIdMapping(externalId, employer.id)
}

@Test
fun `skip registering existing employer`() {
val id = employer.id
whenever(employerService.existsIdMappingById(id)).thenReturn(true)

employerRegistrar.registerCreation(employer)

verify(employerService, never()).convert(employer)
verify(employerService, never()).create(mnEmployerNoId)
verify(employerService, never()).createIdMapping(externalId, id)
}

@Test
fun `NOT finish registering employer without external ID received`() {
givenMNEmployerCreated(employer, mnEmployerNoId, mnEmployerNoId)

val exception = assertFailsWith<Exception> {
employerRegistrar.registerCreation(employer)
}

assertThat(exception.message)
.startsWith("Fail to register employer-creation").contains("employerId=${employer.id}")
with(exception.cause) {
assertThat(this).isNotNull.isInstanceOfAny(AssertionError::class.java)
assertThat(this!!.message)
.startsWith("MN Employer ID is missing!").contains("employerId=${employer.id}")
}
verify(employerService, never()).createIdMapping(externalId, employer.id)
}

@Test
fun `NOT finish registering employer, with ID mapping clash while saving it`() {
whenever(employerService.existsIdMappingById(employer.id)).thenReturn(false, true)
whenever(employerService.convert(employer)).thenReturn(mnEmployerNoId)
whenever(employerService.create(mnEmployerNoId)).thenReturn(mnEmployer)
whenever(employerService.createIdMapping(externalId, employer.id)).thenCallRealMethod()

val exception = assertFailsWith<Exception> {
employerRegistrar.registerCreation(employer)
}

assertThat(exception.message)
.startsWith("Fail to register employer-creation").contains("employerId=${employer.id}")
}
}

private fun givenMNEmployerCreated(employer: Employer, convertedEmployer: MNEmployer, mnEmployer: MNEmployer) {
whenever(employerService.existsIdMappingById(employer.id)).thenReturn(false)
whenever(employerService.convert(employer)).thenReturn(convertedEmployer)
whenever(employerService.create(convertedEmployer)).thenReturn(mnEmployer)
}
}
Loading

0 comments on commit b273291

Please sign in to comment.