-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
37 changes: 37 additions & 0 deletions
37
src/main/kotlin/no/nav/arbeidsgiver/min_side/varslingstatus/VarslingStatusController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) | ||
} |
107 changes: 107 additions & 0 deletions
107
src/main/kotlin/no/nav/arbeidsgiver/min_side/varslingstatus/VarslingStatusRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
||
|
||
|
218 changes: 218 additions & 0 deletions
218
src/test/kotlin/no/nav/arbeidsgiver/min_side/varslingstatus/VarslingStatusIntegrationTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) | ||
) | ||
} | ||
} |