Skip to content

Commit

Permalink
IS-1991: Dialogmøtekandidat ikke aktuell (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
geir-waagboe authored Jun 7, 2024
1 parent e4bed87 commit 8aff458
Show file tree
Hide file tree
Showing 11 changed files with 368 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/main/kotlin/no/nav/syfo/application/api/ApiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import no.nav.syfo.client.veiledertilgang.VeilederTilgangskontrollClient
import no.nav.syfo.client.wellknown.WellKnown
import no.nav.syfo.dialogmotekandidat.DialogmotekandidatService
import no.nav.syfo.dialogmotekandidat.api.registerDialogmotekandidatApi
import no.nav.syfo.ikkeaktuell.IkkeAktuellService
import no.nav.syfo.ikkeaktuell.api.registerIkkeAktuellApi
import no.nav.syfo.oppfolgingstilfelle.OppfolgingstilfelleService
import no.nav.syfo.unntak.UnntakService
import no.nav.syfo.unntak.api.registerUnntakApi
Expand Down Expand Up @@ -61,6 +63,14 @@ fun Application.apiModule(
veilederTilgangskontrollClient = veilederTilgangskontrollClient,
unntakService = unntakService,
)
registerIkkeAktuellApi(
veilederTilgangskontrollClient = veilederTilgangskontrollClient,
ikkeAktuellService = IkkeAktuellService(
database = database,
dialogmotekandidatService = dialogmotekandidatService,
oppfolgingstilfelleService = oppfolgingstilfelleService,
),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum class DialogmotekandidatEndringArsak {
DIALOGMOTE_FERDIGSTILT,
DIALOGMOTE_LUKKET,
UNNTAK,
IKKE_AKTUELL,
LUKKET,
}

Expand Down Expand Up @@ -67,6 +68,14 @@ data class DialogmotekandidatEndring private constructor(
arsak = DialogmotekandidatEndringArsak.UNNTAK
)

fun ikkeAktuell(
personIdentNumber: PersonIdentNumber,
) = create(
personIdentNumber = personIdentNumber,
kandidat = false,
arsak = DialogmotekandidatEndringArsak.IKKE_AKTUELL,
)

fun lukket(
personIdentNumber: PersonIdentNumber,
) = create(
Expand Down
50 changes: 50 additions & 0 deletions src/main/kotlin/no/nav/syfo/ikkeaktuell/IkkeAktuellService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package no.nav.syfo.ikkeaktuell

import no.nav.syfo.application.database.DatabaseInterface
import no.nav.syfo.application.exception.ConflictException
import no.nav.syfo.dialogmotekandidat.DialogmotekandidatService
import no.nav.syfo.dialogmotekandidat.database.getDialogmotekandidatEndringListForPerson
import no.nav.syfo.dialogmotekandidat.database.toDialogmotekandidatEndringList
import no.nav.syfo.dialogmotekandidat.domain.*
import no.nav.syfo.ikkeaktuell.database.createIkkeAktuell
import no.nav.syfo.ikkeaktuell.domain.IkkeAktuell
import no.nav.syfo.oppfolgingstilfelle.OppfolgingstilfelleService

class IkkeAktuellService(
private val database: DatabaseInterface,
private val dialogmotekandidatService: DialogmotekandidatService,
private val oppfolgingstilfelleService: OppfolgingstilfelleService,
) {
suspend fun createIkkeAktuell(
ikkeAktuell: IkkeAktuell,
veilederToken: String,
callId: String,
) {
database.connection.use { connection ->
val ikkeKandidat =
connection.getDialogmotekandidatEndringListForPerson(personIdent = ikkeAktuell.personIdent)
.toDialogmotekandidatEndringList()
.isLatestIkkeKandidat()
if (ikkeKandidat) {
throw ConflictException("Failed to create IkkeAktuell: Person is not kandidat")
}

connection.createIkkeAktuell(ikkeAktuell)
val latestOppfolgingstilfelleArbeidstaker = oppfolgingstilfelleService.getLatestOppfolgingstilfelle(
arbeidstakerPersonIdent = ikkeAktuell.personIdent,
veilederToken = veilederToken,
callId = callId,
)
val newDialogmotekandidatEndring = DialogmotekandidatEndring.ikkeAktuell(
personIdentNumber = ikkeAktuell.personIdent,
)
dialogmotekandidatService.createDialogmotekandidatEndring(
connection = connection,
dialogmotekandidatEndring = newDialogmotekandidatEndring,
tilfelleStart = latestOppfolgingstilfelleArbeidstaker?.tilfelleStart,
unntak = null,
)
connection.commit()
}
}
}
44 changes: 44 additions & 0 deletions src/main/kotlin/no/nav/syfo/ikkeaktuell/api/IkkeAktuellApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package no.nav.syfo.ikkeaktuell.api

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import no.nav.syfo.client.veiledertilgang.VeilederTilgangskontrollClient
import no.nav.syfo.domain.PersonIdentNumber
import no.nav.syfo.ikkeaktuell.IkkeAktuellService
import no.nav.syfo.ikkeaktuell.api.domain.CreateIkkeAktuellDTO
import no.nav.syfo.ikkeaktuell.api.domain.toIkkeAktuell
import no.nav.syfo.util.*

const val ikkeAktuellApiBasePath = "/api/internad/v1/ikkeaktuell"
const val ikkeAktuellApiPersonidentPath = "/personident"

fun Route.registerIkkeAktuellApi(
veilederTilgangskontrollClient: VeilederTilgangskontrollClient,
ikkeAktuellService: IkkeAktuellService,
) {
route(ikkeAktuellApiBasePath) {
post(ikkeAktuellApiPersonidentPath) {
val createIkkeAktuellDTO = call.receive<CreateIkkeAktuellDTO>()
val personIdent = PersonIdentNumber(createIkkeAktuellDTO.personIdent)
validateVeilederAccess(
action = "Create ikke-aktuell for person",
personIdentToAccess = personIdent,
veilederTilgangskontrollClient = veilederTilgangskontrollClient,
) {
val ikkeAktuell = createIkkeAktuellDTO.toIkkeAktuell(
createdByIdent = call.getNAVIdent()
)
ikkeAktuellService.createIkkeAktuell(
ikkeAktuell = ikkeAktuell,
veilederToken = getBearerHeader()!!,
callId = getCallId(),
)

call.respond(HttpStatusCode.Created)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package no.nav.syfo.ikkeaktuell.api.domain

import no.nav.syfo.domain.PersonIdentNumber
import no.nav.syfo.ikkeaktuell.domain.IkkeAktuell
import no.nav.syfo.ikkeaktuell.domain.IkkeAktuellArsak
import no.nav.syfo.util.nowUTC
import java.util.*

data class CreateIkkeAktuellDTO(
val personIdent: String,
val arsak: String,
val beskrivelse: String?,
)

fun CreateIkkeAktuellDTO.toIkkeAktuell(
createdByIdent: String,
) = IkkeAktuell(
uuid = UUID.randomUUID(),
createdAt = nowUTC(),
createdBy = createdByIdent,
personIdent = PersonIdentNumber(this.personIdent),
arsak = IkkeAktuellArsak.valueOf(this.arsak),
beskrivelse = this.beskrivelse,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package no.nav.syfo.ikkeaktuell.database

import no.nav.syfo.application.database.*
import no.nav.syfo.ikkeaktuell.domain.IkkeAktuell
import java.sql.Connection

const val queryCreateIkkeAktuell =
"""
INSERT INTO IKKE_AKTUELL (
id,
uuid,
created_at,
created_by,
personident,
arsak,
beskrivelse
) values (DEFAULT, ?, ?, ?, ?, ?, ?)
RETURNING id
"""

fun Connection.createIkkeAktuell(ikkeAktuell: IkkeAktuell) {
val idList = this.prepareStatement(queryCreateIkkeAktuell).use {
it.setString(1, ikkeAktuell.uuid.toString())
it.setObject(2, ikkeAktuell.createdAt)
it.setString(3, ikkeAktuell.createdBy)
it.setString(4, ikkeAktuell.personIdent.value)
it.setString(5, ikkeAktuell.arsak.name)
it.setString(6, ikkeAktuell.beskrivelse)
it.executeQuery().toList { getInt("id") }
}

if (idList.size != 1) {
throw NoElementInsertedException("Creating IKKEAKTUELL failed, no rows affected.")
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/no/nav/syfo/ikkeaktuell/domain/IkkeAktuell.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package no.nav.syfo.ikkeaktuell.domain

import no.nav.syfo.domain.PersonIdentNumber
import java.time.OffsetDateTime
import java.util.*

enum class IkkeAktuellArsak {
ARBEIDSTAKER_AAP,
ARBEIDSTAKER_DOD,
DIALOGMOTE_AVHOLDT,
}

data class IkkeAktuell(
val uuid: UUID,
val createdAt: OffsetDateTime,
val createdBy: String,
val personIdent: PersonIdentNumber,
val arsak: IkkeAktuellArsak,
val beskrivelse: String?,
)
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM cloudsqliamuser;
-- touch
GRANT SELECT ON ALL TABLES IN SCHEMA public TO cloudsqliamuser;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "isyfo-analyse";
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE IKKE_AKTUELL
(
id SERIAL PRIMARY KEY,
uuid CHAR(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL,
created_by VARCHAR(7) NOT NULL,
personident VARCHAR(11) NOT NULL,
arsak VARCHAR(40) NOT NULL,
beskrivelse TEXT
);

CREATE INDEX IX_IKKE_AKTUELL_PERSONIDENT on IKKE_AKTUELL (personident);
154 changes: 154 additions & 0 deletions src/test/kotlin/no/nav/syfo/ikkeaktuell/api/IkkeAktuellApiSpek.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package no.nav.syfo.ikkeaktuell.api

import com.fasterxml.jackson.databind.ObjectMapper
import io.ktor.http.*
import io.ktor.server.testing.*
import io.mockk.*
import no.nav.syfo.dialogmotekandidat.database.getDialogmotekandidatEndringListForPerson
import no.nav.syfo.dialogmotekandidat.domain.DialogmotekandidatEndringArsak
import no.nav.syfo.dialogmotekandidat.kafka.DialogmotekandidatEndringProducer
import no.nav.syfo.dialogmotekandidat.kafka.KafkaDialogmotekandidatEndring
import no.nav.syfo.domain.PersonIdentNumber
import no.nav.syfo.ikkeaktuell.api.domain.CreateIkkeAktuellDTO
import no.nav.syfo.ikkeaktuell.domain.IkkeAktuellArsak
import no.nav.syfo.testhelper.*
import no.nav.syfo.testhelper.generator.generateDialogmotekandidatEndringStoppunkt
import no.nav.syfo.util.bearerHeader
import no.nav.syfo.util.configuredJacksonMapper

import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldNotBeEqualTo
import org.apache.kafka.clients.producer.*
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.util.concurrent.Future

class IkkeAktuellApiSpek : Spek({
val objectMapper: ObjectMapper = configuredJacksonMapper()
val urlIkkeAktuellPersonIdent = "$ikkeAktuellApiBasePath/$ikkeAktuellApiPersonidentPath"

describe(IkkeAktuellApiSpek::class.java.simpleName) {
with(TestApplicationEngine()) {
start()
val externalMockEnvironment = ExternalMockEnvironment.instance
val database = externalMockEnvironment.database
val kafkaProducer = mockk<KafkaProducer<String, KafkaDialogmotekandidatEndring>>()
val dialogmotekandidatEndringProducer = DialogmotekandidatEndringProducer(
kafkaProducerDialogmotekandidatEndring = kafkaProducer,
)

application.testApiModule(
externalMockEnvironment = externalMockEnvironment,
dialogmotekandidatEndringProducer = dialogmotekandidatEndringProducer,
)

beforeEachTest {
database.dropData()

clearMocks(kafkaProducer)
coEvery {
kafkaProducer.send(any())
} returns mockk<Future<RecordMetadata>>(relaxed = true)
}

val validToken = generateJWT(
audience = externalMockEnvironment.environment.azure.appClientId,
issuer = externalMockEnvironment.wellKnownInternalAzureAD.issuer,
navIdent = UserConstants.VEILEDER_IDENT,
)
val newIkkeAktuellDTO = generateNewIkkeAktuellDTO(personIdent = UserConstants.ARBEIDSTAKER_PERSONIDENTNUMBER)

describe("Create ikke aktuell for person") {
describe("Happy path") {
it("creates IkkeAktuell and DialogmotekandidatEndring (not kandidat) when person is kandidat") {
val dialogmotekandidatEndring = generateDialogmotekandidatEndringStoppunkt(
personIdentNumber = UserConstants.ARBEIDSTAKER_PERSONIDENTNUMBER,
)
database.createDialogmotekandidatEndring(dialogmotekandidatEndring = dialogmotekandidatEndring)
database.isIkkeKandidat(UserConstants.ARBEIDSTAKER_PERSONIDENTNUMBER) shouldBe false
with(
handleRequest(HttpMethod.Post, urlIkkeAktuellPersonIdent) {
addHeader(HttpHeaders.Authorization, bearerHeader(validToken))
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(objectMapper.writeValueAsString(newIkkeAktuellDTO))
}
) {
response.status() shouldBeEqualTo HttpStatusCode.Created
val producerRecordSlot = slot<ProducerRecord<String, KafkaDialogmotekandidatEndring>>()
verify(exactly = 1) {
kafkaProducer.send(capture(producerRecordSlot))
}

database.isIkkeKandidat(UserConstants.ARBEIDSTAKER_PERSONIDENTNUMBER) shouldBe true

val latestDialogmotekandidatEndring =
database.connection.getDialogmotekandidatEndringListForPerson(
personIdent = UserConstants.ARBEIDSTAKER_PERSONIDENTNUMBER
).firstOrNull()
latestDialogmotekandidatEndring shouldNotBeEqualTo null
latestDialogmotekandidatEndring!!.kandidat shouldBeEqualTo false
latestDialogmotekandidatEndring.arsak shouldBeEqualTo DialogmotekandidatEndringArsak.IKKE_AKTUELL.name

val kafkaDialogmoteKandidatEndring = producerRecordSlot.captured.value()
kafkaDialogmoteKandidatEndring.personIdentNumber shouldBeEqualTo UserConstants.ARBEIDSTAKER_PERSONIDENTNUMBER.value
kafkaDialogmoteKandidatEndring.arsak shouldBeEqualTo DialogmotekandidatEndringArsak.IKKE_AKTUELL.name
kafkaDialogmoteKandidatEndring.kandidat shouldBeEqualTo false
kafkaDialogmoteKandidatEndring.unntakArsak shouldBe null
}
}
}
describe("Unhappy paths") {
it("returns status Unauthorized if no token is supplied") {
with(
handleRequest(HttpMethod.Post, urlIkkeAktuellPersonIdent) {}
) {
response.status() shouldBeEqualTo HttpStatusCode.Unauthorized
verify(exactly = 0) {
kafkaProducer.send(any())
}
}
}
it("returns status Forbidden if denied access to person") {
val newIkkeAktuellDTOWithDeniedAccess =
generateNewIkkeAktuellDTO(personIdent = UserConstants.PERSONIDENTNUMBER_VEILEDER_NO_ACCESS)
with(
handleRequest(HttpMethod.Post, urlIkkeAktuellPersonIdent) {
addHeader(HttpHeaders.Authorization, bearerHeader(validToken))
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(objectMapper.writeValueAsString(newIkkeAktuellDTOWithDeniedAccess))
}
) {
response.status() shouldBeEqualTo HttpStatusCode.Forbidden
verify(exactly = 0) {
kafkaProducer.send(any())
}
}
}
it("returns Conflict when person is not kandidat") {
with(
handleRequest(HttpMethod.Post, urlIkkeAktuellPersonIdent) {
addHeader(HttpHeaders.Authorization, bearerHeader(validToken))
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(objectMapper.writeValueAsString(newIkkeAktuellDTO))
}
) {
response.status() shouldBeEqualTo HttpStatusCode.Conflict
verify(exactly = 0) {
kafkaProducer.send(any())
}
}
}
}
}
}
}
})

fun generateNewIkkeAktuellDTO(
personIdent: PersonIdentNumber,
) = CreateIkkeAktuellDTO(
personIdent = personIdent.value,
arsak = IkkeAktuellArsak.DIALOGMOTE_AVHOLDT.name,
beskrivelse = "Dette er en beskrivelse",
)
Loading

0 comments on commit 8aff458

Please sign in to comment.