Skip to content

Commit

Permalink
Legg til og bruk redis store med prefiks i service rivers (#544)
Browse files Browse the repository at this point in the history
* Legg til og bruk redis store med prefiks i service rivers

* Gjør funksjonsnavn konsekvent engelsk

* Fiks navn

* Fjern utdatert hjelpefunksjon
  • Loading branch information
bjerga authored Jun 6, 2024
1 parent a174c4e commit 5ccf1c4
Show file tree
Hide file tree
Showing 22 changed files with 653 additions and 133 deletions.
2 changes: 1 addition & 1 deletion api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ dependencies {
implementation("io.ktor:ktor-server-core:$ktorVersion")
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-server-status-pages:$ktorVersion")
implementation("io.lettuce:lettuce-core:$lettuceVersion")
implementation("no.nav.security:token-client-core:$tokenSupportVersion")
implementation("no.nav.security:token-validation-ktor-v2:$tokenSupportVersion")
implementation("org.valiktor:valiktor-core:$valiktorVersion")

testImplementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
testImplementation("io.ktor:ktor-client-core:$ktorVersion")
testImplementation("io.ktor:ktor-server-test-host:$ktorVersion")
testImplementation("io.lettuce:lettuce-core:$lettuceVersion")
testImplementation("no.nav.security:mock-oauth2-server:$mockOauth2ServerVersion")
testImplementation(project(":felles-test"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.ktor.server.routing.routing
import kotlinx.serialization.builtins.serializer
import no.nav.helse.rapids_rivers.RapidApplication
import no.nav.helse.rapids_rivers.RapidsConnection
import no.nav.helsearbeidsgiver.felles.rapidsrivers.registerShutdownLifecycle
import no.nav.helsearbeidsgiver.inntektsmelding.api.aktiveorgnr.aktiveOrgnrRoute
import no.nav.helsearbeidsgiver.inntektsmelding.api.auth.Tilgangskontroll
import no.nav.helsearbeidsgiver.inntektsmelding.api.hentselvbestemtim.hentSelvbestemtImRoute
Expand Down Expand Up @@ -58,20 +59,24 @@ fun main() {

fun startServer(env: Map<String, String> = System.getenv()) {
val rapid = RapidApplication.create(env)
val redisPoller = RedisPoller()

embeddedServer(
factory = Netty,
port = 8080,
module = { apiModule(rapid) }
module = { apiModule(rapid, redisPoller) }
)
.start(wait = true)

rapid.start()
rapid.registerShutdownLifecycle {
redisPoller.close()
}
.start()
}

fun Application.apiModule(
rapid: RapidsConnection,
redisPoller: RedisPoller = RedisPoller()
redisPoller: RedisPoller
) {
val tilgangskontroll = Tilgangskontroll(
TilgangProducer(rapid),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package no.nav.helsearbeidsgiver.inntektsmelding.api

import io.lettuce.core.RedisClient
import kotlinx.coroutines.delay
import kotlinx.serialization.json.JsonElement
import no.nav.helsearbeidsgiver.felles.rapidsrivers.redis.RedisConnection
import no.nav.helsearbeidsgiver.utils.json.parseJson
import no.nav.helsearbeidsgiver.utils.log.sikkerLogger
import java.util.UUID
Expand All @@ -14,9 +14,7 @@ private const val WAIT_MILLIS = 500L
class RedisPoller {
private val sikkerLogger = sikkerLogger()

private val redisClient = Env.Redis.url.let(RedisClient::create)
private val connection = redisClient.connect()
private val syncCommands = connection.sync()
private val redis = RedisConnection(Env.Redis.url)

suspend fun hent(key: UUID): JsonElement {
val json = hentJsonString(key)
Expand All @@ -37,7 +35,7 @@ class RedisPoller {
repeat(MAX_RETRIES) {
sikkerLogger.debug("Polling redis: $it time(s) for key $key")

val result = syncCommands.get(key.toString())
val result = redis.get(key.toString())

if (result != null) {
return result
Expand All @@ -48,6 +46,10 @@ class RedisPoller {

throw RedisPollerTimeoutException(key)
}

fun close() {
redis.close()
}
}

sealed class RedisPollerException(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package no.nav.helsearbeidsgiver.inntektsmelding.api

import io.kotest.assertions.throwables.shouldThrowExactly
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.lettuce.core.RedisClient
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import no.nav.helsearbeidsgiver.domene.inntektsmelding.v1.Periode
import no.nav.helsearbeidsgiver.felles.Forespoersel
import no.nav.helsearbeidsgiver.felles.ForespurtData
import no.nav.helsearbeidsgiver.felles.rapidsrivers.redis.RedisConnection
import no.nav.helsearbeidsgiver.felles.test.mock.mockForespoersel
import no.nav.helsearbeidsgiver.utils.json.fromJson
import no.nav.helsearbeidsgiver.utils.json.serializer.LocalDateSerializer
import no.nav.helsearbeidsgiver.utils.json.serializer.list
import no.nav.helsearbeidsgiver.utils.json.toJson
import no.nav.helsearbeidsgiver.utils.json.toJsonStr
import no.nav.helsearbeidsgiver.utils.test.mock.mockConstructor
import no.nav.helsearbeidsgiver.utils.test.mock.mockStatic
import org.junit.jupiter.api.assertThrows
import java.util.UUID

class RedisPollerTest : FunSpec({
Expand All @@ -30,24 +36,32 @@ class RedisPollerTest : FunSpec({
}

test("skal finne med tillatt antall forsøk") {
val redisPoller = redisPollerMedMockClient(
answerOnAttemptNo = 10,
answer = dataJsonString
)
mockConstructor(RedisConnection::class) {
every { anyConstructed<RedisConnection>().get(any()) } returnsMany answers(answerOnAttemptNo = 10, answer = dataJsonString)

val json = redisPoller.hent(key)
val redisPoller = mockStatic(RedisClient::class) {
every { RedisClient.create(any<String>()) } returns mockk(relaxed = true)
RedisPoller()
}

val json = redisPoller.hent(key)

json shouldBe dataJson
json shouldBe dataJson
}
}

test("skal ikke finne etter maks forsøk") {
val redisPoller = redisPollerMedMockClient(
answerOnAttemptNo = 11,
answer = dataJsonString
)
mockConstructor(RedisConnection::class) {
every { anyConstructed<RedisConnection>().get(any()) } returnsMany answers(answerOnAttemptNo = 11, answer = dataJsonString)

val redisPoller = mockStatic(RedisClient::class) {
every { RedisClient.create(any<String>()) } returns mockk(relaxed = true)
RedisPoller()
}

shouldThrowExactly<RedisPollerTimeoutException> {
redisPoller.hent(key)
assertThrows<RedisPollerTimeoutException> {
redisPoller.hent(key)
}
}
}

Expand All @@ -67,13 +81,20 @@ class RedisPollerTest : FunSpec({
}
"""

val redisPoller = redisPollerMedMockClient(
answerOnAttemptNo = 1,
answer = expectedJson
)
mockConstructor(RedisConnection::class) {
every { anyConstructed<RedisConnection>().get(any()) } returnsMany answers(answerOnAttemptNo = 1, answer = expectedJson)

val redisPoller = mockStatic(RedisClient::class) {
every { RedisClient.create(any<String>()) } returns mockk(relaxed = true)
RedisPoller()
}

val resultat = redisPoller.hent(key).fromJson(Forespoersel.serializer())
val resultat = redisPoller.hent(key).fromJson(Forespoersel.serializer())

resultat shouldBe expected
resultat shouldBe expected
}
}
})

private fun answers(answerOnAttemptNo: Int, answer: String): List<String?> =
List(answerOnAttemptNo - 1) { null }.plus(answer)
2 changes: 2 additions & 0 deletions felles-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
val lettuceVersion: String by project
val kotestVersion: String by project
val ktorVersion: String by project
val mockkVersion: String by project
Expand All @@ -12,5 +13,6 @@ dependencies {
implementation("io.kotest:kotest-runner-junit5:$kotestVersion")
implementation("io.mockk:mockk:$mockkVersion")

implementation("io.lettuce:lettuce-core:$lettuceVersion")
implementation(testFixtures("no.nav.helsearbeidsgiver:utils:$utilsVersion"))
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package no.nav.helsearbeidsgiver.felles.test.mock

import io.lettuce.core.RedisClient
import io.lettuce.core.api.sync.RedisCommands
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
import no.nav.helsearbeidsgiver.felles.rapidsrivers.redis.RedisConnection
import no.nav.helsearbeidsgiver.felles.rapidsrivers.redis.RedisKey
import no.nav.helsearbeidsgiver.felles.rapidsrivers.redis.RedisPrefix
import no.nav.helsearbeidsgiver.felles.rapidsrivers.redis.RedisStore
import no.nav.helsearbeidsgiver.felles.rapidsrivers.redis.RedisStoreClassSpecific
import no.nav.helsearbeidsgiver.utils.collection.mapValuesNotNull
import no.nav.helsearbeidsgiver.utils.test.mock.mockStatic

class MockRedis {
val store = mockk<RedisStore>()
Expand Down Expand Up @@ -39,3 +46,28 @@ class MockRedis {
}
}
}

class MockRedisClassSpecific(keyPrefix: RedisPrefix) {
private val mockCommands = mockk<RedisCommands<String, String>>()
private val redis = mockStatic(RedisClient::class) {
every { RedisClient.create(any<String>()) } returns mockRedisClient(mockCommands)
RedisConnection("")
}

val store: RedisStoreClassSpecific

init {
setup()

store = spyk(
RedisStoreClassSpecific(
redis = redis,
keyPrefix = keyPrefix
)
)
}

fun setup() {
mockCommands.setupMock(emptyMap())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package no.nav.helsearbeidsgiver.felles.test.mock

import io.lettuce.core.KeyValue
import io.lettuce.core.RedisClient
import io.lettuce.core.api.StatefulRedisConnection
import io.lettuce.core.api.sync.RedisCommands
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import no.nav.helsearbeidsgiver.felles.rapidsrivers.redis.RedisConnection
import no.nav.helsearbeidsgiver.utils.test.mock.mockStatic

fun redisWithMockRedisClient(mockStorageInit: Map<String, String?>): RedisConnection {
val mockCommands = mockk<RedisCommands<String, String>>().setupMock(mockStorageInit)
return mockStatic(RedisClient::class) {
every { RedisClient.create(any<String>()) } returns mockRedisClient(mockCommands)
RedisConnection("")
}
}

fun mockRedisClient(commands: RedisCommands<String, String>): RedisClient {
val connection = mockk<StatefulRedisConnection<String, String>>(relaxed = true) {
every { sync() } returns commands
}

return mockk<RedisClient> {
every { connect() } returns connection
}
}

fun RedisCommands<String, String>.setupMock(mockStorageInit: Map<String, String?>): RedisCommands<String, String> =
also {
val mockStorage = mockStorageInit.toMutableMap()

val key = slot<String>()
val value = slot<String>()

// Fungerer som en capture slot for vararg
val varargKeys = mutableListOf<String>()

every { this@setupMock.get(capture(key)) } answers {
mockStorage[key.captured]
}

every { mget(*varargAll { varargKeys.add(it) }) } answers {
val keyValuePairs = varargKeys.associateWith { mockStorage[it] }
.map { KeyValue.fromNullable(it.key, it.value) }

varargKeys.clear()

keyValuePairs
}

every { setex(capture(key), any(), capture(value)) } answers {
mockStorage[key.captured] = value.captured
"OK"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package no.nav.helsearbeidsgiver.felles.rapidsrivers.redis

import io.lettuce.core.RedisClient
import io.lettuce.core.api.StatefulRedisConnection
import io.lettuce.core.api.sync.RedisCommands
import no.nav.helsearbeidsgiver.utils.collection.mapValuesNotNull

class RedisConnection(
redisUrl: String
) {
private val client: RedisClient = RedisClient.create(redisUrl)
private val connection: StatefulRedisConnection<String, String> = client.connect()
private val syncCommands: RedisCommands<String, String> = connection.sync()

fun get(key: String): String? =
syncCommands.get(key)

internal fun getAll(vararg keys: String): Map<String, String> =
syncCommands.mget(*keys)
.associate { it.key to it.getValueOrElse(null) }
.mapValuesNotNull { it }

internal fun set(key: String, value: String) {
syncCommands.setex(key, 60L, value)
}

fun close() {
connection.close()
client.shutdown()
}
}
Loading

0 comments on commit 5ccf1c4

Please sign in to comment.