From 4339dc52947024f9314eaf16b2bd4d31539b678d Mon Sep 17 00:00:00 2001 From: Mikael Bjerga Date: Thu, 11 Jan 2024 16:30:12 +0100 Subject: [PATCH] Legg til wrappere for fnr og orgnr --- .../helsearbeidsgiver/utils/wrapper/Fnr.kt | 42 ++++++++++ .../helsearbeidsgiver/utils/wrapper/Orgnr.kt | 34 ++++++++ .../utils/wrapper/Sjekksum.kt | 8 ++ .../utils/wrapper/FnrTest.kt | 81 +++++++++++++++++++ .../utils/wrapper/OrgnrTest.kt | 54 +++++++++++++ .../utils/wrapper/SjekksumTest.kt | 72 +++++++++++++++++ 6 files changed, 291 insertions(+) create mode 100644 src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Fnr.kt create mode 100644 src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Orgnr.kt create mode 100644 src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Sjekksum.kt create mode 100644 src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/FnrTest.kt create mode 100644 src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/OrgnrTest.kt create mode 100644 src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/SjekksumTest.kt diff --git a/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Fnr.kt b/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Fnr.kt new file mode 100644 index 0000000..c2da0c7 --- /dev/null +++ b/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Fnr.kt @@ -0,0 +1,42 @@ +package no.nav.helsearbeidsgiver.utils.wrapper + +import kotlinx.serialization.Serializable + +/** Sjekker at streng er riktig lengde, kun består av siffer, og at 6 første siffer er gyldig dato. */ +private val fnrRgx = Regex( + "(?:[04][1-9]|[1256]\\d|[37][01])" + // to første siffer er gyldig dag (+40 for D-nummer) + "(?:[048][1-9]|[159][012])" + // to neste siffer er gyldig måned, med støtte for testpersoner (+40 for NAV, +80 for TestNorge) + "\\d{7}" // resten er tall +) + +private val sifferVekter1 = listOf(3, 7, 6, 1, 8, 9, 4, 5, 2) +private val sifferVekter2 = listOf(5, 4, 3, 2, 7, 6, 5, 4, 3, 2) + + +@Serializable +@JvmInline +value class Fnr(val verdi: String) { + init { + require(erGyldig(verdi)) + } + + override fun toString(): String = + verdi + + companion object { + /** Les [her](https://no.wikipedia.org/wiki/F%C3%B8dselsnummer) for forklaring av regler. */ + fun erGyldig(fnr: String): Boolean = + if (fnr.matches(fnrRgx)) { + val fnrSiffer = fnr.toList().map(Char::digitToInt) + + val sjekksum1 = sjekksum(fnrSiffer, sifferVekter1) + val sjekksum2 = sjekksum(fnrSiffer, sifferVekter2) + + 10 !in listOf(sjekksum1, sjekksum2) && + sjekksum1 == fnrSiffer[9] && + sjekksum2 == fnrSiffer[10] + } else { + false + } + } +} diff --git a/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Orgnr.kt b/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Orgnr.kt new file mode 100644 index 0000000..84251f5 --- /dev/null +++ b/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Orgnr.kt @@ -0,0 +1,34 @@ +package no.nav.helsearbeidsgiver.utils.wrapper + +import kotlinx.serialization.Serializable + +/** Sjekker at streng er riktig lengde og kun består av siffer. */ +private val orgnrRgx = Regex("\\d{9}") + +private val sifferVekter = listOf(3, 2, 7, 6, 5, 4, 3, 2) + +@Serializable +@JvmInline +value class Orgnr(val verdi: String) { + init { + require(erGyldig(verdi)) + } + + override fun toString(): String = + verdi + + companion object { + /** Les [her](https://www.brreg.no/om-oss/registrene-vare/om-enhetsregisteret/organisasjonsnummeret/) for forklaring av regler. */ + fun erGyldig(orgnr: String): Boolean = + if (orgnr.matches(orgnrRgx)) { + val orgnrSiffer = orgnr.toList().map(Char::digitToInt) + + val sjekksum = sjekksum(orgnrSiffer, sifferVekter) + + sjekksum != 10 && + sjekksum == orgnrSiffer.last() + } else { + false + } + } +} diff --git a/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Sjekksum.kt b/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Sjekksum.kt new file mode 100644 index 0000000..f4707df --- /dev/null +++ b/src/main/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/Sjekksum.kt @@ -0,0 +1,8 @@ +package no.nav.helsearbeidsgiver.utils.wrapper + +internal fun sjekksum(siffer: List, sifferVekter: List): Int = + siffer.zip(sifferVekter) + .sumOf { (siffer, vekt) -> siffer * vekt } + .mod(11) + .let { 11 - it } + .mod(11) diff --git a/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/FnrTest.kt b/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/FnrTest.kt new file mode 100644 index 0000000..5971b89 --- /dev/null +++ b/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/FnrTest.kt @@ -0,0 +1,81 @@ +package no.nav.helsearbeidsgiver.utils.wrapper + +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.FunSpec +import io.kotest.datatest.withData +import io.kotest.matchers.shouldBe + +class FnrTest : FunSpec({ + + context("gyldig") { + withData( + listOf( + // Sjekker ikke kontrollsiffer + "01010012345", + "19092212345", + "29104512345", + "31129912345", + "25056712345", + "11085812345", + "41066612345", // D-nummer + "45066612345", // D-nummer + "50066612345", // D-nummer + "57066612345", // D-nummer + "60066612345", // D-nummer + "69066612345", // D-nummer + "70066612345", // D-nummer + "71066612345", // D-nummer + "01490012345", // Testperson fra NAV + "01500012345", // Testperson fra NAV + "01890012345", // Testperson fra TestNorge + "01900012345" // Testperson fra TestNorge + ) + ) { + shouldNotThrowAny { + Fnr(it) + } + } + } + + context("ugyldig") { + withData( + listOf( + "00010012345", // dag 0, andre siffer feil + "32010012345", // dag 32, andre siffer feil + "40010012345", // dag 40, andre siffer feil (D-nummer) + "72010012345", // dag 72, andre siffer feil (D-nummer) + "80010012345", // dag 80, første siffer feil + "01000012345", // måned 0, fjerde siffer feil + "01130012345", // måned 13, fjerde siffer feil + "01200012345", // måned 20, tredje siffer feil + "01390012345", // måned 39, tredje og fjerde siffer feil + "01400012345", // måned 40, fjerde siffer feil (testperson) + "01530012345", // måned 53, fjerde siffer feil (testperson) + "01790012345", // måned 79, tredje og fjerde siffer feil + "01800012345", // måned 80, fjerde siffer feil (testperson) + "01930012345", // måned 93, fjerde siffer feil (testperson) + "0101001234", // for kort + "010100123456", // for langt + "010100x2345", // med bokstav + "010100 1234" // med mellomrom + ) + ) { + shouldThrowExactly { + Fnr(it) + } + } + + test("tom streng") { + shouldThrowExactly { + Fnr("") + } + } + } + + test("toString gir wrappet verdi") { + Fnr("24120612345").let { + it.toString() shouldBe it.verdi + } + } +}) diff --git a/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/OrgnrTest.kt b/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/OrgnrTest.kt new file mode 100644 index 0000000..01c2457 --- /dev/null +++ b/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/OrgnrTest.kt @@ -0,0 +1,54 @@ +package no.nav.helsearbeidsgiver.utils.wrapper + +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.FunSpec +import io.kotest.datatest.withData +import io.kotest.matchers.shouldBe + +class OrgnrTest : FunSpec({ + + context("gyldig") { + withData( + listOf( + "161231654", + "908498460", + "135103210", + "684603132", + "167484979", + "796033020" + ) + ) { + shouldNotThrowAny { + Orgnr(it) + } + } + } + + context("ugyldig") { + withData( + listOf( + "123", // for kort + "0123456789", // for langt + "12x456789", // med bokstav + "1234 6789" // med mellomrom + ) + ) { + shouldThrowExactly { + Orgnr(it) + } + } + + test("tom streng") { + shouldThrowExactly { + Orgnr("") + } + } + } + + test("toString gir wrappet verdi") { + Orgnr("123456789").let { + it.toString() shouldBe it.verdi + } + } +}) diff --git a/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/SjekksumTest.kt b/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/SjekksumTest.kt new file mode 100644 index 0000000..b1a7cd2 --- /dev/null +++ b/src/test/kotlin/no/nav/helsearbeidsgiver/utils/wrapper/SjekksumTest.kt @@ -0,0 +1,72 @@ +package no.nav.helsearbeidsgiver.utils.wrapper + +import io.kotest.core.spec.style.FunSpec +import io.kotest.datatest.withData +import io.kotest.matchers.shouldBe + +class SjekksumTest : FunSpec({ + context("gyldig") { + withData( + mapOf( + "siffer og sifferVekter er tomme" to TestData( + siffer = emptyList(), + sifferVekter = emptyList(), + forventetSjekksum = 0 + ), + "siffer er tom" to TestData( + siffer = emptyList(), + sifferVekter = listOf(1, 2, 3), + forventetSjekksum = 0 + ), + "sifferVekter er tom" to TestData( + siffer = listOf(1, 2, 3), + sifferVekter = emptyList(), + forventetSjekksum = 0 + ), + "siffer er kortere enn sifferVekter" to TestData( + siffer = listOf(3), + sifferVekter = listOf(5, 7, 9), + forventetSjekksum = 7 + ), + "sifferVekter er kortere enn siffer" to TestData( + siffer = listOf(2, 4, 6), + sifferVekter = listOf(8), + forventetSjekksum = 6 + ), + "krever ikke enkeltsiffer" to TestData( + siffer = listOf(13), + sifferVekter = listOf(14), + forventetSjekksum = 5 + ), + "sjekksum kalkuleres korrekt (1 av 2)" to TestData( + siffer = listOf(4, 4, 9), + sifferVekter = listOf(9, 3, 4), + forventetSjekksum = 4 + ), + "sjekksum kalkuleres korrekt (2 av 2)" to TestData( + siffer = listOf(4, 8, 4, 8), + sifferVekter = listOf(3, 8, 2, 1), + forventetSjekksum = 7 + ), + "sjekksum er tosifret" to TestData( + siffer = listOf(3, 1, 0, 5, 3), + sifferVekter = listOf(6, 8, 4, 2, 3), + forventetSjekksum = 10 + ), + "sjekksum er 0" to TestData( + siffer = listOf(7, 5, 6, 5, 1, 2, 1), + sifferVekter = listOf(0, 6, 0, 9, 9, 2, 0), + forventetSjekksum = 0 + ), + ) + ) { + sjekksum(it.siffer, it.sifferVekter) shouldBe it.forventetSjekksum + } + } +}) + +private data class TestData( + val siffer: List, + val sifferVekter: List, + val forventetSjekksum: Int +)