1
0
mirror of https://github.com/pcvolkmer/etl-processor.git synced 2025-04-20 17:56:50 +00:00

refactor: add types for patient id and pseudonym

This commit is contained in:
Paul-Christian Volkmer 2024-07-15 10:27:51 +02:00
parent c949ec07e5
commit c8f6e6efc8
25 changed files with 216 additions and 180 deletions

View File

@ -0,0 +1,30 @@
/*
* This file is part of ETL-Processor
*
* Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package dev.dnpm.etl.processor
import org.mockito.ArgumentMatchers
@Suppress("UNCHECKED_CAST")
inline fun <reified T> anyValueClass(): T {
val unboxedClass = T::class.java.declaredFields.first().type
return ArgumentMatchers.any(unboxedClass as Class<T>)
?: T::class.java.getDeclaredMethod("box-impl", unboxedClass)
.invoke(null, null) as T
}

View File

@ -21,6 +21,7 @@ package dev.dnpm.etl.processor.input
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.anyValueClass
import dev.dnpm.etl.processor.config.AppSecurityConfiguration import dev.dnpm.etl.processor.config.AppSecurityConfiguration
import dev.dnpm.etl.processor.security.TokenRepository import dev.dnpm.etl.processor.security.TokenRepository
import dev.dnpm.etl.processor.security.UserRoleRepository import dev.dnpm.etl.processor.security.UserRoleRepository
@ -29,7 +30,6 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentMatchers.anyString
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any import org.mockito.kotlin.any
import org.mockito.kotlin.never import org.mockito.kotlin.never
@ -141,7 +141,7 @@ class MtbFileRestControllerTest {
status { isAccepted() } status { isAccepted() }
} }
verify(requestProcessor, times(1)).processDeletion(anyString()) verify(requestProcessor, times(1)).processDeletion(anyValueClass())
} }
@Test @Test
@ -152,7 +152,7 @@ class MtbFileRestControllerTest {
status { isUnauthorized() } status { isUnauthorized() }
} }
verify(requestProcessor, never()).processDeletion(anyString()) verify(requestProcessor, never()).processDeletion(anyValueClass())
} }
@Nested @Nested

View File

@ -19,10 +19,8 @@
package dev.dnpm.etl.processor.monitoring package dev.dnpm.etl.processor.monitoring
import dev.dnpm.etl.processor.AbstractTestcontainerTest import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.Fingerprint
import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.output.MtbFileSender
import dev.dnpm.etl.processor.randomRequestId
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
@ -63,8 +61,8 @@ class RequestRepositoryTest : AbstractTestcontainerTest() {
fun shouldSaveRequest() { fun shouldSaveRequest() {
val request = Request( val request = Request(
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.WARNING, RequestStatus.WARNING,

View File

@ -19,14 +19,12 @@
package dev.dnpm.etl.processor.services package dev.dnpm.etl.processor.services
import dev.dnpm.etl.processor.AbstractTestcontainerTest import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.Fingerprint
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
import dev.dnpm.etl.processor.monitoring.RequestRepository import dev.dnpm.etl.processor.monitoring.RequestRepository
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.monitoring.RequestType import dev.dnpm.etl.processor.monitoring.RequestType
import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.output.MtbFileSender
import dev.dnpm.etl.processor.randomRequestId
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -67,7 +65,7 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() {
@Test @Test
fun shouldResultInEmptyRequestList() { fun shouldResultInEmptyRequestList() {
val actual = requestService.allRequestsByPatientPseudonym("TEST_12345678901") val actual = requestService.allRequestsByPatientPseudonym(TEST_PATIENT_PSEUDONYM)
assertThat(actual).isEmpty() assertThat(actual).isEmpty()
} }
@ -78,8 +76,8 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() {
listOf( listOf(
Request( Request(
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
@ -88,8 +86,8 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() {
// Should be ignored - wrong patient ID --> // Should be ignored - wrong patient ID -->
Request( Request(
randomRequestId(), randomRequestId(),
"TEST_12345678902", PatientPseudonym("TEST_12345678902"),
"P2", PatientId("P2"),
Fingerprint("0123456789abcdef2"), Fingerprint("0123456789abcdef2"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.WARNING, RequestStatus.WARNING,
@ -98,8 +96,8 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() {
// <-- // <--
Request( Request(
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P2", PatientId("P2"),
Fingerprint("0123456789abcdee1"), Fingerprint("0123456789abcdee1"),
RequestType.DELETE, RequestType.DELETE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
@ -113,7 +111,7 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() {
fun shouldResultInSortedRequestList() { fun shouldResultInSortedRequestList() {
setupTestData() setupTestData()
val actual = requestService.allRequestsByPatientPseudonym("TEST_12345678901") val actual = requestService.allRequestsByPatientPseudonym(TEST_PATIENT_PSEUDONYM)
assertThat(actual).hasSize(2) assertThat(actual).hasSize(2)
assertThat(actual[0].fingerprint).isEqualTo(Fingerprint("0123456789abcdee1")) assertThat(actual[0].fingerprint).isEqualTo(Fingerprint("0123456789abcdee1"))
@ -124,7 +122,7 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() {
fun shouldReturnDeleteRequestAsLastRequest() { fun shouldReturnDeleteRequestAsLastRequest() {
setupTestData() setupTestData()
val actual = requestService.isLastRequestWithKnownStatusDeletion("TEST_12345678901") val actual = requestService.isLastRequestWithKnownStatusDeletion(TEST_PATIENT_PSEUDONYM)
assertThat(actual).isTrue() assertThat(actual).isTrue()
} }
@ -133,10 +131,14 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() {
fun shouldReturnLastMtbFileRequest() { fun shouldReturnLastMtbFileRequest() {
setupTestData() setupTestData()
val actual = requestService.lastMtbFileRequestForPatientPseudonym("TEST_12345678901") val actual = requestService.lastMtbFileRequestForPatientPseudonym(TEST_PATIENT_PSEUDONYM)
assertThat(actual).isNotNull assertThat(actual).isNotNull
assertThat(actual?.fingerprint).isEqualTo(Fingerprint("0123456789abcdef1")) assertThat(actual?.fingerprint).isEqualTo(Fingerprint("0123456789abcdef1"))
} }
companion object {
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("TEST_12345678901")
}
} }

View File

@ -21,15 +21,13 @@ package dev.dnpm.etl.processor.web
import com.gargoylesoftware.htmlunit.WebClient import com.gargoylesoftware.htmlunit.WebClient
import com.gargoylesoftware.htmlunit.html.HtmlPage import com.gargoylesoftware.htmlunit.html.HtmlPage
import dev.dnpm.etl.processor.Fingerprint import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.NotFoundException
import dev.dnpm.etl.processor.config.AppConfiguration import dev.dnpm.etl.processor.config.AppConfiguration
import dev.dnpm.etl.processor.config.AppSecurityConfiguration import dev.dnpm.etl.processor.config.AppSecurityConfiguration
import dev.dnpm.etl.processor.monitoring.Report import dev.dnpm.etl.processor.monitoring.Report
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.monitoring.RequestType import dev.dnpm.etl.processor.monitoring.RequestType
import dev.dnpm.etl.processor.randomRequestId
import dev.dnpm.etl.processor.services.RequestService import dev.dnpm.etl.processor.services.RequestService
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
@ -37,8 +35,6 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyString
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any import org.mockito.kotlin.any
import org.mockito.kotlin.whenever import org.mockito.kotlin.whenever
@ -83,13 +79,6 @@ class HomeControllerTest {
private lateinit var mockMvc: MockMvc private lateinit var mockMvc: MockMvc
private lateinit var webClient: WebClient private lateinit var webClient: WebClient
inline fun <reified T> anyValueClass(): T {
val unboxedClass = T::class.java.declaredFields.first().type
return ArgumentMatchers.any(unboxedClass as Class<T>)
?: T::class.java.getDeclaredMethod("box-impl", unboxedClass)
.invoke(null, null) as T
}
@BeforeEach @BeforeEach
fun setup( fun setup(
@Autowired mockMvc: MockMvc, @Autowired mockMvc: MockMvc,
@ -129,8 +118,8 @@ class HomeControllerTest {
Request( Request(
2L, 2L,
randomRequestId(), randomRequestId(),
"PSEUDO1", PatientPseudonym("PSEUDO1"),
"PATIENT1", PatientId("PATIENT1"),
Fingerprint("ashdkasdh"), Fingerprint("ashdkasdh"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS RequestStatus.SUCCESS
@ -138,8 +127,8 @@ class HomeControllerTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"PSEUDO1", PatientPseudonym("PSEUDO1"),
"PATIENT1", PatientId("PATIENT1"),
Fingerprint("asdasdasd"), Fingerprint("asdasdasd"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.ERROR RequestStatus.ERROR
@ -163,8 +152,8 @@ class HomeControllerTest {
Request( Request(
2L, 2L,
requestId, requestId,
"PSEUDO1", PatientPseudonym("PSEUDO1"),
"PATIENT1", PatientId("PATIENT1"),
Fingerprint("ashdkasdh"), Fingerprint("ashdkasdh"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
@ -182,14 +171,14 @@ class HomeControllerTest {
@Test @Test
@WithMockUser(username = "admin", roles = ["ADMIN"]) @WithMockUser(username = "admin", roles = ["ADMIN"])
fun testShouldShowPatientDetails() { fun testShouldShowPatientDetails() {
whenever(requestService.findRequestByPatientId(anyString(), any<Pageable>())).thenReturn( whenever(requestService.findRequestByPatientId(anyValueClass(), any<Pageable>())).thenReturn(
PageImpl( PageImpl(
listOf( listOf(
Request( Request(
2L, 2L,
randomRequestId(), randomRequestId(),
"PSEUDO1", PatientPseudonym("PSEUDO1"),
"PATIENT1", PatientId("PATIENT1"),
Fingerprint("ashdkasdh"), Fingerprint("ashdkasdh"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS RequestStatus.SUCCESS
@ -197,8 +186,8 @@ class HomeControllerTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"PSEUDO1", PatientPseudonym("PSEUDO1"),
"PATIENT1", PatientId("PATIENT1"),
Fingerprint("asdasdasd"), Fingerprint("asdasdasd"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.ERROR RequestStatus.ERROR
@ -254,7 +243,7 @@ class HomeControllerTest {
@Test @Test
@WithMockUser(username = "admin", roles = ["ADMIN"]) @WithMockUser(username = "admin", roles = ["ADMIN"])
fun testShouldShowEmptyPatientDetails() { fun testShouldShowEmptyPatientDetails() {
whenever(requestService.findRequestByPatientId(anyString(), any<Pageable>())).thenReturn(Page.empty()) whenever(requestService.findRequestByPatientId(anyValueClass(), any<Pageable>())).thenReturn(Page.empty())
val page = webClient.getPage<HtmlPage>("http://localhost/patient/PSEUDO1") val page = webClient.getPage<HtmlPage>("http://localhost/patient/PSEUDO1")
assertThat(page.querySelectorAll("tbody tr")).isEmpty() assertThat(page.querySelectorAll("tbody tr")).isEmpty()

View File

@ -20,6 +20,8 @@
package dev.dnpm.etl.processor.web package dev.dnpm.etl.processor.web
import dev.dnpm.etl.processor.Fingerprint import dev.dnpm.etl.processor.Fingerprint
import dev.dnpm.etl.processor.PatientId
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.config.AppConfiguration import dev.dnpm.etl.processor.config.AppConfiguration
import dev.dnpm.etl.processor.config.AppSecurityConfiguration import dev.dnpm.etl.processor.config.AppSecurityConfiguration
import dev.dnpm.etl.processor.monitoring.CountedState import dev.dnpm.etl.processor.monitoring.CountedState
@ -188,8 +190,8 @@ class StatisticsRestControllerTest {
Request( Request(
1, 1,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
@ -198,8 +200,8 @@ class StatisticsRestControllerTest {
Request( Request(
2, 2,
randomRequestId(), randomRequestId(),
"TEST_12345678902", PatientPseudonym("TEST_12345678902"),
"P2", PatientId("P2"),
Fingerprint("0123456789abcdef2"), Fingerprint("0123456789abcdef2"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.WARNING, RequestStatus.WARNING,
@ -208,8 +210,8 @@ class StatisticsRestControllerTest {
Request( Request(
3, 3,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P2", PatientId("P2"),
Fingerprint("0123456789abcdee1"), Fingerprint("0123456789abcdee1"),
RequestType.DELETE, RequestType.DELETE,
RequestStatus.ERROR, RequestStatus.ERROR,
@ -218,8 +220,8 @@ class StatisticsRestControllerTest {
Request( Request(
4, 4,
randomRequestId(), randomRequestId(),
"TEST_12345678902", PatientPseudonym("TEST_12345678902"),
"P2", PatientId("P2"),
Fingerprint("0123456789abcdef2"), Fingerprint("0123456789abcdef2"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.DUPLICATION, RequestStatus.DUPLICATION,
@ -228,8 +230,8 @@ class StatisticsRestControllerTest {
Request( Request(
5, 5,
randomRequestId(), randomRequestId(),
"TEST_12345678902", PatientPseudonym("TEST_12345678902"),
"P2", PatientId("P2"),
Fingerprint("0123456789abcdef2"), Fingerprint("0123456789abcdef2"),
RequestType.DELETE, RequestType.DELETE,
RequestStatus.UNKNOWN, RequestStatus.UNKNOWN,

View File

@ -22,6 +22,7 @@ package dev.dnpm.etl.processor.input
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.Consent import de.ukw.ccc.bwhc.dto.Consent
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.PatientId
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
import org.apache.kafka.clients.consumer.ConsumerRecord import org.apache.kafka.clients.consumer.ConsumerRecord
@ -36,6 +37,7 @@ class KafkaInputListener(
override fun onMessage(data: ConsumerRecord<String, String>) { override fun onMessage(data: ConsumerRecord<String, String>) {
val mtbFile = objectMapper.readValue(data.value(), MtbFile::class.java) val mtbFile = objectMapper.readValue(data.value(), MtbFile::class.java)
val patientId = PatientId(mtbFile.patient.id)
val firstRequestIdHeader = data.headers().headers("requestId")?.firstOrNull() val firstRequestIdHeader = data.headers().headers("requestId")?.firstOrNull()
val requestId = if (null != firstRequestIdHeader) { val requestId = if (null != firstRequestIdHeader) {
RequestId(String(firstRequestIdHeader.value())) RequestId(String(firstRequestIdHeader.value()))
@ -53,9 +55,9 @@ class KafkaInputListener(
} else { } else {
logger.debug("Accepted MTB File and process deletion") logger.debug("Accepted MTB File and process deletion")
if (requestId.isBlank()) { if (requestId.isBlank()) {
requestProcessor.processDeletion(mtbFile.patient.id) requestProcessor.processDeletion(patientId)
} else { } else {
requestProcessor.processDeletion(mtbFile.patient.id, requestId) requestProcessor.processDeletion(patientId, requestId)
} }
} }
} }

View File

@ -21,6 +21,7 @@ package dev.dnpm.etl.processor.input
import de.ukw.ccc.bwhc.dto.Consent import de.ukw.ccc.bwhc.dto.Consent
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.PatientId
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
@ -46,7 +47,8 @@ class MtbFileRestController(
requestProcessor.processMtbFile(mtbFile) requestProcessor.processMtbFile(mtbFile)
} else { } else {
logger.debug("Accepted MTB File and process deletion") logger.debug("Accepted MTB File and process deletion")
requestProcessor.processDeletion(mtbFile.patient.id) val patientId = PatientId(mtbFile.patient.id)
requestProcessor.processDeletion(patientId)
} }
return ResponseEntity.accepted().build() return ResponseEntity.accepted().build()
} }
@ -54,7 +56,7 @@ class MtbFileRestController(
@DeleteMapping(path = ["{patientId}"]) @DeleteMapping(path = ["{patientId}"])
fun deleteData(@PathVariable patientId: String): ResponseEntity<Void> { fun deleteData(@PathVariable patientId: String): ResponseEntity<Void> {
logger.debug("Accepted patient ID to process deletion") logger.debug("Accepted patient ID to process deletion")
requestProcessor.processDeletion(patientId) requestProcessor.processDeletion(PatientId(patientId))
return ResponseEntity.accepted().build() return ResponseEntity.accepted().build()
} }

View File

@ -19,9 +19,7 @@
package dev.dnpm.etl.processor.monitoring package dev.dnpm.etl.processor.monitoring
import dev.dnpm.etl.processor.Fingerprint import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.randomRequestId
import dev.dnpm.etl.processor.RequestId
import org.springframework.data.annotation.Id import org.springframework.data.annotation.Id
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable import org.springframework.data.domain.Pageable
@ -38,8 +36,8 @@ import java.util.*
data class Request( data class Request(
@Id val id: Long? = null, @Id val id: Long? = null,
val uuid: RequestId = randomRequestId(), val uuid: RequestId = randomRequestId(),
val patientId: String, val patientId: PatientPseudonym,
val pid: String, val pid: PatientId,
@Column("fingerprint") @Column("fingerprint")
val fingerprint: Fingerprint, val fingerprint: Fingerprint,
val type: RequestType, val type: RequestType,
@ -49,8 +47,8 @@ data class Request(
) { ) {
constructor( constructor(
uuid: RequestId, uuid: RequestId,
patientId: String, patientId: PatientPseudonym,
pid: String, pid: PatientId,
fingerprint: Fingerprint, fingerprint: Fingerprint,
type: RequestType, type: RequestType,
status: RequestStatus status: RequestStatus
@ -59,8 +57,8 @@ data class Request(
constructor( constructor(
uuid: RequestId, uuid: RequestId,
patientId: String, patientId: PatientPseudonym,
pid: String, pid: PatientId,
fingerprint: Fingerprint, fingerprint: Fingerprint,
type: RequestType, type: RequestType,
status: RequestStatus, status: RequestStatus,
@ -83,11 +81,11 @@ data class CountedState(
interface RequestRepository : CrudRepository<Request, Long>, PagingAndSortingRepository<Request, Long> { interface RequestRepository : CrudRepository<Request, Long>, PagingAndSortingRepository<Request, Long> {
fun findAllByPatientIdOrderByProcessedAtDesc(patientId: String): List<Request> fun findAllByPatientIdOrderByProcessedAtDesc(patientId: PatientPseudonym): List<Request>
fun findByUuidEquals(uuid: RequestId): Optional<Request> fun findByUuidEquals(uuid: RequestId): Optional<Request>
fun findRequestByPatientId(patientId: String, pageable: Pageable): Page<Request> fun findRequestByPatientId(patientId: PatientPseudonym, pageable: Pageable): Page<Request>
@Query("SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;") @Query("SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;")
fun countStates(): List<CountedState> fun countStates(): List<CountedState>

View File

@ -63,7 +63,7 @@ class KafkaMtbFileSender(
val dummyMtbFile = MtbFile.builder() val dummyMtbFile = MtbFile.builder()
.withConsent( .withConsent(
Consent.builder() Consent.builder()
.withPatient(request.patientId) .withPatient(request.patientId.value)
.withStatus(Consent.Status.REJECTED) .withStatus(Consent.Status.REJECTED)
.build() .build()
) )
@ -99,7 +99,7 @@ class KafkaMtbFileSender(
} }
private fun key(request: MtbFileSender.DeleteRequest): String { private fun key(request: MtbFileSender.DeleteRequest): String {
return "{\"pid\": \"${request.patientId}\"}" return "{\"pid\": \"${request.patientId.value}\"}"
} }
data class Data(val requestId: RequestId, val content: MtbFile) data class Data(val requestId: RequestId, val content: MtbFile)

View File

@ -20,6 +20,7 @@
package dev.dnpm.etl.processor.output package dev.dnpm.etl.processor.output
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import org.springframework.http.HttpStatusCode import org.springframework.http.HttpStatusCode
@ -35,7 +36,7 @@ interface MtbFileSender {
data class MtbFileRequest(val requestId: RequestId, val mtbFile: MtbFile) data class MtbFileRequest(val requestId: RequestId, val mtbFile: MtbFile)
data class DeleteRequest(val requestId: RequestId, val patientId: String) data class DeleteRequest(val requestId: RequestId, val patientId: PatientPseudonym)
} }

View File

@ -19,6 +19,8 @@
package dev.dnpm.etl.processor.pseudonym package dev.dnpm.etl.processor.pseudonym
import dev.dnpm.etl.processor.PatientId
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.config.PseudonymizeConfigProperties import dev.dnpm.etl.processor.config.PseudonymizeConfigProperties
class PseudonymizeService( class PseudonymizeService(
@ -26,10 +28,10 @@ class PseudonymizeService(
private val configProperties: PseudonymizeConfigProperties private val configProperties: PseudonymizeConfigProperties
) { ) {
fun patientPseudonym(patientId: String): String { fun patientPseudonym(patientId: PatientId): PatientPseudonym {
return when (generator) { return when (generator) {
is GpasPseudonymGenerator -> generator.generate(patientId) is GpasPseudonymGenerator -> PatientPseudonym(generator.generate(patientId.value))
else -> "${configProperties.prefix}_${generator.generate(patientId)}" else -> PatientPseudonym("${configProperties.prefix}_${generator.generate(patientId.value)}")
} }
} }

View File

@ -20,6 +20,7 @@
package dev.dnpm.etl.processor.pseudonym package dev.dnpm.etl.processor.pseudonym
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.PatientId
import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.codec.digest.DigestUtils
/** Replaces patient ID with generated patient pseudonym /** Replaces patient ID with generated patient pseudonym
@ -29,7 +30,7 @@ import org.apache.commons.codec.digest.DigestUtils
* @return The MTB file containing patient pseudonymes * @return The MTB file containing patient pseudonymes
*/ */
infix fun MtbFile.pseudonymizeWith(pseudonymizeService: PseudonymizeService) { infix fun MtbFile.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
val patientPseudonym = pseudonymizeService.patientPseudonym(this.patient.id) val patientPseudonym = pseudonymizeService.patientPseudonym(PatientId(this.patient.id)).value
this.episode?.patient = patientPseudonym this.episode?.patient = patientPseudonym
this.carePlans?.forEach { it.patient = patientPseudonym } this.carePlans?.forEach { it.patient = patientPseudonym }

View File

@ -21,9 +21,7 @@ package dev.dnpm.etl.processor.services
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.Fingerprint import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.randomRequestId
import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.AppConfigProperties
import dev.dnpm.etl.processor.monitoring.Report import dev.dnpm.etl.processor.monitoring.Report
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
@ -56,17 +54,19 @@ class RequestProcessor(
} }
fun processMtbFile(mtbFile: MtbFile, requestId: RequestId) { fun processMtbFile(mtbFile: MtbFile, requestId: RequestId) {
val pid = mtbFile.patient.id val pid = PatientId(mtbFile.patient.id)
mtbFile pseudonymizeWith pseudonymizeService mtbFile pseudonymizeWith pseudonymizeService
mtbFile anonymizeContentWith pseudonymizeService mtbFile anonymizeContentWith pseudonymizeService
val request = MtbFileSender.MtbFileRequest(requestId, transformationService.transform(mtbFile)) val request = MtbFileSender.MtbFileRequest(requestId, transformationService.transform(mtbFile))
val patientPseudonym = PatientPseudonym(request.mtbFile.patient.id)
requestService.save( requestService.save(
Request( Request(
requestId, requestId,
request.mtbFile.patient.id, patientPseudonym,
pid, pid,
fingerprint(request.mtbFile), fingerprint(request.mtbFile),
RequestType.MTB_FILE, RequestType.MTB_FILE,
@ -101,20 +101,22 @@ class RequestProcessor(
} }
private fun isDuplication(pseudonymizedMtbFile: MtbFile): Boolean { private fun isDuplication(pseudonymizedMtbFile: MtbFile): Boolean {
val patientPseudonym = PatientPseudonym(pseudonymizedMtbFile.patient.id)
val lastMtbFileRequestForPatient = val lastMtbFileRequestForPatient =
requestService.lastMtbFileRequestForPatientPseudonym(pseudonymizedMtbFile.patient.id) requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
val isLastRequestDeletion = requestService.isLastRequestWithKnownStatusDeletion(pseudonymizedMtbFile.patient.id) val isLastRequestDeletion = requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
return null != lastMtbFileRequestForPatient return null != lastMtbFileRequestForPatient
&& !isLastRequestDeletion && !isLastRequestDeletion
&& lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFile) && lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFile)
} }
fun processDeletion(patientId: String) { fun processDeletion(patientId: PatientId) {
processDeletion(patientId, randomRequestId()) processDeletion(patientId, randomRequestId())
} }
fun processDeletion(patientId: String, requestId: RequestId) { fun processDeletion(patientId: PatientId, requestId: RequestId) {
try { try {
val patientPseudonym = pseudonymizeService.patientPseudonym(patientId) val patientPseudonym = pseudonymizeService.patientPseudonym(patientId)
@ -123,7 +125,7 @@ class RequestProcessor(
requestId, requestId,
patientPseudonym, patientPseudonym,
patientId, patientId,
fingerprint(patientPseudonym), fingerprint(patientPseudonym.value),
RequestType.DELETE, RequestType.DELETE,
RequestStatus.UNKNOWN RequestStatus.UNKNOWN
) )
@ -147,7 +149,7 @@ class RequestProcessor(
requestService.save( requestService.save(
Request( Request(
uuid = requestId, uuid = requestId,
patientId = "???", patientId = emptyPatientPseudonym(),
pid = patientId, pid = patientId,
fingerprint = Fingerprint.empty(), fingerprint = Fingerprint.empty(),
status = RequestStatus.ERROR, status = RequestStatus.ERROR,

View File

@ -19,6 +19,7 @@
package dev.dnpm.etl.processor.services package dev.dnpm.etl.processor.services
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.monitoring.* import dev.dnpm.etl.processor.monitoring.*
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
@ -40,15 +41,15 @@ class RequestService(
fun findByUuid(uuid: RequestId): Optional<Request> = fun findByUuid(uuid: RequestId): Optional<Request> =
requestRepository.findByUuidEquals(uuid) requestRepository.findByUuidEquals(uuid)
fun findRequestByPatientId(patientId: String, pageable: Pageable): Page<Request> = requestRepository.findRequestByPatientId(patientId, pageable) fun findRequestByPatientId(patientId: PatientPseudonym, pageable: Pageable): Page<Request> = requestRepository.findRequestByPatientId(patientId, pageable)
fun allRequestsByPatientPseudonym(patientPseudonym: String) = requestRepository fun allRequestsByPatientPseudonym(patientPseudonym: PatientPseudonym) = requestRepository
.findAllByPatientIdOrderByProcessedAtDesc(patientPseudonym) .findAllByPatientIdOrderByProcessedAtDesc(patientPseudonym)
fun lastMtbFileRequestForPatientPseudonym(patientPseudonym: String) = fun lastMtbFileRequestForPatientPseudonym(patientPseudonym: PatientPseudonym) =
Companion.lastMtbFileRequestForPatientPseudonym(allRequestsByPatientPseudonym(patientPseudonym)) Companion.lastMtbFileRequestForPatientPseudonym(allRequestsByPatientPseudonym(patientPseudonym))
fun isLastRequestWithKnownStatusDeletion(patientPseudonym: String) = fun isLastRequestWithKnownStatusDeletion(patientPseudonym: PatientPseudonym) =
Companion.isLastRequestWithKnownStatusDeletion(allRequestsByPatientPseudonym(patientPseudonym)) Companion.isLastRequestWithKnownStatusDeletion(allRequestsByPatientPseudonym(patientPseudonym))
fun countStates(): Iterable<CountedState> = requestRepository.countStates() fun countStates(): Iterable<CountedState> = requestRepository.countStates()

View File

@ -39,3 +39,11 @@ value class RequestId(val value: String) {
} }
fun randomRequestId() = RequestId(UUID.randomUUID().toString()) fun randomRequestId() = RequestId(UUID.randomUUID().toString())
@JvmInline
value class PatientId(val value: String)
@JvmInline
value class PatientPseudonym(val value: String)
fun emptyPatientPseudonym() = PatientPseudonym("")

View File

@ -20,6 +20,7 @@
package dev.dnpm.etl.processor.web package dev.dnpm.etl.processor.web
import dev.dnpm.etl.processor.NotFoundException import dev.dnpm.etl.processor.NotFoundException
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.monitoring.ReportService import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.services.RequestService import dev.dnpm.etl.processor.services.RequestService
@ -56,7 +57,7 @@ class HomeController(
@PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC) pageable: Pageable, @PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC) pageable: Pageable,
model: Model model: Model
): String { ): String {
val requests = requestService.findRequestByPatientId(patientId, pageable) val requests = requestService.findRequestByPatientId(PatientPseudonym(patientId), pageable)
model.addAttribute("patientId", patientId) model.addAttribute("patientId", patientId)
model.addAttribute("requests", requests) model.addAttribute("requests", requests)

View File

@ -32,7 +32,6 @@ import org.apache.kafka.common.record.TimestampType
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any import org.mockito.kotlin.any
@ -78,7 +77,7 @@ class KafkaInputListenerTest {
kafkaInputListener.onMessage(ConsumerRecord("testtopic", 0, 0, "", this.objectMapper.writeValueAsString(mtbFile))) kafkaInputListener.onMessage(ConsumerRecord("testtopic", 0, 0, "", this.objectMapper.writeValueAsString(mtbFile)))
verify(requestProcessor, times(1)).processDeletion(anyString()) verify(requestProcessor, times(1)).processDeletion(anyValueClass())
} }
@Test @Test
@ -107,7 +106,7 @@ class KafkaInputListenerTest {
kafkaInputListener.onMessage( kafkaInputListener.onMessage(
ConsumerRecord("testtopic", 0, 0, -1L, TimestampType.NO_TIMESTAMP_TYPE, -1, -1, "", this.objectMapper.writeValueAsString(mtbFile), headers, Optional.empty()) ConsumerRecord("testtopic", 0, 0, -1L, TimestampType.NO_TIMESTAMP_TYPE, -1, -1, "", this.objectMapper.writeValueAsString(mtbFile), headers, Optional.empty())
) )
verify(requestProcessor, times(1)).processDeletion(anyString(), anyValueClass()) verify(requestProcessor, times(1)).processDeletion(anyValueClass(), anyValueClass())
} }
} }

View File

@ -21,8 +21,8 @@ package dev.dnpm.etl.processor.input
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.anyValueClass
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
@ -31,7 +31,6 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.delete import org.springframework.test.web.servlet.delete
@ -129,9 +128,7 @@ class MtbFileRestControllerTest {
} }
} }
val captor = argumentCaptor<String>() verify(requestProcessor, times(1)).processDeletion(anyValueClass())
verify(requestProcessor, times(1)).processDeletion(captor.capture())
assertThat(captor.firstValue).isEqualTo("TEST_12345678")
} }
@Test @Test
@ -142,9 +139,7 @@ class MtbFileRestControllerTest {
} }
} }
val captor = argumentCaptor<String>() verify(requestProcessor, times(1)).processDeletion(anyValueClass())
verify(requestProcessor, times(1)).processDeletion(captor.capture())
assertThat(captor.firstValue).isEqualTo("TEST_12345678")
} }
} }

