Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api for varslingstatus #331

Merged
merged 5 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package no.nav.arbeidsgiver.min_side.varslingstatus

import no.nav.arbeidsgiver.min_side.controller.AuthenticatedUserHolder
import no.nav.arbeidsgiver.min_side.services.altinn.AltinnService
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime

@RestController
class VarslingStatusController(
private val authenticatedUserHolder: AuthenticatedUserHolder,
private val altinnService: AltinnService,
private val repository: VarslingStatusRepository,
) {

@PostMapping("/api/varslingStatus/v1")
fun getVarslingStatus(@RequestBody requestBody: VarslingStatusRequest): VarslingStatus {
val virksomhetsnummer = requestBody.virksomhetsnummer
val harTilgang = altinnService.hentOrganisasjoner(authenticatedUserHolder.fnr)
.any { it.organizationNumber == virksomhetsnummer }

if (!harTilgang) {
return VarslingStatus(
status = Status.OK,
varselTimestamp = LocalDateTime.now(),
eventTimestamp = LocalDateTime.now(),
)
}

return repository.varslingStatus(virksomhetsnummer = virksomhetsnummer)
}

data class VarslingStatusRequest(
val virksomhetsnummer: String,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package no.nav.arbeidsgiver.min_side.varslingstatus

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.springframework.context.annotation.Profile
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.kafka.annotation.KafkaListener
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import java.sql.PreparedStatement
import java.time.LocalDateTime

data class VarslingStatus(
val status: Status,
val varselTimestamp: LocalDateTime,
val eventTimestamp: LocalDateTime,
)

@Repository
class VarslingStatusRepository(
private val jdbcTemplate: JdbcTemplate,
private val namedParameterJdbcTemplate: NamedParameterJdbcTemplate,
) {
fun varslingStatus(virksomhetsnummer: String): VarslingStatus {
return namedParameterJdbcTemplate.queryForList(
"""
select status, varslet_tidspunkt, status_tidspunkt
from varsling_status
where virksomhetsnummer = :virksomhetsnummer
order by status_tidspunkt desc
""".trimIndent(),
mapOf("virksomhetsnummer" to virksomhetsnummer)
).firstOrNull()?.let {
VarslingStatus(
status = Status.valueOf(it["status"] as String),
varselTimestamp = LocalDateTime.parse(it["varslet_tidspunkt"] as String),
eventTimestamp = LocalDateTime.parse(it["status_tidspunkt"] as String),
)
} ?: VarslingStatus(
status = Status.OK,
varselTimestamp = LocalDateTime.now(),
eventTimestamp = LocalDateTime.now(),
)
}

fun processVarslingStatus(varslingStatus: VarslingStatusDto) {
jdbcTemplate.update(
"""
insert into varsling_status(
varsel_id, virksomhetsnummer, status, status_tidspunkt, varslet_tidspunkt
) values(?, ?, ?, ?, ?)
on conflict (varsel_id)
-- upserter bare for sikkerhetsskyld, vil antakelig ikke skje
do update set
status = EXCLUDED.status,
status_tidspunkt = EXCLUDED.status_tidspunkt,
varslet_tidspunkt = EXCLUDED.varslet_tidspunkt;
""".trimIndent()
) { ps: PreparedStatement ->
ps.setString(1, varslingStatus.varselId)
ps.setString(2, varslingStatus.virksomhetsnummer)
ps.setString(3, varslingStatus.status.toString())
ps.setString(4, varslingStatus.eventTimestamp.toString())
ps.setString(5, varslingStatus.varselTimestamp.toString())
}
}
}

@Profile("dev-gcp", "prod-gcp")
@Service
class VarslingStatusKafkaListener(
private val varslingStatusRepository: VarslingStatusRepository,
private val objectMapper: ObjectMapper,
) {
@Profile("dev-gcp", "prod-gcp")
@KafkaListener(
id = "min-side-arbeidsgiver-varsling-status-1",
topics = ["fager.ekstern-varsling-status"],
containerFactory = "errorLoggingKafkaListenerContainerFactory"
)
fun processVarslingStatus(record: ConsumerRecord<String?, String?>) =
varslingStatusRepository.processVarslingStatus(
objectMapper.readValue(record.value(), VarslingStatusDto::class.java)
)
}

@JsonIgnoreProperties(ignoreUnknown = true)
data class VarslingStatusDto @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) constructor(
@param:JsonProperty("virksomhetsnummer") val virksomhetsnummer: String,
@param:JsonProperty("varselId") val varselId: String,
@param:JsonProperty("varselTimestamp") val varselTimestamp: LocalDateTime,
@param:JsonProperty("eventTimestamp") val eventTimestamp: LocalDateTime,
@param:JsonProperty("status") val status: Status,
@param:JsonProperty("version") val version: String,
)

enum class Status {
OK,
MANGLER_KOFUVI,
ANNEN_FEIL,
}


12 changes: 12 additions & 0 deletions src/main/resources/db/migration/V10__varsling_status.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
create table varsling_status
(
varsel_id text not null primary key,
virksomhetsnummer text not null,
status text not null,
status_tidspunkt text not null,
varslet_tidspunkt text not null
);
create index varsling_status_virksomhetsnummer_idx on varsling_status (virksomhetsnummer);



Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package no.nav.arbeidsgiver.min_side.varslingstatus

import com.fasterxml.jackson.databind.ObjectMapper
import no.nav.arbeidsgiver.min_side.controller.SecurityMockMvcUtil.Companion.jwtWithPid
import no.nav.arbeidsgiver.min_side.models.Organisasjon
import no.nav.arbeidsgiver.min_side.services.altinn.AltinnService
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.flywaydb.core.Flyway
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when`
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.post

@SpringBootTest(
properties = [
"server.servlet.context-path=/",
"spring.flyway.cleanDisabled=false",
]
)
@AutoConfigureMockMvc
class VarslingStatusIntegrationTest {
@Autowired
lateinit var mockMvc: MockMvc

@Autowired
lateinit var varslingStatusRepository: VarslingStatusRepository

@Autowired
lateinit var objectMapper: ObjectMapper

lateinit var varslingStatusKafkaListener: VarslingStatusKafkaListener

@MockBean // the real jwt decoder is bypassed by SecurityMockMvcRequestPostProcessors.jwt
lateinit var jwtDecoder: JwtDecoder

@MockBean
lateinit var altinnService: AltinnService

@Autowired
lateinit var flyway: Flyway

@BeforeEach
fun setup() {
flyway.clean()
flyway.migrate()
varslingStatusKafkaListener = VarslingStatusKafkaListener(
varslingStatusRepository,
objectMapper,
)
}

@Test
fun `bruker som ikke har tilgang får status ok som default`() {
`when`(
altinnService.hentOrganisasjoner("42")
).thenReturn(emptyList())

processVarslingStatus(
"""
{
"virksomhetsnummer": "314",
"varselId": "vid1",
"varselTimestamp": "2021-01-01T00:00:00",
"eventTimestamp": "2021-01-01T00:00:00",
"status": "MANGLER_KOFUVI",
"version": "1"
}
"""
)

mockMvc.post("/api/varslingStatus/v1") {
content = """{"virksomhetsnummer": "314"}"""
contentType = APPLICATION_JSON
accept = APPLICATION_JSON
with(jwtWithPid("42"))
}.andExpect {
status { isOk() }
content { json("""{"status": "OK"}""") }
}
}

@Test
fun `bruker med tilgang men ingen status i databasen får OK som default`() {
`when`(
altinnService.hentOrganisasjoner("42")
).thenReturn(listOf(Organisasjon(organizationNumber = "314", name = "Foo & Co")))

processVarslingStatus(
"""
{
"virksomhetsnummer": "86",
"varselId": "vid1",
"varselTimestamp": "2021-01-01T00:00:00",
"eventTimestamp": "2021-01-01T00:00:00",
"status": "MANGLER_KOFUVI",
"version": "1"
}
"""
)

mockMvc.post("/api/varslingStatus/v1") {
content = """{"virksomhetsnummer": "314"}"""
contentType = APPLICATION_JSON
accept = APPLICATION_JSON
with(jwtWithPid("42"))
}.andExpect {
status { isOk() }
content { json("""{"status": "OK"}""") }
}
}

@Test
fun `returnerer siste status for virksomhet`() {
`when`(
altinnService.hentOrganisasjoner("42")
).thenReturn(listOf(Organisasjon(organizationNumber = "314", name = "Foo & Co")))

listOf(
"MANGLER_KOFUVI" to "2021-01-02T00:00:00",
"OK" to "2021-01-01T00:00:00",
"MANGLER_KOFUVI" to "2021-01-04T00:00:00",
"ANNEN_FEIL" to "2021-01-03T00:00:00",
).forEachIndexed { index, (status, timestamp) ->
processVarslingStatus(
"""
{
"virksomhetsnummer": "314",
"varselId": "vid$index",
"varselTimestamp": "2021-01-01T00:00:00",
"eventTimestamp": "$timestamp",
"status": "$status",
"version": "1"
}
"""
)
}

mockMvc.post("/api/varslingStatus/v1") {
content = """{"virksomhetsnummer": "314"}"""
contentType = APPLICATION_JSON
accept = APPLICATION_JSON
with(jwtWithPid("42"))
}.andExpect {
status { isOk() }
content {
json(
"""{
"status": "MANGLER_KOFUVI",
"varselTimestamp": "2021-01-01T00:00:00",
"eventTimestamp": "2021-01-04T00:00:00"
}""",
true
)
}
}
}

@Test
fun `returnerer siste status for virksomhet OK`() {
`when`(
altinnService.hentOrganisasjoner("42")
).thenReturn(listOf(Organisasjon(organizationNumber = "314", name = "Foo & Co")))

listOf(
"MANGLER_KOFUVI" to "2021-01-01T00:00:00",
"OK" to "2021-01-07T00:00:00",
"ANNEN_FEIL" to "2021-01-02T00:00:00",
"MANGLER_KOFUVI" to "2021-01-03T00:00:00",
).forEachIndexed { index, (status, timestamp) ->
processVarslingStatus(
"""
{
"virksomhetsnummer": "314",
"varselId": "vid$index",
"varselTimestamp": "2021-01-01T00:00:00",
"eventTimestamp": "$timestamp",
"status": "$status",
"version": "1"
}
"""
)
}

mockMvc.post("/api/varslingStatus/v1") {
content = """{"virksomhetsnummer": "314"}"""
contentType = APPLICATION_JSON
accept = APPLICATION_JSON
with(jwtWithPid("42"))
}.andExpect {
status { isOk() }
content {
json(
"""{
"status": "OK",
"varselTimestamp": "2021-01-01T00:00:00",
"eventTimestamp": "2021-01-07T00:00:00"
}""",
true
)
}
}
}

private fun processVarslingStatus(value: String) {
varslingStatusKafkaListener.processVarslingStatus(
ConsumerRecord(
"", 0, 0, "", value
)
)
}
}