From 50f4afb150f534817bf8fa7bc4451cce2ae9abd9 Mon Sep 17 00:00:00 2001 From: terjenilssen Date: Fri, 7 Nov 2025 14:54:07 +0100 Subject: [PATCH 1/6] =?UTF-8?q?Utvidet=20CPA-tabellen=20med=20last=5Fused.?= =?UTF-8?q?=20Lagt=20til=20funksjoner=20for=20=C3=A5=20oppdatere=20last=5F?= =?UTF-8?q?used,=20og=20for=20=C3=A5=20hente=20last=5Fused=20for=20alle=20?= =?UTF-8?q?CPA'er.=20Lagt=20inn=20manglende=20testing=20av=20eksisterende?= =?UTF-8?q?=20timestamp-funksjoner=20fra=20cpa-repo.=20Har=20samtidig=20re?= =?UTF-8?q?faktorisert=20navn=20p=C3=A5=20andre=20timestamp-funksjoner=20i?= =?UTF-8?q?=20CPARepository.kt.=20Sp=C3=B8rsm=C3=A5let=20er=20om=20vi=20og?= =?UTF-8?q?s=C3=A5=20skal=20refaktorisere=20i=20Routes.kt=20og=20dermed=20?= =?UTF-8?q?ogs=C3=A5=20REST-endepunktene=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/kotlin/no/nav/emottak/cpa/App.kt | 1 + .../main/kotlin/no/nav/emottak/cpa/Routes.kt | 17 +++++-- .../no/nav/emottak/cpa/persistence/CPA.kt | 1 + .../emottak/cpa/persistence/CPARepository.kt | 45 +++++++++++----- .../db/migration/V11___cpa_add_last_used.sql | 1 + .../cpa/persistence/CPARepositoryTest.kt | 51 ++++++++++++++++++- settings.gradle.kts | 3 +- 7 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 cpa-repo/src/main/resources/db/migration/V11___cpa_add_last_used.sql diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/App.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/App.kt index 67ba0f4e4..ae02eea9b 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/App.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/App.kt @@ -94,6 +94,7 @@ fun cpaApplicationModule( getCPA(cpaRepository) getTimeStamps(cpaRepository) getTimeStampsLatest(cpaRepository) + getTimeStampsLastUsed(cpaRepository) getCertificate(cpaRepository) signingCertificate(cpaRepository) getMessagingCharacteristics(cpaRepository) diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/Routes.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/Routes.kt index 911c3a308..9dfd19be1 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/Routes.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/Routes.kt @@ -142,7 +142,7 @@ fun Route.getTimeStamps(cpaRepository: CPARepository): Route = get("/cpa/timesta log.info("Timestamps") call.respond( HttpStatusCode.OK, - cpaRepository.findCpaTimestamps( + cpaRepository.findTimestampsCpaUpdated( withContext(Dispatchers.IO) { return@withContext call.request.headers[CPA_IDS] .let { @@ -160,7 +160,7 @@ fun Route.getTimeStamps(cpaRepository: CPARepository): Route = get("/cpa/timesta fun Route.getTimeStampsLatest(cpaRepository: CPARepository) = get("/cpa/timestamps/latest") { log.info("Timestamplatest") val latestTimestamp = withContext(Dispatchers.IO) { - cpaRepository.findLatestUpdatedCpaTimestamp() + cpaRepository.findTimestampCpaLatestUpdated() } when (latestTimestamp) { null -> call.respond(HttpStatusCode.NotFound, "No timestamps found") @@ -168,6 +168,14 @@ fun Route.getTimeStampsLatest(cpaRepository: CPARepository) = get("/cpa/timestam } } +fun Route.getTimeStampsLastUsed(cpaRepository: CPARepository): Route = get("/cpa/timestamps/last_used") { + log.info("Timestamps last_used") + call.respond( + HttpStatusCode.OK, + cpaRepository.findTimestampsCpaLastUsed() + ) +} + fun Route.postCpa(cpaRepository: CPARepository) = post("/cpa") { log.info("post-cpa") val cpaString = call.receive() @@ -196,6 +204,9 @@ fun Route.validateCpa( log.info(validateRequest.marker(), "Validerer ebms mot CPA") val cpa = cpaRepository.findCpa(validateRequest.cpaId) ?: throw NotFoundException("Fant ikke CPA (${validateRequest.cpaId})") + if (!cpaRepository.updateCpaLastUsed(validateRequest.cpaId)) { + log.warn(validateRequest.marker(), "Feilet med å oppdatere last_used for CPA '${validateRequest.cpaId}'") + } if (!validateRequest.isSignalMessage()) { cpa.validate(validateRequest) } // Delivery Failure @@ -354,7 +365,7 @@ fun Routing.registerHealthEndpoints( } get("/internal/health/readiness") { runCatching { - cpaRepository.findLatestUpdatedCpaTimestamp() + cpaRepository.findTimestampCpaLatestUpdated() }.onSuccess { call.respond(HttpStatusCode.OK, "Readiness OK") }.onFailure { diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPA.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPA.kt index adf558dbf..7f96e0f83 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPA.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPA.kt @@ -14,6 +14,7 @@ object CPA : Table("cpa") { val cpa = json("cpa", CollaborationProtocolAgreement::class.java) val updated_date = timestamp("updated_date") val entryCreated = timestamp("create_date") + val lastUsed: Column = timestamp("last_used").nullable() } fun Table.json( diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt index 75d61ee69..9e140f31a 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt @@ -17,6 +17,7 @@ import org.jetbrains.exposed.sql.deleteAll import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.upsert import org.oasis_open.committees.ebxml_cppa.schema.cpp_cpa_2_0.CollaborationProtocolAgreement import java.time.Instant @@ -42,7 +43,7 @@ class CPARepository(val database: Database) { return xmlMarshaller.unmarshal(cpaString, CollaborationProtocolAgreement::class.java) } - fun findCpaTimestamps(idList: List): Map { + fun findTimestampsCpaUpdated(idList: List): Map { return transaction(db = database.db) { if (idList.isNotEmpty()) { CPA.select(CPA.id, CPA.updated_date).where { CPA.id inList idList } @@ -54,7 +55,7 @@ class CPARepository(val database: Database) { } } - fun findLatestUpdatedCpaTimestamp(): String? { + fun findTimestampCpaLatestUpdated(): String? { return transaction(db = database.db) { CPA.select(CPA.id, CPA.updated_date) .where { CPA.updated_date.isNotNull() } @@ -73,7 +74,8 @@ class CPARepository(val database: Database) { it[CPA.cpa], it[CPA.updated_date], it[CPA.entryCreated], - it[CPA.herId] + it[CPA.herId], + it[CPA.lastUsed] ) } } @@ -87,6 +89,7 @@ class CPARepository(val database: Database) { it[entryCreated] = cpa.createdDate it[updated_date] = cpa.updatedDate it[herId] = cpa.herId + it[lastUsed] = cpa.lastUsed } } return cpa.id @@ -157,19 +160,44 @@ class CPARepository(val database: Database) { } } + fun updateCpaLastUsed(cpaId: String): Boolean { + if (cpaId == "nav:qass:30823" && !isProdEnv()) { + return true + } + return 1 == transaction(database.db) { + CPA.update({ + CPA.id eq cpaId + }) { + it[lastUsed] = Instant.now().truncatedTo(ChronoUnit.SECONDS) + } + } + } + + fun findTimestampsCpaLastUsed(): Map { + return transaction(db = database.db) { + CPA.select(CPA.id, CPA.lastUsed) + .orderBy(CPA.id, SortOrder.ASC) + .associate { + it[CPA.id] to it[CPA.lastUsed].toString() + } + } + } + data class CpaDbEntry( val id: String, val cpa: CollaborationProtocolAgreement? = null, val updatedDate: Instant, val createdDate: Instant, - val herId: String? + val herId: String?, + val lastUsed: Instant? ) { constructor(cpa: CollaborationProtocolAgreement, updatedDateString: String?) : this( id = cpa.cpaid, cpa = cpa, updatedDate = parseOrDefault(updatedDateString), createdDate = Instant.now().truncatedTo(ChronoUnit.SECONDS), - herId = cpa.getPartnerPartyIdByType(PartyTypeEnum.HER)?.value + herId = cpa.getPartnerPartyIdByType(PartyTypeEnum.HER)?.value, + lastUsed = null ) companion object { @@ -182,11 +210,4 @@ class CPARepository(val database: Database) { } } } - - // @Serializable - // data class TimestampResponse( - // val idMap: Map - // ) - - // fun List>.toTimestampResponse() {} } diff --git a/cpa-repo/src/main/resources/db/migration/V11___cpa_add_last_used.sql b/cpa-repo/src/main/resources/db/migration/V11___cpa_add_last_used.sql new file mode 100644 index 000000000..8a9ee47db --- /dev/null +++ b/cpa-repo/src/main/resources/db/migration/V11___cpa_add_last_used.sql @@ -0,0 +1 @@ +ALTER TABLE cpa ADD last_used timestamp; \ No newline at end of file diff --git a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/persistence/CPARepositoryTest.kt b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/persistence/CPARepositoryTest.kt index 4a2b96478..2819222b9 100644 --- a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/persistence/CPARepositoryTest.kt +++ b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/persistence/CPARepositoryTest.kt @@ -6,6 +6,7 @@ import org.junit.jupiter.api.assertThrows import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.assertTrue class CPARepositoryTest : PostgresTest() { @@ -21,7 +22,7 @@ class CPARepositoryTest : PostgresTest() { } @Test - fun `CPA db entry bør ha timestamps`() { + fun `CPA db entry bør ha updated-timestamps`() { val cpaRepository = CPARepository(postgres) assertEquals(getPostgresTimestamp(), cpaRepository.findCpaEntry(CPA_ID)?.updatedDate) } @@ -32,6 +33,19 @@ class CPARepositoryTest : PostgresTest() { assertEquals("8141253", cpaRepository.findCpaEntry(CPA_ID)?.herId) } + @Test + fun `CPA db entry har lastUsed-timestamp som null`() { + val cpaRepository = CPARepository(postgres) + assertNull(cpaRepository.findCpaEntry(CPA_ID)?.lastUsed) // Null første gangen + } + + @Test + fun `CPA db entry har lastUsed-timestamp etter updateCpaLastUsed()`() { + val cpaRepository = CPARepository(postgres) + cpaRepository.updateCpaLastUsed(CPA_ID) + assertNotNull(cpaRepository.findCpaEntry(CPA_ID)?.lastUsed) + } + @Test fun `Hent process config med gyldig role service action kombo`() { val cpaRepository = CPARepository(postgres) @@ -55,4 +69,39 @@ class CPARepositoryTest : PostgresTest() { ) } } + + @Test + fun `Hent timestamp for nyligst oppdaterte CPA`() { + val cpaRepository = CPARepository(postgres) + assertEquals(getPostgresTimestamp().toString(), cpaRepository.findTimestampCpaLatestUpdated()) + } + + @Test + fun `Hent timestamps for alle CPA'er for når de ble sist oppdatert`() { + val cpaRepository = CPARepository(postgres) + val map = cpaRepository.findTimestampsCpaUpdated(emptyList()) + assertEquals(3, map.size) + for (entry in map) { + assertEquals(getPostgresTimestamp().toString(), entry.value) + } + } + + @Test + fun `Hent timestamps for utvalgte CPA'er for når de ble sist oppdatert`() { + val cpaRepository = CPARepository(postgres) + val map = cpaRepository.findTimestampsCpaUpdated(listOf(CPA_ID)) + assertEquals(1, map.size) + assertEquals(getPostgresTimestamp().toString(), map[CPA_ID]) + } + + @Test + fun `Hent timestamps for alle CPA'er for når de ble sist brukt`() { + val cpaRepository = CPARepository(postgres) + cpaRepository.updateCpaLastUsed(CPA_ID) + cpaRepository.updateCpaLastUsed("multiple_channels_and_multiple_endpoints") + val map = cpaRepository.findTimestampsCpaLastUsed() + assertEquals(3, map.size) + assertNotNull(map[CPA_ID]) + assertNull(map["nav-qass-31162"]) // Aldri brukt + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8542fd5c8..97dd66dfd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,6 +7,8 @@ * in the user manual at https://docs.gradle.org/8.1.1/userguide/multi_project_builds.html */ +rootProject.name = "ebxml-processor" + dependencyResolutionManagement { versionCatalogs { @@ -153,5 +155,4 @@ dependencyResolutionManagement { } } -rootProject.name = "ebxml-processor" include("felles", "ebxml-processing-model", "cpa-repo", "ebms-provider", "ebms-payload", "ebms-async") From cb30fc7465a9addb11101a2293d809e87deea8d8 Mon Sep 17 00:00:00 2001 From: terjenilssen Date: Fri, 7 Nov 2025 15:03:23 +0100 Subject: [PATCH 2/6] =?UTF-8?q?Legge=20til=20rette=20for=20at=20emottak-mo?= =?UTF-8?q?nitor=20kan=20kalle=20p=C3=A5=20cpa-repo=20endepunkter.=20Har?= =?UTF-8?q?=20samtidig=20oppdatert=20prosjektets=20readme.md=20litt.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .nais/cpa-repo-dev.yaml | 1 + .nais/cpa-repo-prod.yaml | 1 + readme.md | 20 +++++++------------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.nais/cpa-repo-dev.yaml b/.nais/cpa-repo-dev.yaml index e8a0977a6..a1a7d4cbb 100644 --- a/.nais/cpa-repo-dev.yaml +++ b/.nais/cpa-repo-dev.yaml @@ -68,6 +68,7 @@ spec: - application: ebms-async - application: ebms-provider - application: ebms-payload + - application: emottak-monitor outbound: external: - host: crl.buypass.no diff --git a/.nais/cpa-repo-prod.yaml b/.nais/cpa-repo-prod.yaml index 1cd21626e..89154e88f 100644 --- a/.nais/cpa-repo-prod.yaml +++ b/.nais/cpa-repo-prod.yaml @@ -69,6 +69,7 @@ spec: - application: ebms-provider - application: ebms-async - application: ebms-payload + - application: emottak-monitor outbound: external: - host: crl.buypassca.com diff --git a/readme.md b/readme.md index 7b227886f..a7f816f57 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,6 @@ Prosjektet består av fire hovedmoduler for behandling av ebXML-meldinger. * EbMS Provider * EbMS Payload behandling * CPA Repo -* EbMS Send In I tillegg er det en modul for asynkron trafikk som lytter på epost, og router meldinger til ebMS-Provider for behandling. * SMTP-Listener @@ -15,23 +14,17 @@ I tillegg er det en modul for asynkron trafikk som lytter på epost, og router m ### EbMS Provider Selve motoren i ebMS-håndteringen. Det er her ebMS-spesifikasjonen er implementert. Validerer ebXML-konvolutten, pakker ut fagmeldingen og sender den til ebMS-payload. +REST-endepunkt: https://ebms-provider-fss.intern.dev.nav.no/ebms/sync (post) ### EbMS Payload Behandling av fagmeldingene som er pakket inn i ebXML. Mottar meldinger fra ebMS-provider, og validerer at innholdet er en korrekt fagmelding og er klar for videreformidling til fagsystem. +REST-endepunkt: https://ebms-payload-fss.intern.dev.nav.no/payload (post) ### CPA Repo Holder på alle godkjente CPAer. Mottar ebXML-header informasjon fra ebMS-provider, og validerer innholdet mot relevant CPA. - -### EbMS Send In -Denne tjenesten har ansvaret for å route fagmeldingene til og fra fagsystemene. På vei inn mottar den ferdigbehandlede -fagmeldinger som routes videre til riktig fagsystem. På vei ut mottar den fagmeldinger fra fagsystemene, og router dem -videre til EbMS provider som validerer, pakker og sender ut meldingene. - -### SMTP Listener -Liten modul som henter eposter fra definert innboks, og router meldingene videre til ebMS-Provider. -Trigges av periodisk NAISJob, og leser alle mail i innboksen ved aktivering. +REST-baseUrl: https://cpa-repo-fss.intern.dev.nav.no ## Utvikling @@ -43,6 +36,7 @@ Alle modulene kjører som selvstendig applikasjoner, og er bygd opp av følgende ### Avhengigheter Avhengigheter og relasjoner til andre repoer i teamet +* https://github.com/navikt/emottak-utils * https://github.com/navikt/emottak-payload-xsd * https://github.com/navikt/ebxml-protokoll * https://github.com/navikt/ebms-sync-router @@ -54,13 +48,13 @@ For å bygge prosjektet brukes gradle. ./gradlew build ``` -Noen av testene bruker [testcontainers](https://github.com/testcontainers/testcontainers-java) for å bygge opp et mer komplett -kjøretidsmiljø. Disse er avhengig av et fungerende docker miljø. For eksempel docker eller [colima](https://github.com/abiosoft/colima) på mac. +Noen av testene bruker [testcontainers](https://github.com/testcontainers/testcontainers-java) for å bygge opp et mer komplett kjøretidsmiljø. +Disse er avhengig av et fungerende Docker-miljø. For eksempel Docker, [Colima](https://github.com/abiosoft/colima) på mac, eller [Rancher Desktop](https://rancherdesktop.io/) (win/mac/linux). Hvis du har en mac som bruker de nyere "M"-chipene (eks. M3) vil du møte på problemer når du spinner opp testcontainers i dette prosjektet. Dette gjelder containerne for Oracle DB (gvenzl/oracle-xe:21-slim-faststart) når man bruker docker desktop. For å unngå dette må man heller bruke Colima. Under er steg for hvordan: -1. [Innstaller Colima](https://github.com/abiosoft/colima?tab=readme-ov-file#installation). +1. [Installér Colima](https://github.com/abiosoft/colima?tab=readme-ov-file#installation). 2. Sett DOCKER_HOST env-variabel til Colima (mulig dette må reverseres når man bygger andre prosjekter). - `export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock"` 3. Start Colima med x86_64. From 8f16d63495dd8c865ed5019c2eebd7dd297eef34 Mon Sep 17 00:00:00 2001 From: OleksandrChmyrNAV Date: Thu, 6 Nov 2025 16:30:39 +0100 Subject: [PATCH 3/6] =?UTF-8?q?#277=20Issuer=20settes=20idag=20som=20en=20?= =?UTF-8?q?property.=20Det=20anbefales=20=C3=A5=20hente=20det=20fra=20open?= =?UTF-8?q?id-configuration=20endepunktet=20(#187)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Utført OpenIdConfigProvider * Lagt til standardverdier for issuer og JWK set URL * Justert openIdConfigCacheTimeInSec innstillingen * Fikset caching * Fikset logging --- .../emottak/payload/configuration/Config.kt | 6 +- .../payload/helseid/HelseIdTokenValidator.kt | 6 +- .../helseid/util/OpenIdConfigProvider.kt | 58 +++++++++++++++++++ .../src/main/resources/application_dev.conf | 6 +- .../src/main/resources/application_local.conf | 6 +- .../src/main/resources/application_prod.conf | 6 +- 6 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/util/OpenIdConfigProvider.kt diff --git a/ebms-payload/src/main/kotlin/no/nav/emottak/payload/configuration/Config.kt b/ebms-payload/src/main/kotlin/no/nav/emottak/payload/configuration/Config.kt index 6721ccbdb..5b989107b 100644 --- a/ebms-payload/src/main/kotlin/no/nav/emottak/payload/configuration/Config.kt +++ b/ebms-payload/src/main/kotlin/no/nav/emottak/payload/configuration/Config.kt @@ -19,8 +19,10 @@ data class CertificateAuthority( ) data class HelseId( - val issuerUrl: String, - val jwksUrl: String, + val nhnUrl: String, + val openIdConfigCacheTimeInSec: Long, + val issuerDefaultValue: String, + val jwksUrlDefaultValue: String, val allowedClockSkewInMs: Long, val allowedMessageGenerationGapInMs: Long ) diff --git a/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/HelseIdTokenValidator.kt b/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/HelseIdTokenValidator.kt index d5c620594..5b31cc7b8 100644 --- a/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/HelseIdTokenValidator.kt +++ b/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/HelseIdTokenValidator.kt @@ -18,10 +18,10 @@ import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.jwt.JWTParser import com.nimbusds.jwt.SignedJWT import no.nav.emottak.payload.configuration.config +import no.nav.emottak.payload.helseid.util.OpenIdConfigProvider import no.nav.emottak.payload.helseid.util.XPathEvaluator import no.nav.emottak.payload.helseid.util.msgHeadNamespaceContext import org.w3c.dom.Document -import java.net.URI import java.text.ParseException import java.time.Instant import java.time.ZoneId @@ -31,11 +31,11 @@ import java.util.Date import java.util.Locale class HelseIdTokenValidator( - private val issuer: String = config().helseId.issuerUrl, + private val issuer: String = OpenIdConfigProvider.issuer, private val allowedClockSkewInMs: Long = config().helseId.allowedClockSkewInMs, private val allowedMessageGenerationGapInMs: Long = config().helseId.allowedMessageGenerationGapInMs, private val helseIdJwkSource: JWKSource = JWKSourceBuilder - .create(URI.create(config().helseId.jwksUrl).toURL()).build() + .create(OpenIdConfigProvider.jwksUrl).build() ) { fun getValidatedNin(base64Token: String, messageGenerationDate: Instant): String? = parseSignedJwt(base64Token) .also { diff --git a/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/util/OpenIdConfigProvider.kt b/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/util/OpenIdConfigProvider.kt new file mode 100644 index 000000000..04467d46c --- /dev/null +++ b/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/util/OpenIdConfigProvider.kt @@ -0,0 +1,58 @@ +package no.nav.emottak.payload.helseid.util + +import com.nimbusds.oauth2.sdk.id.Issuer +import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata +import no.nav.emottak.payload.configuration.config +import no.nav.emottak.payload.log +import java.net.URI +import java.net.URL +import java.time.Instant + +object OpenIdConfigProvider { + private val nhnUrl: String = config().helseId.nhnUrl + private val cacheTimeInSec = config().helseId.openIdConfigCacheTimeInSec + private var cachedConfig: OIDCProviderMetadata? = null + private var lastFetched: Instant? = null + + val issuer: String + get() { + try { + return this.getConfig().issuer.value + } catch (e: Exception) { + val defaultIssuer = config().helseId.issuerDefaultValue + log.warn("Failed to get OpenID issuer from $nhnUrl, use default value $defaultIssuer", e) + return defaultIssuer + } + } + + val jwksUrl: URL + get() { + try { + return this.getConfig().jwkSetURI.toURL() + } catch (e: Exception) { + val defaultJwksUrl = config().helseId.jwksUrlDefaultValue + log.warn("Failed to get OpenID JWK set URL from $nhnUrl, use default value $defaultJwksUrl", e) + return URI.create(defaultJwksUrl).toURL() + } + } + + private fun getConfig(): OIDCProviderMetadata { + val now = Instant.now() + if (cachedConfig != null && + lastFetched != null && + now.isBefore(lastFetched?.plusSeconds(cacheTimeInSec)) + ) { + return cachedConfig!! + } + + return loadOpenIdConfig() + } + + private fun loadOpenIdConfig(): OIDCProviderMetadata { + val issuer = Issuer(nhnUrl) + val metadata = OIDCProviderMetadata.resolve(issuer) + lastFetched = Instant.now() + cachedConfig = metadata + return metadata + } +} diff --git a/ebms-payload/src/main/resources/application_dev.conf b/ebms-payload/src/main/resources/application_dev.conf index f2d14b291..b887c05ba 100644 --- a/ebms-payload/src/main/resources/application_dev.conf +++ b/ebms-payload/src/main/resources/application_dev.conf @@ -18,8 +18,10 @@ kafka { } helseId { - issuerUrl = "https://helseid-sts.test.nhn.no" - jwksUrl = "https://helseid-sts.test.nhn.no/.well-known/openid-configuration/jwks" + nhnUrl = "https://helseid-sts.test.nhn.no" + openIdConfigCacheTimeInSec = 600 + issuerDefaultValue = "https://helseid-sts.test.nhn.no" + jwksUrlDefaultValue = "https://helseid-sts.test.nhn.no/.well-known/openid-configuration/jwks" allowedClockSkewInMs = 0 allowedMessageGenerationGapInMs = 10000 } diff --git a/ebms-payload/src/main/resources/application_local.conf b/ebms-payload/src/main/resources/application_local.conf index 061482986..9638746d1 100644 --- a/ebms-payload/src/main/resources/application_local.conf +++ b/ebms-payload/src/main/resources/application_local.conf @@ -18,8 +18,10 @@ kafka { } helseId { - issuerUrl = "https://helseid-sts.test.nhn.no" - jwksUrl = "https://helseid-sts.test.nhn.no/.well-known/openid-configuration/jwks" + nhnUrl = "https://helseid-sts.test.nhn.no" + openIdConfigCacheTimeInSec = 600 + issuerDefaultValue = "https://helseid-sts.test.nhn.no" + jwksUrlDefaultValue = "https://helseid-sts.test.nhn.no/.well-known/openid-configuration/jwks" allowedClockSkewInMs = 0 allowedMessageGenerationGapInMs = 10000 } diff --git a/ebms-payload/src/main/resources/application_prod.conf b/ebms-payload/src/main/resources/application_prod.conf index 5462aa29c..0820af1d9 100644 --- a/ebms-payload/src/main/resources/application_prod.conf +++ b/ebms-payload/src/main/resources/application_prod.conf @@ -16,8 +16,10 @@ kafka { } helseId { - issuerUrl = "https://helseid-sts.nhn.no" - jwksUrl = "https://helseid-sts.nhn.no/.well-known/openid-configuration/jwks" + nhnUrl = "https://helseid-sts.nhn.no" + openIdConfigCacheTimeInSec = 600 + issuerDefaultValue = "https://helseid-sts.nhn.no" + jwksUrlDefaultValue = "https://helseid-sts.nhn.no/.well-known/openid-configuration/jwks" allowedClockSkewInMs = 0 allowedMessageGenerationGapInMs = 10000 } From 92fccec0589425ae497c44dc98d6ac72d661331c Mon Sep 17 00:00:00 2001 From: terjenilssen Date: Wed, 12 Nov 2025 13:56:41 +0100 Subject: [PATCH 4/6] =?UTF-8?q?Endret=20slik=20at=20updateCpaLastUsed=20ku?= =?UTF-8?q?n=20blir=20kalt=20=C3=A9n=20gang=20pr=20dag.=20Har=20ogs=C3=A5?= =?UTF-8?q?=20oppdatert=20tester.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/no/nav/emottak/cpa/Routes.kt | 7 +- .../emottak/cpa/persistence/CPARepository.kt | 19 +- .../nav/emottak/cpa/CPARepoIntegrationTest.kt | 371 +++++++++++++----- .../payload/helseid/HelseIdTokenValidator.kt | 3 +- .../no/nav/emottak/util/DateTimeUtil.kt | 16 + 5 files changed, 309 insertions(+), 107 deletions(-) create mode 100644 felles/src/main/kotlin/no/nav/emottak/util/DateTimeUtil.kt diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/Routes.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/Routes.kt index b06ca4b1a..017d570d2 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/Routes.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/Routes.kt @@ -42,6 +42,7 @@ import no.nav.emottak.message.model.SignatureDetailsRequest import no.nav.emottak.message.model.ValidationRequest import no.nav.emottak.message.model.ValidationResult import no.nav.emottak.util.createX509Certificate +import no.nav.emottak.util.isToday import no.nav.emottak.util.marker import no.nav.emottak.utils.common.model.EbmsProcessing import no.nav.emottak.utils.environment.getEnvVar @@ -206,9 +207,9 @@ fun Route.validateCpa( try { log.info(validateRequest.marker(), "Validerer ebms mot CPA") - val cpa = cpaRepository.findCpa(validateRequest.cpaId) - ?: throw NotFoundException("Fant ikke CPA (${validateRequest.cpaId})") - if (!cpaRepository.updateCpaLastUsed(validateRequest.cpaId)) { + val (cpa, lastUsed) = cpaRepository.findCpaAndLastUsed(validateRequest.cpaId) + if (cpa == null) throw NotFoundException("Fant ikke CPA (${validateRequest.cpaId})") + if (!lastUsed.isToday() && !cpaRepository.updateCpaLastUsed(validateRequest.cpaId)) { log.warn(validateRequest.marker(), "Feilet med å oppdatere last_used for CPA '${validateRequest.cpaId}'") } if (!validateRequest.isSignalMessage()) { diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt index 9e140f31a..a55468a2f 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt @@ -25,22 +25,23 @@ import java.time.temporal.ChronoUnit class CPARepository(val database: Database) { - fun findCpa(cpaId: String): CollaborationProtocolAgreement? { + fun findCpa(cpaId: String): CollaborationProtocolAgreement? = findCpaAndLastUsed(cpaId).first + + fun findCpaAndLastUsed(cpaId: String): Pair { if (cpaId == "nav:qass:30823" && !isProdEnv()) { return loadOverrideCPA() } - return transaction(db = database.db) { + val resultRow = transaction(db = database.db) { CPA.selectAll().where { CPA.id.eq(cpaId) - }.firstOrNull()?.get( - CPA.cpa - ) + }.firstOrNull() } + return Pair(resultRow?.get(CPA.cpa), resultRow?.get(CPA.lastUsed)) } - fun loadOverrideCPA(): CollaborationProtocolAgreement { + private fun loadOverrideCPA(): Pair { val cpaString = String(object {}::class.java.classLoader.getResource("cpa/nav_qass_30823_modified.xml").readBytes()) - return xmlMarshaller.unmarshal(cpaString, CollaborationProtocolAgreement::class.java) + return Pair(xmlMarshaller.unmarshal(cpaString, CollaborationProtocolAgreement::class.java), Instant.now()) } fun findTimestampsCpaUpdated(idList: List): Map { @@ -173,12 +174,12 @@ class CPARepository(val database: Database) { } } - fun findTimestampsCpaLastUsed(): Map { + fun findTimestampsCpaLastUsed(): Map { return transaction(db = database.db) { CPA.select(CPA.id, CPA.lastUsed) .orderBy(CPA.id, SortOrder.ASC) .associate { - it[CPA.id] to it[CPA.lastUsed].toString() + it[CPA.id] to it[CPA.lastUsed]?.toString() } } } diff --git a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/CPARepoIntegrationTest.kt b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/CPARepoIntegrationTest.kt index ca46a6f37..d741087a8 100644 --- a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/CPARepoIntegrationTest.kt +++ b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/CPARepoIntegrationTest.kt @@ -15,33 +15,44 @@ import io.ktor.client.request.header import io.ktor.client.request.headers import io.ktor.client.request.post import io.ktor.client.request.setBody +import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType.Application.Json import io.ktor.http.HttpStatusCode import io.ktor.http.contentType import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.install +import io.ktor.server.routing.routing import io.ktor.server.testing.ApplicationTestBuilder import io.ktor.server.testing.testApplication +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk import kotlinx.serialization.json.Json import no.nav.emottak.cpa.auth.AZURE_AD_AUTH import no.nav.emottak.cpa.auth.AuthConfig import no.nav.emottak.cpa.databasetest.PostgresOracleTest +import no.nav.emottak.cpa.persistence.CPARepository +import no.nav.emottak.cpa.persistence.gammel.PartnerRepository import no.nav.emottak.cpa.util.EventRegistrationServiceFake import no.nav.emottak.message.model.Direction.IN import no.nav.emottak.message.model.EmailAddress import no.nav.emottak.message.model.ErrorCode import no.nav.emottak.message.model.MessagingCharacteristicsRequest import no.nav.emottak.message.model.MessagingCharacteristicsResponse +import no.nav.emottak.message.model.ProcessConfig import no.nav.emottak.message.model.SignatureDetails import no.nav.emottak.message.model.SignatureDetailsRequest import no.nav.emottak.message.model.ValidationRequest import no.nav.emottak.message.model.ValidationResult +import no.nav.emottak.util.OSLO_ZONE import no.nav.emottak.utils.common.model.Addressing import no.nav.emottak.utils.common.model.Party import no.nav.emottak.utils.common.model.PartyId import no.nav.emottak.utils.environment.getEnvVar import no.nav.security.mock.oauth2.MockOAuth2Server -import no.nav.security.mock.oauth2.http.json import org.apache.commons.lang3.StringUtils import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -49,20 +60,24 @@ import org.oasis_open.committees.ebxml_cppa.schema.cpp_cpa_2_0.CollaborationProt import org.oasis_open.committees.ebxml_cppa.schema.cpp_cpa_2_0.EndpointTypeType import org.oasis_open.committees.ebxml_cppa.schema.cpp_cpa_2_0.PerMessageCharacteristicsType import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import kotlin.test.assertContains import kotlin.test.assertEquals -import kotlin.test.assertNotEquals import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.uuid.Uuid @TestInstance(TestInstance.Lifecycle.PER_CLASS) class CPARepoIntegrationTest : PostgresOracleTest() { + val eventRegistrationService = EventRegistrationServiceFake() + private lateinit var cpaRepositoryMock: CPARepository + private lateinit var partnerRepositoryMock: PartnerRepository private fun cpaRepoTestApp(testBlock: suspend ApplicationTestBuilder.() -> T) = testApplication { - val eventRegistrationService = EventRegistrationServiceFake() - application( cpaApplicationModule( postgres.dataSource, @@ -74,6 +89,28 @@ class CPARepoIntegrationTest : PostgresOracleTest() { testBlock() } + private fun validateCpaMockTestApp(testBlock: suspend ApplicationTestBuilder.() -> T) = testApplication { + clearAllMocks() + cpaRepositoryMock = mockk() + partnerRepositoryMock = mockk(relaxed = true) + application(validateCpaApplicationModule(cpaRepositoryMock, partnerRepositoryMock)) + testBlock() + } + + private fun validateCpaApplicationModule( + cpaRepository: CPARepository, + partnerRepository: PartnerRepository + ): Application.() -> Unit { + return { + install(io.ktor.server.plugins.contentnegotiation.ContentNegotiation) { + json() + } + routing { + validateCpa(cpaRepository, partnerRepository, eventRegistrationService) + } + } + } + private val mockOAuth2Server = MockOAuth2Server().also { it.start(port = 3344) } @Test @@ -109,12 +146,8 @@ class CPARepoIntegrationTest : PostgresOracleTest() { json() } } - - val validationRequest = ValidationRequest( - IN, - "e17eb03e-9e43-43fb-874c-1fde9a28c308", - "1234", - "nav:qass:31162", + val response = runValidateCpa( + httpClient, Addressing( Party(listOf(PartyId("HER", "79768")), "Frikortregister"), Party(listOf(PartyId("HER", "8090595")), "Utleverer"), @@ -122,10 +155,6 @@ class CPARepoIntegrationTest : PostgresOracleTest() { "EgenandelForesporsel" ) ) - val response = httpClient.post("/cpa/validate/121212") { - setBody(validationRequest) - contentType(Json) - } val validationResult = response.body() assertNotNull(validationResult) @@ -142,12 +171,8 @@ class CPARepoIntegrationTest : PostgresOracleTest() { json() } } - - val validationRequest = ValidationRequest( - IN, - "e17eb03e-9e43-43fb-874c-1fde9a28c308", - "1234", - "nav:qass:31162", + val response = runValidateCpa( + httpClient, Addressing( Party(listOf(PartyId("HER", "8090595")), "Utleverer"), Party(listOf(PartyId("HER", "79768")), "Frikortregister"), @@ -155,10 +180,6 @@ class CPARepoIntegrationTest : PostgresOracleTest() { "EgenandelForesporsel" ) ) - val response = httpClient.post("/cpa/validate/121212") { - setBody(validationRequest) - contentType(Json) - } val validationResult = response.body() assertNotNull(validationResult) @@ -176,12 +197,8 @@ class CPARepoIntegrationTest : PostgresOracleTest() { json() } } - - val validationRequest = ValidationRequest( - IN, - "e17eb03e-9e43-43fb-874c-1fde9a28c308", - "1234", - "nav:qass:31162", + val response = runValidateCpa( + httpClient, Addressing( Party(listOf(PartyId("HER", "79768")), "KontrollUtbetaler"), Party(listOf(PartyId("HER", "8090595")), "Utleverer"), @@ -189,10 +206,6 @@ class CPARepoIntegrationTest : PostgresOracleTest() { "Oppgjorskrav" ) ) - val response = httpClient.post("/cpa/validate/121212") { - setBody(validationRequest) - contentType(Json) - } val validationResult = response.body() assertNotNull(validationResult) @@ -210,23 +223,16 @@ class CPARepoIntegrationTest : PostgresOracleTest() { json() } } - - val validationRequest = ValidationRequest( - IN, - "e17eb03e-9e43-43fb-874c-1fde9a28c308", - "1234", - "multiple_channels_and_multiple_endpoints", + val response = runValidateCpa( + httpClient, Addressing( Party(listOf(PartyId("HER", "79768")), "KontrollUtbetaler"), Party(listOf(PartyId("HER", "8090595")), "Utleverer"), "OppgjorsKontroll", "Oppgjorskrav" - ) + ), + "multiple_channels_and_multiple_endpoints" ) - val response = httpClient.post("/cpa/validate/121212") { - setBody(validationRequest) - contentType(Json) - } val validationResult = response.body() assertNotNull(validationResult) @@ -289,62 +295,77 @@ class CPARepoIntegrationTest : PostgresOracleTest() { } } - val response = httpClient.get("/cpa/timestamps") { - headers { - header("cpaIds", "nav:qass:35065") - } - } - println("RESPONSE WAS: " + response.bodyAsText()) - - // ingen header gir alle verdier val responseMedAlle = httpClient.get("/cpa/timestamps") - assertContains(responseMedAlle.bodyAsText(), "nav:qass:35065") + assertEquals(HttpStatusCode.OK, responseMedAlle.status) + val responseMapAlle = responseMedAlle.body>() + assertEquals(3, responseMapAlle.size, "Forventer alle tre timestamps") + assertTrue( + responseMapAlle.containsKey("nav:qass:35065"), + "Forventet 'nav:qass:35065' i keys: ${responseMapAlle.keys}" + ) + assertTrue( + responseMapAlle.containsKey("nav:qass:31162"), + "Forventet 'nav:qass:31162' i keys: ${responseMapAlle.keys}" + ) + assertTrue( + responseMapAlle.containsKey("multiple_channels_and_multiple_endpoints"), + "Forventet 'multiple_channels_and_multiple_endpoints' i keys: ${responseMapAlle.keys}" + ) } @Test - fun `Upsert true på CPA`() = cpaRepoTestApp { + fun `Hent cpaId map - med CPA-id i header`() = cpaRepoTestApp { val httpClient = createClient { install(ContentNegotiation) { json() } } - - val response = httpClient.get("/cpa/timestamps") { + val responseMedEn = httpClient.get("/cpa/timestamps") { headers { header("cpaIds", "nav:qass:35065") } } - println("RESPONSE WAS: " + response.bodyAsText()) + assertEquals(HttpStatusCode.OK, responseMedEn.status) + val responseMap1 = responseMedEn.body>() + assertEquals(1, responseMap1.size, "Forventer én timestamp") + assertEquals(setOf("nav:qass:35065"), responseMap1.keys) + } - // ingen header gir alle verdier - val responseMedAlle = httpClient.get("/cpa/timestamps") - assertContains(responseMedAlle.bodyAsText(), "nav:qass:35065") + @Test + fun `Post CPA uten token blir avvist`() = cpaRepoTestApp { + val client = createClient { + install(ContentNegotiation) { + json() + } + } + val response = client.post("/cpa") + assertEquals(HttpStatusCode.Unauthorized, response.status) } - // @Test // TODO fix - fun `Henter latest timestamp`() = cpaRepoTestApp { + @Test + fun `Henter latest updated timestamp`() = cpaRepoTestApp { val httpClient = createClient { install(ContentNegotiation) { json() } + installCpaRepoAuthentication() } - val updatedTimestamp = Instant.now().minus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.SECONDS) - // Putter CPA + // Oppdatér CPA 35065 med gårdagens dato + val updatedTimestamp1 = Instant.now().minus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.SECONDS) + postCpaRequest(httpClient, updatedTimestamp1, "nav-qass-35065.xml") - httpClient.post("/cpa") { - headers { - header("updated_date", updatedTimestamp) - header("upsert", true) - } - setBody( - xmlMarshaller.marshal(loadTestCPA("nav-qass-35065.xml")) - ) - } - // ingen header gir alle verdier - val responseMedAlle = httpClient.get("/cpa/timestamps/latest") + // Oppdatér CPA 31162 med dato forgårs + val updatedTimestamp2 = Instant.now().minus(2, ChronoUnit.DAYS).truncatedTo(ChronoUnit.SECONDS) + postCpaRequest(httpClient, updatedTimestamp2, "nav-qass-31162.xml") + + // Slett den siste CPA'ene slik at vi kun har to CPA'er i databasen + val deleteResponse = httpClient.delete("/cpa/delete/multiple_channels_and_multiple_endpoints") + assertEquals(HttpStatusCode.OK, deleteResponse.status) - assertEquals(updatedTimestamp.toString(), responseMedAlle.bodyAsText()) + // Forvent at update-datoen til CPA 35065 blir returnert + val responseMedSiste = httpClient.get("/cpa/timestamps/latest") + assertEquals(updatedTimestamp1.toString(), responseMedSiste.bodyAsText()) } @Test @@ -353,21 +374,28 @@ class CPARepoIntegrationTest : PostgresOracleTest() { install(ContentNegotiation) { json() } + installCpaRepoAuthentication() } val updatedTimestamp = Instant.now().minus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.SECONDS) - // Putter CPA - httpClient.post("/cpa") { - headers { - header("updated_date", updatedTimestamp) - header("upsert", true) - } - setBody( - xmlMarshaller.marshal(loadTestCPA("nav-qass-35065.xml")) - ) - } - val responseMedAlle = httpClient.get("/cpa/timestamps") - .body>() + postCpaRequest(httpClient, updatedTimestamp, "nav-qass-35065.xml") + + val responseMedAlle = httpClient.get("/cpa/timestamps").body>() assertNotNull(responseMedAlle) + assertEquals( + postgresTestSetup.timestamp.toString(), + responseMedAlle["nav:qass:31162"], + "CPA 31162 skal ha timestamp fra postgresTestSetup" + ) + assertEquals( + updatedTimestamp.toString(), + responseMedAlle["nav:qass:35065"], + "CPA 35065 skal ha timestamp satt av testen" + ) + assertEquals( + postgresTestSetup.timestamp.toString(), + responseMedAlle["multiple_channels_and_multiple_endpoints"], + "CPA multiple_channels_and_multiple_endpoints skal ha timestamp fra postgresTestSetup" + ) } @Test @@ -472,14 +500,13 @@ class CPARepoIntegrationTest : PostgresOracleTest() { val response = httpClient.get("/whoami") { header( "Authorization", - "Bearer " + - token.serialize() + "Bearer " + token.serialize() ) } assertTrue(response.bodyAsText().contains("Gyldig")) } - // @Test + @Test fun `Delete CPA without token is rejected`() = cpaRepoTestApp { val client = createClient { install(ContentNegotiation) { @@ -487,7 +514,7 @@ class CPARepoIntegrationTest : PostgresOracleTest() { } } val response = client.delete("/cpa/delete/nav:qass:35065") - assertNotEquals("nav:qass:35065 slettet!", response.bodyAsText()) + assertEquals(HttpStatusCode.Unauthorized, response.status) } @Test @@ -499,9 +526,126 @@ class CPARepoIntegrationTest : PostgresOracleTest() { installCpaRepoAuthentication() } val response = c.delete("/cpa/delete/nav:qass:35065") + assertEquals(HttpStatusCode.OK, response.status) assertEquals("nav:qass:35065 slettet!", response.bodyAsText()) } + @Test + fun `validateCpa should update CPA's last used date`() = cpaRepoTestApp { + val httpClient = createClient { + install(ContentNegotiation) { + json() + } + } + + var lastUsedMap = getLastUsedMap(httpClient) + assertNull(lastUsedMap["nav:qass:31162"]) // Never used + + runValidateCpa( + httpClient, + Addressing( + Party(listOf(PartyId("HER", "79768")), "KontrollUtbetaler"), + Party(listOf(PartyId("HER", "8090595")), "Utleverer"), + "OppgjorsKontroll", + "Oppgjorskrav" + ) + ) + + lastUsedMap = getLastUsedMap(httpClient) + val cpaLastUsed = lastUsedMap["nav:qass:31162"] + assertNotNull(cpaLastUsed) // Now it's been used + + val today = LocalDateTime.ofInstant(Instant.now(), OSLO_ZONE) + val cpaLastUsedTimestamp = LocalDateTime.parse(cpaLastUsed, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + + assertEquals(today.year, cpaLastUsedTimestamp.year) + assertEquals(today.month, cpaLastUsedTimestamp.month) + assertEquals(today.dayOfMonth, cpaLastUsedTimestamp.dayOfMonth) + } + + @Test + fun `last used should be null after CPA update`() = cpaRepoTestApp { + val httpClient = createClient { + install(ContentNegotiation) { + json() + } + installCpaRepoAuthentication() + } + // ValidateCpa sets the lastUsed timestamp + runValidateCpa( + httpClient, + Addressing( + Party(listOf(PartyId("HER", "79768")), "KontrollUtbetaler"), + Party(listOf(PartyId("HER", "8090595")), "Utleverer"), + "OppgjorsKontroll", + "Oppgjorskrav" + ) + ) + + // Verify that lastUsed is set + var lastUsedMap = getLastUsedMap(httpClient) + val cpaLastUsed = lastUsedMap["nav:qass:31162"] + assertNotNull(cpaLastUsed) + val timezone = ZoneId.of("Europe/Oslo") + val today = LocalDateTime.ofInstant(Instant.now(), timezone) + val cpaLastUsedTimestamp = LocalDateTime.parse(cpaLastUsed, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + assertEquals(today.dayOfYear, cpaLastUsedTimestamp.dayOfYear) + + // Update the CPA (resets the lastUsed timestamp) + val updatedTimestamp = Instant.now() + postCpaRequest(httpClient, updatedTimestamp, "nav-qass-31162.xml") + + // Verify that lastUsed is null now + lastUsedMap = getLastUsedMap(httpClient) + assertNull(lastUsedMap["nav:qass:31162"], "CPA 31162 should be null again after an update") + } + + @Test + fun `validateCpa should only call CPARepository_updateCpaLastUsed once pr day`() = validateCpaMockTestApp { + val httpClient = createClient { + install(ContentNegotiation) { + json() + } + } + val cpaId = "nav:qass:31162" + val cpa = loadTestCPA("nav-qass-31162.xml") + val timestamp = Instant.now() + val firstResult = Pair(cpa, timestamp.minus(1, ChronoUnit.DAYS)) // Last used: Yesterday + val secondResult = Pair(cpa, timestamp.minus(1, ChronoUnit.HOURS)) // Last used: An hour ago + val thirdResult = Pair(cpa, timestamp.minus(2, ChronoUnit.HOURS)) // Last used: Two hours ago + val processConfig = ProcessConfig( + kryptering = false, + komprimering = false, + signering = false, + internformat = false, + validering = false, + apprec = false, + ocspSjekk = false, + juridiskLogg = false, + adapter = null, + errorAction = null + ) + + coEvery { cpaRepositoryMock.findCpaAndLastUsed(cpaId) } returnsMany listOf(firstResult, secondResult, thirdResult) + coEvery { cpaRepositoryMock.updateCpaLastUsed(cpaId) } returns true + coEvery { partnerRepositoryMock.findPartnerId(cpaId) } returns 12345L + coEvery { cpaRepositoryMock.getProcessConfig(any(), any(), any()) } returns processConfig + + val addressing = Addressing( + Party(listOf(PartyId("HER", "79768")), "KontrollUtbetaler"), + Party(listOf(PartyId("HER", "8090595")), "Utleverer"), + "OppgjorsKontroll", + "Oppgjorskrav" + ) + + runValidateCpa(httpClient, addressing) + runValidateCpa(httpClient, addressing) + runValidateCpa(httpClient, addressing) + + coVerify(exactly = 3) { cpaRepositoryMock.findCpaAndLastUsed(cpaId) } + coVerify(exactly = 1) { cpaRepositoryMock.updateCpaLastUsed(cpaId) } + } + suspend fun getCpaRepoToken(): BearerTokens { val requestBody = "client_id=" + getEnvVar("AZURE_APP_CLIENT_ID", "cpa-repo") + @@ -551,4 +695,45 @@ class CPARepoIntegrationTest : PostgresOracleTest() { val testCpaString = String(this::class.java.classLoader.getResource("cpa/$cpaName").readBytes()) return xmlMarshaller.unmarshal(testCpaString, CollaborationProtocolAgreement::class.java) } + + private suspend fun runValidateCpa(httpClient: HttpClient, addressing: Addressing, cpaId: String = "nav:qass:31162"): HttpResponse { + val validationRequest = ValidationRequest( + IN, + "e17eb03e-9e43-43fb-874c-1fde9a28c308", + "1234", + cpaId = cpaId, + addressing + ) + val validateResponse = httpClient.post("/cpa/validate/121212") { + setBody(validationRequest) + contentType(Json) + } + assertEquals(HttpStatusCode.OK, validateResponse.status, "Expected OK status for validate") + return validateResponse + } + + private suspend fun postCpaRequest( + httpClient: HttpClient, + updatedTimestamp: Instant, + filename: String = "nav-qass-31162.xml", + expectedStatusCode: HttpStatusCode = HttpStatusCode.OK + ): HttpResponse { + val postResponse = httpClient.post("/cpa") { + headers { + header("updated_date", updatedTimestamp) + header("upsert", true) + } + setBody( + xmlMarshaller.marshal(loadTestCPA(filename)) + ) + } + assertEquals(expectedStatusCode, postResponse.status, "Expected ${expectedStatusCode.description} status for validate") + return postResponse + } + + private suspend fun getLastUsedMap(httpClient: HttpClient): Map { + val lastUsedResponse = httpClient.get("/cpa/timestamps/last_used") + assertEquals(HttpStatusCode.OK, lastUsedResponse.status, "Expected OK status for /cpa/timestamps/last_used") + return lastUsedResponse.body>() + } } diff --git a/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/HelseIdTokenValidator.kt b/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/HelseIdTokenValidator.kt index 5b31cc7b8..688736aa5 100644 --- a/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/HelseIdTokenValidator.kt +++ b/ebms-payload/src/main/kotlin/no/nav/emottak/payload/helseid/HelseIdTokenValidator.kt @@ -21,10 +21,10 @@ import no.nav.emottak.payload.configuration.config import no.nav.emottak.payload.helseid.util.OpenIdConfigProvider import no.nav.emottak.payload.helseid.util.XPathEvaluator import no.nav.emottak.payload.helseid.util.msgHeadNamespaceContext +import no.nav.emottak.util.OSLO_ZONE import org.w3c.dom.Document import java.text.ParseException import java.time.Instant -import java.time.ZoneId import java.time.format.DateTimeFormatter import java.util.Base64 import java.util.Date @@ -149,7 +149,6 @@ class HelseIdTokenValidator( private fun timePrefix(date: Date): String = DATE_FMT.format(date.toInstant()) companion object { - private val OSLO_ZONE: ZoneId = ZoneId.of("Europe/Oslo") private val DATE_FMT: DateTimeFormatter = DateTimeFormatter .ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US) diff --git a/felles/src/main/kotlin/no/nav/emottak/util/DateTimeUtil.kt b/felles/src/main/kotlin/no/nav/emottak/util/DateTimeUtil.kt new file mode 100644 index 000000000..5ead17d61 --- /dev/null +++ b/felles/src/main/kotlin/no/nav/emottak/util/DateTimeUtil.kt @@ -0,0 +1,16 @@ +package no.nav.emottak.util + +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId + +fun Instant?.isToday() = + if (null == this) { + false + } else { + val today = LocalDateTime.ofInstant(Instant.now(), OSLO_ZONE) + val cpaLastUsed = LocalDateTime.ofInstant(this, OSLO_ZONE) + today.dayOfYear == cpaLastUsed.dayOfYear + } + +val OSLO_ZONE: ZoneId = ZoneId.of("Europe/Oslo") From 7b9b2e88f2d601c8849520e14eb2c0a66dbdf07f Mon Sep 17 00:00:00 2001 From: terjenilssen Date: Mon, 17 Nov 2025 16:13:21 +0100 Subject: [PATCH 5/6] Migrert fra jetbrains exposed v0.47.0 til v1.0.0-rc-3. Endret CPARepository.updateOrInsert() slik at CPA.lastUsed ikke blir satt til null ved update. --- .../no/nav/emottak/cpa/persistence/CPA.kt | 26 +++++++++------- .../emottak/cpa/persistence/CPARepository.kt | 31 ++++++++++++------- .../nav/emottak/cpa/persistence/Database.kt | 2 +- .../cpa/persistence/ProcessConfigTable.kt | 4 +-- .../emottak/cpa/persistence/gammel/PARTNER.kt | 2 +- .../persistence/gammel/PartnerRepository.kt | 5 +-- .../nav/emottak/cpa/CPARepoIntegrationTest.kt | 30 ++++++++++++++---- .../nav/emottak/cpa/PartnerIntegrationTest.kt | 6 ++-- .../databasetest/setup/PostgresTestSetup.kt | 6 ++-- .../ebms/async/persistence/Database.kt | 2 +- .../repository/PayloadRepository.kt | 6 ++-- .../async/persistence/table/PayloadTable.kt | 9 +++--- ebms-payload/build.gradle.kts | 4 +-- settings.gradle.kts | 6 +++- 14 files changed, 87 insertions(+), 52 deletions(-) diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPA.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPA.kt index 7f96e0f83..b15957b22 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPA.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPA.kt @@ -2,10 +2,10 @@ package no.nav.emottak.cpa.persistence import no.nav.emottak.cpa.marshal import no.nav.emottak.cpa.unmarshal -import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.ColumnType -import org.jetbrains.exposed.sql.Table -import org.jetbrains.exposed.sql.javatime.timestamp +import org.jetbrains.exposed.v1.core.Column +import org.jetbrains.exposed.v1.core.ColumnType +import org.jetbrains.exposed.v1.core.Table +import org.jetbrains.exposed.v1.javatime.timestamp import org.oasis_open.committees.ebxml_cppa.schema.cpp_cpa_2_0.CollaborationProtocolAgreement object CPA : Table("cpa") { @@ -19,23 +19,27 @@ object CPA : Table("cpa") { fun Table.json( name: String, - clazz: Class + clazz: Class, + nullable: Boolean = false ): Column = registerColumn( name = name, - type = JsonColumnType(clazz) + type = JsonColumnType(clazz, nullable) ) -class JsonColumnType(private val clazz: Class) : ColumnType() { - override fun sqlType(): String = - "json" +class JsonColumnType( + private val clazz: Class, + nullable: Boolean +) : ColumnType(nullable) { + override fun sqlType(): String = "json" override fun valueFromDB(value: Any): T = unmarshal(value as String, clazz) - override fun notNullValueToDB(value: Any): String = marshal(value) + override fun notNullValueToDB(value: T): String = marshal(value) - override fun valueToString(value: Any?): String = + override fun valueToString(value: T?): String = when (value) { + null -> if (nullable) "NULL" else error("Null value for non‑nullable column") is Iterable<*> -> notNullValueToDB(value) else -> super.valueToString(value) } diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt index a55468a2f..d79c2edbf 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/CPARepository.kt @@ -10,15 +10,18 @@ import no.nav.emottak.message.ebxml.EbXMLConstants.MESSAGE_ERROR_ACTION import no.nav.emottak.message.ebxml.PartyTypeEnum import no.nav.emottak.message.model.ProcessConfig import no.nav.emottak.utils.environment.isProdEnv -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteAll -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update -import org.jetbrains.exposed.sql.upsert +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.core.isNotNull +import org.jetbrains.exposed.v1.jdbc.deleteAll +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update +import org.jetbrains.exposed.v1.jdbc.upsert import org.oasis_open.committees.ebxml_cppa.schema.cpp_cpa_2_0.CollaborationProtocolAgreement import java.time.Instant import java.time.temporal.ChronoUnit @@ -84,13 +87,17 @@ class CPARepository(val database: Database) { fun updateOrInsert(cpa: CpaDbEntry): String { transaction(database.db) { - CPA.upsert(CPA.id) { + cpa.cpa ?: throw IllegalArgumentException("Kan ikke sette null verdi for CPA i DB") + CPA.upsert( + CPA.id, + onUpdateExclude = listOf(CPA.lastUsed) + ) { it[id] = cpa.id - it[CPA.cpa] = cpa.cpa ?: throw IllegalArgumentException("Kan ikke sette null verdi for CPA i DB") + it[CPA.cpa] = cpa.cpa it[entryCreated] = cpa.createdDate it[updated_date] = cpa.updatedDate it[herId] = cpa.herId - it[lastUsed] = cpa.lastUsed + it[lastUsed] = null } } return cpa.id diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/Database.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/Database.kt index 1c439ac7c..2c1e49a07 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/Database.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/Database.kt @@ -4,7 +4,7 @@ import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource import no.nav.emottak.utils.environment.getEnvVar import org.flywaydb.core.Flyway -import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.v1.jdbc.Database class Database( dbConfig: HikariConfig diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/ProcessConfigTable.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/ProcessConfigTable.kt index d31e81c07..0a2971835 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/ProcessConfigTable.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/ProcessConfigTable.kt @@ -1,7 +1,7 @@ package no.nav.emottak.cpa.persistence -import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.v1.core.Column +import org.jetbrains.exposed.v1.core.Table object ProcessConfigTable : Table("process_config") { val role: Column = varchar("role", 50) diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/gammel/PARTNER.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/gammel/PARTNER.kt index ab0f15c7a..8717ffd3e 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/gammel/PARTNER.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/gammel/PARTNER.kt @@ -1,6 +1,6 @@ package no.nav.emottak.cpa.persistence.gammel -import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.v1.core.Table object PARTNER : Table("PARTNER") { val partnerId = ulong("PARTNER_ID") diff --git a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/gammel/PartnerRepository.kt b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/gammel/PartnerRepository.kt index 066cb5bf2..244a47a9b 100644 --- a/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/gammel/PartnerRepository.kt +++ b/cpa-repo/src/main/kotlin/no/nav/emottak/cpa/persistence/gammel/PartnerRepository.kt @@ -3,8 +3,9 @@ package no.nav.emottak.cpa.persistence.gammel import no.nav.emottak.cpa.feil.MultiplePartnerException import no.nav.emottak.cpa.feil.PartnerNotFoundException import no.nav.emottak.cpa.persistence.Database -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction class PartnerRepository(val database: Database) { diff --git a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/CPARepoIntegrationTest.kt b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/CPARepoIntegrationTest.kt index d741087a8..0fb5f37d2 100644 --- a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/CPARepoIntegrationTest.kt +++ b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/CPARepoIntegrationTest.kt @@ -564,13 +564,21 @@ class CPARepoIntegrationTest : PostgresOracleTest() { } @Test - fun `last used should be null after CPA update`() = cpaRepoTestApp { + fun `last used should not be null after CPA update`() = cpaRepoTestApp { val httpClient = createClient { install(ContentNegotiation) { json() } installCpaRepoAuthentication() } + + var response = httpClient.get("/cpa/nav:qass:31162") + assertEquals(HttpStatusCode.OK, response.status) + assertTrue( + StringUtils.isNotBlank(response.bodyAsText()), + "Response can't be null or blank" + ) + // ValidateCpa sets the lastUsed timestamp runValidateCpa( httpClient, @@ -588,16 +596,26 @@ class CPARepoIntegrationTest : PostgresOracleTest() { assertNotNull(cpaLastUsed) val timezone = ZoneId.of("Europe/Oslo") val today = LocalDateTime.ofInstant(Instant.now(), timezone) - val cpaLastUsedTimestamp = LocalDateTime.parse(cpaLastUsed, DateTimeFormatter.ISO_OFFSET_DATE_TIME) - assertEquals(today.dayOfYear, cpaLastUsedTimestamp.dayOfYear) + val cpaLastUsedTimestamp1 = LocalDateTime.parse(cpaLastUsed, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + assertEquals(today.dayOfYear, cpaLastUsedTimestamp1.dayOfYear) - // Update the CPA (resets the lastUsed timestamp) + // Update the CPA val updatedTimestamp = Instant.now() postCpaRequest(httpClient, updatedTimestamp, "nav-qass-31162.xml") - // Verify that lastUsed is null now + // Verify that lastUsed is not null, and is the same timestamp as previously lastUsedMap = getLastUsedMap(httpClient) - assertNull(lastUsedMap["nav:qass:31162"], "CPA 31162 should be null again after an update") + assertNotNull(lastUsedMap["nav:qass:31162"], "CPA 31162 should not be null after an update") + val cpaLastUsedTimestamp2 = LocalDateTime.parse(cpaLastUsed, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + assertEquals(cpaLastUsedTimestamp1, cpaLastUsedTimestamp2) + + // Verify that we still can retrieve the CPA + response = httpClient.get("/cpa/nav:qass:31162") + assertEquals(HttpStatusCode.OK, response.status) + assertTrue( + StringUtils.isNotBlank(response.bodyAsText()), + "Response can't be null or blank" + ) } @Test diff --git a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/PartnerIntegrationTest.kt b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/PartnerIntegrationTest.kt index 059d537fb..f0deac792 100644 --- a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/PartnerIntegrationTest.kt +++ b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/PartnerIntegrationTest.kt @@ -10,9 +10,9 @@ import io.ktor.server.testing.testApplication import no.nav.emottak.cpa.databasetest.PostgresOracleTest import no.nav.emottak.cpa.persistence.gammel.PARTNER_CPA import no.nav.emottak.cpa.util.EventRegistrationServiceFake -import org.jetbrains.exposed.sql.deleteAll -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.deleteAll +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import kotlin.test.BeforeTest diff --git a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/databasetest/setup/PostgresTestSetup.kt b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/databasetest/setup/PostgresTestSetup.kt index ac167cdb8..57c6a055c 100644 --- a/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/databasetest/setup/PostgresTestSetup.kt +++ b/cpa-repo/src/test/kotlin/no/nav/emottak/cpa/databasetest/setup/PostgresTestSetup.kt @@ -8,9 +8,9 @@ import no.nav.emottak.cpa.persistence.Database import no.nav.emottak.cpa.xmlMarshaller import no.nav.emottak.message.ebxml.PartyTypeEnum import org.flywaydb.core.Flyway -import org.jetbrains.exposed.sql.deleteAll -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.deleteAll +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.oasis_open.committees.ebxml_cppa.schema.cpp_cpa_2_0.CollaborationProtocolAgreement import org.testcontainers.containers.PostgreSQLContainer import java.time.Instant diff --git a/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/Database.kt b/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/Database.kt index ddd9b9473..5f4b20c70 100644 --- a/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/Database.kt +++ b/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/Database.kt @@ -4,7 +4,7 @@ import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource import no.nav.emottak.utils.environment.getEnvVar import org.flywaydb.core.Flyway -import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.v1.jdbc.Database class Database( dbConfig: HikariConfig diff --git a/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/repository/PayloadRepository.kt b/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/repository/PayloadRepository.kt index 1d554335f..2a88c9d07 100644 --- a/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/repository/PayloadRepository.kt +++ b/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/repository/PayloadRepository.kt @@ -5,8 +5,10 @@ import no.nav.emottak.ebms.async.persistence.table.PayloadTable import no.nav.emottak.ebms.async.persistence.table.PayloadTable.contentId import no.nav.emottak.ebms.async.persistence.table.PayloadTable.referenceId import no.nav.emottak.message.model.AsyncPayload -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.upsert +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.upsert import kotlin.uuid.Uuid import kotlin.uuid.toJavaUuid import kotlin.uuid.toKotlinUuid diff --git a/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt b/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt index 1818a8e6b..f25e94c8d 100644 --- a/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt +++ b/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt @@ -1,9 +1,8 @@ package no.nav.emottak.ebms.async.persistence.table -import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.Table -import org.jetbrains.exposed.sql.javatime.CurrentTimestamp -import org.jetbrains.exposed.sql.javatime.timestamp +import org.jetbrains.exposed.v1.core.Column +import org.jetbrains.exposed.v1.core.Table +import org.jetbrains.exposed.v1.javatime.timestamp import java.time.Instant import java.util.UUID @@ -12,7 +11,7 @@ object PayloadTable : Table("payload") { val contentId: Column = varchar("content_id", 256) val contentType: Column = varchar("content_type", 256) val content: Column = binary("content") - val contentAt: Column = timestamp("created_at").defaultExpression(CurrentTimestamp()) + val contentAt: Column = timestamp("created_at") //.defaultExpression(CurrentTimestamp()) override val primaryKey = PrimaryKey(referenceId, contentId) } diff --git a/ebms-payload/build.gradle.kts b/ebms-payload/build.gradle.kts index e896c26e9..886f98604 100644 --- a/ebms-payload/build.gradle.kts +++ b/ebms-payload/build.gradle.kts @@ -56,8 +56,8 @@ dependencies { implementation(libs.emottak.payload.xsd) implementation(libs.emottak.utils) implementation("net.sf.saxon:Saxon-HE:12.7") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.7.1-2") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.7.1") + implementation(libs.jackson.module.kotlin) + implementation(libs.jackson.dataformat.yaml) runtimeOnly("net.java.dev.jna:jna:5.12.1") testImplementation(testLibs.junit.jupiter.api) diff --git a/settings.gradle.kts b/settings.gradle.kts index 97dd66dfd..52a0a2d05 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,7 +14,7 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { version("bouncycastle", "1.76") - version("exposed", "0.47.0") + version("exposed", "1.0.0-rc-3") version("ktor", "3.0.3") version("token-validation-ktor", "5.0.15") version("arrow", "1.2.4") @@ -23,6 +23,7 @@ dependencyResolutionManagement { version("hoplite", "2.8.2") version("logback", "1.5.17") version("logstash", "8.0") + version("fasterxml-jackson", "2.18.2") version("emottak-utils", "0.3.5") library("bcpkix-jdk18on", "org.bouncycastle", "bcpkix-jdk18on").versionRef("bouncycastle") @@ -65,6 +66,9 @@ dependencyResolutionManagement { library("kotlin-kafka", "io.github.nomisrev", "kotlin-kafka").versionRef("kotlin-kafka") + library("jackson-module-kotlin", "com.fasterxml.jackson.module", "jackson-module-kotlin").versionRef("fasterxml-jackson") + library("jackson-dataformat-yaml", "com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml").versionRef("fasterxml-jackson") + library("ebxml-protokoll", "no.nav.emottak:ebxml-protokoll:0.0.6") library("emottak-payload-xsd", "no.nav.emottak:emottak-payload-xsd:0.0.9") library("emottak-utils", "no.nav.emottak", "emottak-utils").versionRef("emottak-utils") From 3776c586a2c4b4e1412aea1174e2b2e8f4638267 Mon Sep 17 00:00:00 2001 From: terjenilssen Date: Mon, 17 Nov 2025 16:18:39 +0100 Subject: [PATCH 6/6] ktlintFormat --- .../no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt b/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt index f25e94c8d..69e5d933c 100644 --- a/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt +++ b/ebms-async/src/main/kotlin/no/nav/emottak/ebms/async/persistence/table/PayloadTable.kt @@ -11,7 +11,7 @@ object PayloadTable : Table("payload") { val contentId: Column = varchar("content_id", 256) val contentType: Column = varchar("content_type", 256) val content: Column = binary("content") - val contentAt: Column = timestamp("created_at") //.defaultExpression(CurrentTimestamp()) + val contentAt: Column = timestamp("created_at") // .defaultExpression(CurrentTimestamp()) override val primaryKey = PrimaryKey(referenceId, contentId) }