View File

@ -21,6 +21,7 @@ package dev.dnpm.etl.processor.output
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.config.KafkaProperties import dev.dnpm.etl.processor.config.KafkaProperties
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
@ -87,7 +88,7 @@ class KafkaMtbFileSenderTest {
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(anyString(), anyString(), anyString())
val response = kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, "PID")) val response = kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(testData.requestStatus) assertThat(response.status).isEqualTo(testData.requestStatus)
} }
@ -113,7 +114,7 @@ class KafkaMtbFileSenderTest {
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(anyString(), anyString(), anyString())
kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, "PID")) kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
val captor = argumentCaptor<String>() val captor = argumentCaptor<String>()
verify(kafkaTemplate, times(1)).send(anyString(), captor.capture(), captor.capture()) verify(kafkaTemplate, times(1)).send(anyString(), captor.capture(), captor.capture())
@ -163,7 +164,7 @@ class KafkaMtbFileSenderTest {
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(anyString(), anyString(), anyString())
kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, "PID")) kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
val expectedCount = when (testData.exception) { val expectedCount = when (testData.exception) {
// OK - No Retry // OK - No Retry
@ -177,6 +178,7 @@ class KafkaMtbFileSenderTest {
companion object { companion object {
val TEST_REQUEST_ID = RequestId("TestId") val TEST_REQUEST_ID = RequestId("TestId")
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID")
fun mtbFile(consentStatus: Consent.Status): MtbFile { fun mtbFile(consentStatus: Consent.Status): MtbFile {
return if (consentStatus == Consent.Status.ACTIVE) { return if (consentStatus == Consent.Status.ACTIVE) {

View File

@ -20,6 +20,7 @@
package dev.dnpm.etl.processor.output package dev.dnpm.etl.processor.output
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
@ -65,7 +66,7 @@ class RestMtbFileSenderTest {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, "PID")) val response = restMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
@ -138,7 +139,7 @@ class RestMtbFileSenderTest {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, "PID")) val response = restMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
@ -151,6 +152,7 @@ class RestMtbFileSenderTest {
) )
val TEST_REQUEST_ID = RequestId("TestId") val TEST_REQUEST_ID = RequestId("TestId")
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID")
private val warningBody = """ private val warningBody = """
{ {

View File

@ -21,11 +21,11 @@ package dev.dnpm.etl.processor.pseudonym
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.anyValueClass
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentMatchers
import org.mockito.Mock import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doAnswer
@ -52,7 +52,7 @@ class ExtensionsTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
"PSEUDO-ID" "PSEUDO-ID"
}.whenever(pseudonymizeService).patientPseudonym(ArgumentMatchers.anyString()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
val mtbFile = fakeMtbFile() val mtbFile = fakeMtbFile()
@ -67,7 +67,7 @@ class ExtensionsTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
"PSEUDO-ID" "PSEUDO-ID"
}.whenever(pseudonymizeService).patientPseudonym(ArgumentMatchers.anyString()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
"TESTDOMAIN" "TESTDOMAIN"
@ -95,7 +95,7 @@ class ExtensionsTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
"PSEUDO-ID" "PSEUDO-ID"
}.whenever(pseudonymizeService).patientPseudonym(ArgumentMatchers.anyString()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
"TESTDOMAIN" "TESTDOMAIN"
@ -139,7 +139,7 @@ class ExtensionsTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
"PSEUDO-ID" "PSEUDO-ID"
}.whenever(pseudonymizeService).patientPseudonym(ArgumentMatchers.anyString()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
"TESTDOMAIN" "TESTDOMAIN"

View File

@ -21,8 +21,7 @@ package dev.dnpm.etl.processor.services
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.Fingerprint import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.randomRequestId
import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.AppConfigProperties
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
@ -34,7 +33,6 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.* import org.mockito.Mockito.*
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
@ -91,22 +89,22 @@ class RequestProcessorTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("zdlzv5s5ydmd4ktw2v5piohegc4jcyrm6j66bq6tv2uxuerndmga"), Fingerprint("zdlzv5s5ydmd4ktw2v5piohegc4jcyrm6j66bq6tv2uxuerndmga"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
Instant.parse("2023-08-08T02:00:00Z") Instant.parse("2023-08-08T02:00:00Z")
) )
}.whenever(requestService).lastMtbFileRequestForPatientPseudonym(anyString()) }.whenever(requestService).lastMtbFileRequestForPatientPseudonym(anyValueClass())
doAnswer { doAnswer {
false false
}.whenever(requestService).isLastRequestWithKnownStatusDeletion(anyString()) }.whenever(requestService).isLastRequestWithKnownStatusDeletion(anyValueClass())
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
}.whenever(pseudonymizeService).patientPseudonym(any()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
@ -150,22 +148,22 @@ class RequestProcessorTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("zdlzv5s5ydmd4ktw2v5piohegc4jcyrm6j66bq6tv2uxuerndmga"), Fingerprint("zdlzv5s5ydmd4ktw2v5piohegc4jcyrm6j66bq6tv2uxuerndmga"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
Instant.parse("2023-08-08T02:00:00Z") Instant.parse("2023-08-08T02:00:00Z")
) )
}.whenever(requestService).lastMtbFileRequestForPatientPseudonym(anyString()) }.whenever(requestService).lastMtbFileRequestForPatientPseudonym(anyValueClass())
doAnswer { doAnswer {
false false
}.whenever(requestService).isLastRequestWithKnownStatusDeletion(anyString()) }.whenever(requestService).isLastRequestWithKnownStatusDeletion(anyValueClass())
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
}.whenever(pseudonymizeService).patientPseudonym(any()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
@ -209,18 +207,18 @@ class RequestProcessorTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("different"), Fingerprint("different"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
Instant.parse("2023-08-08T02:00:00Z") Instant.parse("2023-08-08T02:00:00Z")
) )
}.whenever(requestService).lastMtbFileRequestForPatientPseudonym(anyString()) }.whenever(requestService).lastMtbFileRequestForPatientPseudonym(anyValueClass())
doAnswer { doAnswer {
false false
}.whenever(requestService).isLastRequestWithKnownStatusDeletion(anyString()) }.whenever(requestService).isLastRequestWithKnownStatusDeletion(anyValueClass())
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.SUCCESS) MtbFileSender.Response(status = RequestStatus.SUCCESS)
@ -228,7 +226,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
}.whenever(pseudonymizeService).patientPseudonym(any()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
@ -272,18 +270,18 @@ class RequestProcessorTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("different"), Fingerprint("different"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
Instant.parse("2023-08-08T02:00:00Z") Instant.parse("2023-08-08T02:00:00Z")
) )
}.whenever(requestService).lastMtbFileRequestForPatientPseudonym(anyString()) }.whenever(requestService).lastMtbFileRequestForPatientPseudonym(anyValueClass())
doAnswer { doAnswer {
false false
}.whenever(requestService).isLastRequestWithKnownStatusDeletion(anyString()) }.whenever(requestService).isLastRequestWithKnownStatusDeletion(anyValueClass())
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.ERROR) MtbFileSender.Response(status = RequestStatus.ERROR)
@ -291,7 +289,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
}.whenever(pseudonymizeService).patientPseudonym(any()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
@ -333,13 +331,13 @@ class RequestProcessorTest {
fun testShouldSendDeleteRequestAndSaveUnknownRequestStatusAtFirst() { fun testShouldSendDeleteRequestAndSaveUnknownRequestStatusAtFirst() {
doAnswer { doAnswer {
"PSEUDONYM" "PSEUDONYM"
}.whenever(pseudonymizeService).patientPseudonym(anyString()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.UNKNOWN) MtbFileSender.Response(status = RequestStatus.UNKNOWN)
}.whenever(sender).send(any<MtbFileSender.DeleteRequest>()) }.whenever(sender).send(any<MtbFileSender.DeleteRequest>())
this.requestProcessor.processDeletion("TEST_12345678901") this.requestProcessor.processDeletion(TEST_PATIENT_ID)
val requestCaptor = argumentCaptor<Request>() val requestCaptor = argumentCaptor<Request>()
verify(requestService, times(1)).save(requestCaptor.capture()) verify(requestService, times(1)).save(requestCaptor.capture())
@ -351,13 +349,13 @@ class RequestProcessorTest {
fun testShouldSendDeleteRequestAndSendSuccessEvent() { fun testShouldSendDeleteRequestAndSendSuccessEvent() {
doAnswer { doAnswer {
"PSEUDONYM" "PSEUDONYM"
}.whenever(pseudonymizeService).patientPseudonym(anyString()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.SUCCESS) MtbFileSender.Response(status = RequestStatus.SUCCESS)
}.whenever(sender).send(any<MtbFileSender.DeleteRequest>()) }.whenever(sender).send(any<MtbFileSender.DeleteRequest>())
this.requestProcessor.processDeletion("TEST_12345678901") this.requestProcessor.processDeletion(TEST_PATIENT_ID)
val eventCaptor = argumentCaptor<ResponseEvent>() val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@ -369,13 +367,13 @@ class RequestProcessorTest {
fun testShouldSendDeleteRequestAndSendErrorEvent() { fun testShouldSendDeleteRequestAndSendErrorEvent() {
doAnswer { doAnswer {
"PSEUDONYM" "PSEUDONYM"
}.whenever(pseudonymizeService).patientPseudonym(anyString()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.ERROR) MtbFileSender.Response(status = RequestStatus.ERROR)
}.whenever(sender).send(any<MtbFileSender.DeleteRequest>()) }.whenever(sender).send(any<MtbFileSender.DeleteRequest>())
this.requestProcessor.processDeletion("TEST_12345678901") this.requestProcessor.processDeletion(TEST_PATIENT_ID)
val eventCaptor = argumentCaptor<ResponseEvent>() val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@ -385,9 +383,9 @@ class RequestProcessorTest {
@Test @Test
fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() { fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() {
doThrow(RuntimeException()).whenever(pseudonymizeService).patientPseudonym(anyString()) doThrow(RuntimeException()).whenever(pseudonymizeService).patientPseudonym(anyValueClass())
this.requestProcessor.processDeletion("TEST_12345678901") this.requestProcessor.processDeletion(TEST_PATIENT_ID)
val requestCaptor = argumentCaptor<Request>() val requestCaptor = argumentCaptor<Request>()
verify(requestService, times(1)).save(requestCaptor.capture()) verify(requestService, times(1)).save(requestCaptor.capture())
@ -401,7 +399,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
}.whenever(pseudonymizeService).patientPseudonym(any()) }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
@ -443,4 +441,8 @@ class RequestProcessorTest {
assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
} }
companion object {
val TEST_PATIENT_ID = PatientId("TEST_12345678901")
}
} }

View File

@ -19,8 +19,7 @@
package dev.dnpm.etl.processor.services package dev.dnpm.etl.processor.services
import dev.dnpm.etl.processor.Fingerprint import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.randomRequestId
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
import dev.dnpm.etl.processor.monitoring.RequestRepository import dev.dnpm.etl.processor.monitoring.RequestRepository
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
@ -45,8 +44,8 @@ class RequestServiceTest {
private fun anyRequest() = any(Request::class.java) ?: Request( private fun anyRequest() = any(Request::class.java) ?: Request(
0L, 0L,
randomRequestId(), randomRequestId(),
"TEST_dummy", PatientPseudonym("TEST_dummy"),
"PX", PatientId("PX"),
Fingerprint("dummy"), Fingerprint("dummy"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
@ -67,8 +66,8 @@ class RequestServiceTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.WARNING, RequestStatus.WARNING,
@ -77,8 +76,8 @@ class RequestServiceTest {
Request( Request(
2L, 2L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdefd"), Fingerprint("0123456789abcdefd"),
RequestType.DELETE, RequestType.DELETE,
RequestStatus.WARNING, RequestStatus.WARNING,
@ -87,8 +86,8 @@ class RequestServiceTest {
Request( Request(
3L, 3L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.UNKNOWN, RequestStatus.UNKNOWN,
@ -107,8 +106,8 @@ class RequestServiceTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.WARNING, RequestStatus.WARNING,
@ -117,8 +116,8 @@ class RequestServiceTest {
Request( Request(
2L, 2L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.WARNING, RequestStatus.WARNING,
@ -127,8 +126,8 @@ class RequestServiceTest {
Request( Request(
3L, 3L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.UNKNOWN, RequestStatus.UNKNOWN,
@ -147,8 +146,8 @@ class RequestServiceTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.DELETE, RequestType.DELETE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
@ -157,8 +156,8 @@ class RequestServiceTest {
Request( Request(
1L, 1L,
randomRequestId(), randomRequestId(),
"TEST_12345678902", PatientPseudonym("TEST_12345678902"),
"P2", PatientId("P2"),
Fingerprint("0123456789abcdef2"), Fingerprint("0123456789abcdef2"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.WARNING, RequestStatus.WARNING,
@ -190,8 +189,8 @@ class RequestServiceTest {
val request = Request( val request = Request(
randomRequestId(), randomRequestId(),
"TEST_12345678901", PatientPseudonym("TEST_12345678901"),
"P1", PatientId("P1"),
Fingerprint("0123456789abcdef1"), Fingerprint("0123456789abcdef1"),
RequestType.DELETE, RequestType.DELETE,
RequestStatus.SUCCESS, RequestStatus.SUCCESS,
@ -205,23 +204,23 @@ class RequestServiceTest {
@Test @Test
fun allRequestsByPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() { fun allRequestsByPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() {
requestService.allRequestsByPatientPseudonym("TEST_12345678901") requestService.allRequestsByPatientPseudonym(PatientPseudonym("TEST_12345678901"))
verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyString()) verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyValueClass())
} }
@Test @Test
fun lastMtbFileRequestForPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() { fun lastMtbFileRequestForPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() {
requestService.lastMtbFileRequestForPatientPseudonym("TEST_12345678901") requestService.lastMtbFileRequestForPatientPseudonym(PatientPseudonym("TEST_12345678901"))
verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyString()) verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyValueClass())
} }
@Test @Test
fun isLastRequestDeletionShouldRequestAllRequestsForPatientPseudonym() { fun isLastRequestDeletionShouldRequestAllRequestsForPatientPseudonym() {
requestService.isLastRequestWithKnownStatusDeletion("TEST_12345678901") requestService.isLastRequestWithKnownStatusDeletion(PatientPseudonym("TEST_12345678901"))
verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyString()) verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyValueClass())
} }
} }

View File

@ -19,9 +19,7 @@
package dev.dnpm.etl.processor.services package dev.dnpm.etl.processor.services
import dev.dnpm.etl.processor.Fingerprint import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.anyValueClass
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.monitoring.RequestType import dev.dnpm.etl.processor.monitoring.RequestType
@ -49,8 +47,8 @@ class ResponseProcessorTest {
private val testRequest = Request( private val testRequest = Request(
1L, 1L,
RequestId("TestID1234"), RequestId("TestID1234"),
"PSEUDONYM-A", PatientPseudonym("PSEUDONYM-A"),
"1", PatientId("1"),
Fingerprint("dummyfingerprint"), Fingerprint("dummyfingerprint"),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.UNKNOWN RequestStatus.UNKNOWN