diff --git a/nais/dev-gcp/nais.yaml b/nais/dev-gcp/nais.yaml index fb07e4f..47af2d8 100644 --- a/nais/dev-gcp/nais.yaml +++ b/nais/dev-gcp/nais.yaml @@ -35,6 +35,7 @@ spec: accessPolicy: inbound: rules: + - application: tms-varsler-frontend - application: "*" namespace: personbruker cluster: dev-gcp diff --git a/nais/prod-gcp/nais.yaml b/nais/prod-gcp/nais.yaml index 0ae4245..cf668b2 100644 --- a/nais/prod-gcp/nais.yaml +++ b/nais/prod-gcp/nais.yaml @@ -34,6 +34,7 @@ spec: accessPolicy: inbound: rules: + - application: tms-varsler-frontend - application: "*" namespace: personbruker cluster: prod-gcp diff --git a/src/main/kotlin/no/nav/tms/varsel/api/varsel/AktiveVarsler.kt b/src/main/kotlin/no/nav/tms/varsel/api/varsel/AktiveVarsler.kt index 9d326d1..58b23a1 100644 --- a/src/main/kotlin/no/nav/tms/varsel/api/varsel/AktiveVarsler.kt +++ b/src/main/kotlin/no/nav/tms/varsel/api/varsel/AktiveVarsler.kt @@ -16,7 +16,7 @@ data class AktivtVarsel( val eksternVarslingKanaler: List ) { companion object { - fun fromVarsel(varsel: Varsel) = AktivtVarsel( + fun fromVarsel(varsel: VarselAuthority.Varsel) = AktivtVarsel( eventId = varsel.varselId, varselId = varsel.varselId, forstBehandlet = varsel.opprettet, @@ -37,7 +37,7 @@ data class AktiveVarsler( val innbokser: List ) { companion object { - fun fromVarsler(varsler: List) = AktiveVarsler( + fun fromVarsler(varsler: List) = AktiveVarsler( beskjeder = varsler.filter { it.type == VarselType.beskjed }.map { AktivtVarsel.fromVarsel(it) }, oppgaver = varsler.filter { it.type == VarselType.oppgave }.map { AktivtVarsel.fromVarsel(it) }, innbokser = varsler.filter { it.type == VarselType.innboks }.map { AktivtVarsel.fromVarsel(it) } diff --git a/src/main/kotlin/no/nav/tms/varsel/api/varsel/InaktivtVarsel.kt b/src/main/kotlin/no/nav/tms/varsel/api/varsel/InaktivtVarsel.kt index 0e503ed..2f1ffcb 100644 --- a/src/main/kotlin/no/nav/tms/varsel/api/varsel/InaktivtVarsel.kt +++ b/src/main/kotlin/no/nav/tms/varsel/api/varsel/InaktivtVarsel.kt @@ -15,7 +15,7 @@ data class InaktivtVarsel( val eksternVarslingKanaler: List ) { companion object { - fun fromVarsel(varsel: Varsel) = InaktivtVarsel( + fun fromVarsel(varsel: VarselAuthority.Varsel) = InaktivtVarsel( type = varsel.type, eventId = varsel.varselId, varselId = varsel.varselId, diff --git a/src/main/kotlin/no/nav/tms/varsel/api/varsel/VarselConsumer.kt b/src/main/kotlin/no/nav/tms/varsel/api/varsel/VarselConsumer.kt index 2e539b9..75a918a 100644 --- a/src/main/kotlin/no/nav/tms/varsel/api/varsel/VarselConsumer.kt +++ b/src/main/kotlin/no/nav/tms/varsel/api/varsel/VarselConsumer.kt @@ -28,6 +28,10 @@ class VarselConsumer( .let(VarselbjelleVarsler::fromVarsler) } + suspend fun getAlleVarsler(userToken: String, preferertSpraak: String?): AlleVarsler { + return getVarsler(userToken, "/varsel/sammendrag/alle", preferertSpraak = preferertSpraak) + .let(AlleVarsler::fromVarsler) + } suspend fun postInaktiver(userToken: String, varselId: String) { val authorityToken = tokendingsService.exchangeToken(userToken, varselAuthorityClientId) @@ -38,7 +42,7 @@ class VarselConsumer( } } - private suspend fun getVarsler(userToken: String, path: String, preferertSpraak: String? = null): List { + private suspend fun getVarsler(userToken: String, path: String, preferertSpraak: String? = null): List { val authorityToken = tokendingsService.exchangeToken(userToken, targetApp = varselAuthorityClientId) return client.request { @@ -51,23 +55,28 @@ class VarselConsumer( } } -data class Varsel( - val type: VarselType, - val varselId: String, - val aktiv: Boolean, - val innhold: VarselInnhold?, - val eksternVarslingSendt: Boolean, - val eksternVarslingKanaler: List, - val opprettet: ZonedDateTime, - val aktivFremTil: ZonedDateTime?, - val inaktivert: ZonedDateTime? -) +object VarselAuthority { + data class Varsel( + val type: VarselType, + val varselId: String, + val aktiv: Boolean, + val innhold: Innhold?, + val eksternVarslingSendt: Boolean, + val eksternVarslingKanaler: List, + val opprettet: ZonedDateTime, + val aktivFremTil: ZonedDateTime?, + val inaktivert: ZonedDateTime? + ) + + data class Innhold( + val spraakkode: String, + val tekst: String, + val link: String? + ) +} + + -data class VarselInnhold( - val spraakkode: String, - val tekst: String, - val link: String? -) enum class VarselType { oppgave, diff --git a/src/main/kotlin/no/nav/tms/varsel/api/varsel/VarselbjelleVarsler.kt b/src/main/kotlin/no/nav/tms/varsel/api/varsel/VarselbjelleVarsler.kt index 6eab0e3..34799d2 100644 --- a/src/main/kotlin/no/nav/tms/varsel/api/varsel/VarselbjelleVarsler.kt +++ b/src/main/kotlin/no/nav/tms/varsel/api/varsel/VarselbjelleVarsler.kt @@ -9,7 +9,7 @@ data class VarselbjelleVarsler( val oppgaver: List, ) { companion object { - fun fromVarsler(varsler: List): VarselbjelleVarsler { + fun fromVarsler(varsler: List): VarselbjelleVarsler { val groupedVarsler = varsler.groupBy { it.type }.mapValues { (_, varsler) -> varsler.map(VarselbjelleVarsel::fromVarsel) } @@ -36,7 +36,7 @@ data class VarselbjelleVarsel( val eksternVarslingKanaler: List ) { companion object { - fun fromVarsel(varsel: Varsel) = with(varsel) { + fun fromVarsel(varsel: VarselAuthority.Varsel) = with(varsel) { VarselbjelleVarsel( eventId = varselId, varselId = varselId, diff --git a/src/main/kotlin/no/nav/tms/varsel/api/varsel/alleVarsler.kt b/src/main/kotlin/no/nav/tms/varsel/api/varsel/alleVarsler.kt new file mode 100644 index 0000000..facdb88 --- /dev/null +++ b/src/main/kotlin/no/nav/tms/varsel/api/varsel/alleVarsler.kt @@ -0,0 +1,77 @@ +package no.nav.tms.varsel.api.varsel + +import java.time.ZonedDateTime + +data class Varsel( + @Deprecated("Use varselId") val eventId: String, + @Deprecated("Use tidspunkt") val forstBehandlet: ZonedDateTime, + val isMasked: Boolean, + val spraakkode: String?, + val tekst: String?, + val link: String?, + val eksternVarslingSendt: Boolean, + val eksternVarslingKanaler: List, + val type: VarselType, + val isInaktiverbar: Boolean, + + ) { + companion object { + fun fromVarsel(varsel: VarselAuthority.Varsel): Varsel { + val isMasked = varsel.innhold == null + + return Varsel( + eventId = varsel.varselId, + forstBehandlet = varsel.opprettet, + isMasked = isMasked, + spraakkode = varsel.innhold?.spraakkode, + tekst = varsel.innhold?.tekst, + link = varsel.innhold?.link, + eksternVarslingSendt = varsel.eksternVarslingSendt, + eksternVarslingKanaler = varsel.eksternVarslingKanaler, + type = if (varsel.type == VarselType.oppgave) VarselType.oppgave else VarselType.beskjed, + isInaktiverbar = varsel.type == VarselType.beskjed && varsel.aktiv && !isMasked, + ) + } + } +} + +data class AlleVarsler( + val hasMaskedVarsel: Boolean, + val aktive: AktivtVarselV2, + val inaktive: List, +) { + companion object { + fun fromVarsler(varsler: List): AlleVarsler { + val aktivBeskjeder = mutableListOf() + val aktivOppgaver = mutableListOf() + val inaktivtVarseler = mutableListOf() + var hasMaskedVarsel = false + + varsler.map { + if(it.innhold == null) { + hasMaskedVarsel = true + } + + if (it.aktiv) { + if (it.type == VarselType.oppgave) { + aktivOppgaver.add(Varsel.fromVarsel(it)) + } else { + aktivBeskjeder.add(Varsel.fromVarsel(it)) + } + } else { + inaktivtVarseler.add(Varsel.fromVarsel(it)) + } + } + return AlleVarsler( + hasMaskedVarsel = hasMaskedVarsel, + aktive = AktivtVarselV2(aktivBeskjeder, aktivOppgaver), + inaktive = inaktivtVarseler + ) + } + } +} + +data class AktivtVarselV2( + val beskjeder: List, + val oppgaver: List, +) \ No newline at end of file diff --git a/src/main/kotlin/no/nav/tms/varsel/api/varsel/varselRoutes.kt b/src/main/kotlin/no/nav/tms/varsel/api/varsel/varselRoutes.kt index 958a46f..fd62b79 100644 --- a/src/main/kotlin/no/nav/tms/varsel/api/varsel/varselRoutes.kt +++ b/src/main/kotlin/no/nav/tms/varsel/api/varsel/varselRoutes.kt @@ -8,10 +8,9 @@ import io.ktor.server.response.respond import io.ktor.server.routing.Route import io.ktor.server.routing.get import io.ktor.server.routing.post -import io.ktor.util.pipeline.PipelineContext import kotlinx.serialization.Serializable import no.nav.tms.token.support.idporten.sidecar.user.IdportenUserFactory - +import no.nav.tms.token.support.tokenx.validation.user.TokenXUserFactory fun Route.varsel( varselConsumer: VarselConsumer @@ -49,13 +48,29 @@ fun Route.varsel( } } + post("beskjed/inaktiver") { varselConsumer.postInaktiver(varselId = call.varselId(), userToken = call.userToken) call.respond(HttpStatusCode.OK) } } +fun Route.alleVarsler( + varselConsumer: VarselConsumer +) { + get("/alle"){ + varselConsumer.getAlleVarsler( + userToken = call.tokenXUser.tokenString, + preferertSpraak = call.request.preferertSpraak + ).let { alleVarsler -> + call.respond(HttpStatusCode.OK, alleVarsler) + } + } +} + private val ApplicationCall.userToken get() = IdportenUserFactory.createIdportenUser(this).tokenString +private val ApplicationCall.tokenXUser get() = TokenXUserFactory.createTokenXUser(this) + @Serializable data class AntallVarsler(val beskjeder: Int, val oppgaver: Int, val innbokser: Int) diff --git a/src/main/kotlin/no/nav/tms/varsel/api/varselApi.kt b/src/main/kotlin/no/nav/tms/varsel/api/varselApi.kt index 26fa675..5e0d180 100644 --- a/src/main/kotlin/no/nav/tms/varsel/api/varselApi.kt +++ b/src/main/kotlin/no/nav/tms/varsel/api/varselApi.kt @@ -27,6 +27,7 @@ import no.nav.tms.varsel.api.varsel.VarselConsumer import no.nav.tms.varsel.api.varsel.bjellevarsler import no.nav.tms.varsel.api.varsel.varsel import no.nav.tms.varsel.api.varsel.varselbjelle +import no.nav.tms.varsel.api.varsel.alleVarsler fun Application.varselApi( corsAllowedOrigins: String, @@ -81,6 +82,7 @@ fun Application.varselApi( } authenticate(TokenXAuthenticator.name) { bjellevarsler(varselConsumer) + alleVarsler(varselConsumer) } } diff --git a/src/test/kotlin/no/nav/tms/varsel/api/VarselRoutesTest.kt b/src/test/kotlin/no/nav/tms/varsel/api/`VarselAuthority.Varsel`RoutesTest.kt similarity index 87% rename from src/test/kotlin/no/nav/tms/varsel/api/VarselRoutesTest.kt rename to src/test/kotlin/no/nav/tms/varsel/api/`VarselAuthority.Varsel`RoutesTest.kt index 2a0c3e3..da00a4f 100644 --- a/src/test/kotlin/no/nav/tms/varsel/api/VarselRoutesTest.kt +++ b/src/test/kotlin/no/nav/tms/varsel/api/`VarselAuthority.Varsel`RoutesTest.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.shouldBe import io.ktor.client.* -import io.ktor.client.call.* import io.ktor.client.request.get import io.ktor.client.request.header import io.ktor.client.request.post @@ -26,18 +25,50 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import no.nav.tms.token.support.idporten.sidecar.mock.LevelOfAssurance -import no.nav.tms.varsel.api.varsel.AktiveVarsler -import no.nav.tms.varsel.api.varsel.AntallVarsler -import no.nav.tms.varsel.api.varsel.InaktivtVarsel -import no.nav.tms.varsel.api.varsel.VarselType import org.junit.jupiter.api.Test import java.time.ZonedDateTime import com.fasterxml.jackson.module.kotlin.readValue +import no.nav.tms.varsel.api.varsel.* class VarselRoutesTest { private val objectMapper = jacksonObjectMapper().jsonConfig() + @Test + fun `Henter alle varsler, inkative og aktive`() = varselRoutesTest{ client -> + val aktiveVarsler = listOf( + VarselTestData.varsel(type = VarselType.beskjed), + VarselTestData.varsel(type = VarselType.oppgave), + VarselTestData.varsel(type = VarselType.oppgave), + VarselTestData.varsel(type = VarselType.innboks), + VarselTestData.varsel(type = VarselType.innboks), + VarselTestData.varsel(type = VarselType.innboks), + ) + val inaktivtVarsel = listOf( + VarselTestData.varsel(type = VarselType.beskjed, aktiv = false), + VarselTestData.varsel(type = VarselType.beskjed, aktiv = false), + VarselTestData.varsel(type = VarselType.beskjed, aktiv = false), + VarselTestData.varsel(type = VarselType.oppgave, aktiv = false), + VarselTestData.varsel(type = VarselType.innboks, aktiv = false), + VarselTestData.varsel(type = VarselType.innboks,aktiv = false), + ) + setupVarselAuthority(inaktiveVarslerFromEventHandler = inaktivtVarsel, aktiveVarslerFromEventHandler = aktiveVarsler) + + mockVarselApi( + varselConsumer = setupVarselConsumer(), + authMockInstaller = installAuthenticatedMock(LevelOfAssurance.LEVEL_4) + ) + + val response = client.get("/alle") + response.status shouldBe HttpStatusCode.OK + + val alleVarsler: AlleVarsler = response.bodyFromJson() + alleVarsler.aktive.beskjeder.size shouldBe 4 + alleVarsler.aktive.oppgaver.size shouldBe 2 + alleVarsler.inaktive.size shouldBe 6 + + } + @Test fun `Henter inaktiverte varsler`() = varselRoutesTest { client -> val varsler = listOf( diff --git a/src/test/kotlin/no/nav/tms/varsel/api/varsel/AlleVarslerTest.kt b/src/test/kotlin/no/nav/tms/varsel/api/varsel/AlleVarslerTest.kt new file mode 100644 index 0000000..6048cd6 --- /dev/null +++ b/src/test/kotlin/no/nav/tms/varsel/api/varsel/AlleVarslerTest.kt @@ -0,0 +1,73 @@ +package no.nav.tms.varsel.api.varsel + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class AlleVarslerTest { + @Test + fun `Skal markere maskert varsel i output`() { + val incomingVarselList = listOf(AlleVarslerTestData.incomingVarsel(innhold = null), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave, innhold = null), + AlleVarslerTestData.incomingVarsel()) + + + AlleVarsler.fromVarsler(incomingVarselList).apply { + hasMaskedVarsel shouldBe true + aktive.beskjeder[0].isMasked shouldBe true + aktive.oppgaver[0].isMasked shouldBe false + aktive.beskjeder[1].isMasked shouldBe false + } + } + + @Test + fun `Riktig antall varsler i output `() { + val incomingVarselList = listOf( + AlleVarslerTestData.incomingVarsel(type = VarselType.beskjed), + AlleVarslerTestData.incomingVarsel(type = VarselType.beskjed), + AlleVarslerTestData.incomingVarsel(type = VarselType.innboks), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave), + AlleVarslerTestData.incomingVarsel(type = VarselType.beskjed, aktiv = false), + AlleVarslerTestData.incomingVarsel(type = VarselType.beskjed, aktiv = false), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave, aktiv = false), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave, aktiv = false), + AlleVarslerTestData.incomingVarsel(type = VarselType.innboks, aktiv = false) + ) + + AlleVarsler.fromVarsler(incomingVarselList).apply { + aktive.beskjeder.size shouldBe 3 + aktive.oppgaver.size shouldBe 2 + inaktive.size shouldBe 5 + } + } + + @Test + fun `Kun aktive og ikke-maskerte beskjeder skal være inaktiverbare`() { + val incomingVarselList = listOf( + AlleVarslerTestData.incomingVarsel(type = VarselType.beskjed), + AlleVarslerTestData.incomingVarsel(type = VarselType.beskjed), + AlleVarslerTestData.incomingVarsel(type = VarselType.innboks), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave), + AlleVarslerTestData.incomingVarsel(type = VarselType.beskjed, aktiv = false), + AlleVarslerTestData.incomingVarsel(type = VarselType.beskjed, aktiv = false), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave, aktiv = false), + AlleVarslerTestData.incomingVarsel(type = VarselType.oppgave, aktiv = false), + AlleVarslerTestData.incomingVarsel(type = VarselType.innboks, aktiv = false) + ) + + AlleVarsler.fromVarsler(incomingVarselList).apply { + aktive.beskjeder[0].isInaktiverbar shouldBe true + aktive.beskjeder[1].isInaktiverbar shouldBe true + aktive.beskjeder[2].isInaktiverbar shouldBe false + aktive.oppgaver.forEach() { + it.isInaktiverbar shouldBe false + } + inaktive.forEach() { + it.isInaktiverbar shouldBe false + } + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/no/nav/tms/varsel/api/varsel/alleVarslerTestData.kt b/src/test/kotlin/no/nav/tms/varsel/api/varsel/alleVarslerTestData.kt new file mode 100644 index 0000000..b9a9be5 --- /dev/null +++ b/src/test/kotlin/no/nav/tms/varsel/api/varsel/alleVarslerTestData.kt @@ -0,0 +1,33 @@ +package no.nav.tms.varsel.api.varsel + +import java.time.ZonedDateTime + +object AlleVarslerTestData { + fun incomingVarsel( + type: VarselType = VarselType.beskjed, + varselId: String = "123143", + aktiv: Boolean = true, + innhold: VarselAuthority.Innhold? = VarselAuthority.Innhold( + link = "test . no", + spraakkode = "nb", + tekst = "Varsel test tekst" + ), + eksternVarslingSendt: Boolean = false, + eksternVarslingKanaler: List = listOf("SMS", "EPOST"), + opprettet: ZonedDateTime = ZonedDateTime.now(), + aktivFremTil: ZonedDateTime = ZonedDateTime.now().plusDays(7), + inaktivert: ZonedDateTime? = null + ): VarselAuthority.Varsel { + return VarselAuthority.Varsel( + type = type, + varselId = varselId, + aktiv = aktiv, + innhold = innhold, + eksternVarslingSendt = eksternVarslingSendt, + eksternVarslingKanaler = eksternVarslingKanaler, + opprettet = opprettet, + aktivFremTil = aktivFremTil, + inaktivert = inaktivert + ) + } +} diff --git a/src/test/kotlin/no/nav/tms/varsel/api/varselTestData.kt b/src/test/kotlin/no/nav/tms/varsel/api/varselTestData.kt index dee2c67..fcda96a 100644 --- a/src/test/kotlin/no/nav/tms/varsel/api/varselTestData.kt +++ b/src/test/kotlin/no/nav/tms/varsel/api/varselTestData.kt @@ -100,6 +100,7 @@ fun ApplicationTestBuilder.setupVarselAuthority(vararg varsler: TestVarsel) = se fun ApplicationTestBuilder.setupVarselAuthority( aktiveVarslerFromEventHandler: List = emptyList(), inaktiveVarslerFromEventHandler: List = emptyList(), + alleVarslerFromEventHandler: List = aktiveVarslerFromEventHandler + inaktiveVarslerFromEventHandler, expectedSpraakkodeParam: String? = null ) { externalServices { @@ -124,6 +125,14 @@ fun ApplicationTestBuilder.setupVarselAuthority( call.respond(HttpStatusCode.OK, inaktiveVarslerFromEventHandler) } + + get("/varsel/sammendrag/alle") { + call.request.headers["Authorization"] shouldBe "Bearer authorityToken" + + call.request.preferertSpraak shouldBe expectedSpraakkodeParam + + call.respond(HttpStatusCode.OK, alleVarslerFromEventHandler) + } } } }