diff --git a/.github/workflows/dependency-review-pr.yaml b/.github/workflows/dependency-review-pr.yaml index 4ad6500..5236183 100644 --- a/.github/workflows/dependency-review-pr.yaml +++ b/.github/workflows/dependency-review-pr.yaml @@ -18,7 +18,7 @@ jobs: - name: Dependency submission uses: gradle/actions/dependency-submission@v4 - name: Dependency review - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@v4.3.3 with: comment-summary-in-pr: true - allow-licenses: MIT, Apache-2.0 + allow-licenses: MIT, Apache-2.0, Apache-2.0 AND BSD-3-Clause diff --git a/build.gradle.kts b/build.gradle.kts index e60a742..61f171e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,9 +56,6 @@ dependencies { // OpenAPI implementation(libs.kompendium.core) - - testImplementation(libs.kotlin.test) - testImplementation(libs.assertj) } tasks { @@ -84,6 +81,15 @@ testing { useJUnitJupiter() } + named("test") { + dependencies { + implementation(libs.kotlin.test) + implementation(libs.assertk) + implementation(libs.mockk) + implementation(testFixtures(project(":matrikkel-api"))) + } + } + register("integrationTest") { dependencies { implementation(project()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fab8b5c..647c9c6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,8 @@ jaxwsVersion = "4.0.3" hikariVersion = "5.1.0" assertjVersion = "3.26.3" testcontainersVersion = "1.20.1" +mockkVersion = "1.13.12" +assertkVersion = "0.28.1" [libraries] ktor-server-core = { group = "io.ktor", name = "ktor-server-core-jvm", version.ref = "ktorVersion" } @@ -45,6 +47,9 @@ jaxws-tools = { group = "com.sun.xml.ws", name = "jaxws-tools", version.ref = "j assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertjVersion" } testcontainers-postgresql = { group = "org.testcontainers", name = "postgresql", version.ref = "testcontainersVersion" } +mockk = { group = "io.mockk", name = "mockk", version.ref = "mockkVersion" } +mockk-dsl = { group = "io.mockk", name = "mockk-dsl", version.ref = "mockkVersion" } +assertk = { group = "com.willowtreeapps.assertk", name = "assertk", version.ref = "assertkVersion" } [plugins] kotlin-jvm = { id = "jvm", version.ref = "kotlinVersion" } diff --git a/matrikkel-api/build.gradle.kts b/matrikkel-api/build.gradle.kts index 3c1220d..1bd8ce3 100644 --- a/matrikkel-api/build.gradle.kts +++ b/matrikkel-api/build.gradle.kts @@ -2,6 +2,7 @@ import no.kartverket.matrikkel.bygning.gradle.wsimport.WsImportTask plugins { `java-library` + `java-test-fixtures` kotlin("jvm") idea } @@ -16,6 +17,8 @@ dependencies { exclude(group = "org.eclipse.angus") // Ekskluderer angus email } + testFixturesApi(libs.mockk.dsl) + jaxws(libs.jaxws.tools) } diff --git a/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/Ids.kt b/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/Ids.kt new file mode 100644 index 0000000..064759a --- /dev/null +++ b/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/Ids.kt @@ -0,0 +1,8 @@ +package no.kartverket.matrikkel.bygning.matrikkelapi + +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.BruksenhetId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.BygningId + +fun bygningId(value: Long): BygningId = BygningId().apply { this.value = value } + +fun bruksenhetId(value: Long): BruksenhetId = BruksenhetId().apply { this.value = value } diff --git a/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/MatrikkelApi.kt b/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/MatrikkelApi.kt index 6d3d406..b7ccc91 100644 --- a/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/MatrikkelApi.kt +++ b/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/MatrikkelApi.kt @@ -2,9 +2,6 @@ package no.kartverket.matrikkel.bygning.matrikkelapi import jakarta.xml.ws.BindingProvider import jakarta.xml.ws.handler.MessageContext -import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelBubbleId -import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelBubbleIdList -import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelBubbleObject import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelContext import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.geometri.koder.KoordinatsystemKodeId import no.statkart.matrikkel.matrikkelapi.wsapi.v1.service.bygning.BygningService @@ -95,20 +92,3 @@ class MatrikkelApi(private val baseUrl: URI) { } } } - -// TODO Kanskje finne et eget sted for disse -inline fun StoreService.getObjectAs( - id: MatrikkelBubbleId?, - matrikkelContext: MatrikkelContext, -): T { - val obj = this.getObject(id, matrikkelContext) - return T::class.java.cast(obj) -} - -inline fun StoreService.getObjectsAs( - ids: List, - matrikkelContext: MatrikkelContext, -): List { - val objects = this.getObjects(MatrikkelBubbleIdList().apply { item.addAll(ids) }, matrikkelContext) - return objects.item.filterIsInstance().map { T::class.java.cast(it) } -} diff --git a/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/SafeStore.kt b/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/SafeStore.kt new file mode 100644 index 0000000..ce797de --- /dev/null +++ b/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/SafeStore.kt @@ -0,0 +1,18 @@ +package no.kartverket.matrikkel.bygning.matrikkelapi + +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelBubbleIdList +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelContext +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.Bruksenhet +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.BruksenhetId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.Bygning +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.BygningId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.service.store.StoreService + +fun StoreService.getBygning(id: BygningId, matrikkelContext: MatrikkelContext): Bygning { + return getObject(id, matrikkelContext) as Bygning +} + +fun StoreService.getBruksenheter(ids: Iterable, matrikkelContext: MatrikkelContext): List { + val objects = getObjects(MatrikkelBubbleIdList().apply { item.addAll(ids) }, matrikkelContext) + return objects.item.map { it as Bruksenhet } +} diff --git a/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/XmlDate.kt b/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/XmlDate.kt new file mode 100644 index 0000000..024e51b --- /dev/null +++ b/matrikkel-api/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/XmlDate.kt @@ -0,0 +1,9 @@ +package no.kartverket.matrikkel.bygning.matrikkelapi + +import java.time.Instant +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.Timestamp as MatrikkelTimestamp + +fun MatrikkelTimestamp.toInstant(): Instant { + val calendar = timestamp.toGregorianCalendar() // TODO: Må sjekke litt mer på hvor trygt dette er + return calendar.toInstant() +} diff --git a/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/BruksenhetBuilder.kt b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/BruksenhetBuilder.kt new file mode 100644 index 0000000..cb0aff6 --- /dev/null +++ b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/BruksenhetBuilder.kt @@ -0,0 +1,16 @@ +package no.kartverket.matrikkel.bygning.matrikkelapi.builders + +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.Bruksenhet + +fun bruksenhet(scope: Bruksenhet.() -> Unit) = Bruksenhet() + .apply { + // Fyll inn verdier som aldri vil være null i matrikkelen, bortsett fra de man alltid bør spesifisere eksplisitt i testen. + antallRom = 0 + antallBad = 0 + antallWC = 0 + bruksareal = 0.0 + kjokkentilgangId = MatrikkelKjokkentilgangKode.IkkeOppgitt() + isSkalUtga = false + isByggSkjermingsverdig = false + } + .apply(scope) diff --git a/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/BygningBuilder.kt b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/BygningBuilder.kt new file mode 100644 index 0000000..3aa53fe --- /dev/null +++ b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/BygningBuilder.kt @@ -0,0 +1,46 @@ +package no.kartverket.matrikkel.bygning.matrikkelapi.builders + +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.BruksenhetId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.BruksenhetIdList +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.Bygning +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.Etasje +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.EtasjeList +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.Etasjedata +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.EnergikildeKodeIdList +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.OppvarmingsKodeIdList + +fun bygning(scope: Bygning.() -> Unit): Bygning = Bygning() + .apply { + // Fyll inn verdier som aldri vil være null i matrikkelen. + etasjedata = Etasjedata() + etasjedata.antallBoenheter = 0 + etasjedata.bruksarealTilBolig = 0.0 + etasjedata.bruksarealTilAnnet = 0.0 + etasjedata.bruksarealTotalt = 0.0 + etasjedata.alternativtAreal = 0.0 + etasjedata.alternativtAreal2 = 0.0 + etasjedata.bruttoarealTilBolig = 0.0 + etasjedata.bruttoarealTilAnnet = 0.0 + etasjedata.bruttoarealTotalt = 0.0 + } + .apply(scope) + .apply { + // Fyller inn tomme lister hvis det ikke har blitt fylt inn noe annet. + if (bruksenhetIds == null) bruksenhetIds = BruksenhetIdList() + if (energikildeKodeIds == null) energikildeKodeIds = EnergikildeKodeIdList() + if (oppvarmingsKodeIds == null) oppvarmingsKodeIds = OppvarmingsKodeIdList() + } + +fun Bygning.bruksenhetIds(vararg bruksenhetIds: BruksenhetId) { + this.bruksenhetIds = BruksenhetIdList().apply { + item.addAll(bruksenhetIds) + } +} + +fun Bygning.etasjer(vararg etasjer: Etasje) { + this.etasjer = EtasjeList().apply { + item.addAll(etasjer) + } +} + +fun etasje(scope: Etasje.() -> Unit): Etasje = Etasje().apply(scope) diff --git a/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/DatoBuilders.kt b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/DatoBuilders.kt new file mode 100644 index 0000000..8e71e96 --- /dev/null +++ b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/DatoBuilders.kt @@ -0,0 +1,10 @@ +package no.kartverket.matrikkel.bygning.matrikkelapi.builders + +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.Timestamp +import javax.xml.datatype.DatatypeFactory + +private val datatypeFactory by lazy { DatatypeFactory.newDefaultInstance() } + +fun timestampUtc(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0) = Timestamp().apply { + this.timestamp = datatypeFactory.newXMLGregorianCalendar(year, month, day, hour, minute, second, 0, 0) +} diff --git a/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/EnumIds.kt b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/EnumIds.kt new file mode 100644 index 0000000..28ab0ae --- /dev/null +++ b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/EnumIds.kt @@ -0,0 +1,70 @@ +@file:Suppress("unused") + +package no.kartverket.matrikkel.bygning.matrikkelapi.builders + +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.AvlopsKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.EnergikildeKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.EtasjeplanKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.KjokkentilgangKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.OppvarmingsKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.VannforsyningsKodeId + +enum class MatrikkelEtasjeplanKode(private val idValue: Long) { + IkkeOppgitt(0), + Hovedetasje(1), + Kjelleretasje(2), + Loft(3), + Underetasje(4); + + operator fun invoke() = EtasjeplanKodeId().apply { value = idValue } +} + +enum class MatrikkelVannforsyningKode(private val idValue: Long) { + IkkeOppgitt(0), + TilknyttetOffVannverk(1), + TilknyttetPrivatVannverk(2), + AnnenPrivatInnlagt(3), + AnnenPrivatIkkeInnlagt(4); + + operator fun invoke() = VannforsyningsKodeId().apply { value = idValue } +} + +enum class MatrikkelAvlopKode(private val idValue: Long) { + IkkeOppgitt(0), + OffentligKloakk(1), + PrivatKloakk(2), + IngenKloakk(3); + + operator fun invoke() = AvlopsKodeId().apply { value = idValue } +} + +enum class MatrikkelEnergikildeKode(private val idValue: Long) { + Elektrisitet(0), + OljeParafin(1), + Biobrensel(2), + Solenergi(3), + Varmepumpe(4), + Gass(5), + Fjernvarme(6), + AnnenEnergiKilde(7); + + operator fun invoke() = EnergikildeKodeId().apply { value = idValue } +} + +enum class MatrikkelOppvarmingKode(private val idValue: Long) { + Elektrisk(0), + Sentralvarme(1), + AnnenOppvarming(2); + + operator fun invoke() = OppvarmingsKodeId().apply { value = idValue } +} + +enum class MatrikkelKjokkentilgangKode(private val idValue: Long) { + IkkeOppgitt(0), + Kjokken(1), + IkkeKjokken(2), + FellesKjokken(3), + Ukjent(4); + + operator fun invoke() = KjokkentilgangKodeId().apply { value = idValue } +} diff --git a/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/ListBuilders.kt b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/ListBuilders.kt new file mode 100644 index 0000000..1da8657 --- /dev/null +++ b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/builders/ListBuilders.kt @@ -0,0 +1,20 @@ +package no.kartverket.matrikkel.bygning.matrikkelapi.builders + +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelBubbleObject +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelBubbleObjectList +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.EnergikildeKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.EnergikildeKodeIdList +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.OppvarmingsKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.OppvarmingsKodeIdList + +fun matrikkelBubbleObjectList(vararg bubbles: MatrikkelBubbleObject) = MatrikkelBubbleObjectList().apply { + item.addAll(bubbles) +} + +fun energikildeKodeIdList(vararg ids: EnergikildeKodeId) = EnergikildeKodeIdList().apply { + item.addAll(ids) +} + +fun oppvarmingsKodeIdList(vararg ids: OppvarmingsKodeId) = OppvarmingsKodeIdList().apply { + item.addAll(ids) +} diff --git a/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/matchers/IdMatchers.kt b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/matchers/IdMatchers.kt new file mode 100644 index 0000000..e528ed7 --- /dev/null +++ b/matrikkel-api/src/testFixtures/kotlin/no/kartverket/matrikkel/bygning/matrikkelapi/matchers/IdMatchers.kt @@ -0,0 +1,19 @@ +package no.kartverket.matrikkel.bygning.matrikkelapi.matchers + +import io.mockk.MockKMatcherScope +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelBubbleId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelBubbleIdList +import kotlin.reflect.KClass + +inline fun MockKMatcherScope.matchId(id: I) = match { + it.value == id.value +} + +fun MockKMatcherScope.matchIds(vararg ids: MatrikkelBubbleId): MatrikkelBubbleIdList { + val idMap = HashMap, HashSet>() + ids.forEach { id -> idMap.getOrPut(id::class, ::HashSet).add(id.value) } + + return match { idList -> + idList.item.all { idMap[it::class]?.contains(it.value) ?: false } + } +} diff --git a/src/integrationTest/kotlin/no/kartverket/matrikkel/bygning/v1/EgenregistreringRouteTest.kt b/src/integrationTest/kotlin/no/kartverket/matrikkel/bygning/v1/EgenregistreringRouteTest.kt index a970245..9246c93 100644 --- a/src/integrationTest/kotlin/no/kartverket/matrikkel/bygning/v1/EgenregistreringRouteTest.kt +++ b/src/integrationTest/kotlin/no/kartverket/matrikkel/bygning/v1/EgenregistreringRouteTest.kt @@ -85,39 +85,64 @@ class EgenregistreringRouteTest : TestApplicationWithDb() { val bygning = bygningResponse.body() val now = Instant.now() - assertThat(bygning.bruksareal?.data).isEqualTo(125.0) - assertThat(bygning.bruksareal?.metadata?.registreringstidspunkt) - .isCloseTo(now, within(1, ChronoUnit.SECONDS)) - - assertThat(bygning.byggeaar?.data).isEqualTo(2010) - assertThat(bygning.byggeaar?.metadata?.registreringstidspunkt) - .isCloseTo(now, within(1, ChronoUnit.SECONDS)) - - assertThat(bygning.vannforsyning?.data).isEqualTo(VannforsyningKode.OffentligVannverk) - assertThat(bygning.vannforsyning?.metadata?.registreringstidspunkt) - .isCloseTo(now, within(1, ChronoUnit.SECONDS)) - - assertThat(bygning.avlop?.data).isEqualTo(AvlopKode.OffentligKloakk) - assertThat(bygning.avlop?.metadata?.registreringstidspunkt) - .isCloseTo(now, within(1, ChronoUnit.SECONDS)) - - val bruksenhet = bygning.bruksenheter.find { it.bruksenhetId == 1L } ?: throw IllegalStateException("Fant ikke bruksenhet") - assertThat(bruksenhet.bruksareal?.data).isEqualTo(100.0) - assertThat(bruksenhet.bruksareal?.metadata?.registreringstidspunkt) - .isCloseTo(now, within(1, ChronoUnit.SECONDS)) - - assertThat(bruksenhet.energikilder).satisfiesExactly( - { energikilde -> - assertThat(energikilde.data).isEqualTo(EnergikildeKode.Elektrisitet) - assertThat(energikilde.metadata.registreringstidspunkt).isCloseTo(now, within(1, ChronoUnit.SECONDS)) + assertThat(bygning.bruksareal.egenregistrert).satisfies( + { bruksareal -> + assertThat(bruksareal?.data).isEqualTo(125.0) + assertThat(bruksareal?.metadata?.registreringstidspunkt) + .isCloseTo(now, within(1, ChronoUnit.SECONDS)) }, ) - assertThat(bruksenhet.oppvarminger).satisfiesExactly( - { oppvarming -> - assertThat(oppvarming.data).isEqualTo(OppvarmingKode.Elektrisk) - assertThat(oppvarming.metadata.registreringstidspunkt).isCloseTo(now, within(1, ChronoUnit.SECONDS)) + + assertThat(bygning.byggeaar.egenregistrert).satisfies( + { byggeaar -> + assertThat(byggeaar?.data).isEqualTo(2010) + assertThat(byggeaar?.metadata?.registreringstidspunkt) + .isCloseTo(now, within(1, ChronoUnit.SECONDS)) + }, + ) + + assertThat(bygning.vannforsyning.egenregistrert).satisfies( + { vannforsyning -> + assertThat(vannforsyning?.data).isEqualTo(VannforsyningKode.OffentligVannverk) + assertThat(vannforsyning?.metadata?.registreringstidspunkt) + .isCloseTo(now, within(1, ChronoUnit.SECONDS)) + }, + ) + + assertThat(bygning.avlop.egenregistrert).satisfies( + { avlop -> + assertThat(avlop?.data).isEqualTo(AvlopKode.OffentligKloakk) + assertThat(avlop?.metadata?.registreringstidspunkt) + .isCloseTo(now, within(1, ChronoUnit.SECONDS)) }, ) + + assertThat(bygning.bruksenheter).satisfiesOnlyOnce( + { bruksenhet -> + assertThat(bruksenhet.bruksenhetId).isEqualTo(1L) + + assertThat(bruksenhet.bruksareal.egenregistrert).satisfies( + { bruksareal -> + assertThat(bruksareal?.data).isEqualTo(100.0) + assertThat(bruksareal?.metadata?.registreringstidspunkt) + .isCloseTo(now, within(1, ChronoUnit.SECONDS)) + } + ) + + assertThat(bruksenhet.energikilder.egenregistrert).satisfiesExactly( + { energikilde -> + assertThat(energikilde.data).isEqualTo(EnergikildeKode.Elektrisitet) + assertThat(energikilde.metadata.registreringstidspunkt).isCloseTo(now, within(1, ChronoUnit.SECONDS)) + }, + ) + assertThat(bruksenhet.oppvarminger.egenregistrert).satisfiesExactly( + { oppvarming -> + assertThat(oppvarming.data).isEqualTo(OppvarmingKode.Elektrisk) + assertThat(oppvarming.metadata.registreringstidspunkt).isCloseTo(now, within(1, ChronoUnit.SECONDS)) + }, + ) + } + ) } @Test @@ -140,17 +165,21 @@ class EgenregistreringRouteTest : TestApplicationWithDb() { val now = Instant.now() assertThat(bruksenhetResponse.status).isEqualTo(HttpStatusCode.OK) val bruksenhet = bruksenhetResponse.body() - assertThat(bruksenhet.bruksareal?.data).isEqualTo(100.0) - assertThat(bruksenhet.bruksareal?.metadata?.registreringstidspunkt) - .isCloseTo(now, within(1, ChronoUnit.SECONDS)) - - assertThat(bruksenhet.energikilder).satisfiesExactly( + assertThat(bruksenhet.bruksenhetId).isEqualTo(1L) + assertThat(bruksenhet.bruksareal.egenregistrert).satisfies( + { bruksareal -> + assertThat(bruksareal?.data).isEqualTo(100.0) + assertThat(bruksareal?.metadata?.registreringstidspunkt) + .isCloseTo(now, within(1, ChronoUnit.SECONDS)) + } + ) + assertThat(bruksenhet.energikilder.egenregistrert).satisfiesExactly( { energikilde -> assertThat(energikilde.data).isEqualTo(EnergikildeKode.Elektrisitet) assertThat(energikilde.metadata.registreringstidspunkt).isCloseTo(now, within(1, ChronoUnit.SECONDS)) }, ) - assertThat(bruksenhet.oppvarminger).satisfiesExactly( + assertThat(bruksenhet.oppvarminger.egenregistrert).satisfiesExactly( { oppvarming -> assertThat(oppvarming.data).isEqualTo(OppvarmingKode.Elektrisk) assertThat(oppvarming.metadata.registreringstidspunkt).isCloseTo(now, within(1, ChronoUnit.SECONDS)) @@ -198,18 +227,18 @@ class EgenregistreringRouteTest : TestApplicationWithDb() { assertThat(bygningResponse.status).isEqualTo(HttpStatusCode.OK) val bygning = bygningResponse.body() - assertThat(bygning.bruksareal?.data).isEqualTo(120.0) - assertThat(bygning.byggeaar?.data).isEqualTo(2008) + assertThat(bygning.bruksareal.egenregistrert?.data).isEqualTo(120.0) + assertThat(bygning.byggeaar.egenregistrert?.data).isEqualTo(2008) assertThat(bygning.bruksenheter).satisfiesExactly( - { bruksenhet1 -> - assertThat(bruksenhet1.bruksenhetId).isEqualTo(1L) - assertThat(bruksenhet1.bruksareal?.data).isEqualTo(40.0) - }, - { bruksenhet2 -> - assertThat(bruksenhet2.bruksenhetId).isEqualTo(2L) - assertThat(bruksenhet2.bruksareal).isNull() - }, + { bruksenhet1 -> + assertThat(bruksenhet1.bruksenhetId).isEqualTo(1L) + assertThat(bruksenhet1.bruksareal.egenregistrert?.data).isEqualTo(40.0) + }, + { bruksenhet2 -> + assertThat(bruksenhet2.bruksenhetId).isEqualTo(2L) + assertThat(bruksenhet2.bruksareal.egenregistrert).isNull() + }, ) } diff --git a/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/KodeMappers.kt b/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/KodeMappers.kt new file mode 100644 index 0000000..e9b7c0a --- /dev/null +++ b/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/KodeMappers.kt @@ -0,0 +1,46 @@ +package no.kartverket.matrikkel.bygning.matrikkel.adapters + +import no.kartverket.matrikkel.bygning.models.kodelister.AvlopKode +import no.kartverket.matrikkel.bygning.models.kodelister.EnergikildeKode +import no.kartverket.matrikkel.bygning.models.kodelister.OppvarmingKode +import no.kartverket.matrikkel.bygning.models.kodelister.VannforsyningKode +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.AvlopsKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.EnergikildeKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.OppvarmingsKodeId +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.koder.VannforsyningsKodeId + +fun mapEnergikilde(kodeId: EnergikildeKodeId): EnergikildeKode = when (kodeId.value) { + 0L -> EnergikildeKode.Elektrisitet + 1L -> EnergikildeKode.OljeParafin + 2L -> EnergikildeKode.Biobrensel + 3L -> EnergikildeKode.Solenergi + 4L -> EnergikildeKode.Varmepumpe + 5L -> EnergikildeKode.Gass + 6L -> EnergikildeKode.Fjernvarme + 7L -> EnergikildeKode.AnnenEnergikilde + else -> throw RuntimeException("Ukjent energikildekode: ${kodeId.value}") +} + +fun mapOppvarming(kodeId: OppvarmingsKodeId): OppvarmingKode = when (kodeId.value) { + 0L -> OppvarmingKode.Elektrisk + 1L -> OppvarmingKode.Sentralvarme + 2L -> OppvarmingKode.AnnenOppvarming + else -> throw RuntimeException("Ukjent oppvarmingskode: ${kodeId.value}") +} + +fun mapAvloep(kodeId: AvlopsKodeId): AvlopKode? = when (kodeId.value) { + 0L -> null + 1L -> AvlopKode.OffentligKloakk + 2L -> AvlopKode.PrivatKloakk + 3L -> AvlopKode.IngenKloakk + else -> throw RuntimeException("Ukjent avløpsskode: ${kodeId.value}") +} + +fun mapVannforsyning(kodeId: VannforsyningsKodeId): VannforsyningKode? = when (kodeId.value) { + 0L -> null + 1L -> VannforsyningKode.OffentligVannverk + 2L -> VannforsyningKode.TilknyttetPrivatVannverk + 3L -> VannforsyningKode.AnnenPrivatInnlagtVann + 4L -> VannforsyningKode.AnnenPrivatIkkeInnlagtVann + else -> throw RuntimeException("Ukjent vannforsyningskode: ${kodeId.value}") +} diff --git a/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/MatrikkelBygningClient.kt b/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/MatrikkelBygningClient.kt index 2f612eb..287ea13 100644 --- a/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/MatrikkelBygningClient.kt +++ b/src/main/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/MatrikkelBygningClient.kt @@ -2,39 +2,102 @@ package no.kartverket.matrikkel.bygning.matrikkel.adapters import no.kartverket.matrikkel.bygning.matrikkel.BygningClient import no.kartverket.matrikkel.bygning.matrikkelapi.MatrikkelApi -import no.kartverket.matrikkel.bygning.matrikkelapi.getObjectAs -import no.kartverket.matrikkel.bygning.matrikkelapi.getObjectsAs +import no.kartverket.matrikkel.bygning.matrikkelapi.bygningId +import no.kartverket.matrikkel.bygning.matrikkelapi.getBruksenheter +import no.kartverket.matrikkel.bygning.matrikkelapi.getBygning +import no.kartverket.matrikkel.bygning.matrikkelapi.toInstant +import no.kartverket.matrikkel.bygning.models.Avlop +import no.kartverket.matrikkel.bygning.models.Bruksareal import no.kartverket.matrikkel.bygning.models.Bruksenhet import no.kartverket.matrikkel.bygning.models.Bygning +import no.kartverket.matrikkel.bygning.models.Energikilde +import no.kartverket.matrikkel.bygning.models.Multikilde +import no.kartverket.matrikkel.bygning.models.Oppvarming +import no.kartverket.matrikkel.bygning.models.RegisterMetadata +import no.kartverket.matrikkel.bygning.models.Vannforsyning import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.BygningId import no.statkart.matrikkel.matrikkelapi.wsapi.v1.service.store.ServiceException import org.slf4j.Logger import org.slf4j.LoggerFactory -import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.Bruksenhet as MatrikkelBruksenhet -import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.bygning.Bygning as MatrikkelBygning // TODO Håndtering av at matrikkel servicene thrower på visse vanlige HTTP koder, ikke bare full try/catch internal class MatrikkelBygningClient( - val matrikkelApi: MatrikkelApi.WithAuth + private val matrikkelApi: MatrikkelApi.WithAuth ) : BygningClient { private val log: Logger = LoggerFactory.getLogger(javaClass) override fun getBygningById(id: Long): Bygning? { - val bygningId: BygningId = BygningId().apply { value = id } + val bygningId: BygningId = bygningId(id) try { - val bygning = matrikkelApi.storeService().getObjectAs(bygningId, matrikkelApi.matrikkelContext) + val bygning = matrikkelApi.storeService().getBygning(bygningId, matrikkelApi.matrikkelContext) - val bruksenheter = - matrikkelApi.storeService().getObjectsAs(bygning.bruksenhetIds.item, matrikkelApi.matrikkelContext) + val bruksenheter = matrikkelApi.storeService().getBruksenheter(bygning.bruksenhetIds.item, matrikkelApi.matrikkelContext) + val bygningsmetadata = RegisterMetadata( + bygning.oppdateringsdato.toInstant(), + ) return Bygning( bygningId = bygning.id.value, bygningsnummer = bygning.bygningsnummer, + // TODO: Hvordan innse at arealet er ukjent og hvordan håndtere dette + bruksareal = Multikilde( + autoritativ = Bruksareal( + bygning.etasjedata.bruksarealTotalt, + bygningsmetadata, + ), + ), + // TODO: Burde vi ha kode for Ikke oppgitt? + vannforsyning = Multikilde( + autoritativ = mapVannforsyning(bygning.vannforsyningsKodeId)?.let { + Vannforsyning( + it, + bygningsmetadata, + ) + }, + ), + // TODO: Burde vi ha kode for Ikke oppgitt? + avlop = Multikilde( + autoritativ = mapAvloep(bygning.avlopsKodeId)?.let { + Avlop( + it, + bygningsmetadata, + ) + }, + ), + // TODO: Skal tom liste i matrikkelen tolkes som "vet ikke" eller "ingen"? + energikilder = Multikilde( + autoritativ = bygning.energikildeKodeIds.item.map { + Energikilde( + mapEnergikilde(it), + bygningsmetadata, + ) + }.ifEmpty { null }, // Tolker som "vet ikke" + ), + // TODO: Skal tom liste i matrikkelen tolkes som "vet ikke" eller "ingen"? + oppvarminger = Multikilde( + autoritativ = bygning.oppvarmingsKodeIds.item.map { + Oppvarming( + mapOppvarming(it), + bygningsmetadata, + ) + }.ifEmpty { null }, // Tolker som "vet ikke" + ), + // TODO: Burde vi ha en måte å angi ukjent / ikke oppgitt? bruksenheter = bruksenheter.map { + val bruksenhetsmetadata = RegisterMetadata( + it.oppdateringsdato.toInstant(), + ) Bruksenhet( bruksenhetId = it.id.value, bygningId = it.byggId.value, + // TODO: Hvordan innse at arealet er ukjent og hvordan håndtere dette + bruksareal = Multikilde( + autoritativ = Bruksareal( + it.bruksareal, + bruksenhetsmetadata, + ), + ), ) }, ) @@ -50,7 +113,7 @@ internal class MatrikkelBygningClient( return getBygningById(bygningId.value) } catch (exception: ServiceException) { - log.warn("Noe gikk galt under henting av bygning med nummer {}", bygningsnummer, exception) + log.warn("Noe gikk galt under henting av bygning med bygningsnummer {}", bygningsnummer, exception) return null } } diff --git a/src/main/kotlin/no/kartverket/matrikkel/bygning/models/Bygning.kt b/src/main/kotlin/no/kartverket/matrikkel/bygning/models/Bygning.kt index 8352a71..8bd9269 100644 --- a/src/main/kotlin/no/kartverket/matrikkel/bygning/models/Bygning.kt +++ b/src/main/kotlin/no/kartverket/matrikkel/bygning/models/Bygning.kt @@ -6,15 +6,16 @@ import no.kartverket.matrikkel.bygning.models.kodelister.OppvarmingKode import no.kartverket.matrikkel.bygning.models.kodelister.VannforsyningKode import java.time.Instant -// TODO Sette opp DTOer for Bygning/Bruksenhet hentet fra Matrikkel data class Bygning( val bygningId: Long, val bygningsnummer: Long, val bruksenheter: List, - val byggeaar: Byggeaar? = null, - val bruksareal: Bruksareal? = null, - val vannforsyning: Vannforsyning? = null, - val avlop: Avlop? = null, + val byggeaar: Multikilde = Multikilde(), + val bruksareal: Multikilde = Multikilde(), + val energikilder: Multikilde> = Multikilde(), + val oppvarminger: Multikilde> = Multikilde(), + val vannforsyning: Multikilde = Multikilde(), + val avlop: Multikilde = Multikilde(), ) { fun withBruksenheter(bruksenheter: List): Bygning { return this.copy( @@ -23,6 +24,10 @@ data class Bygning( } } +data class Multikilde(val autoritativ: T? = null, val egenregistrert: T? = null) { + fun withEgenregistrert(verdi: T?): Multikilde = copy(egenregistrert = verdi) +} + data class RegisterMetadata(val registreringstidspunkt: Instant) data class Bruksareal(val data: Double, val metadata: RegisterMetadata) data class Byggeaar(val data: Int, val metadata: RegisterMetadata) @@ -34,7 +39,7 @@ data class Oppvarming(val data: OppvarmingKode, val metadata: RegisterMetadata) data class Bruksenhet( val bruksenhetId: Long, val bygningId: Long, - val bruksareal: Bruksareal? = null, - val energikilder: List = emptyList(), - val oppvarminger: List = emptyList(), + val bruksareal: Multikilde = Multikilde(), + val energikilder: Multikilde> = Multikilde(), + val oppvarminger: Multikilde> = Multikilde(), ) diff --git a/src/main/kotlin/no/kartverket/matrikkel/bygning/models/BygningExtensions.kt b/src/main/kotlin/no/kartverket/matrikkel/bygning/models/BygningExtensions.kt index 5d693cd..c7ca13d 100644 --- a/src/main/kotlin/no/kartverket/matrikkel/bygning/models/BygningExtensions.kt +++ b/src/main/kotlin/no/kartverket/matrikkel/bygning/models/BygningExtensions.kt @@ -7,41 +7,46 @@ package no.kartverket.matrikkel.bygning.models fun Bygning.withEgenregistrertData(egenregistreringer: List): Bygning { return egenregistreringer.fold(this) { bygningAggregate, egenregistrering -> - Bygning( - bygningId = bygningAggregate.bygningId, - bygningsnummer = bygningAggregate.bygningsnummer, - bruksenheter = bygningAggregate.bruksenheter, - byggeaar = bygningAggregate.byggeaar ?: egenregistrering.bygningRegistrering.byggeaarRegistrering?.let { - Byggeaar( - data = it.byggeaar, - metadata = RegisterMetadata( - registreringstidspunkt = egenregistrering.registreringstidspunkt, - ), - ) + bygningAggregate.copy( + byggeaar = bygningAggregate.byggeaar.aggregate { + egenregistrering.bygningRegistrering.byggeaarRegistrering?.let { + Byggeaar( + data = it.byggeaar, + metadata = RegisterMetadata( + registreringstidspunkt = egenregistrering.registreringstidspunkt, + ), + ) + } }, - bruksareal = bygningAggregate.bruksareal ?: egenregistrering.bygningRegistrering.bruksarealRegistrering?.let { - Bruksareal( - data = it.bruksareal, - metadata = RegisterMetadata( - registreringstidspunkt = egenregistrering.registreringstidspunkt, - ), - ) + bruksareal = bygningAggregate.bruksareal.aggregate { + egenregistrering.bygningRegistrering.bruksarealRegistrering?.let { + Bruksareal( + data = it.bruksareal, + metadata = RegisterMetadata( + registreringstidspunkt = egenregistrering.registreringstidspunkt, + ), + ) + } }, - vannforsyning = bygningAggregate.vannforsyning ?: egenregistrering.bygningRegistrering.vannforsyningRegistrering?.let { - Vannforsyning( - data = it.vannforsyning, - metadata = RegisterMetadata( - registreringstidspunkt = egenregistrering.registreringstidspunkt, - ), - ) + vannforsyning = bygningAggregate.vannforsyning.aggregate { + egenregistrering.bygningRegistrering.vannforsyningRegistrering?.let { + Vannforsyning( + data = it.vannforsyning, + metadata = RegisterMetadata( + registreringstidspunkt = egenregistrering.registreringstidspunkt, + ), + ) + } }, - avlop = bygningAggregate.avlop ?: egenregistrering.bygningRegistrering.avlopRegistrering?.let { - Avlop( - data = it.avlop, - metadata = RegisterMetadata( - registreringstidspunkt = egenregistrering.registreringstidspunkt, - ), - ) + avlop = bygningAggregate.avlop.aggregate { + egenregistrering.bygningRegistrering.avlopRegistrering?.let { + Avlop( + data = it.avlop, + metadata = RegisterMetadata( + registreringstidspunkt = egenregistrering.registreringstidspunkt, + ), + ) + } }, ) } @@ -53,7 +58,7 @@ fun Bruksenhet.withEgenregistrertData(egenregistreringer: List // Likevel har vi ikke noen logisk sjekk på dette ved registrering, så det bør vi nok ha val bruksenhetRegistreringer = egenregistreringer.mapNotNull { egenregistrering -> val bruksenhetRegistrering = - egenregistrering.bygningRegistrering.bruksenhetRegistreringer.filter { it.bruksenhetId == this.bruksenhetId }.firstOrNull() + egenregistrering.bygningRegistrering.bruksenhetRegistreringer.firstOrNull { it.bruksenhetId == this.bruksenhetId } if (bruksenhetRegistrering != null) { egenregistrering.registreringstidspunkt to bruksenhetRegistrering @@ -65,19 +70,19 @@ fun Bruksenhet.withEgenregistrertData(egenregistreringer: List // Jeg er litt usikker på om det er nødvendig å filtrere ut og lage et Pair, så folde på bare bruksenhetregistreringene // fremfor å gjøre det på hele bygningregistreringen dersom vi legger til logikk for å kun godkjenne én registrering return bruksenhetRegistreringer.fold(this) { bruksenhetAggregate, egenregistrering -> - Bruksenhet( - bruksenhetId = bruksenhetAggregate.bruksenhetId, - bygningId = bruksenhetAggregate.bygningId, - bruksareal = bruksenhetAggregate.bruksareal ?: egenregistrering.second.bruksarealRegistrering?.let { - Bruksareal( - data = it.bruksareal, - metadata = RegisterMetadata( - registreringstidspunkt = egenregistrering.first, - ), - ) + bruksenhetAggregate.copy( + bruksareal = bruksenhetAggregate.bruksareal.aggregate { + egenregistrering.second.bruksarealRegistrering?.let { + Bruksareal( + data = it.bruksareal, + metadata = RegisterMetadata( + registreringstidspunkt = egenregistrering.first, + ), + ) + } }, - energikilder = bruksenhetAggregate.energikilder.takeIf { it.isNotEmpty() } - ?: egenregistrering.second.energikildeRegistrering?.let { + energikilder = bruksenhetAggregate.energikilder.aggregate { + egenregistrering.second.energikildeRegistrering?.let { it.energikilder.map { registrertKilde -> Energikilde( data = registrertKilde, @@ -86,9 +91,10 @@ fun Bruksenhet.withEgenregistrertData(egenregistreringer: List ), ) } - } ?: emptyList(), - oppvarminger = bruksenhetAggregate.oppvarminger.takeIf { it.isNotEmpty() } - ?: egenregistrering.second.oppvarmingRegistrering?.let { + } + }, + oppvarminger = bruksenhetAggregate.oppvarminger.aggregate { + egenregistrering.second.oppvarmingRegistrering?.let { it.oppvarminger.map { registrertOppvarming -> Oppvarming( data = registrertOppvarming, @@ -97,7 +103,11 @@ fun Bruksenhet.withEgenregistrertData(egenregistreringer: List ), ) } - } ?: emptyList(), + } + }, ) } } + +private fun Multikilde.aggregate(mapper: () -> T?): Multikilde = + takeIf { it.egenregistrert != null } ?: withEgenregistrert(mapper()) diff --git a/src/main/kotlin/no/kartverket/matrikkel/bygning/routes/v1/dto/response/BygningResponse.kt b/src/main/kotlin/no/kartverket/matrikkel/bygning/routes/v1/dto/response/BygningResponse.kt index 6a7b7a8..5c4427e 100644 --- a/src/main/kotlin/no/kartverket/matrikkel/bygning/routes/v1/dto/response/BygningResponse.kt +++ b/src/main/kotlin/no/kartverket/matrikkel/bygning/routes/v1/dto/response/BygningResponse.kt @@ -7,7 +7,9 @@ import no.kartverket.matrikkel.bygning.models.Bruksenhet import no.kartverket.matrikkel.bygning.models.Byggeaar import no.kartverket.matrikkel.bygning.models.Bygning import no.kartverket.matrikkel.bygning.models.Energikilde +import no.kartverket.matrikkel.bygning.models.Multikilde import no.kartverket.matrikkel.bygning.models.Oppvarming +import no.kartverket.matrikkel.bygning.models.RegisterMetadata import no.kartverket.matrikkel.bygning.models.Vannforsyning import no.kartverket.matrikkel.bygning.models.kodelister.AvlopKode import no.kartverket.matrikkel.bygning.models.kodelister.EnergikildeKode @@ -20,13 +22,18 @@ import java.time.Instant data class BygningResponse( val bygningId: Long, val bygningsnummer: Long, - val byggeaar: ByggeaarResponse? = null, - val bruksareal: BruksarealResponse? = null, - val vannforsyning: VannforsyningKodeResponse? = null, - val avlop: AvlopKodeResponse? = null, + val byggeaar: MultikildeResponse, + val bruksareal: MultikildeResponse, + val vannforsyning: MultikildeResponse, + val avlop: MultikildeResponse, + val energikilder: MultikildeResponse>, + val oppvarming: MultikildeResponse>, val bruksenheter: List, ) +@Serializable +data class MultikildeResponse(val autoritativ: T? = null, val egenregistrert: T? = null) + @Serializable data class RegisterMetadataResponse(@Serializable(with = InstantSerializer::class) val registreringstidspunkt: Instant) @@ -51,66 +58,67 @@ data class OppvarmingResponse(val data: OppvarmingKode, val metadata: RegisterMe @Serializable data class BruksenhetResponse( val bruksenhetId: Long, - val bruksareal: BruksarealResponse? = null, - val energikilder: List = emptyList(), - val oppvarminger: List = emptyList(), + val bruksareal: MultikildeResponse, + val energikilder: MultikildeResponse>, + val oppvarminger: MultikildeResponse>, +) + +fun RegisterMetadata.toRegistermetadataResponse() = RegisterMetadataResponse( + registreringstidspunkt = this.registreringstidspunkt, ) +fun Multikilde.toMultikildeResponse(mapper: T.() -> R): MultikildeResponse { + return MultikildeResponse( + autoritativ?.mapper(), + egenregistrert?.mapper(), + ) +} + fun Bygning.toBygningResponse(): BygningResponse = BygningResponse( bygningId = this.bygningId, bygningsnummer = this.bygningsnummer, - byggeaar = this.byggeaar?.toByggeaarResponse(), - bruksareal = this.bruksareal?.toBruksarealResponse(), - bruksenheter = this.bruksenheter.map { it.toBruksenhetResponse() }, - vannforsyning = this.vannforsyning?.toVannforsyningResponse(), - avlop = this.avlop?.toAvlopKodeResponse(), + byggeaar = this.byggeaar.toMultikildeResponse(Byggeaar::toByggeaarResponse), + bruksareal = this.bruksareal.toMultikildeResponse(Bruksareal::toBruksarealResponse), + bruksenheter = this.bruksenheter.map(Bruksenhet::toBruksenhetResponse), + vannforsyning = this.vannforsyning.toMultikildeResponse(Vannforsyning::toVannforsyningResponse), + avlop = this.avlop.toMultikildeResponse(Avlop::toAvlopKodeResponse), + energikilder = this.energikilder.toMultikildeResponse { map(Energikilde::toEnergikildeResponse) }, + oppvarming = this.oppvarminger.toMultikildeResponse { map(Oppvarming::toOppvarmingResponse) }, ) private fun Byggeaar.toByggeaarResponse(): ByggeaarResponse = ByggeaarResponse( data = this.data, - metadata = RegisterMetadataResponse( - registreringstidspunkt = metadata.registreringstidspunkt - ) + metadata = metadata.toRegistermetadataResponse(), ) private fun Bruksareal.toBruksarealResponse(): BruksarealResponse = BruksarealResponse( data = this.data, - metadata = RegisterMetadataResponse( - registreringstidspunkt = metadata.registreringstidspunkt, - ), + metadata = metadata.toRegistermetadataResponse(), ) private fun Vannforsyning.toVannforsyningResponse(): VannforsyningKodeResponse = VannforsyningKodeResponse( data = this.data, - metadata = RegisterMetadataResponse( - registreringstidspunkt = metadata.registreringstidspunkt - ) + metadata = metadata.toRegistermetadataResponse(), ) private fun Avlop.toAvlopKodeResponse(): AvlopKodeResponse = AvlopKodeResponse( data = this.data, - metadata = RegisterMetadataResponse( - registreringstidspunkt = metadata.registreringstidspunkt - ) + metadata = metadata.toRegistermetadataResponse(), ) fun Bruksenhet.toBruksenhetResponse(): BruksenhetResponse = BruksenhetResponse( bruksenhetId = this.bruksenhetId, - bruksareal = this.bruksareal?.toBruksarealResponse(), - energikilder = this.energikilder.map { it.toEnergikildeResponse() }, - oppvarminger = this.oppvarminger.map { it.toOppvarmingResponse() }, + bruksareal = this.bruksareal.toMultikildeResponse(Bruksareal::toBruksarealResponse), + energikilder = this.energikilder.toMultikildeResponse { map(Energikilde::toEnergikildeResponse) }, + oppvarminger = this.oppvarminger.toMultikildeResponse { map(Oppvarming::toOppvarmingResponse) }, ) private fun Energikilde.toEnergikildeResponse() = EnergikildeResponse( data = this.data, - metadata = RegisterMetadataResponse( - registreringstidspunkt = metadata.registreringstidspunkt - ) + metadata = metadata.toRegistermetadataResponse(), ) private fun Oppvarming.toOppvarmingResponse() = OppvarmingResponse( data = this.data, - metadata = RegisterMetadataResponse( - registreringstidspunkt = metadata.registreringstidspunkt - ) + metadata = metadata.toRegistermetadataResponse(), ) diff --git a/src/test/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/MatrikkelBygningClientTest.kt b/src/test/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/MatrikkelBygningClientTest.kt new file mode 100644 index 0000000..26d3b9e --- /dev/null +++ b/src/test/kotlin/no/kartverket/matrikkel/bygning/matrikkel/adapters/MatrikkelBygningClientTest.kt @@ -0,0 +1,212 @@ +package no.kartverket.matrikkel.bygning.matrikkel.adapters + +import assertk.Assert +import assertk.all +import assertk.assertThat +import assertk.assertions.each +import assertk.assertions.exactly +import assertk.assertions.hasSize +import assertk.assertions.isEqualTo +import assertk.assertions.isNotNull +import assertk.assertions.prop +import assertk.assertions.single +import io.mockk.checkUnnecessaryStub +import io.mockk.every +import io.mockk.mockk +import no.kartverket.matrikkel.bygning.matrikkelapi.MatrikkelApi +import no.kartverket.matrikkel.bygning.matrikkelapi.bruksenhetId +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.MatrikkelAvlopKode +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.MatrikkelEnergikildeKode +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.MatrikkelEtasjeplanKode +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.MatrikkelOppvarmingKode +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.MatrikkelVannforsyningKode +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.bruksenhet +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.bruksenhetIds +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.bygning +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.energikildeKodeIdList +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.etasje +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.etasjer +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.matrikkelBubbleObjectList +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.oppvarmingsKodeIdList +import no.kartverket.matrikkel.bygning.matrikkelapi.builders.timestampUtc +import no.kartverket.matrikkel.bygning.matrikkelapi.bygningId +import no.kartverket.matrikkel.bygning.matrikkelapi.matchers.matchId +import no.kartverket.matrikkel.bygning.matrikkelapi.matchers.matchIds +import no.kartverket.matrikkel.bygning.models.Avlop +import no.kartverket.matrikkel.bygning.models.Bruksareal +import no.kartverket.matrikkel.bygning.models.Bruksenhet +import no.kartverket.matrikkel.bygning.models.Bygning +import no.kartverket.matrikkel.bygning.models.Energikilde +import no.kartverket.matrikkel.bygning.models.Oppvarming +import no.kartverket.matrikkel.bygning.models.RegisterMetadata +import no.kartverket.matrikkel.bygning.models.Vannforsyning +import no.kartverket.matrikkel.bygning.models.erAutoritativIkkeEgenregistrert +import no.kartverket.matrikkel.bygning.models.isEmpty +import no.kartverket.matrikkel.bygning.models.kodelister.AvlopKode +import no.kartverket.matrikkel.bygning.models.kodelister.EnergikildeKode +import no.kartverket.matrikkel.bygning.models.kodelister.OppvarmingKode +import no.kartverket.matrikkel.bygning.models.kodelister.VannforsyningKode +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.domain.MatrikkelContext +import no.statkart.matrikkel.matrikkelapi.wsapi.v1.service.store.StoreService +import java.time.Instant +import kotlin.test.Test + +class MatrikkelBygningClientTest { + @Test + fun bygningUnummerertBruksenhetUtenEtasje() { + val mockStoreService = mockk { + val bygningId = bygningId(1L) + val bruksenhetId = bruksenhetId(2L) + every { getObject(matchId(bygningId), any()) } returns bygning { + id = bygningId + bygningsnummer = 1000L + oppdateringsdato = timestampUtc(2024, 9, 13) + vannforsyningsKodeId = MatrikkelVannforsyningKode.IkkeOppgitt() + avlopsKodeId = MatrikkelAvlopKode.IkkeOppgitt() + etasjedata.bruksarealTotalt = 250.0 + bruksenhetIds(bruksenhetId) + } + every { getObjects(matchIds(bruksenhetId), any()) } returns matrikkelBubbleObjectList( + bruksenhet { + id = bruksenhetId + byggId = bygningId + oppdateringsdato = timestampUtc(2024, 9, 12) + }, + ) + } + val mockApi = mockk { + every { matrikkelContext } returns MatrikkelContext() + every { storeService() } returns mockStoreService + } + + val client = MatrikkelBygningClient(mockApi) + val bygning = client.getBygningById(1L) + + val isMatrikkelfoertBygningstidspunkt = createIsMatrikkelfoertAssert(Instant.parse("2024-09-13T00:00:00.00Z")) + val isMatrikkelfoertBruksenhetstidspunkt = createIsMatrikkelfoertAssert(Instant.parse("2024-09-12T00:00:00.00Z")) + + assertThat(bygning, "bygning").isNotNull().all { + prop(Bygning::bygningId).isEqualTo(1L) + prop(Bygning::bygningsnummer).isEqualTo(1000L) + prop(Bygning::bruksareal).erAutoritativIkkeEgenregistrert { + prop(Bruksareal::data).isEqualTo(250.0) + prop(Bruksareal::metadata).isMatrikkelfoertBygningstidspunkt() + } + prop(Bygning::avlop).isEmpty() + prop(Bygning::vannforsyning).isEmpty() + prop(Bygning::energikilder).isEmpty() + prop(Bygning::oppvarminger).isEmpty() + prop(Bygning::bruksenheter).single().all { + prop(Bruksenhet::bruksenhetId).isEqualTo(2L) + prop(Bruksenhet::bygningId).isEqualTo(1L) + prop(Bruksenhet::bruksareal).erAutoritativIkkeEgenregistrert { + // TODO: Dette skal egentlig være "vet ikke", som kanskje ikke skal representeres slik + prop(Bruksareal::data).isEqualTo(0.0) + prop(Bruksareal::metadata).isMatrikkelfoertBruksenhetstidspunkt() + } + } + } + + checkUnnecessaryStub(mockStoreService, mockApi) + } + + @Test + fun bygningEneboligMedEtasje() { + val mockStoreService = mockk { + val bygningId = bygningId(1L) + val bruksenhetId = bruksenhetId(2L) + every { getObject(matchId(bygningId), any()) } returns bygning { + id = bygningId + bygningsnummer = 1000L + oppdateringsdato = timestampUtc(2024, 9, 12) + vannforsyningsKodeId = MatrikkelVannforsyningKode.TilknyttetOffVannverk() + avlopsKodeId = MatrikkelAvlopKode.OffentligKloakk() + energikildeKodeIds = energikildeKodeIdList( + MatrikkelEnergikildeKode.Elektrisitet(), + MatrikkelEnergikildeKode.Varmepumpe(), + ) + oppvarmingsKodeIds = oppvarmingsKodeIdList( + MatrikkelOppvarmingKode.Elektrisk(), + ) + etasjedata.bruksarealTotalt = 150.0 + etasjer( + etasje { + etasjeplanKodeId = MatrikkelEtasjeplanKode.Hovedetasje() + etasjenummer = 1 + bruksarealTotalt = 150.0 + }, + ) + bruksenhetIds(bruksenhetId) + } + every { getObjects(matchIds(bruksenhetId), any()) } returns matrikkelBubbleObjectList( + bruksenhet { + id = bruksenhetId + byggId = bygningId + oppdateringsdato = timestampUtc(2024, 9, 13) + bruksareal = 140.0 + }, + ) + } + val mockApi = mockk { + every { matrikkelContext } returns MatrikkelContext() + every { storeService() } returns mockStoreService + } + + val client = MatrikkelBygningClient(mockApi) + val bygning = client.getBygningById(1L) + + val isMatrikkelfoertBygningstidspunkt = createIsMatrikkelfoertAssert(Instant.parse("2024-09-12T00:00:00.00Z")) + val isMatrikkelfoertBruksenhetstidspunkt = createIsMatrikkelfoertAssert(Instant.parse("2024-09-13T00:00:00.00Z")) + + assertThat(bygning, "bygning").isNotNull().all { + prop(Bygning::bygningId).isEqualTo(1L) + prop(Bygning::bygningsnummer).isEqualTo(1000L) + prop(Bygning::bruksareal).erAutoritativIkkeEgenregistrert { + prop(Bruksareal::data).isEqualTo(150.0) + prop(Bruksareal::metadata).isMatrikkelfoertBygningstidspunkt() + } + prop(Bygning::avlop).erAutoritativIkkeEgenregistrert { + prop(Avlop::data).isEqualTo(AvlopKode.OffentligKloakk) + prop(Avlop::metadata).isMatrikkelfoertBygningstidspunkt() + } + prop(Bygning::vannforsyning).erAutoritativIkkeEgenregistrert { + prop(Vannforsyning::data).isEqualTo(VannforsyningKode.OffentligVannverk) + prop(Vannforsyning::metadata).isMatrikkelfoertBygningstidspunkt() + } + prop(Bygning::energikilder).erAutoritativIkkeEgenregistrert { + hasSize(2) + exactly(1) { + it.prop(Energikilde::data).isEqualTo(EnergikildeKode.Elektrisitet) + } + exactly(1) { + it.prop(Energikilde::data).isEqualTo(EnergikildeKode.Varmepumpe) + } + each { + it.prop(Energikilde::metadata).isMatrikkelfoertBygningstidspunkt() + } + } + prop(Bygning::oppvarminger).erAutoritativIkkeEgenregistrert { + single().all { + prop(Oppvarming::data).isEqualTo(OppvarmingKode.Elektrisk) + prop(Oppvarming::metadata).isMatrikkelfoertBygningstidspunkt() + } + } + prop(Bygning::bruksenheter).single().all { + prop(Bruksenhet::bruksenhetId).isEqualTo(2L) + prop(Bruksenhet::bygningId).isEqualTo(1L) + prop(Bruksenhet::bruksareal).erAutoritativIkkeEgenregistrert { + prop(Bruksareal::data).isEqualTo(140.0) + prop(Bruksareal::metadata).isMatrikkelfoertBruksenhetstidspunkt() + } + } + } + + checkUnnecessaryStub(mockStoreService, mockApi) + } + + private fun createIsMatrikkelfoertAssert(expectedRegistreringstidspunkt: Instant): Assert.() -> Unit { + return { + prop(RegisterMetadata::registreringstidspunkt).isEqualTo(expectedRegistreringstidspunkt) + } + } +} diff --git a/src/test/kotlin/no/kartverket/matrikkel/bygning/models/BygningExtensionsTest.kt b/src/test/kotlin/no/kartverket/matrikkel/bygning/models/BygningExtensionsTest.kt index 97dc43e..194f772 100644 --- a/src/test/kotlin/no/kartverket/matrikkel/bygning/models/BygningExtensionsTest.kt +++ b/src/test/kotlin/no/kartverket/matrikkel/bygning/models/BygningExtensionsTest.kt @@ -1,6 +1,7 @@ package no.kartverket.matrikkel.bygning.models -import org.assertj.core.api.Assertions.assertThat +import assertk.assertThat +import assertk.assertions.isEqualTo import java.time.Instant import java.util.UUID import kotlin.test.Test @@ -10,18 +11,18 @@ class BygningExtensionsTest { bygningId = 1L, bygningsnummer = 100, bruksenheter = emptyList(), - byggeaar = null, - bruksareal = null, - vannforsyning = null, - avlop = null, + byggeaar = Multikilde(), + bruksareal = Multikilde(), + vannforsyning = Multikilde(), + avlop = Multikilde(), ) val defaultBruksenhet = Bruksenhet( bruksenhetId = 1L, bygningId = 1L, - bruksareal = null, - energikilder = emptyList(), - oppvarminger = emptyList(), + bruksareal = Multikilde(), + energikilder = Multikilde(), + oppvarminger = Multikilde(), ) val defaultBruksenhetRegistrering = BruksenhetRegistrering( @@ -64,7 +65,7 @@ class BygningExtensionsTest { val aggregatedBygning = defaultBygning.withEgenregistrertData(listOf(laterRegistrering, defaultEgenregistrering)) - assertThat(aggregatedBygning.bruksareal?.data).isEqualTo(150.0) + assertThat(aggregatedBygning.bruksareal.egenregistrert?.data).isEqualTo(150.0) } @Test diff --git a/src/test/kotlin/no/kartverket/matrikkel/bygning/models/MultikildeAsserts.kt b/src/test/kotlin/no/kartverket/matrikkel/bygning/models/MultikildeAsserts.kt new file mode 100644 index 0000000..46dc2f0 --- /dev/null +++ b/src/test/kotlin/no/kartverket/matrikkel/bygning/models/MultikildeAsserts.kt @@ -0,0 +1,17 @@ +package no.kartverket.matrikkel.bygning.models + +import assertk.Assert +import assertk.all +import assertk.assertions.isNotNull +import assertk.assertions.isNull +import assertk.assertions.prop + +fun Assert>.isEmpty() { + prop(Multikilde::autoritativ).isNull() + prop(Multikilde::egenregistrert).isNull() +} + +fun Assert>.erAutoritativIkkeEgenregistrert(body: Assert.() -> Unit) = all { + prop(Multikilde::autoritativ).isNotNull().all(body) + prop(Multikilde::egenregistrert).isNull() +}