diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a34f3da --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.{kt,kts}] +indent_size=4 +insert_final_newline=true +disabled_rules=no-wildcard-imports diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 044de75..921ffe1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -61,27 +61,3 @@ jobs: docker push ${DOCKERHUB_IMAGE}:latest docker push ${DOCKERHUB_IMAGE}:${GITHUB_SHA} docker push ${DOCKERHUB_IMAGE}:${version} - - deployDev: - name: Deploy to dev - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: nais/deploy/actions/deploy@v1 - env: - APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }} - CLUSTER: dev-fss - RESOURCE: naiserator.yaml - - deployProd: - name: Deploy to prod - needs: [build, deployDev] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: nais/deploy/actions/deploy@v1 - env: - APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }} - CLUSTER: prod-fss - RESOURCE: naiserator.yaml diff --git a/.github/workflows/devdeploy.yml b/.github/workflows/devdeploy.yml index eb35e8a..cd915b4 100644 --- a/.github/workflows/devdeploy.yml +++ b/.github/workflows/devdeploy.yml @@ -1,4 +1,4 @@ -name: Deploy to dev +name: Run tests on: push: branches: @@ -9,11 +9,16 @@ env: jobs: build: - name: Checkout code and create docker tag + name: Checkout code and run tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@master + - name: Set up Java 14 + uses: actions/setup-java@v1 + with: + java-version: '14' + architecture: x64 - uses: actions/cache@v1 with: path: ~/.gradle/caches @@ -38,16 +43,3 @@ jobs: ORG_GRADLE_PROJECT_githubPassword: ${{ secrets.GITHUB_TOKEN }} run: | ./gradlew shadowJar -x test - - name: Build and publish Docker image - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - docker build --tag ${IMAGE} . - docker login docker.pkg.github.com -u ${GITHUB_REPOSITORY} -p ${GITHUB_TOKEN} - docker push ${IMAGE} - - name: Deploy to Preprod - uses: nais/deploy/actions/deploy@master - env: - APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }} - CLUSTER: dev-fss - RESOURCE: naiserator.yaml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 537af3b..4f86e56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM navikt/java:11 -COPY build/libs/pdfgen-*-all.jar app.jar +FROM navikt/java:14 +COPY build/libs/pdfgen-1.3.1-all.jar app.jar COPY fonts fonts COPY templates templates COPY resources resources ENV JAVA_OPTS='-Dlogback.configurationFile=logback-remote.xml' -ENV DISABLE_PDF_GET="true" \ No newline at end of file +ENV DISABLE_PDF_GET="true" diff --git a/build.gradle.kts b/build.gradle.kts index ba5fba0..e0cf023 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,24 +1,25 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile group = "no.nav.pdfgen" -version = "1.2.1" +version = "1.3.1" -val handlebarsVersion = "4.1.2" -val jacksonVersion = "2.9.8" -val jaxbVersion = "2.3.1" -val jsoupVersion = "1.11.3" -val kluentVersion = "1.49" -val ktorVersion = "1.2.6" +val handlebarsVersion = "4.2.0" +val jacksonVersion = "2.12.0" +val jaxbVersion = "3.0.0" +val jaxbApiVersion = "2.3.1" +val jsoupVersion = "1.13.1" +val kluentVersion = "1.65" +val ktorVersion = "1.5.1" val logbackVersion = "1.2.3" -val logstashEncoderVersion = "5.2" -val openHtmlToPdfVersion = "0.0.1-RC17" -val prometheusVersion = "0.6.0" -val spekVersion = "2.0.9" -val verapdfVersion = "1.12.1" +val logstashEncoderVersion = "6.6" +val openHtmlToPdfVersion = "1.0.6" +val prometheusVersion = "0.10.0" +val spekVersion = "2.0.15" +val verapdfVersion = "1.16.1" plugins { - kotlin("jvm") version "1.3.71" - id("org.jmailen.kotlinter") version "2.2.0" + kotlin("jvm") version "1.4.30" + id("org.jmailen.kotlinter") version "3.3.0" id("com.github.johnrengelman.shadow") version "5.2.0" id("com.github.ben-manes.versions") version "0.21.0" } @@ -39,7 +40,7 @@ tasks { } } withType { - kotlinOptions.jvmTarget = "1.8" + kotlinOptions.jvmTarget = "14" } withType { manifest.attributes("Main-Class" to "no.nav.pdfgen.BootstrapKt") @@ -49,24 +50,24 @@ tasks { repositories { mavenCentral() jcenter() - maven(url="https://dl.bintray.com/kotlin/ktor") - maven(url= "https://dl.bintray.com/spekframework/spek-dev") + maven(url = "https://dl.bintray.com/kotlin/ktor") + maven(url = "https://dl.bintray.com/spekframework/spek-dev") } dependencies { - compile(kotlin("stdlib")) + implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("com.github.jknack:handlebars:$handlebarsVersion") implementation("com.github.jknack:handlebars-jackson2:$handlebarsVersion") implementation("com.openhtmltopdf:openhtmltopdf-pdfbox:$openHtmlToPdfVersion") implementation("com.openhtmltopdf:openhtmltopdf-slf4j:$openHtmlToPdfVersion") - implementation("com.openhtmltopdf:openhtmltopdf-svg-support:$openHtmlToPdfVersion") + // implementation("com.openhtmltopdf:openhtmltopdf-svg-support:$openHtmlToPdfVersion") implementation("org.jsoup:jsoup:$jsoupVersion") implementation("com.fasterxml.jackson.core:jackson-core:$jacksonVersion") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion") - implementation("javax.xml.bind:jaxb-api:2.1") + implementation("javax.xml.bind:jaxb-api:$jaxbApiVersion") implementation("org.glassfish.jaxb:jaxb-runtime:$jaxbVersion") implementation("io.ktor:ktor-server-netty:$ktorVersion") @@ -74,6 +75,8 @@ dependencies { implementation("io.prometheus:simpleclient_common:$prometheusVersion") implementation("io.prometheus:simpleclient_hotspot:$prometheusVersion") + implementation("org.verapdf:validation-model:$verapdfVersion") + implementation("ch.qos.logback:logback-classic:$logbackVersion") implementation("net.logstash.logback:logstash-logback-encoder:$logstashEncoderVersion") @@ -82,6 +85,5 @@ dependencies { testRuntimeOnly("org.spekframework.spek2:spek-runtime-jvm:$spekVersion") testRuntimeOnly("org.spekframework.spek2:spek-runner-junit5:$spekVersion") - testImplementation("org.verapdf:validation-model:$verapdfVersion") testImplementation("io.ktor:ktor-client-cio:$ktorVersion") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 49c67d9..1901db8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "pdfgen" \ No newline at end of file +rootProject.name = "pdfgen" diff --git a/src/main/kotlin/no/nav/pdfgen/Bootstrap.kt b/src/main/kotlin/no/nav/pdfgen/Bootstrap.kt index e2724d3..73cde9e 100644 --- a/src/main/kotlin/no/nav/pdfgen/Bootstrap.kt +++ b/src/main/kotlin/no/nav/pdfgen/Bootstrap.kt @@ -3,17 +3,9 @@ package no.nav.pdfgen // Uncommemt to enable debug to file // import java.io.File -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.registerKotlinModule -import com.github.jknack.handlebars.Context -import com.github.jknack.handlebars.Handlebars -import com.github.jknack.handlebars.JsonNodeValueResolver import com.github.jknack.handlebars.Template -import com.github.jknack.handlebars.context.MapValueResolver -import com.github.jknack.handlebars.io.FileTemplateLoader -import com.github.jknack.handlebars.io.StringTemplateSource import io.ktor.application.call import io.ktor.application.feature import io.ktor.application.install @@ -21,107 +13,68 @@ import io.ktor.features.ContentNegotiation import io.ktor.features.StatusPages import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.http.content.OutgoingContent import io.ktor.http.content.TextContent import io.ktor.http.withCharset import io.ktor.jackson.jackson -import io.ktor.request.contentType import io.ktor.request.path -import io.ktor.request.receive -import io.ktor.request.receiveStream -import io.ktor.request.receiveText import io.ktor.response.respond -import io.ktor.response.respondBytes import io.ktor.response.respondText import io.ktor.response.respondTextWriter -import io.ktor.routing.HttpMethodRouteSelector -import io.ktor.routing.Route -import io.ktor.routing.Routing -import io.ktor.routing.get -import io.ktor.routing.post -import io.ktor.routing.routing +import io.ktor.routing.* import io.ktor.server.engine.ApplicationEngine import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty -import io.ktor.util.extension +import io.ktor.utils.io.* +import io.ktor.utils.io.jvm.javaio.* import io.prometheus.client.CollectorRegistry import io.prometheus.client.exporter.common.TextFormat -import java.io.ByteArrayOutputStream -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.time.format.DateTimeFormatter -import java.util.Base64.Encoder -import kotlin.streams.toList -import kotlinx.coroutines.io.ByteWriteChannel -import kotlinx.coroutines.io.jvm.javaio.toOutputStream -import net.logstash.logback.argument.StructuredArguments.keyValue -import org.jsoup.Jsoup -import org.jsoup.helper.W3CDom +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import no.nav.pdfgen.api.setupGeneratePdfApi +import no.nav.pdfgen.template.loadTemplates import org.slf4j.Logger import org.slf4j.LoggerFactory -import org.w3c.dom.Document +import org.verapdf.pdfa.VeraGreenfieldFoundryProvider import java.util.* -val APPLICATION_PDF = ContentType.parse("application/pdf") - -val collectorRegistry: CollectorRegistry = CollectorRegistry.defaultRegistry val objectMapper: ObjectMapper = ObjectMapper() - .registerKotlinModule() -val base64encoder: Encoder = Base64.getEncoder() -val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") -val dateFormatLong: DateTimeFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy") - .withLocale(Locale("no", "NO")) -val datetimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm") -val templateRoot: Path = Paths.get("templates/") -val imagesRoot: Path = Paths.get("resources/") -val fontsRoot: Path = Paths.get("fonts/") -val images = loadImages() -val resources = loadResources() -val handlebars: Handlebars = Handlebars(FileTemplateLoader(templateRoot.toFile())).apply { - registerNavHelpers(this) - infiniteLoops(true) -} - -val fonts: Array = objectMapper.readValue(Files.newInputStream(fontsRoot.resolve("config.json"))) + .registerKotlinModule() val log: Logger = LoggerFactory.getLogger("pdf-gen") -fun main(args: Array) { +fun main() { initializeApplication(8080).start(wait = true) } -class PdfContent( - private val w3Doc: Document, - override val contentType: ContentType = APPLICATION_PDF -) : OutgoingContent.WriteChannelContent() { - override suspend fun writeTo(channel: ByteWriteChannel) { - channel.toOutputStream().use { - createPDFA(w3Doc, it) - } - } -} - fun initializeApplication(port: Int): ApplicationEngine { System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider") - val templates = loadTemplates() - val disablePdfGet = System.getenv("DISABLE_PDF_GET")?.let { it == "true" } ?: false + val env = Environment() + if (!env.disablePdfGet) { + VeraGreenfieldFoundryProvider.initialise() // to enable validation of PDF/A while developing templates + } + val templates = loadTemplates(env) + val collectorRegistry: CollectorRegistry = CollectorRegistry.defaultRegistry - return embeddedServer(Netty, port, configure = { - // Increase timeout of Netty to handle large content bodies - responseWriteTimeoutSeconds = 60 - }) { + return embeddedServer( + Netty, port, + configure = { + responseWriteTimeoutSeconds = 60 // Increase timeout of Netty to handle large content bodies + } + ) { install(ContentNegotiation) { jackson {} } install(StatusPages) { status(HttpStatusCode.NotFound) { - call.respond(TextContent( + call.respond( + TextContent( messageFor404(templates, feature(Routing), call.request.path()), ContentType.Text.Plain.withCharset(Charsets.UTF_8), it - )) + ) + ) } } routing { @@ -134,151 +87,27 @@ fun initializeApplication(port: Int): ApplicationEngine { get("/prometheus") { val names = call.request.queryParameters.getAll("name[]")?.toSet() ?: setOf() call.respondTextWriter(ContentType.parse(TextFormat.CONTENT_TYPE_004)) { - TextFormat.write004(this, collectorRegistry.filteredMetricFamilySamples(names)) - } - } - if (!disablePdfGet) { - get("/api/v1/genpdf/{applicationName}/{template}") { - val template = call.parameters["template"]!! - val applicationName = call.parameters["applicationName"]!! - val dataFile = Paths.get("data", applicationName, "$template.json") - val data = objectMapper.readValue(if (Files.exists(dataFile)) { - Files.readAllBytes(dataFile) - } else { - "{}".toByteArray(Charsets.UTF_8) - }, JsonNode::class.java) - render(applicationName, template, loadTemplates(), data)?.let { document -> - call.respond(PdfContent(document)) - } ?: call.respondText("Template or application not found", status = HttpStatusCode.NotFound) - } - } - post("/api/v1/genpdf/{applicationName}/{template}") { - val startTime = System.currentTimeMillis() - val template = call.parameters["template"]!! - val applicationName = call.parameters["applicationName"]!! - val jsonNode = call.receive() - log.debug("JSON: {}", objectMapper.writeValueAsString(jsonNode)) - render(applicationName, template, templates, jsonNode)?.let { document -> - call.respond(PdfContent(document)) - log.info("Done generating PDF in ${System.currentTimeMillis() - startTime}ms") - } ?: call.respondText("Template or application not found", status = HttpStatusCode.NotFound) - } - post("/api/v1/genpdf/html/{applicationName}") { - val applicationName = call.parameters["applicationName"]!! - val timer = OPENHTMLTOPDF_RENDERING_SUMMARY.labels(applicationName, "converthtml").startTimer() - - val html = call.receiveText() - - ByteArrayOutputStream().use { bytes -> - createPDFA(fromHtmlToDocument(html), bytes) - call.respondBytes(bytes.toByteArray(), contentType = APPLICATION_PDF) - } - - log.info("Generated PDF using HTML template for $applicationName om ${timer.observeDuration()}ms") - } - post("/api/v1/genpdf/image/{applicationName}") { - val applicationName = call.parameters["applicationName"]!! - val timer = OPENHTMLTOPDF_RENDERING_SUMMARY.labels(applicationName, "convertjpeg").startTimer() - - when (call.request.contentType()) { - ContentType.Image.JPEG, ContentType.Image.PNG -> { - ByteArrayOutputStream().use { outputStream -> - createPDFA(call.receiveStream(), outputStream) - call.respondBytes(outputStream.toByteArray(), contentType = APPLICATION_PDF) + CoroutineScope(Dispatchers.IO).launch { + runCatching { + TextFormat.write004(this@respondTextWriter, collectorRegistry.filteredMetricFamilySamples(names)) } } - else -> call.respond(HttpStatusCode.UnsupportedMediaType) } - log.info("Generated PDF using image for $applicationName om ${timer.observeDuration()}ms") } + setupGeneratePdfApi(env, templates) } } } private fun messageFor404(templates: Map, Template>, routing: Routing, path: String) = - "Unkown path '$path'. Known paths:" + templates.map { (app, _) -> - val (applicationName, template) = app - apiRoutes(routing) - .map { it.replace("{applicationName}", applicationName) } - .map { it.replace("{template}", template) } - .joinToString("\n") - }.joinToString("\n") + "Unkown path '$path'. Known paths:" + templates.map { (app, _) -> + val (applicationName, template) = app + apiRoutes(routing) + .map { it.replace("{applicationName}", applicationName) }.joinToString("\n") { it.replace("{template}", template) } + }.joinToString("\n") private fun apiRoutes(routing: Routing) = allRoutes(routing).filter { it.selector is HttpMethodRouteSelector && it.toString().startsWith("/api") }.map { it.toString() } private fun allRoutes(route: Route): List = listOf(route) + route.children.flatMap(::allRoutes) - -fun fromHtmlToDocument(html: String): Document = JSOUP_PARSE_SUMMARY.startTimer().use { - val doc = Jsoup.parse(html) - W3CDom().fromJsoup(doc) -} - -fun render(applicationName: String, template: String, templates: Map, Template>, jsonNode: JsonNode): Document? { - return HANDLEBARS_RENDERING_SUMMARY.startTimer().use { - templates[applicationName to template]?.apply(Context - .newBuilder(jsonNode) - .resolver(JsonNodeValueResolver.INSTANCE, - MapValueResolver.INSTANCE) - .build()) - }?.let { html -> - log.debug("Generated HTML {}", keyValue("html", html)) - -/* Uncomment to output html to file for easier debug -* File("pdf.html").bufferedWriter().use { out -> -* out.write(html) -* } -*/ - fromHtmlToDocument(html) - } -} - -fun loadTemplates() = Files.list(templateRoot) - .filter { - !Files.isHidden(it) && Files.isDirectory(it) - } - .map { - it.fileName.toString() to Files.list(it).filter { it.fileName.extension == "hbs" } - } - .flatMap { (applicationName, templateFiles) -> - templateFiles.map { - val fileName = it.fileName.toString() - val templateName = fileName.substring(0..fileName.length - 5) - val templateBytes = Files.readAllBytes(it).toString(Charsets.UTF_8) - val xhtml = handlebars.compile(StringTemplateSource(fileName, templateBytes)) - (applicationName to templateName) to xhtml - } - } - .toList() - .toMap() - -fun loadImages() = Files.list(imagesRoot) - .filter { - val validExtensions = setOf("jpg", "jpeg", "png", "bmp", "svg") - !Files.isHidden(it) && it.fileName.extension in validExtensions - } - .map { - val fileName = it.fileName.toString() - val extension = when (it.fileName.extension) { - "jpg" -> "jpeg" // jpg is not a valid mime-type - "svg" -> "svg+xml" - else -> it.fileName.extension - } - val base64string = base64encoder.encodeToString(Files.readAllBytes(it)) - val base64 = "data:image/$extension;base64,$base64string" - fileName to base64 - } - .toList() - .toMap() - -fun loadResources() = Files.list(imagesRoot) - .filter { - val validExtensions = setOf("svg") - !Files.isHidden(it) && it.fileName.extension in validExtensions - } - .map { - it.fileName.toString() to Files.readAllBytes(it) - } - .toList() - .toMap() diff --git a/src/main/kotlin/no/nav/pdfgen/Environment.kt b/src/main/kotlin/no/nav/pdfgen/Environment.kt new file mode 100644 index 0000000..38e792f --- /dev/null +++ b/src/main/kotlin/no/nav/pdfgen/Environment.kt @@ -0,0 +1,68 @@ +package no.nav.pdfgen + +import com.fasterxml.jackson.module.kotlin.readValue +import io.ktor.util.* +import no.nav.pdfgen.util.FontMetadata +import org.apache.pdfbox.io.IOUtils +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.* +import kotlin.streams.toList + +val templateRoot: Path = Paths.get("templates/") +val imagesRoot: Path = Paths.get("resources/") +val fontsRoot: Path = Paths.get("fonts/") + +data class Environment( + val images: Map = loadImages(), + val resources: Map = loadResources(), + val colorProfile: ByteArray = IOUtils.toByteArray(Environment::class.java.getResourceAsStream("/sRGB2014.icc")), + val fonts: List = objectMapper.readValue(Files.newInputStream(fontsRoot.resolve("config.json"))), + val disablePdfGet: Boolean = System.getenv("DISABLE_PDF_GET")?.let { it == "true" } ?: false +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Environment + + if (!colorProfile.contentEquals(other.colorProfile)) return false + + return true + } + + override fun hashCode(): Int { + return colorProfile.contentHashCode() + } +} + +fun loadImages() = Files.list(imagesRoot) + .filter { + val validExtensions = setOf("jpg", "jpeg", "png", "bmp", "svg") + !Files.isHidden(it) && it.fileName.extension in validExtensions + } + .map { + val fileName = it.fileName.toString() + val extension = when (it.fileName.extension) { + "jpg" -> "jpeg" // jpg is not a valid mime-type + "svg" -> "svg+xml" + else -> it.fileName.extension + } + val base64string = Base64.getEncoder().encodeToString(Files.readAllBytes(it)) + val base64 = "data:image/$extension;base64,$base64string" + fileName to base64 + } + .toList() + .toMap() + +fun loadResources() = Files.list(imagesRoot) + .filter { + val validExtensions = setOf("svg") + !Files.isHidden(it) && it.fileName.extension in validExtensions + } + .map { + it.fileName.toString() to Files.readAllBytes(it) + } + .toList() + .toMap() diff --git a/src/main/kotlin/no/nav/pdfgen/Helpers.kt b/src/main/kotlin/no/nav/pdfgen/Helpers.kt deleted file mode 100644 index 9f074aa..0000000 --- a/src/main/kotlin/no/nav/pdfgen/Helpers.kt +++ /dev/null @@ -1,198 +0,0 @@ -package no.nav.pdfgen - -import com.fasterxml.jackson.databind.node.ArrayNode -import com.github.jknack.handlebars.Context -import com.github.jknack.handlebars.Handlebars -import com.github.jknack.handlebars.Helper -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import java.time.temporal.ChronoUnit -import no.nav.pdfgen.domain.syfosoknader.Periode -import no.nav.pdfgen.domain.syfosoknader.PeriodeMapper - -fun registerNavHelpers(handlebars: Handlebars) { - handlebars.apply { - registerHelper("iso_to_nor_date", Helper { context, _ -> - if (context == null) return@Helper "" - try { - dateFormat.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parseBest(context)) - } catch (e: Exception) { - dateFormat.format(DateTimeFormatter.ISO_DATE_TIME.parse(context)) - } - }) - - registerHelper("iso_to_nor_datetime", Helper { context, _ -> - if (context == null) return@Helper "" - try { - datetimeFormat.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parseBest(context)) - } catch (e: Exception) { - datetimeFormat.format(DateTimeFormatter.ISO_DATE_TIME.parse(context)) - } - }) - - registerHelper("iso_to_date", Helper { context, _ -> - if (context == null) return@Helper "" - dateFormat.format(DateTimeFormatter.ISO_DATE.parse(context)) - }) - - registerHelper("iso_to_long_date", Helper { context, _ -> - if (context == null) return@Helper "" - try { - dateFormatLong.format(DateTimeFormatter.ISO_DATE_TIME.parse(context)) - } catch (e: Exception) { - dateFormatLong.format(DateTimeFormatter.ISO_DATE.parse(context)) - } - }) - - registerHelper("duration", Helper { context, options -> - ChronoUnit.DAYS.between( - LocalDate.from(DateTimeFormatter.ISO_DATE.parse(context)), - LocalDate.from(DateTimeFormatter.ISO_DATE.parse(options.param(0))) - ) - }) - - // Expects json-objects of the form { "fom": "2018-05-20", "tom": "2018-05-29" } - registerHelper("json_to_period", Helper { context, _ -> - if (context == null) { - return@Helper "" - } else { - val periode: Periode = PeriodeMapper.jsonTilPeriode(context) - return@Helper periode.fom!!.format(dateFormat) + " - " + periode.tom!!.format(dateFormat) - } - }) - registerHelper("insert_at", Helper { context, options -> - if (context == null) return@Helper "" - val divider = options.hash("divider", " ") - options.params - .map { it as Int } - .fold(context.toString()) { v, idx -> v.substring(0, idx) + divider + v.substring(idx, v.length) } - }) - - registerHelper("eq", Helper { context, options -> - if (context.toString() == options.param(0).toString()) options.fn() else options.inverse() - }) - - registerHelper("not_eq", Helper { context, options -> - if (context.toString() != options.param(0).toString()) options.fn() else options.inverse() - }) - - registerHelper("gt", Helper> { context, options -> - val param = options.param(0) as Comparable - if (context > param) options.fn() else options.inverse() - }) - - registerHelper("lt", Helper> { context, options -> - val param = options.param(0) as Comparable - if (context < param) options.fn() else options.inverse() - }) - - registerHelper("safe", Helper { context, _ -> - if (context == null) "" else Handlebars.SafeString(context) - }) - - registerHelper("image", Helper { context, _ -> - if (context == null) "" else images[context] - }) - - registerHelper("resource", Helper { context, _ -> - resources[context]?.toString(Charsets.UTF_8) ?: "" - }) - - registerHelper("capitalize", Helper { context, _ -> - if (context == null) "" else context.toLowerCase().capitalize() - }) - - registerHelper("capitalize_names", Helper { context, _ -> - if (context == null) "" else - Handlebars.SafeString(context - .trim() - .replace("\\s+".toRegex(), " ") - .toLowerCase() - .capitalizeWords(" ") - .capitalizeWords("-") - .capitalizeWords("'")) - }) - - - registerHelper("inc", Helper { context, _ -> - context + 1 - }) - - registerHelper("formatComma", Helper { context, _ -> - if (context == null) "" else context.toString().replace(".", ",") - }) - - registerHelper("any", Helper { first, options -> - if ((listOf(first) + options.params).all { options.isFalsy(it) }) { - options.inverse() - } else { - options.fn() - } - }) - - registerHelper("contains_field", Helper?> { list, options -> - val checkfor = options.param(0, null as String?) - - val contains = list - ?.map { Context.newContext(options.context, it) } - ?.any { ctx -> !options.isFalsy(ctx.get(checkfor)) } - ?: false - - if (contains) { - options.fn() - } else { - options.inverse() - } - }) - - registerHelper("contains_all", Helper { list, options -> - val textValues = list.map { it.textValue() } - - val params = options.params.toList() - val contains = if (params.isEmpty()) false else textValues.containsAll(params) - - if (contains) { - options.fn() - } else { - options.inverse() - } - }) - - registerHelper("currency_no", Helper { context, options -> - if (context == null) return@Helper "" - val withoutDecimals = options.param(0, false) - - val splitNumber = context.toString().split(".") - - val formattedNumber = splitNumber.first().reversed().chunked(3).joinToString(" ").reversed() - if (withoutDecimals) { - formattedNumber - } else { - val decimals = splitNumber.drop(1).firstOrNull()?.let { (it + "0").substring(0, 2) } ?: "00" - "$formattedNumber,$decimals" - } - }) - - registerHelper("is_defined", Helper { context, options -> - if (context != null) options.fn() else options.inverse() - }) - - registerHelper("breaklines", Helper { context, _ -> - if (context == null) { - "" - } else { - val santizedText = Handlebars.Utils.escapeExpression(context) - val withLineBreak = santizedText.toString() - .replace("\\r\\n", "
") - .replace("\\n", "
") - .replace("\r\n", "
") - .replace("\n", "
") - Handlebars.SafeString(withLineBreak) - } - }) - - } -} - -private fun String.capitalizeWords(wordSplitter: String) = - this.split(wordSplitter).joinToString(wordSplitter) { it.trim().capitalize() } diff --git a/src/main/kotlin/no/nav/pdfgen/MetricRegistry.kt b/src/main/kotlin/no/nav/pdfgen/MetricRegistry.kt index 4fea5df..448d1ef 100644 --- a/src/main/kotlin/no/nav/pdfgen/MetricRegistry.kt +++ b/src/main/kotlin/no/nav/pdfgen/MetricRegistry.kt @@ -3,15 +3,15 @@ package no.nav.pdfgen import io.prometheus.client.Summary val HANDLEBARS_RENDERING_SUMMARY: Summary = Summary.Builder() - .name("handlebars_rendering") - .help("Time it takes for handlebars to render the template") - .register() + .name("handlebars_rendering") + .help("Time it takes for handlebars to render the template") + .register() val OPENHTMLTOPDF_RENDERING_SUMMARY: Summary = Summary.Builder() - .name("openhtmltopdf_rendering_summary") - .help("Time it takes to render a PDF") - .labelNames("application_name", "template_type") - .register() + .name("openhtmltopdf_rendering_summary") + .help("Time it takes to render a PDF") + .labelNames("application_name", "template_type") + .register() val JSOUP_PARSE_SUMMARY: Summary = Summary.Builder() - .name("jsoup_parse") - .help("Time it takes jsoup to parse the template") - .register() + .name("jsoup_parse") + .help("Time it takes jsoup to parse the template") + .register() diff --git a/src/main/kotlin/no/nav/pdfgen/Utils.kt b/src/main/kotlin/no/nav/pdfgen/Utils.kt deleted file mode 100644 index 1757cbb..0000000 --- a/src/main/kotlin/no/nav/pdfgen/Utils.kt +++ /dev/null @@ -1,102 +0,0 @@ -package no.nav.pdfgen - -import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder -import com.openhtmltopdf.pdfboxout.PdfRendererBuilder -import com.openhtmltopdf.svgsupport.BatikSVGDrawer -import java.awt.geom.AffineTransform -import java.awt.image.AffineTransformOp -import java.awt.image.AffineTransformOp.TYPE_BILINEAR -import java.awt.image.BufferedImage -import java.io.ByteArrayInputStream -import java.io.InputStream -import java.io.OutputStream -import java.nio.file.Files -import javax.imageio.ImageIO -import org.apache.pdfbox.io.IOUtils -import org.apache.pdfbox.pdmodel.PDDocument -import org.apache.pdfbox.pdmodel.PDPage -import org.apache.pdfbox.pdmodel.PDPageContentStream -import org.apache.pdfbox.pdmodel.common.PDRectangle -import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory -import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject -import org.apache.pdfbox.util.Matrix -import org.w3c.dom.Document - -class Utils - -val colorProfile: ByteArray = IOUtils.toByteArray(Utils::class.java.getResourceAsStream("/sRGB2014.icc")) - -data class FontMetadata( - val family: String, - val path: String, - val weight: Int, - val style: BaseRendererBuilder.FontStyle, - val subset: Boolean -) { - val bytes: ByteArray = Files.readAllBytes(fontsRoot.resolve(path)) -} - -fun createPDFA(w3doc: Document, outputStream: OutputStream) = PdfRendererBuilder() - .apply { - for (font in fonts) { - useFont({ ByteArrayInputStream(font.bytes) }, font.family, font.weight, font.style, font.subset) - } - } - // .useFastMode() wait with fast mode until it doesn't print a bunch of errors - .useColorProfile(colorProfile) - .useSVGDrawer(BatikSVGDrawer()) - .usePdfAConformance(PdfRendererBuilder.PdfAConformance.PDFA_2_U) - .withW3cDocument(w3doc, "") - .toStream(outputStream) - .buildPdfRenderer() - .createPDF() - -fun createPDFA(imageStream: InputStream, outputStream: OutputStream) { - PDDocument().use { document -> - - val page = PDPage(PDRectangle.A4) - document.addPage(page) - val image = toPortait(ImageIO.read(imageStream)) - - val quality = 1.0f - - val pdImage = JPEGFactory.createFromImage(document, image, quality) - val imageSize = scale(pdImage, page) - - PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, false).use { - it.drawImage(pdImage, Matrix(imageSize.width, 0f, 0f, imageSize.height, 0f, 0f)) - } - - document.save(outputStream) - } -} - -private fun toPortait(image: BufferedImage): BufferedImage { - if (image.height >= image.width) { - return image - } - - val rotateTransform = AffineTransform.getRotateInstance(Math.toRadians(90.0), (image.height / 2f).toDouble(), (image.height / 2f).toDouble()) - - return AffineTransformOp(rotateTransform, TYPE_BILINEAR) - .filter(image, BufferedImage(image.height, image.width, image.type)) -} - -data class ImageSize(val width: Float, val height: Float) - -private fun scale(image: PDImageXObject, page: PDPage): ImageSize { - var width = image.width.toFloat() - var height = image.height.toFloat() - - if (width > page.cropBox.width) { - width = page.cropBox.width - height = width * image.height.toFloat() / image.width.toFloat() - } - - if (height > page.cropBox.height) { - height = page.cropBox.height - width = height * image.width.toFloat() / image.height.toFloat() - } - - return ImageSize(width, height) -} diff --git a/src/main/kotlin/no/nav/pdfgen/api/GeneratePdfApi.kt b/src/main/kotlin/no/nav/pdfgen/api/GeneratePdfApi.kt new file mode 100644 index 0000000..52cf0e2 --- /dev/null +++ b/src/main/kotlin/no/nav/pdfgen/api/GeneratePdfApi.kt @@ -0,0 +1,108 @@ +package no.nav.pdfgen.api + +import com.fasterxml.jackson.databind.JsonNode +import com.github.jknack.handlebars.Context +import com.github.jknack.handlebars.JsonNodeValueResolver +import com.github.jknack.handlebars.Template +import com.github.jknack.handlebars.context.MapValueResolver +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import net.logstash.logback.argument.StructuredArguments +import no.nav.pdfgen.* +import no.nav.pdfgen.pdf.PdfContent +import no.nav.pdfgen.pdf.createPDFA +import no.nav.pdfgen.template.TemplateMap +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Paths + +fun Routing.setupGeneratePdfApi(env: Environment, templates: TemplateMap) { + route("/api/v1/genpdf") { + if (!env.disablePdfGet) { + get("/{applicationName}/{template}") { + val template = call.parameters["template"]!! + val applicationName = call.parameters["applicationName"]!! + val dataFile = Paths.get("data", applicationName, "$template.json") + val data = objectMapper.readValue( + if (Files.exists(dataFile)) { + Files.readAllBytes(dataFile) + } else { + "{}".toByteArray(Charsets.UTF_8) + }, + JsonNode::class.java + ) + render(applicationName, template, templates, data)?.let { document -> + call.respond(PdfContent(document, env)) + } ?: call.respondText("Template or application not found", status = HttpStatusCode.NotFound) + } + } + post("/{applicationName}/{template}") { + val startTime = System.currentTimeMillis() + val template = call.parameters["template"]!! + val applicationName = call.parameters["applicationName"]!! + val jsonNode = call.receive() + log.debug("JSON: {}", objectMapper.writeValueAsString(jsonNode)) + render(applicationName, template, templates, jsonNode)?.let { document -> + call.respond(PdfContent(document, env)) + log.info("Done generating PDF in ${System.currentTimeMillis() - startTime}ms") + } ?: call.respondText("Template or application not found", status = HttpStatusCode.NotFound) + } + post("/html/{applicationName}") { + val applicationName = call.parameters["applicationName"]!! + val timer = OPENHTMLTOPDF_RENDERING_SUMMARY.labels(applicationName, "converthtml").startTimer() + + val html = call.receiveText() + + call.respond(PdfContent(html, env)) + log.info("Generated PDF using HTML template for $applicationName om ${timer.observeDuration()}ms") + } + post("/image/{applicationName}") { + val applicationName = call.parameters["applicationName"]!! + val timer = OPENHTMLTOPDF_RENDERING_SUMMARY.labels(applicationName, "convertjpeg").startTimer() + + when (call.request.contentType()) { + ContentType.Image.JPEG, ContentType.Image.PNG -> { + withContext(Dispatchers.IO) { + call.receive().use { inputStream -> + ByteArrayOutputStream().use { outputStream -> + createPDFA(inputStream, outputStream, env) + call.respondBytes(outputStream.toByteArray(), contentType = ContentType.Application.Pdf) + } + } + } + } + else -> call.respond(HttpStatusCode.UnsupportedMediaType) + } + log.info("Generated PDF using image for $applicationName om ${timer.observeDuration()}ms") + } + } +} + +fun render(applicationName: String, template: String, templates: Map, Template>, jsonNode: JsonNode): String? { + return HANDLEBARS_RENDERING_SUMMARY.startTimer().use { + templates[applicationName to template]?.apply( + Context + .newBuilder(jsonNode) + .resolver( + JsonNodeValueResolver.INSTANCE, + MapValueResolver.INSTANCE + ) + .build() + ) + }?.let { html -> + log.debug("Generated HTML {}", StructuredArguments.keyValue("html", html)) + +/* Uncomment to output html to file for easier debug +* File("pdf.html").bufferedWriter().use { out -> +* out.write(html) +* } +*/ + html + } +} diff --git a/src/main/kotlin/no/nav/pdfgen/pdf/Create.kt b/src/main/kotlin/no/nav/pdfgen/pdf/Create.kt new file mode 100644 index 0000000..de2e48b --- /dev/null +++ b/src/main/kotlin/no/nav/pdfgen/pdf/Create.kt @@ -0,0 +1,131 @@ +package no.nav.pdfgen.pdf + +import com.openhtmltopdf.pdfboxout.PdfRendererBuilder +import io.ktor.http.* +import io.ktor.http.content.* +import no.nav.pdfgen.Environment +import no.nav.pdfgen.log +import no.nav.pdfgen.util.scale +import no.nav.pdfgen.util.toPortait +import org.apache.pdfbox.pdmodel.PDDocument +import org.apache.pdfbox.pdmodel.PDPage +import org.apache.pdfbox.pdmodel.PDPageContentStream +import org.apache.pdfbox.pdmodel.common.PDMetadata +import org.apache.pdfbox.pdmodel.common.PDRectangle +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot +import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent +import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory +import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences +import org.apache.pdfbox.util.Matrix +import org.apache.xmpbox.XMPMetadata +import org.apache.xmpbox.type.BadFieldValueException +import org.apache.xmpbox.xml.XmpSerializer +import org.verapdf.pdfa.Foundries +import org.verapdf.pdfa.flavours.PDFAFlavour +import org.verapdf.pdfa.results.TestAssertion +import java.io.* +import java.lang.IllegalArgumentException +import java.util.* +import javax.imageio.ImageIO + +fun createPDFA(html: String, env: Environment): ByteArray { + val pdf = ByteArrayOutputStream().apply { + PdfRendererBuilder() + .apply { + for (font in env.fonts) { + useFont({ ByteArrayInputStream(font.bytes) }, font.family, font.weight, font.style, font.subset) + } + } + .usePdfAConformance(PdfRendererBuilder.PdfAConformance.PDFA_2_U) + .useColorProfile(env.colorProfile) + .withHtmlContent(html, null) + .toStream(this) + .run() + }.toByteArray() + require(verifyCompliance(pdf)) { "Non-compliant PDF/A :(" } + return pdf +} + +fun createPDFA(imageStream: InputStream, outputStream: OutputStream, env: Environment) { + PDDocument().use { document -> + val page = PDPage(PDRectangle.A4) + document.addPage(page) + val image = toPortait(ImageIO.read(imageStream)) + + val quality = 1.0f + + val pdImage = JPEGFactory.createFromImage(document, image, quality) + val imageSize = scale(pdImage, page) + + PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, false).use { + it.drawImage(pdImage, Matrix(imageSize.width, 0f, 0f, imageSize.height, 0f, 0f)) + } + + val xmp = XMPMetadata.createXMPMetadata() + val catalog = document.documentCatalog + val cal = Calendar.getInstance() + + try { + val dc = xmp.createAndAddDublinCoreSchema() + dc.addCreator("pdfgen") + dc.addDate(cal) + + val id = xmp.createAndAddPFAIdentificationSchema() + id.part = 2 + id.conformance = "U" + + val serializer = XmpSerializer() + val baos = ByteArrayOutputStream() + serializer.serialize(xmp, baos, true) + + val metadata = PDMetadata(document) + metadata.importXMPMetadata(baos.toByteArray()) + catalog.metadata = metadata + } catch (e: BadFieldValueException) { + throw IllegalArgumentException(e) + } + + val intent = PDOutputIntent(document, env.colorProfile.inputStream()) + intent.info = "sRGB IEC61966-2.1" + intent.outputCondition = "sRGB IEC61966-2.1" + intent.outputConditionIdentifier = "sRGB IEC61966-2.1" + intent.registryName = "http://www.color.org" + catalog.addOutputIntent(intent) + catalog.language = "nb-NO" + + val pdViewer = PDViewerPreferences(page.cosObject) + pdViewer.setDisplayDocTitle(true) + catalog.viewerPreferences = pdViewer + + catalog.markInfo = PDMarkInfo(page.cosObject) + catalog.structureTreeRoot = PDStructureTreeRoot() + catalog.markInfo.isMarked = true + + document.save(outputStream) + document.close() + } +} + +private fun verifyCompliance(input: ByteArray, flavour: PDFAFlavour = PDFAFlavour.PDFA_2_U): Boolean { + val pdf = ByteArrayInputStream(input) + val validator = Foundries.defaultInstance().createValidator(flavour, false) + val result = Foundries.defaultInstance().createParser(pdf).use { validator.validate(it) } + val failures = result.testAssertions + .filter { it.status != TestAssertion.Status.PASSED } + failures.forEach { test -> + log.warn(test.message) + log.warn("Location ${test.location.context} ${test.location.level}") + log.warn("Status ${test.status}") + log.warn("Test number ${test.ruleId.testNumber}") + } + return failures.isEmpty() +} + +class PdfContent( + private val html: String, + private val env: Environment, + override val contentType: ContentType = ContentType.Application.Pdf +) : OutgoingContent.ByteArrayContent() { + override fun bytes(): ByteArray = createPDFA(html, env) +} diff --git a/src/main/kotlin/no/nav/pdfgen/template/Helpers.kt b/src/main/kotlin/no/nav/pdfgen/template/Helpers.kt new file mode 100644 index 0000000..049d8bc --- /dev/null +++ b/src/main/kotlin/no/nav/pdfgen/template/Helpers.kt @@ -0,0 +1,275 @@ +package no.nav.pdfgen.template + +import com.fasterxml.jackson.databind.node.ArrayNode +import com.github.jknack.handlebars.Context +import com.github.jknack.handlebars.Handlebars +import com.github.jknack.handlebars.Helper +import no.nav.pdfgen.Environment +import no.nav.pdfgen.domain.syfosoknader.Periode +import no.nav.pdfgen.domain.syfosoknader.PeriodeMapper +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import java.util.* + +val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") +val dateFormatLong: DateTimeFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy") + .withLocale(Locale("no", "NO")) +val datetimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm") + +fun formatDate(formatter: DateTimeFormatter, context: CharSequence): String = try { + formatter.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parseBest(context)) +} catch (e: Exception) { + formatter.format(DateTimeFormatter.ISO_DATE_TIME.parse(context)) +} + +fun registerNavHelpers(handlebars: Handlebars, env: Environment) { + handlebars.apply { + registerHelper( + "iso_to_nor_date", + Helper { context, _ -> + if (context == null) return@Helper "" + formatDate(dateFormat, context) + } + ) + + registerHelper( + "iso_to_nor_datetime", + Helper { context, _ -> + if (context == null) return@Helper "" + formatDate(datetimeFormat, context) + } + ) + + registerHelper( + "iso_to_date", + Helper { context, _ -> + if (context == null) return@Helper "" + dateFormat.format(DateTimeFormatter.ISO_DATE.parse(context)) + } + ) + + registerHelper( + "iso_to_long_date", + Helper { context, _ -> + if (context == null) return@Helper "" + try { + dateFormatLong.format(DateTimeFormatter.ISO_DATE_TIME.parse(context)) + } catch (e: Exception) { + dateFormatLong.format(DateTimeFormatter.ISO_DATE.parse(context)) + } + } + ) + + registerHelper( + "duration", + Helper { context, options -> + ChronoUnit.DAYS.between( + LocalDate.from(DateTimeFormatter.ISO_DATE.parse(context)), + LocalDate.from(DateTimeFormatter.ISO_DATE.parse(options.param(0))) + ) + } + ) + + // Expects json-objects of the form { "fom": "2018-05-20", "tom": "2018-05-29" } + registerHelper( + "json_to_period", + Helper { context, _ -> + if (context == null) { + return@Helper "" + } else { + val periode: Periode = PeriodeMapper.jsonTilPeriode(context) + return@Helper periode.fom!!.format(dateFormat) + " - " + periode.tom!!.format(dateFormat) + } + } + ) + registerHelper( + "insert_at", + Helper { context, options -> + if (context == null) return@Helper "" + val divider = options.hash("divider", " ") + options.params + .map { it as Int } + .fold(context.toString()) { v, idx -> v.substring(0, idx) + divider + v.substring(idx, v.length) } + } + ) + + registerHelper( + "eq", + Helper { context, options -> + if (context?.toString() == options.param(0)?.toString()) options.fn() else options.inverse() + } + ) + + registerHelper( + "not_eq", + Helper { context, options -> + if (context?.toString() != options.param(0)?.toString()) options.fn() else options.inverse() + } + ) + + registerHelper( + "gt", + Helper> { context, options -> + val param = options.param(0) as Comparable + if (context > param) options.fn() else options.inverse() + } + ) + + registerHelper( + "lt", + Helper> { context, options -> + val param = options.param(0) as Comparable + if (context < param) options.fn() else options.inverse() + } + ) + + registerHelper( + "safe", + Helper { context, _ -> + if (context == null) "" else Handlebars.SafeString(context) + } + ) + + registerHelper( + "image", + Helper { context, _ -> + if (context == null) "" else env.images[context] + } + ) + + registerHelper( + "resource", + Helper { context, _ -> + env.resources[context]?.toString(Charsets.UTF_8) ?: "" + } + ) + + registerHelper( + "capitalize", + Helper { context, _ -> + context?.toLowerCase()?.capitalize() ?: "" + } + ) + + registerHelper( + "capitalize_names", + Helper { context, _ -> + if (context == null) "" else + Handlebars.SafeString( + context + .trim() + .replace("\\s+".toRegex(), " ") + .toLowerCase() + .capitalizeWords(" ") + .capitalizeWords("-") + .capitalizeWords("'") + ) + } + ) + + registerHelper( + "inc", + Helper { context, _ -> + context + 1 + } + ) + + registerHelper( + "formatComma", + Helper { context, _ -> + context?.toString()?.replace(".", ",") ?: "" + } + ) + + registerHelper( + "any", + Helper { first, options -> + if ((listOf(first) + options.params).all { options.isFalsy(it) }) { + options.inverse() + } else { + options.fn() + } + } + ) + + registerHelper( + "contains_field", + Helper?> { list, options -> + val checkfor = options.param(0, null as String?) + + val contains = list + ?.map { Context.newContext(options.context, it) } + ?.any { ctx -> !options.isFalsy(ctx.get(checkfor)) } + ?: false + + if (contains) { + options.fn() + } else { + options.inverse() + } + } + ) + + registerHelper( + "contains_all", + Helper { list, options -> + val textValues = list.map { it.textValue() } + + val params = options.params.toList() + val contains = if (params.isEmpty()) false else textValues.containsAll(params) + + if (contains) { + options.fn() + } else { + options.inverse() + } + } + ) + + registerHelper( + "currency_no", + Helper { context, options -> + if (context == null) return@Helper "" + val withoutDecimals = options.param(0, false) + + val splitNumber = context.toString().split(".") + + val formattedNumber = splitNumber.first().reversed().chunked(3).joinToString(" ").reversed() + if (withoutDecimals) { + formattedNumber + } else { + val decimals = splitNumber.drop(1).firstOrNull()?.let { (it + "0").substring(0, 2) } ?: "00" + "$formattedNumber,$decimals" + } + } + ) + + registerHelper( + "is_defined", + Helper { context, options -> + if (context != null) options.fn() else options.inverse() + } + ) + + registerHelper( + "breaklines", + Helper { context, _ -> + if (context == null) { + "" + } else { + val santizedText = Handlebars.Utils.escapeExpression(context) + val withLineBreak = santizedText.toString() + .replace("\\r\\n", "
") + .replace("\\n", "
") + .replace("\r\n", "
") + .replace("\n", "
") + Handlebars.SafeString(withLineBreak) + } + } + ) + } +} + +private fun String.capitalizeWords(wordSplitter: String) = + this.split(wordSplitter).joinToString(wordSplitter) { it.trim().capitalize() } diff --git a/src/main/kotlin/no/nav/pdfgen/template/Templates.kt b/src/main/kotlin/no/nav/pdfgen/template/Templates.kt new file mode 100644 index 0000000..b68a91a --- /dev/null +++ b/src/main/kotlin/no/nav/pdfgen/template/Templates.kt @@ -0,0 +1,37 @@ +package no.nav.pdfgen.template + +import com.github.jknack.handlebars.Handlebars +import com.github.jknack.handlebars.Template +import com.github.jknack.handlebars.io.FileTemplateLoader +import com.github.jknack.handlebars.io.StringTemplateSource +import io.ktor.util.* +import no.nav.pdfgen.Environment +import no.nav.pdfgen.templateRoot +import java.nio.file.Files +import kotlin.streams.toList + +typealias TemplateMap = Map, Template> + +fun setupHandlebars(env: Environment) = Handlebars(FileTemplateLoader(templateRoot.toFile())).apply { + registerNavHelpers(this, env) + infiniteLoops(true) +} + +fun loadTemplates(env: Environment): TemplateMap = Files.list(templateRoot) + .filter { + !Files.isHidden(it) && Files.isDirectory(it) + } + .map { + it.fileName.toString() to Files.list(it).filter { b -> b.fileName.extension == "hbs" } + } + .flatMap { (applicationName, templateFiles) -> + templateFiles.map { + val fileName = it.fileName.toString() + val templateName = fileName.substring(0..fileName.length - 5) + val templateBytes = Files.readAllBytes(it).toString(Charsets.UTF_8) + val xhtml = setupHandlebars(env).compile(StringTemplateSource(fileName, templateBytes)) + (applicationName to templateName) to xhtml + } + } + .toList() + .toMap() diff --git a/src/main/kotlin/no/nav/pdfgen/util/FontMetadata.kt b/src/main/kotlin/no/nav/pdfgen/util/FontMetadata.kt new file mode 100644 index 0000000..2aecb76 --- /dev/null +++ b/src/main/kotlin/no/nav/pdfgen/util/FontMetadata.kt @@ -0,0 +1,15 @@ +package no.nav.pdfgen.util + +import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder +import no.nav.pdfgen.fontsRoot +import java.nio.file.Files + +data class FontMetadata( + val family: String, + val path: String, + val weight: Int, + val style: BaseRendererBuilder.FontStyle, + val subset: Boolean +) { + val bytes: ByteArray = Files.readAllBytes(fontsRoot.resolve(path)) +} diff --git a/src/main/kotlin/no/nav/pdfgen/util/Image.kt b/src/main/kotlin/no/nav/pdfgen/util/Image.kt new file mode 100644 index 0000000..d69b8ec --- /dev/null +++ b/src/main/kotlin/no/nav/pdfgen/util/Image.kt @@ -0,0 +1,37 @@ +package no.nav.pdfgen.util + +import org.apache.pdfbox.pdmodel.PDPage +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject +import java.awt.geom.AffineTransform +import java.awt.image.AffineTransformOp +import java.awt.image.BufferedImage + +data class ImageSize(val width: Float, val height: Float) + +fun toPortait(image: BufferedImage): BufferedImage { + if (image.height >= image.width) { + return image + } + + val rotateTransform = AffineTransform.getRotateInstance(Math.toRadians(90.0), (image.height / 2f).toDouble(), (image.height / 2f).toDouble()) + + return AffineTransformOp(rotateTransform, AffineTransformOp.TYPE_BILINEAR) + .filter(image, BufferedImage(image.height, image.width, image.type)) +} + +fun scale(image: PDImageXObject, page: PDPage): ImageSize { + var width = image.width.toFloat() + var height = image.height.toFloat() + + if (width > page.cropBox.width) { + width = page.cropBox.width + height = width * image.height.toFloat() / image.width.toFloat() + } + + if (height > page.cropBox.height) { + height = page.cropBox.height + width = height * image.width.toFloat() / image.height.toFloat() + } + + return ImageSize(width, height) +} diff --git a/src/test/kotlin/no/nav/pdfgen/HelperSpek.kt b/src/test/kotlin/no/nav/pdfgen/HelperSpek.kt index 2f7ae68..0fef9f0 100644 --- a/src/test/kotlin/no/nav/pdfgen/HelperSpek.kt +++ b/src/test/kotlin/no/nav/pdfgen/HelperSpek.kt @@ -8,26 +8,27 @@ import com.github.jknack.handlebars.Handlebars import com.github.jknack.handlebars.JsonNodeValueResolver import com.github.jknack.handlebars.context.MapValueResolver import com.github.jknack.handlebars.io.ClassPathTemplateLoader -import org.amshove.kluent.AnyException -import org.amshove.kluent.invoking -import org.amshove.kluent.shouldEqual -import org.amshove.kluent.shouldThrow +import no.nav.pdfgen.template.registerNavHelpers +import org.amshove.kluent.* import org.spekframework.spek2.Spek import org.spekframework.spek2.style.specification.describe object HelperSpek : Spek({ val jsonNodeFactory = JsonNodeFactory.instance + val env = Environment() val handlebars = Handlebars(ClassPathTemplateLoader()).apply { - registerNavHelpers(this) + registerNavHelpers(this, env) } fun jsonContext(jsonNode: JsonNode): Context { println(ObjectMapper().writeValueAsString(jsonNode)) return Context - .newBuilder(jsonNode) - .resolver(JsonNodeValueResolver.INSTANCE, - MapValueResolver.INSTANCE) - .build() + .newBuilder(jsonNode) + .resolver( + JsonNodeValueResolver.INSTANCE, + MapValueResolver.INSTANCE + ) + .build() } describe("List contains helper") { @@ -36,19 +37,19 @@ object HelperSpek : Spek({ it("A array containing the field fish should result in the string IT_CONTAINS") { val jsonNode = jsonNodeFactory.objectNode().apply { putArray("list") - .addObject().put("fish", "test") + .addObject().put("fish", "test") } - template.apply(jsonContext(jsonNode)).trim() shouldEqual "IT_CONTAINS" + template.apply(jsonContext(jsonNode)).trim() shouldBeEqualTo "IT_CONTAINS" } it("A array containing the field fish, but its a false boolean should result in NO_CONTAINS") { val jsonNode = jsonNodeFactory.objectNode().apply { putArray("list") - .addObject().put("fish", false) + .addObject().put("fish", false) } - template.apply(jsonContext(jsonNode)).trim() shouldEqual "NO_CONTAINS" + template.apply(jsonContext(jsonNode)).trim() shouldBeEqualTo "NO_CONTAINS" } it("A empty array should result in NO_CONTAINS") { @@ -56,16 +57,16 @@ object HelperSpek : Spek({ putArray("list") } - template.apply(jsonContext(jsonNode)).trim() shouldEqual "NO_CONTAINS" + template.apply(jsonContext(jsonNode)).trim() shouldBeEqualTo "NO_CONTAINS" } it("A array without the field fish should result in NO_CONTAINS") { val jsonNode = jsonNodeFactory.objectNode().apply { putArray("list") - .addObject().put("shark", "something") + .addObject().put("shark", "something") } - template.apply(jsonContext(jsonNode)).trim() shouldEqual "NO_CONTAINS" + template.apply(jsonContext(jsonNode)).trim() shouldBeEqualTo "NO_CONTAINS" } it("A array a null fish field results in IT_CONTAINS") { @@ -75,7 +76,7 @@ object HelperSpek : Spek({ } } - template.apply(jsonContext(jsonNode)).trim() shouldEqual "NO_CONTAINS" + template.apply(jsonContext(jsonNode)).trim() shouldBeEqualTo "NO_CONTAINS" } it("A array with two nodes, where the second contains the field fish results in IT_CONTAINS") { @@ -86,7 +87,7 @@ object HelperSpek : Spek({ } } - template.apply(jsonContext(jsonNode)).trim() shouldEqual "IT_CONTAINS" + template.apply(jsonContext(jsonNode)).trim() shouldBeEqualTo "IT_CONTAINS" } it("A array with two nodes, where the field fish contains null on the first and a normal value on the second results in IT_CONTAINS") { @@ -97,179 +98,183 @@ object HelperSpek : Spek({ } } - template.apply(jsonContext(jsonNode)).trim() shouldEqual "IT_CONTAINS" + template.apply(jsonContext(jsonNode)).trim() shouldBeEqualTo "IT_CONTAINS" } } describe("Any operator") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - put("a", "a") - put("b", "b") - put("c", "c") - }) + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + put("a", "a") + put("b", "b") + put("c", "c") + } + ) it("Should result in empty result when a single statement fails") { - handlebars.compileInline("{{#any d}}YES{{/any}}").apply(context) shouldEqual "" + handlebars.compileInline("{{#any d}}YES{{/any}}").apply(context) shouldBeEqualTo "" } it("Should result in a YES when a single statement is ok") { - handlebars.compileInline("{{#any a}}YES{{/any}}").apply(context) shouldEqual "YES" + handlebars.compileInline("{{#any a}}YES{{/any}}").apply(context) shouldBeEqualTo "YES" } it("Should result in a YES when one of multiple statements is ok") { - handlebars.compileInline("{{#any d e f a}}YES{{/any}}").apply(context) shouldEqual "YES" + handlebars.compileInline("{{#any d e f a}}YES{{/any}}").apply(context) shouldBeEqualTo "YES" } it("Should result in a YES when the first of multiple statements is ok") { - handlebars.compileInline("{{#any a d e f}}YES{{/any}}").apply(context) shouldEqual "YES" + handlebars.compileInline("{{#any a d e f}}YES{{/any}}").apply(context) shouldBeEqualTo "YES" } it("Should result in empty result when many statements fails") { - handlebars.compileInline("{{#any d e f g}}YES{{/any}}").apply(context) shouldEqual "" + handlebars.compileInline("{{#any d e f g}}YES{{/any}}").apply(context) shouldBeEqualTo "" } } describe("Datetime formatting") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - put("timestamp", "2020-03-03T10:15:30") - put("timestampLong", "2020-10-03T10:15:30") - put("date", "2020-02-12") - }) + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + put("timestamp", "2020-03-03T10:15:30") + put("timestampLong", "2020-10-03T10:15:30") + put("date", "2020-02-12") + } + ) it("should format as Norwegian short date and time") { - handlebars.compileInline("{{ iso_to_nor_datetime timestamp }}").apply(context) shouldEqual "03.03.2020 10:15" + handlebars.compileInline("{{ iso_to_nor_datetime timestamp }}").apply(context) shouldBeEqualTo "03.03.2020 10:15" } it("should format as Norwegian short date") { - handlebars.compileInline("{{ iso_to_nor_date timestamp }}").apply(context) shouldEqual "03.03.2020" + handlebars.compileInline("{{ iso_to_nor_date timestamp }}").apply(context) shouldBeEqualTo "03.03.2020" } it("should format timestamp as Norwegian long date") { - handlebars.compileInline("{{ iso_to_long_date timestampLong }}").apply(context) shouldEqual "3. oktober 2020" + handlebars.compileInline("{{ iso_to_long_date timestampLong }}").apply(context) shouldBeEqualTo "3. oktober 2020" } it("should format date as Norwegian long date") { - handlebars.compileInline("{{ iso_to_long_date date }}").apply(context) shouldEqual "12. februar 2020" + handlebars.compileInline("{{ iso_to_long_date date }}").apply(context) shouldBeEqualTo "12. februar 2020" } } describe("eq") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - put("an_int", 1337) - put("a_double", 1337.67) - put("string_with_int", "1337") - put("string_with_double", "1337.67") - put("a_string", "imma string") - }) + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + put("an_int", 1337) + put("a_double", 1337.67) + put("string_with_int", "1337") + put("string_with_double", "1337.67") + put("a_string", "imma string") + } + ) it("should equal self") { - handlebars.compileInline("{{#eq an_int an_int }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#eq string_with_int string_with_int }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#eq string_with_double string_with_double }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#eq a_string a_string }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#eq a_double a_double }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" - + handlebars.compileInline("{{#eq an_int an_int }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#eq string_with_int string_with_int }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#eq string_with_double string_with_double }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#eq a_string a_string }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#eq a_double a_double }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should equal to content") { - handlebars.compileInline("{{#eq an_int \"1337\" }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#eq an_int \"1337\" }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should equal to content - reverse") { - handlebars.compileInline("{{#eq \"1337\" an_int }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#eq \"1337\" an_int }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should equal int to string if string has same content") { - handlebars.compileInline("{{#eq an_int string_with_int }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#eq an_int string_with_int }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should equal int to string if string has same content - reverse") { - handlebars.compileInline("{{#eq string_with_int an_int }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#eq string_with_int an_int }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should equal double to string if string has same content") { - handlebars.compileInline("{{#eq a_double string_with_double }}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#eq a_double string_with_double }}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should equal double to string if string has same content - reverse") { - handlebars.compileInline("{{#eq string_with_double a_double}}TRUE{{/eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#eq string_with_double a_double}}TRUE{{/eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should not equal if not equal") { - handlebars.compileInline("{{#eq an_int a_double}}TRUE{{else}}FALSE{{/eq}}").apply(context) shouldEqual "FALSE" + handlebars.compileInline("{{#eq an_int a_double}}TRUE{{else}}FALSE{{/eq}}").apply(context) shouldBeEqualTo "FALSE" } it("should not equal if null or empty") { - handlebars.compileInline("{{#eq an_int doesnt_exist}}TRUE{{else}}FALSE{{/eq}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#eq an_int null}}TRUE{{else}}FALSE{{/eq}}").apply(context) shouldEqual "FALSE" + handlebars.compileInline("{{#eq an_int doesnt_exist}}TRUE{{else}}FALSE{{/eq}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#eq an_int null}}TRUE{{else}}FALSE{{/eq}}").apply(context) shouldBeEqualTo "FALSE" } } describe("not_eq") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - put("an_int", 1337) - put("a_double", 1337.67) - put("string_with_int", "1337") - put("string_with_double", "1337.67") - put("a_string", "imma string") - }) + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + put("an_int", 1337) + put("a_double", 1337.67) + put("string_with_int", "1337") + put("string_with_double", "1337.67") + put("a_string", "imma string") + } + ) it("should return false when compared to one self") { - handlebars.compileInline("{{#not_eq an_int an_int }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#not_eq string_with_int string_with_int }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#not_eq string_with_double string_with_double }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#not_eq a_string a_string }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#not_eq a_double a_double }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldEqual "FALSE" - + handlebars.compileInline("{{#not_eq an_int an_int }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#not_eq string_with_int string_with_int }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#not_eq string_with_double string_with_double }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#not_eq a_string a_string }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#not_eq a_double a_double }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldBeEqualTo "FALSE" } it("should return true if different content") { - handlebars.compileInline("{{#not_eq an_int \"1338\" }}TRUE{{/not_eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#not_eq an_int \"1338\" }}TRUE{{/not_eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should return true if different content - reverse") { - handlebars.compileInline("{{#not_eq \"1338\" an_int }}TRUE{{/not_eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#not_eq \"1338\" an_int }}TRUE{{/not_eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should return true if different values") { - handlebars.compileInline("{{#not_eq an_int a_double}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#not_eq an_int a_double}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldBeEqualTo "TRUE" } it("should return true compared two null or empty") { - handlebars.compileInline("{{#not_eq an_int doesnt_exist}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#not_eq an_int null}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldEqual "TRUE" + handlebars.compileInline("{{#not_eq an_int doesnt_exist}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#not_eq an_int null}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) shouldBeEqualTo "TRUE" } } describe("gt - greater than") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - put("small_int", -1) - put("large_int", 1337) - put("small_double", -1.67) - put("large_double", 1337.67) - put("a_string", "Adam") - put("z_string", "Zorro") - }) + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + put("small_int", -1) + put("large_int", 1337) + put("small_double", -1.67) + put("large_double", 1337.67) + put("a_string", "Adam") + put("z_string", "Zorro") + } + ) it("should return false when compared to one self") { - handlebars.compileInline("{{#gt small_int small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#gt small_double small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#gt a_string a_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "FALSE" - + handlebars.compileInline("{{#gt small_int small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#gt small_double small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#gt a_string a_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "FALSE" } it("should return true when first param greater than second param") { - handlebars.compileInline("{{#gt large_int small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#gt large_double small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#gt z_string a_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "TRUE" - + handlebars.compileInline("{{#gt large_int small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#gt large_double small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#gt z_string a_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "TRUE" } it("should return false when first param less than second param") { - handlebars.compileInline("{{#gt small_int large_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#gt small_double large_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#gt a_string z_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldEqual "FALSE" - - + handlebars.compileInline("{{#gt small_int large_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#gt small_double large_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#gt a_string z_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) shouldBeEqualTo "FALSE" } it("should fail if argument are not of same type") { @@ -282,7 +287,6 @@ object HelperSpek : Spek({ invoking { handlebars.compileInline("{{#gt small_double small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) } shouldThrow AnyException - } it("should fail if comparing two null or empty") { @@ -295,41 +299,37 @@ object HelperSpek : Spek({ invoking { handlebars.compileInline("{{#gt small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) } shouldThrow AnyException - } - } describe("lt - less than") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - put("small_int", -1) - put("large_int", 1337) - put("small_double", -1.67) - put("large_double", 1337.67) - put("a_string", "Adam") - put("z_string", "Zorro") - }) + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + put("small_int", -1) + put("large_int", 1337) + put("small_double", -1.67) + put("large_double", 1337.67) + put("a_string", "Adam") + put("z_string", "Zorro") + } + ) it("should return false when compared to one self") { - handlebars.compileInline("{{#lt small_int small_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#lt small_double small_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#lt a_string a_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "FALSE" - + handlebars.compileInline("{{#lt small_int small_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#lt small_double small_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#lt a_string a_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "FALSE" } it("should return false when first param greater than second param") { - handlebars.compileInline("{{#lt large_int small_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#lt large_double small_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "FALSE" - handlebars.compileInline("{{#lt z_string a_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "FALSE" - + handlebars.compileInline("{{#lt large_int small_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#lt large_double small_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "FALSE" + handlebars.compileInline("{{#lt z_string a_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "FALSE" } it("should return true when first param less than second param") { - handlebars.compileInline("{{#lt small_int large_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#lt small_double large_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "TRUE" - handlebars.compileInline("{{#lt a_string z_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldEqual "TRUE" - - + handlebars.compileInline("{{#lt small_int large_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#lt small_double large_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "TRUE" + handlebars.compileInline("{{#lt a_string z_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) shouldBeEqualTo "TRUE" } it("should fail if argument are not of same type") { @@ -342,7 +342,6 @@ object HelperSpek : Spek({ invoking { handlebars.compileInline("{{#gt small_double small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) } shouldThrow AnyException - } it("should fail if comparing two null or empty") { @@ -355,77 +354,81 @@ object HelperSpek : Spek({ invoking { handlebars.compileInline("{{#gt small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) } shouldThrow AnyException - } - } describe("Currency formatting") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - put("beløp", 1337.69) - put("beløp_single_decimal", 1337.6) - put("beløp_integer", 9001) - put("beløp_stort", 1337420.69) - }) + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + put("beløp", 1337.69) + put("beløp_single_decimal", 1337.6) + put("beløp_integer", 9001) + put("beløp_stort", 1337420.69) + } + ) it("should format number as currency") { - handlebars.compileInline("{{ currency_no beløp }}").apply(context) shouldEqual "1 337,69" - handlebars.compileInline("{{ currency_no beløp_single_decimal }}").apply(context) shouldEqual "1 337,60" - handlebars.compileInline("{{ currency_no beløp_integer }}").apply(context) shouldEqual "9 001,00" - handlebars.compileInline("{{ currency_no beløp_stort }}").apply(context) shouldEqual "1 337 420,69" + handlebars.compileInline("{{ currency_no beløp }}").apply(context) shouldBeEqualTo "1 337,69" + handlebars.compileInline("{{ currency_no beløp_single_decimal }}").apply(context) shouldBeEqualTo "1 337,60" + handlebars.compileInline("{{ currency_no beløp_integer }}").apply(context) shouldBeEqualTo "9 001,00" + handlebars.compileInline("{{ currency_no beløp_stort }}").apply(context) shouldBeEqualTo "1 337 420,69" } it("should format number as currency without decimals") { - handlebars.compileInline("{{ currency_no beløp true }}").apply(context) shouldEqual "1 337" - handlebars.compileInline("{{ currency_no beløp_stort true }}").apply(context) shouldEqual "1 337 420" + handlebars.compileInline("{{ currency_no beløp true }}").apply(context) shouldBeEqualTo "1 337" + handlebars.compileInline("{{ currency_no beløp_stort true }}").apply(context) shouldBeEqualTo "1 337 420" } } describe("Is defined") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - put("someProperty", false) - }) + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + put("someProperty", false) + } + ) it("should output IS DEFINED if someProperty is defined") { - handlebars.compileInline("{{#is_defined someProperty }}IS DEFINED{{/is_defined }}").apply(context) shouldEqual "IS DEFINED" + handlebars.compileInline("{{#is_defined someProperty }}IS DEFINED{{/is_defined }}").apply(context) shouldBeEqualTo "IS DEFINED" } it("should output empty string if someOtherProperty is not defined") { - handlebars.compileInline("{{#is_defined someOtherProperty }}IS DEFINED{{/is_defined }}").apply(context) shouldEqual "" + handlebars.compileInline("{{#is_defined someOtherProperty }}IS DEFINED{{/is_defined }}").apply(context) shouldBeEqualTo "" } } describe("contains_all") { - val context = jsonContext(jsonNodeFactory.objectNode().apply { - putArray("myList") + val context = jsonContext( + jsonNodeFactory.objectNode().apply { + putArray("myList") .add("FIRST_VAL") .add("SECOND_VAL") .add("THIRD_VAL") - putArray("emptyList") - }) + putArray("emptyList") + } + ) it("should find single param that matches") { - handlebars.compileInline("{{#contains_all myList \"FIRST_VAL\"}}FOUND!{{else}}NOTHING!{{/contains_all }}").apply(context) shouldEqual "FOUND!" + handlebars.compileInline("{{#contains_all myList \"FIRST_VAL\"}}FOUND!{{else}}NOTHING!{{/contains_all }}").apply(context) shouldBeEqualTo "FOUND!" } it("should find all values without order") { - handlebars.compileInline("""{{#contains_all myList "FIRST_VAL" "THIRD_VAL" "SECOND_VAL"}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldEqual "FOUND!" + handlebars.compileInline("""{{#contains_all myList "FIRST_VAL" "THIRD_VAL" "SECOND_VAL"}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldBeEqualTo "FOUND!" } it("should not find if at least one did not match") { - handlebars.compileInline("""{{#contains_all myList "FIRST_VAL" "THIRD_VAL" "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldEqual "NOTHING!" + handlebars.compileInline("""{{#contains_all myList "FIRST_VAL" "THIRD_VAL" "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldBeEqualTo "NOTHING!" } it("should not find if empty parameter") { - handlebars.compileInline("""{{#contains_all myList ""}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldEqual "NOTHING!" + handlebars.compileInline("""{{#contains_all myList ""}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldBeEqualTo "NOTHING!" } it("should not find if no parameter") { - handlebars.compileInline("""{{#contains_all myList}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldEqual "NOTHING!" + handlebars.compileInline("""{{#contains_all myList}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldBeEqualTo "NOTHING!" } it("should not fail if list is empty") { - handlebars.compileInline("""{{#contains_all emptyList "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldEqual "NOTHING!" + handlebars.compileInline("""{{#contains_all emptyList "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""").apply(context) shouldBeEqualTo "NOTHING!" } it("should throw exception if unknown list") { @@ -437,57 +440,55 @@ object HelperSpek : Spek({ val context = jsonContext(jsonNodeFactory.objectNode()) it("should capitalize all uppers") { - handlebars.compileInline("{{capitalize_names \"BRAGE BRUKER OLSEN\"}}").apply(context) shouldEqual "Brage Bruker Olsen" + handlebars.compileInline("{{capitalize_names \"BRAGE BRUKER OLSEN\"}}").apply(context) shouldBeEqualTo "Brage Bruker Olsen" } it("should capitalize all lower") { - handlebars.compileInline("{{capitalize_names \"brage bruker olsen\"}}").apply(context) shouldEqual "Brage Bruker Olsen" + handlebars.compileInline("{{capitalize_names \"brage bruker olsen\"}}").apply(context) shouldBeEqualTo "Brage Bruker Olsen" } it("should capitalize all when mixed upper then lower") { - handlebars.compileInline("{{capitalize_names \"BRage BRUker OLSEn\"}}").apply(context) shouldEqual "Brage Bruker Olsen" + handlebars.compileInline("{{capitalize_names \"BRage BRUker OLSEn\"}}").apply(context) shouldBeEqualTo "Brage Bruker Olsen" } it("should capitalize all when mixed lower then upper") { - handlebars.compileInline("{{capitalize_names \"brAGE bruKer oLsEn\"}}").apply(context) shouldEqual "Brage Bruker Olsen" + handlebars.compileInline("{{capitalize_names \"brAGE bruKer oLsEn\"}}").apply(context) shouldBeEqualTo "Brage Bruker Olsen" } it("should handle multiple space") { - handlebars.compileInline("{{capitalize_names \" BRAGE BRUKER OLSEN \"}}").apply(context) shouldEqual "Brage Bruker Olsen" + handlebars.compileInline("{{capitalize_names \" BRAGE BRUKER OLSEN \"}}").apply(context) shouldBeEqualTo "Brage Bruker Olsen" } it("should capitalize names splitted by dash ") { - handlebars.compileInline("{{capitalize_names \" BRAGE-BRUKER OLSEN \"}}").apply(context) shouldEqual "Brage-Bruker Olsen" + handlebars.compileInline("{{capitalize_names \" BRAGE-BRUKER OLSEN \"}}").apply(context) shouldBeEqualTo "Brage-Bruker Olsen" } it("should capitalize names splitted by dash with spacec in between ") { - handlebars.compileInline("{{capitalize_names \" BRAGE - BRUKER OLSEN \"}}").apply(context) shouldEqual "Brage-Bruker Olsen" + handlebars.compileInline("{{capitalize_names \" BRAGE - BRUKER OLSEN \"}}").apply(context) shouldBeEqualTo "Brage-Bruker Olsen" } it("should capitalize names splitted by apostrophe") { - handlebars.compileInline("{{capitalize_names \" O'SHEA OLSEN \"}}").apply(context) shouldEqual "O'Shea Olsen" + handlebars.compileInline("{{capitalize_names \" O'SHEA OLSEN \"}}").apply(context) shouldBeEqualTo "O'Shea Olsen" } it("should do nothing if already capitilized") { - handlebars.compileInline("{{capitalize_names \"Brage Bruker Olsen\"}}").apply(context) shouldEqual "Brage Bruker Olsen" + handlebars.compileInline("{{capitalize_names \"Brage Bruker Olsen\"}}").apply(context) shouldBeEqualTo "Brage Bruker Olsen" } it("should do nothing if already capitilized - single word") { - handlebars.compileInline("{{capitalize_names \"Brage\"}}").apply(context) shouldEqual "Brage" + handlebars.compileInline("{{capitalize_names \"Brage\"}}").apply(context) shouldBeEqualTo "Brage" } - } describe("breaklines") { val context = jsonContext(jsonNodeFactory.objectNode()) it("Should replace \\r\\n with newline") { - handlebars.compileInline("{{breaklines \"I pitty the fool \\r\\n Who doesn't br\"}}").apply(context) shouldEqual "I pitty the fool
Who doesn't br" + handlebars.compileInline("{{breaklines \"I pitty the fool \\r\\n Who doesn't br\"}}").apply(context) shouldBeEqualTo "I pitty the fool
Who doesn't br" } it("Should replace \\n with newline") { - handlebars.compileInline("{{breaklines \"I pitty the fool \\n Who doesn't br\"}}").apply(context) shouldEqual "I pitty the fool
Who doesn't br" + handlebars.compileInline("{{breaklines \"I pitty the fool \\n Who doesn't br\"}}").apply(context) shouldBeEqualTo "I pitty the fool
Who doesn't br" } - } }) diff --git a/src/test/kotlin/no/nav/pdfgen/HtmlTemplates.kt b/src/test/kotlin/no/nav/pdfgen/HtmlTemplates.kt index a50bef4..354d17e 100644 --- a/src/test/kotlin/no/nav/pdfgen/HtmlTemplates.kt +++ b/src/test/kotlin/no/nav/pdfgen/HtmlTemplates.kt @@ -1,16 +1,23 @@ package no.nav.pdfgen +import io.ktor.util.* + +@KtorExperimentalAPI val testTemplateIncludedFonts: String = getResource("/html/test_template_included_fonts.html") +@KtorExperimentalAPI val testTemplateInvalidFonts: String = getResource("/html/test_template_invalid_fonts.html") +@KtorExperimentalAPI val testJpg: ByteArray = getResource("/image/test.jpg") +@KtorExperimentalAPI val testPng: ByteArray = getResource("/image/test.png") +@KtorExperimentalAPI inline fun getResource(path: String): T = - PdfGenITSpek::class.java.getResourceAsStream(path).use { stream -> - when (T::class) { - String::class -> stream.bufferedReader(Charsets.UTF_8).use { it.readText() } as T - ByteArray::class -> stream.readBytes() as T - else -> throw UnsupportedOperationException() - } + PdfGenITSpek::class.java.getResourceAsStream(path).use { stream -> + when (T::class) { + String::class -> stream.bufferedReader(Charsets.UTF_8).use { it.readText() } as T + ByteArray::class -> stream.readBytes() as T + else -> throw UnsupportedOperationException() } + } diff --git a/src/test/kotlin/no/nav/pdfgen/PdfGenITSpek.kt b/src/test/kotlin/no/nav/pdfgen/PdfGenITSpek.kt index eb7d2db..3987da2 100644 --- a/src/test/kotlin/no/nav/pdfgen/PdfGenITSpek.kt +++ b/src/test/kotlin/no/nav/pdfgen/PdfGenITSpek.kt @@ -1,46 +1,43 @@ +@file:Suppress("BlockingMethodInNonBlockingContext") + package no.nav.pdfgen import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.request.post -import io.ktor.client.response.HttpResponse -import io.ktor.client.response.readBytes -import io.ktor.client.response.readText +import io.ktor.client.statement.* import io.ktor.http.ContentType import io.ktor.http.content.ByteArrayContent import io.ktor.http.content.TextContent import io.ktor.http.isSuccess import io.ktor.util.KtorExperimentalAPI -import java.nio.file.Files -import java.nio.file.Paths -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.amshove.kluent.shouldBeGreaterThan -import org.amshove.kluent.shouldBeTrue -import org.amshove.kluent.shouldContain -import org.amshove.kluent.shouldEqual -import org.amshove.kluent.shouldNotBeEmpty -import org.amshove.kluent.shouldNotBeNull -import org.amshove.kluent.shouldNotEqual +import kotlinx.coroutines.* +import no.nav.pdfgen.template.loadTemplates +import org.amshove.kluent.* import org.apache.pdfbox.io.IOUtils import org.apache.pdfbox.pdmodel.PDDocument import org.spekframework.spek2.Spek import org.spekframework.spek2.style.specification.describe import org.spekframework.spek2.style.specification.xdescribe +import java.nio.file.Files +import java.nio.file.Paths +import java.util.concurrent.Executors +import kotlin.io.use @KtorExperimentalAPI object PdfGenITSpek : Spek({ val applicationPort = getRandomPort() val application = initializeApplication(applicationPort) - val client = HttpClient(CIO) - val templates = loadTemplates() + val client = HttpClient(CIO) { + expectSuccess = false + } + val env = Environment() + val templates = loadTemplates(env) + val timeoutSeconds: Long = 10 application.start() afterGroup { - application.stop(10, 10, TimeUnit.SECONDS) + application.stop(timeoutSeconds * 1000, timeoutSeconds * 1000) } describe("POST to /api/v1/genpdf/{applicationName}/{templateName}") { @@ -48,22 +45,22 @@ object PdfGenITSpek : Spek({ val (applicationName, templateName) = it it("With $templateName for $applicationName results in a valid PDF") { val json = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json") - ?.readBytes()?.toString(Charsets.UTF_8) ?: "{}" + ?.readBytes()?.toString(Charsets.UTF_8) ?: "{}" val response = runBlocking { client.post("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { body = TextContent(json, contentType = ContentType.Application.Json) } } - response.status.isSuccess() shouldEqual true + response.status.isSuccess() shouldBeEqualTo true val bytes = runBlocking { response.readBytes() } - bytes shouldNotEqual null - // Load the document in pdfbox to ensure its valid + bytes shouldNotBeEqualTo null + // Load the document in pdfbox to ensure it's valid val document = PDDocument.load(bytes) - document shouldNotEqual null + document shouldNotBeEqualTo null document.pages.count shouldBeGreaterThan 0 println(document.documentInformation.title) - response.close() + document.close() } } } @@ -73,7 +70,7 @@ object PdfGenITSpek : Spek({ val (applicationName, templateName) = it it("$templateName for $applicationName generates a sample PDF") { val json = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json") - ?.readBytes()?.toString(Charsets.UTF_8) ?: "{}" + ?.readBytes()?.toString(Charsets.UTF_8) ?: "{}" val response = runBlocking { client.post("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { @@ -81,11 +78,10 @@ object PdfGenITSpek : Spek({ } } - response.status.isSuccess() shouldEqual true + response.status.isSuccess() shouldBeEqualTo true val bytes = runBlocking { response.readBytes() } - bytes shouldNotEqual null + bytes shouldNotBeEqualTo null Files.write(Paths.get("build", "${it.first}-${it.second}.pdf"), bytes) - response.close() } } } @@ -97,48 +93,51 @@ object PdfGenITSpek : Spek({ body = testTemplateIncludedFonts } } - response.status.isSuccess() shouldEqual true + response.status.isSuccess() shouldBeEqualTo true val bytes = runBlocking { response.readBytes() } - bytes shouldNotEqual null + bytes shouldNotBeEqualTo null Files.write(Paths.get("build", "html.pdf"), bytes) // Load the document in pdfbox to ensure its valid val document = PDDocument.load(bytes) - document shouldNotEqual null + document shouldNotBeEqualTo null document.pages.count shouldBeGreaterThan 0 println(document.documentInformation.title) - response.close() } - it("Should return non OK status code when rendering templates with invalid font names") { + /* openhtmltopdf seems to strip any input that causes non-conforming PDF/A, so i can't get this test to work :( + it("Should return non OK status code when rendering templates that result in non-conformant PDF/A") { val response = runBlocking { client.post("http://localhost:$applicationPort/api/v1/genpdf/html/integration-test") { body = TextContent(testTemplateInvalidFonts, contentType = ContentType.Application.Json) } } - response.status.isSuccess() shouldEqual false + response.status.isSuccess() shouldBeEqualTo false } + */ } describe("Using the image convert endpoint") { mapOf( - ByteArrayContent(testJpg, ContentType.Image.JPEG) to "jpg.pdf", - ByteArrayContent(testPng, ContentType.Image.PNG) to "png.pdf" + ByteArrayContent(testJpg, ContentType.Image.JPEG) to "jpg.pdf", + ByteArrayContent(testPng, ContentType.Image.PNG) to "png.pdf" ).forEach { (payload, outputFile) -> it("Should render a document using input image, $outputFile") { - runBlocking { - client.post("http://localhost:$applicationPort/api/v1/genpdf/image/integration-test") { - body = payload + runBlocking { + runBlocking { + client.post("http://localhost:$applicationPort/api/v1/genpdf/image/integration-test") { + body = payload + } + }.execute { response -> + response.status.isSuccess().shouldBeTrue() + val bytes = response.readBytes() + bytes.shouldNotBeEmpty() + Files.write(Paths.get("build", outputFile), bytes) + // Load the document in pdfbox to ensure its valid + val document = PDDocument.load(bytes) + document.shouldNotBeNull() + document.pages.count shouldBeGreaterThan 0 } - }.use { response -> - response.status.isSuccess().shouldBeTrue() - val bytes = runBlocking { response.readBytes() } - bytes.shouldNotBeEmpty() - Files.write(Paths.get("build", outputFile), bytes) - // Load the document in pdfbox to ensure its valid - val document = PDDocument.load(bytes) - document.shouldNotBeNull() - document.pages.count shouldBeGreaterThan 0 } } } @@ -146,12 +145,14 @@ object PdfGenITSpek : Spek({ describe("Calls to unknown endpoints") { it("Should respond with helpful information") { - runBlocking { - client.config { expectSuccess = false }.post("http://localhost:$applicationPort/whodis") - }.use { response -> - response.status.value shouldEqual 404 - val text = runBlocking { response.readText() } - text shouldContain "Known paths:/api/v1/genpdf" + runBlocking { + runBlocking { + client.config { expectSuccess = false }.post("http://localhost:$applicationPort/whodis") + }.execute { response -> + response.status.value shouldBeEqualTo 404 + val text = runBlocking { response.readText() } + text shouldContain "Known paths:/api/v1/genpdf" + } } } } @@ -173,8 +174,8 @@ object PdfGenITSpek : Spek({ val response = client.post("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { body = TextContent(json, contentType = ContentType.Application.Json) } - response.readBytes() shouldNotEqual null - response.status.isSuccess() shouldEqual true + response.readBytes() shouldNotBeEqualTo null + response.status.isSuccess() shouldBeEqualTo true } }.toList().forEach { it.join() } } @@ -191,8 +192,8 @@ object PdfGenITSpek : Spek({ val response = client.post("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { body = TextContent(json, contentType = ContentType.Application.Json) } - response.readBytes() shouldNotEqual null - response.status.isSuccess() shouldEqual true + response.readBytes() shouldNotBeEqualTo null + response.status.isSuccess() shouldBeEqualTo true } }.toList() tasks.forEach { it.join() } @@ -210,8 +211,8 @@ object PdfGenITSpek : Spek({ val response = client.post("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { body = TextContent(json, contentType = ContentType.Application.Json) } - response.readBytes() shouldNotEqual null - response.status.isSuccess() shouldEqual true + response.readBytes() shouldNotBeEqualTo null + response.status.isSuccess() shouldBeEqualTo true } } println("Single-thread performance testing $templateName for $applicationName took ${System.currentTimeMillis() - startTime}ms") diff --git a/src/test/kotlin/no/nav/pdfgen/RenderingSpek.kt b/src/test/kotlin/no/nav/pdfgen/RenderingSpek.kt index aa3c383..6977b00 100644 --- a/src/test/kotlin/no/nav/pdfgen/RenderingSpek.kt +++ b/src/test/kotlin/no/nav/pdfgen/RenderingSpek.kt @@ -2,8 +2,10 @@ package no.nav.pdfgen import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream +import io.ktor.util.* +import no.nav.pdfgen.api.render +import no.nav.pdfgen.pdf.createPDFA +import no.nav.pdfgen.template.loadTemplates import org.amshove.kluent.shouldBe import org.spekframework.spek2.Spek import org.spekframework.spek2.style.specification.describe @@ -11,17 +13,20 @@ import org.verapdf.pdfa.Foundries import org.verapdf.pdfa.VeraGreenfieldFoundryProvider import org.verapdf.pdfa.flavours.PDFAFlavour import org.verapdf.pdfa.results.TestAssertion +import java.io.ByteArrayInputStream +@KtorExperimentalAPI object RenderingSpek : Spek({ - val templates = loadTemplates() + val env = Environment() + val templates = loadTemplates(env) val objectMapper = ObjectMapper() VeraGreenfieldFoundryProvider.initialise() describe("All pdfs should render with default values") { - templates.map { it.key }.forEach { + templates.map { it.key }.forEach { it -> val (applicationName, templateName) = it - val node = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json")?.use { - objectMapper.readValue(it, JsonNode::class.java) + val node = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json")?.use { that -> + objectMapper.readValue(that, JsonNode::class.java) } ?: objectMapper.createObjectNode() it("Renders the template $templateName for application $applicationName without exceptions") { render(applicationName, templateName, templates, node) @@ -35,42 +40,40 @@ object RenderingSpek : Spek({ templates.map { it.key }.filterNot(blackList::contains).forEach { val (applicationName, templateName) = it - val node = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json")?.use { - objectMapper.readValue(it, JsonNode::class.java) + val node = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json")?.use { that -> + objectMapper.readValue(that, JsonNode::class.java) } ?: objectMapper.createObjectNode() it("Renders the template $templateName for application $applicationName to a PDF/A compliant document") { val doc = render(applicationName, templateName, templates, node) - val bytesOut = ByteArrayOutputStream() - createPDFA(doc!!, bytesOut) - Foundries.defaultInstance().createParser(ByteArrayInputStream(bytesOut.toByteArray())).use { - val validationResult = validator.validate(it) + val pdf = createPDFA(doc!!, env) + Foundries.defaultInstance().createParser(ByteArrayInputStream(pdf)).use { that -> + val validationResult = validator.validate(that) validationResult.testAssertions - .filter { it.status != TestAssertion.Status.PASSED } - .forEach { - println(it.message) - println("Location ${it.location.context} ${it.location.level}") - println("Status ${it.status}") - println("Test number ${it.ruleId.testNumber}") - } + .filter { test -> test.status != TestAssertion.Status.PASSED } + .forEach { test -> + println(test.message) + println("Location ${test.location.context} ${test.location.level}") + println("Status ${test.status}") + println("Test number ${test.ruleId.testNumber}") + } validationResult.isCompliant shouldBe true } } } it("Renders a HTML payload to a PDF/A compliant document") { - val doc = fromHtmlToDocument(testTemplateIncludedFonts) - val bytesOut = ByteArrayOutputStream() - createPDFA(doc, bytesOut) - Foundries.defaultInstance().createParser(ByteArrayInputStream(bytesOut.toByteArray())).use { + val doc = testTemplateIncludedFonts + val pdf = createPDFA(doc, env) + Foundries.defaultInstance().createParser(ByteArrayInputStream(pdf)).use { val validationResult = validator.validate(it) validationResult.testAssertions - .filter { it.status != TestAssertion.Status.PASSED } - .forEach { - println(it.message) - println("Location ${it.location.context} ${it.location.level}") - println("Status ${it.status}") - println("Test number ${it.ruleId.testNumber}") - } + .filter { test -> test.status != TestAssertion.Status.PASSED } + .forEach { test -> + println(test.message) + println("Location ${test.location.context} ${test.location.level}") + println("Status ${test.status}") + println("Test number ${test.ruleId.testNumber}") + } validationResult.isCompliant shouldBe true } } diff --git a/src/test/resources/data/syfosoknader/annetarbeidsforhold.json b/src/test/resources/data/syfosoknader/annetarbeidsforhold.json new file mode 100644 index 0000000..a34bfe5 --- /dev/null +++ b/src/test/resources/data/syfosoknader/annetarbeidsforhold.json @@ -0,0 +1,496 @@ +{ + "soknadsId": "847a7647-02cf-4615-8a70-b033ec18f061", + "aktorId": "aktorId-745463060", + "navn": "Karina Mostad", + "fnr": "16086801683", + "sykmeldingId": "43694281-bd45-4fe0-bc19-3580f09c9595", + "soknadstype": "ANNET_ARBEIDSFORHOLD", + "status": "SENDT", + "fom": "2020-03-13", + "tom": "2020-03-20", + "opprettetDato": "2020-03-20", + "innsendtDato": "2020-03-20", + "sendtTilNAVDato": "2020-03-20T13:36:26.107", + "sendtTilArbeidsgiverDato": null, + "avbruttDato": null, + "startSykeforlop": "2020-03-13", + "sykmeldingUtskrevet": "2020-03-13", + "arbeidsgiver": null, + "korrigerer": null, + "korrigertAv": null, + "arbeidssituasjon": "ANNET", + + "egenmeldtSykmelding": null, + "sporsmal": [ + { + "id": "2", + "tag": "ANSVARSERKLARING", + "sporsmalstekst": "Jeg vet at jeg kan miste retten til sykepenger hvis opplysningene jeg gir ikke er riktige eller fullstendige. Jeg vet også at NAV kan holde igjen eller kreve tilbake penger, og at å gi feil opplysninger kan være straffbart.", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED", + "avgittAv": null + } + ], + "undersporsmal": [] + }, + { + "id": "3", + "tag": "FRISKMELDT", + "sporsmalstekst": "Brukte du hele sykmeldingen fram til 20. mars 2020?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "NEI", + "svar": [ + { + "verdi": "JA", + "avgittAv": null + } + ], + "undersporsmal": [ + { + "id": "4", + "tag": "FRISKMELDT_START", + "sporsmalstekst": "Fra hvilken dato har du ikke lenger behov for sykmelding?", + "undertekst": null, + "svartype": "DATO", + "min": "2020-03-13", + "max": "2020-03-20", + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "5", + "tag": "PERMISJON_V2", + "sporsmalstekst": "Tok du permisjon mens du var sykmeldt 13. - 20. mars 2020?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "NEI", + "avgittAv": null + } + ], + "undersporsmal": [ + { + "id": "6", + "tag": "PERMISJON_NAR_V2", + "sporsmalstekst": "Når tok du permisjon?", + "undertekst": null, + "svartype": "PERIODER", + "min": "2020-03-13", + "max": "2020-03-20", + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "7", + "tag": "ANDRE_INNTEKTSKILDER", + "sporsmalstekst": "Har du hatt inntekt mens du har vært sykmeldt i perioden 13. - 20. mars 2020? Du trenger ikke oppgi penger fra NAV.", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "NEI", + "avgittAv": null + } + ], + "undersporsmal": [ + { + "id": "8", + "tag": "HVILKE_ANDRE_INNTEKTSKILDER", + "sporsmalstekst": "Hvilke inntektskilder har du hatt?", + "undertekst": null, + "svartype": "CHECKBOX_GRUPPE", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [ + { + "id": "9", + "tag": "INNTEKTSKILDE_ANDRE_ARBEIDSFORHOLD", + "sporsmalstekst": "andre arbeidsforhold", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "10", + "tag": "INNTEKTSKILDE_ANDRE_ARBEIDSFORHOLD_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "11", + "tag": "INNTEKTSKILDE_SELVSTENDIG", + "sporsmalstekst": "selvstendig næringsdrivende", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "12", + "tag": "INNTEKTSKILDE_SELVSTENDIG_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "13", + "tag": "INNTEKTSKILDE_SELVSTENDIG_DAGMAMMA", + "sporsmalstekst": "dagmamma", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "14", + "tag": "INNTEKTSKILDE_SELVSTENDIG_DAGMAMMA_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "15", + "tag": "INNTEKTSKILDE_JORDBRUKER", + "sporsmalstekst": "jordbruk / fiske / reindrift", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "16", + "tag": "INNTEKTSKILDE_JORDBRUKER_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "17", + "tag": "INNTEKTSKILDE_FRILANSER", + "sporsmalstekst": "frilanser", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "18", + "tag": "INNTEKTSKILDE_FRILANSER_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "19", + "tag": "INNTEKTSKILDE_OMSORGSLONN", + "sporsmalstekst": "omsorgslønn fra kommunen", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "20", + "tag": "INNTEKTSKILDE_OMSORGSLONN_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "21", + "tag": "INNTEKTSKILDE_FOSTERHJEM", + "sporsmalstekst": "fosterhjemgodtgjørelse", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "22", + "tag": "INNTEKTSKILDE_FOSTERHJEM_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "23", + "tag": "INNTEKTSKILDE_ANNET", + "sporsmalstekst": "annet", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + } + ] + }, + { + "id": "24", + "tag": "UTDANNING", + "sporsmalstekst": "Har du vært under utdanning i løpet av perioden 13. - 20. mars 2020?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "NEI", + "avgittAv": null + } + ], + "undersporsmal": [ + { + "id": "25", + "tag": "UTDANNING_START", + "sporsmalstekst": "Når startet du på utdanningen?", + "undertekst": null, + "svartype": "DATO", + "min": null, + "max": "2020-03-20", + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "26", + "tag": "FULLTIDSSTUDIUM", + "sporsmalstekst": "Er utdanningen et fulltidsstudium?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "27", + "tag": "ARBEIDSLEDIG_UTLAND", + "sporsmalstekst": "Var du på reise utenfor EØS mens du var sykmeldt 13. - 20. mars 2020?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "NEI", + "avgittAv": null + } + ], + "undersporsmal": [ + { + "id": "28", + "tag": "UTLAND_NAR", + "sporsmalstekst": "Når var du utenfor EØS?", + "undertekst": null, + "svartype": "PERIODER", + "min": "2020-03-13", + "max": "2020-03-20", + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "29", + "tag": "UTLANDSOPPHOLD_SOKT_SYKEPENGER", + "sporsmalstekst": "Har du søkt om å beholde sykepengene for disse dagene?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "NEI", + "svar": [], + "undersporsmal": [ + { + "id": "30", + "tag": "IKKE_SOKT_UTENLANDSOPPHOLD_INFORMASJON", + "sporsmalstekst": null, + "undertekst": "

I utgangspunktet kan du bare få sykepenger mens du er i et land innenfor EØS. Du kan likevel søke NAV om å få reise ut av EØS og beholde sykepengene i en begrenset periode.

", + "svartype": "IKKE_RELEVANT", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + } + ] + }, + { + "id": "31", + "tag": "ARBEID_UTENFOR_NORGE", + "sporsmalstekst": "Har du arbeidet i utlandet i løpet av de siste 12 månedene?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "NEI", + "avgittAv": null + } + ], + "undersporsmal": [] + }, + { + "id": "32", + "tag": "VAER_KLAR_OVER_AT", + "sporsmalstekst": "Viktig å være klar over:", + "undertekst": "
  • Du kan bare få sykepenger hvis det er din egen sykdom eller skade som hindrer deg i å jobbe. Sosiale eller økonomiske problemer gir ikke rett til sykepenger.
  • Du kan miste retten til sykepenger hvis du nekter å opplyse om din egen arbeidsevne, eller hvis du ikke tar imot behandling eller tilrettelegging.
  • Retten til sykepenger gjelder bare inntekt du har mottatt som lønn og betalt skatt av på sykmeldingstidspunktet.
  • NAV kan innhente opplysninger som er nødvendige for å behandle søknaden.
  • Du må melde fra til NAV hvis du satt i varetekt, sonet straff eller var under forvaring i sykmeldingsperioden.
  • Fristen for å søke sykepenger er som hovedregel 3 måneder

Du kan lese mer om rettigheter og plikter på nav.no/sykepenger.

", + "svartype": "IKKE_RELEVANT", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "33", + "tag": "BEKREFT_OPPLYSNINGER", + "sporsmalstekst": "Jeg har lest all informasjonen jeg har fått i søknaden og bekrefter at opplysningene jeg har gitt er korrekte.", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED", + "avgittAv": null + } + ], + "undersporsmal": [] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/data/syfosoknader/arbeidsledig.json b/src/test/resources/data/syfosoknader/arbeidsledig.json new file mode 100644 index 0000000..b2abf97 --- /dev/null +++ b/src/test/resources/data/syfosoknader/arbeidsledig.json @@ -0,0 +1,448 @@ +{ + "soknadsId": "5f2d742c-db00-448b-9178-321b6a44dc55", + "aktorId": "aktorId-745463060", + "sykmeldingId": "289148ba-4c3c-4b3f-b7a3-385b7e7c927d", + "soknadstype": "ARBEIDSLEDIG", + "navn": "Karina Mostad", + "fnr": "16086801683", + "status": "NY", + "fom": "2019-08-08", + "tom": "2019-08-17", + "opprettetDato": "2019-08-27", + "innsendtDato": "2019-08-27", + "sendtTilNAVDato": "2019-08-27", + "sendtTilArbeidsgiverDato": null, + "avbruttDato": null, + "startSykeforlop": "2019-08-03", + "sykmeldingUtskrevet": "2019-08-08", + "arbeidsgiver": null, + "korrigerer": null, + "korrigertAv": null, + "arbeidssituasjon": "ARBEIDSLEDIG", + "soknadPerioder": [ + { + "fom": "2019-08-08", + "tom": "2019-08-12", + "grad": 100 + }, + { + "fom": "2019-08-13", + "tom": "2019-08-17", + "grad": 40 + } + ], + "sporsmal": [ + { + "id": "1", + "tag": "ANSVARSERKLARING", + "sporsmalstekst": "Jeg vet at jeg kan miste retten til sykepenger hvis opplysningene jeg gir ikke er riktige eller fullstendige. Jeg vet også at NAV kan holde igjen eller kreve tilbake penger, og at å gi feil opplysninger kan være straffbart.", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "2", + "tag": "JOBBET_DU_100_PROSENT_0", + "sporsmalstekst": "I perioden 8. - 12. august 2019 var du 100 % sykmeldt som arbeidsledig. Har du hatt inntekt mens du har vært sykmeldt?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "3", + "tag": "HVILKE_ANDRE_INNTEKTSKILDER0", + "sporsmalstekst": "Hvilke inntektskilder har du hatt?", + "undertekst": "Du trenger ikke oppgi penger fra NAV", + "svartype": "CHECKBOX_GRUPPE", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [ + { + "id": "4", + "tag": "INNTEKTSKILDE_ANDRE_ARBEIDSFORHOLD0", + "sporsmalstekst": "andre arbeidsforhold", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "5", + "tag": "INNTEKTSKILDE_SELVSTENDIG0", + "sporsmalstekst": "selvstendig næringsdrivende", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "6", + "tag": "INNTEKTSKILDE_SELVSTENDIG_DAGMAMMA0", + "sporsmalstekst": "dagmamma", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "7", + "tag": "INNTEKTSKILDE_JORDBRUKER0", + "sporsmalstekst": "jordbruk / fiske / reindrift", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "8", + "tag": "INNTEKTSKILDE_FRILANSER0", + "sporsmalstekst": "frilanser", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "9", + "tag": "INNTEKTSKILDE_OMSORGSLONN0", + "sporsmalstekst": "omsorgslønn fra kommunen", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "10", + "tag": "INNTEKTSKILDE_FOSTERHJEMGODTGJORSELSE0", + "sporsmalstekst": "fosterhjemgodtgjørelse", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "11", + "tag": "INNTEKTSKILDE_ANNET", + "sporsmalstekst": "annet", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + } + ] + }, + { + "id": "12", + "tag": "JOBBET_DU_GRADERT_1", + "sporsmalstekst": "I perioden 13. - 17. august 2019 var du 40 % sykmeldt som arbeidsledig. Har du hatt inntekt mens du har vært sykmeldt?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "13", + "tag": "HVILKE_ANDRE_INNTEKTSKILDER1", + "sporsmalstekst": "Hvilke inntektskilder har du hatt?", + "undertekst": "Du trenger ikke oppgi penger fra NAV", + "svartype": "CHECKBOX_GRUPPE", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [ + { + "id": "14", + "tag": "INNTEKTSKILDE_ANDRE_ARBEIDSFORHOLD1", + "sporsmalstekst": "andre arbeidsforhold", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "15", + "tag": "INNTEKTSKILDE_SELVSTENDIG1", + "sporsmalstekst": "selvstendig næringsdrivende", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "16", + "tag": "INNTEKTSKILDE_SELVSTENDIG_DAGMAMMA1", + "sporsmalstekst": "dagmamma", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "17", + "tag": "INNTEKTSKILDE_JORDBRUKER1", + "sporsmalstekst": "jordbruk / fiske / reindrift", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "18", + "tag": "INNTEKTSKILDE_FRILANSER1", + "sporsmalstekst": "frilanser", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "19", + "tag": "INNTEKTSKILDE_OMSORGSLONN1", + "sporsmalstekst": "omsorgslønn fra kommunen", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "20", + "tag": "INNTEKTSKILDE_FOSTERHJEMGODTGJORSELSE1", + "sporsmalstekst": "fosterhjemgodtgjørelse", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "21", + "tag": "INNTEKTSKILDE_ANNET", + "sporsmalstekst": "annet", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + } + ] + } + ] + }, + { + "id": "22", + "tag": "SYKMELDT_FRA_ANDRE_INNTEKTSKILDER", + "sporsmalstekst": "Er du sykmeldt fra andre arbeidsforhold?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [] + }, + { + "id": "23", + "tag": "UTDANNING", + "sporsmalstekst": "Har du vært under utdanning i løpet av perioden 8. - 17. august 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [] + }, + { + "id": "24", + "tag": "UTLAND", + "sporsmalstekst": "Reise utenfor Norge", + "undertekst": "Var du på reise utenfor Norge mens du var sykmeldt 8. - 17. august 2019?", + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "25", + "tag": "UTLAND_NAR", + "sporsmalstekst": "Når var du utenfor Norge?", + "undertekst": null, + "svartype": "PERIODER", + "min": "2019-08-08", + "max": "2019-08-17", + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "{\"fom\": \"2019-08-09\", \"tom\": \"2019-08-10\"}" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "27", + "tag": "BEKREFT_OPPLYSNINGER", + "sporsmalstekst": "Jeg har lest all informasjonen jeg har fått i søknaden og bekrefter at opplysningene jeg har gitt er korrekte.", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "26", + "tag": "VAER_KLAR_OVER_AT", + "sporsmalstekst": "Viktig å være klar over:", + "undertekst": "
  • Du kan bare få sykepenger hvis det er din egen sykdom eller skade som hindrer deg i å jobbe. Sosiale eller økonomiske problemer gir ikke rett til sykepenger.
  • Du kan miste retten til sykepenger hvis du nekter å opplyse om din egen arbeidsevne, eller hvis du ikke tar imot behandling eller tilrettelegging.
  • Retten til sykepenger gjelder bare inntekt du har mottatt som lønn og betalt skatt av på sykmeldingstidspunktet.
  • NAV kan innhente opplysninger som er nødvendige for å behandle søknaden.
  • Du må melde fra til NAV hvis du satt i varetekt, sonet straff eller var under forvaring i sykmeldingsperioden.
  • Fristen for å søke sykepenger er som hovedregel 3 måneder

Du kan lese mer om rettigheter og plikter på nav.no/sykepenger.

", + "svartype": "IKKE_RELEVANT", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/data/syfosoknader/arbeidstakere.json b/src/test/resources/data/syfosoknader/arbeidstakere.json new file mode 100644 index 0000000..258fa2a --- /dev/null +++ b/src/test/resources/data/syfosoknader/arbeidstakere.json @@ -0,0 +1,643 @@ +{ + "soknadsId": "498b4f1a-47e3-4549-9e27-923efc246281", + "fnr": "16086801683", + "navn": "Karina Mostad", + "innsendtDato": "2019-09-09", + "sendtArbeidsgiver": "2019-09-09", + "sykmeldingUtskrevet": "2019-09-01", + "arbeidsgiver": "LØNNS- OG REGNSKAPSSENTERET", + "korrigerer": null, + "egenmeldtSykmelding": true, + "avsendertype": "BRUKER", + "soknadPerioder": [ + { + "fom": "2019-09-02", + "tom": "2019-09-08", + "grad": 100, + "faktiskGrad": 97, + "avtaltTimer": 37.5, + "faktiskTimer": null + } + ], + "sporsmal": [ + { + "id": "660076", + "tag": "EGENMELDINGER", + "sporsmalstekst": "Vi har registrert at du ble sykmeldt mandag 10. februar 2020. Var du syk og borte fra jobb i perioden 25. januar - 9. februar 2020?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [{ + "verdi": "JA", + "avgittAv": null + }], + "undersporsmal": [{ + "id": "660077", + "tag": "TIDLIGERE_SYK", + "sporsmalstekst": null, + "undertekst": null, + "svartype": "CHECKBOX_GRUPPE", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [{ + "id": "660078", + "tag": "TIDLIGERE_EGENMELDING", + "sporsmalstekst": "Jeg var syk med egenmelding", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [{ + "verdi": "CHECKED", + "avgittAv": null + }], + "undersporsmal": [{ + "id": "660079", + "tag": "EGENMELDINGER_NAR", + "sporsmalstekst": "Hvilke dager var du syk med egenmelding? Du trenger bare oppgi dager før 10. februar 2020.", + "undertekst": null, + "svartype": "PERIODER", + "min": "2019-08-10", + "max": "2020-02-09", + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [{ + "verdi": "{\"fom\":\"2020-01-23\",\"tom\":\"2020-02-09\"}", + "avgittAv": null + }], + "undersporsmal": [] + }] + }, { + "id": "660080", + "tag": "TIDLIGERE_PAPIRSYKMELDING", + "sporsmalstekst": "Jeg var syk med papirsykmelding", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [{ + "verdi": "CHECKED", + "avgittAv": null + }], + "undersporsmal": [{ + "id": "660081", + "tag": "PAPIRSYKMELDING_NAR", + "sporsmalstekst": "Hvilke dager var du syk med papirsykmelding? Du trenger bare oppgi dager før 10. februar 2020.", + "undertekst": null, + "svartype": "PERIODER", + "min": "2019-08-10", + "max": "2020-02-09", + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [{ + "verdi": "{\"fom\":\"2020-01-23\",\"tom\":\"2020-02-09\"}", + "avgittAv": null + }], + "undersporsmal": [] + }] + }] + }] + }, + { + "id": "308903", + "tag": "TILBAKE_I_ARBEID", + "sporsmalstekst": "Var du tilbake i fullt arbeid hos LØNNS- OG REGNSKAPSSENTERET i løpet av perioden 2. - 8. september 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308904", + "tag": "TILBAKE_NAR", + "sporsmalstekst": "Når begynte du å jobbe igjen?", + "undertekst": null, + "svartype": "DATO", + "min": "2019-09-02", + "max": "2019-09-08", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "2019-09-07" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308905", + "tag": "FERIE_V2", + "sporsmalstekst": "Tok du ut ferie mens du var sykmeldt 2. - 6. september 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308906", + "tag": "FERIE_NAR_V2", + "sporsmalstekst": "Når tok du ut ferie?", + "undertekst": null, + "svartype": "PERIODER", + "min": "2019-09-02", + "max": "2019-09-06", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "{\"fom\":\"2019-09-03\",\"tom\":\"2019-09-04\"}" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308907", + "tag": "PERMISJON_V2", + "sporsmalstekst": "Tok du permisjon mens du var sykmeldt 2. - 6. september 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308908", + "tag": "PERMISJON_NAR_V2", + "sporsmalstekst": "Når tok du permisjon?", + "undertekst": null, + "svartype": "PERIODER", + "min": "2019-09-02", + "max": "2019-09-06", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "{\"fom\":\"2019-09-02\",\"tom\":\"2019-09-03\"}" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308909", + "tag": "UTLAND_V2", + "sporsmalstekst": "Var du på reise utenfor Norge mens du var sykmeldt 2. - 6. september 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308910", + "tag": "UTLAND_NAR_V2", + "sporsmalstekst": "Når var du utenfor Norge?", + "undertekst": null, + "svartype": "PERIODER", + "min": "2019-09-02", + "max": "2019-09-06", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "{\"fom\":\"2019-09-05\",\"tom\":\"2019-09-06\"}" + }, + { + "verdi": "{\"fom\":\"2019-09-02\",\"tom\":\"2019-09-03\"}" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308911", + "tag": "UTLANDSOPPHOLD_SOKT_SYKEPENGER", + "sporsmalstekst": "Har du søkt om å beholde sykepengene for de dagene du var i utlandet?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [] + }, + { + "id": "308912", + "tag": "JOBBET_DU_100_PROSENT_0", + "sporsmalstekst": "I perioden 2. - 6. september 2019 var du 100 % sykmeldt fra LØNNS- OG REGNSKAPSSENTERET. Jobbet du noe i denne perioden?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308913", + "tag": "HVOR_MANGE_TIMER_PER_UKE_0", + "sporsmalstekst": "Hvor mange timer i uken jobber du vanligvis? Varierer det, kan du oppgi gjennomsnittet.", + "undertekst": "timer per uke", + "svartype": "TALL", + "min": "1", + "max": "150", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "37,5" + } + ], + "undersporsmal": [] + }, + { + "id": "308914", + "tag": "HVOR_MYE_HAR_DU_JOBBET_0", + "sporsmalstekst": "Hvor mye jobbet du totalt 2. - 6. september 2019 hos LØNNS- OG REGNSKAPSSENTERET?", + "undertekst": null, + "svartype": "RADIO_GRUPPE_TIMER_PROSENT", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [ + { + "id": "308915", + "tag": "HVOR_MYE_PROSENT_0", + "sporsmalstekst": "prosent", + "undertekst": null, + "svartype": "RADIO", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [ + { + "id": "308916", + "tag": "HVOR_MYE_PROSENT_VERDI_0", + "sporsmalstekst": null, + "undertekst": "prosent", + "svartype": "TALL", + "min": "1", + "max": "99", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "97" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308917", + "tag": "HVOR_MYE_TIMER_0", + "sporsmalstekst": "timer", + "undertekst": null, + "svartype": "RADIO", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "308918", + "tag": "HVOR_MYE_TIMER_VERDI_0", + "sporsmalstekst": null, + "undertekst": "timer totalt", + "svartype": "TALL", + "min": "1", + "max": "107", + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + } + ] + } + ] + }, + { + "id": "308919", + "tag": "ANDRE_INNTEKTSKILDER", + "sporsmalstekst": "Har du andre inntektskilder enn LØNNS- OG REGNSKAPSSENTERET?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308920", + "tag": "HVILKE_ANDRE_INNTEKTSKILDER", + "sporsmalstekst": "Hvilke andre inntektskilder har du?", + "undertekst": "Du trenger ikke oppgi penger fra NAV", + "svartype": "CHECKBOX_GRUPPE", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [ + { + "id": "308921", + "tag": "INNTEKTSKILDE_ANDRE_ARBEIDSFORHOLD", + "sporsmalstekst": "andre arbeidsforhold", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "308922", + "tag": "INNTEKTSKILDE_ANDRE_ARBEIDSFORHOLD_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "308923", + "tag": "INNTEKTSKILDE_SELVSTENDIG", + "sporsmalstekst": "selvstendig næringsdrivende", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [ + { + "id": "308924", + "tag": "INNTEKTSKILDE_SELVSTENDIG_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "NEI" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308925", + "tag": "INNTEKTSKILDE_SELVSTENDIG_DAGMAMMA", + "sporsmalstekst": "dagmamma", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "308926", + "tag": "INNTEKTSKILDE_SELVSTENDIG_DAGMAMMA_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "308927", + "tag": "INNTEKTSKILDE_JORDBRUKER", + "sporsmalstekst": "jordbruk / fiske / reindrift", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [ + { + "id": "308928", + "tag": "INNTEKTSKILDE_JORDBRUKER_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308929", + "tag": "INNTEKTSKILDE_FRILANSER", + "sporsmalstekst": "frilanser", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [], + "undersporsmal": [ + { + "id": "308930", + "tag": "INNTEKTSKILDE_FRILANSER_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + }, + { + "id": "308931", + "tag": "INNTEKTSKILDE_ANNET", + "sporsmalstekst": "annet", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + } + ] + }, + { + "id": "308932", + "tag": "UTDANNING", + "sporsmalstekst": "Har du vært under utdanning i løpet av perioden 2. - 6. september 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308933", + "tag": "UTDANNING_START", + "sporsmalstekst": "Når startet du på utdanningen?", + "undertekst": null, + "svartype": "DATO", + "min": null, + "max": "2019-09-06", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "2019-09-01" + } + ], + "undersporsmal": [] + }, + { + "id": "308934", + "tag": "FULLTIDSSTUDIUM", + "sporsmalstekst": "Er utdanningen et fulltidsstudium?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308900", + "tag": "ANSVARSERKLARING", + "sporsmalstekst": "Jeg vet at jeg kan miste retten til sykepenger hvis opplysningene jeg gir ikke er riktige eller fullstendige. Jeg vet også at NAV kan holde igjen eller kreve tilbake penger, og at å gi feil opplysninger kan være straffbart.", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "308936", + "tag": "BEKREFT_OPPLYSNINGER", + "sporsmalstekst": "Jeg har lest all informasjonen jeg har fått i søknaden og bekrefter at opplysningene jeg har gitt er korrekte.", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "308935", + "tag": "VAER_KLAR_OVER_AT", + "sporsmalstekst": "Viktig å være klar over:", + "undertekst": "
  • Du kan bare få sykepenger hvis det er din egen sykdom eller skade som hindrer deg i å jobbe. Sosiale eller økonomiske problemer gir ikke rett til sykepenger.
  • Du kan miste retten til sykepenger hvis du nekter å opplyse om din egen arbeidsevne, eller hvis du ikke tar imot behandling eller tilrettelegging.
  • Retten til sykepenger gjelder bare inntekt du har mottatt som lønn og betalt skatt av på sykmeldingstidspunktet.
  • NAV kan innhente opplysninger som er nødvendige for å behandle søknaden.
  • Du må melde fra til NAV hvis du satt i varetekt, sonet straff eller var under forvaring i sykmeldingsperioden.
  • Fristen for å søke sykepenger er som hovedregel 3 måneder

Du kan lese mer om rettigheter og plikter på nav.no/sykepenger.

", + "svartype": "IKKE_RELEVANT", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] +} diff --git a/src/test/resources/data/syfosoknader/behandlingsdager.json b/src/test/resources/data/syfosoknader/behandlingsdager.json new file mode 100644 index 0000000..d644be6 --- /dev/null +++ b/src/test/resources/data/syfosoknader/behandlingsdager.json @@ -0,0 +1,133 @@ +{ + "soknadsId": "c982ce69-07f4-4fa3-bb2b-6474f18c099e", + "sykmeldingId": "sykmeldingId", + "fnr": "13026928499", + "navn": "Luguber Muldvarp", + "fom": "2018-01-01", + "tom": "2018-10-01", + "innsendtDato": "2019-12-14", + "sendtArbeidsgiver": null, + "sykmeldingUtskrevet": "2018-01-01", + "soknadstype": "BEHANDLINGSDAGER", + "status": "NY", + "arbeidsgiver": "Bedrift AS", + "arbeidssituasjon": "ARBEIDSTAKER", + "soknadPerioder": [ + { + "fom": "2018-01-01", + "tom": "2018-10-01", + "grad": 100, + "sykmeldingstype": "BEHANDLINGSDAGER" + } + ], + "sporsmal": [ + { + "id": "15406", + "tag": "ANSVARSERKLARING", + "sporsmalstekst": "Jeg vet at jeg kan miste retten til sykepenger hvis opplysningene jeg gir ikke er riktige eller fullstendige. Jeg vet også at NAV kan holde igjen eller kreve tilbake penger, og at å gi feil opplysninger kan være straffbart.", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "53983", + "tag": "ENKELTSTAENDE_BEHANDLINGSDAGER_0", + "sporsmalstekst": "Var du til behandling mellom 26. desember 2017 - 10. januar 2018?", + "undertekst": null, + "svartype": "INFO_BEHANDLINGSDAGER", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [ + { + "id": "2549", + "tag": "ENKELTSTAENDE_BEHANDLINGSDAGER_UKE_0", + "sporsmalstekst": "26. desember - 29. desember 2017", + "undertekst": null, + "svartype": "RADIO_GRUPPE_UKEKALENDER", + "min": "2017-12-26", + "max": "2017-12-29", + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "2017-12-27" + } + ], + "undersporsmal": [] + }, + { + "id": "6894", + "tag": "ENKELTSTAENDE_BEHANDLINGSDAGER_UKE_1", + "sporsmalstekst": "1. januar - 5. januar 2018", + "undertekst": null, + "svartype": "RADIO_GRUPPE_UKEKALENDER", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "2018-01-02" + } + ], + "undersporsmal": [] + }, + { + "id": "46116", + "tag": "ENKELTSTAENDE_BEHANDLINGSDAGER_UKE_2", + "sporsmalstekst": "8. januar - 10. januar 2018", + "undertekst": null, + "svartype": "RADIO_GRUPPE_UKEKALENDER", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "50766", + "tag": "VAER_KLAR_OVER_AT", + "sporsmalstekst": "Viktig å være klar over:", + "undertekst": "
  • Du kan bare få sykepenger hvis det er din egen sykdom eller skade som hindrer deg i å jobbe. Sosiale eller økonomiske problemer gir ikke rett til sykepenger.
  • Du kan miste retten til sykepenger hvis du nekter å opplyse om din egen arbeidsevne, eller hvis du ikke tar imot behandling eller tilrettelegging.
  • Retten til sykepenger gjelder bare inntekt du har mottatt som lønn og betalt skatt av på sykmeldingstidspunktet.
  • NAV kan innhente opplysninger som er nødvendige for å behandle søknaden.
  • Du må melde fra til NAV hvis du satt i varetekt, sonet straff eller var under forvaring i sykmeldingsperioden.
  • Fristen for å søke sykepenger er som hovedregel 3 måneder

Du kan lese mer om rettigheter og plikter på nav.no/sykepenger.

", + "svartype": "IKKE_RELEVANT", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + }, + { + "id": "62882", + "tag": "BEKREFT_OPPLYSNINGER", + "sporsmalstekst": "Jeg har lest all informasjonen jeg har fått i søknaden og bekrefter at opplysningene jeg har gitt er korrekte.", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "pavirkerAndreSporsmal": false, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] +} diff --git a/src/test/resources/data/syfosoknader/selvstendignaeringsdrivende.json b/src/test/resources/data/syfosoknader/selvstendignaeringsdrivende.json new file mode 100644 index 0000000..7f26ec2 --- /dev/null +++ b/src/test/resources/data/syfosoknader/selvstendignaeringsdrivende.json @@ -0,0 +1,412 @@ +{ + "aktorId": "1556025402895", + "soknadsId": "a5e62b14-9294-440d-a293-0415c4f1ac23", + "fnr": "16086801683", + "navn": "Karina Mostad", + "soknadstype": "SELVSTENDIGE_OG_FRILANSERE", + "fom": "2019-09-02", + "tom": "2019-09-08", + "innsendtDato": "2019-09-09", + "sendtArbeidsgiver": null, + "startSykeforlop": "2019-08-22", + "sykmeldingUtskrevet": "2019-09-01", + "arbeidsgiver": null, + "korrigerer": null, + "korrigertAv": null, + "egenmeldtSykmelding": true, + "arbeidssituasjon": "FRILANSER", + "soknadPerioder": [ + { + "fom": "2019-09-02", + "tom": "2019-09-08", + "grad": 100, + "faktiskGrad": null, + "avtaltTimer": null, + "faktiskTimer": null + } + ], + "sporsmal": [ + { + "id": "308938", + "tag": "TILBAKE_I_ARBEID", + "sporsmalstekst": "Var du tilbake i fullt arbeid som frilanser før sykmeldingsperioden utløp 8. september 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308939", + "tag": "TILBAKE_NAR", + "sporsmalstekst": "Når begynte du å jobbe igjen?", + "undertekst": null, + "svartype": "DATO", + "min": "2019-09-02", + "max": "2019-09-09", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "2019-09-02" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308940", + "tag": "JOBBET_DU_100_PROSENT_0", + "sporsmalstekst": "I perioden 2. - 8. september 2019 var du 100% sykmeldt som frilanser. Jobbet du noe i denne perioden?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308941", + "tag": "HVOR_MANGE_TIMER_0", + "sporsmalstekst": "Hvor mange timer jobber du normalt per uke som frilanser?", + "undertekst": null, + "svartype": "TIMER", + "min": "1", + "max": "150", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "150" + } + ], + "undersporsmal": [] + }, + { + "id": "308942", + "tag": "HVOR_MYE_HAR_DU_JOBBET_0", + "sporsmalstekst": "Hvor mye jobbet du totalt i denne perioden som frilanser?", + "undertekst": null, + "svartype": "PROSENT", + "min": "1", + "max": "99", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "99" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308943", + "tag": "ANDRE_INNTEKTSKILDER", + "sporsmalstekst": "Har du annen inntekt?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308944", + "tag": "HVILKE_ANDRE_INNTEKTSKILDER", + "sporsmalstekst": "Hvilke inntektskilder har du?", + "undertekst": "Du trenger ikke oppgi penger fra NAV", + "svartype": "CHECKBOX_GRUPPE", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [ + { + "id": "308945", + "tag": "INNTEKTSKILDE_ARBEIDSFORHOLD", + "sporsmalstekst": "arbeidsforhold", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [ + { + "id": "308946", + "tag": "INNTEKTSKILDE_ARBEIDSFORHOLD_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "NEI" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308947", + "tag": "INNTEKTSKILDE_JORDBRUKER", + "sporsmalstekst": "jordbruk / fiske / reindrift", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [ + { + "id": "308948", + "tag": "INNTEKTSKILDE_JORDBRUKER_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "NEI" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308949", + "tag": "INNTEKTSKILDE_FRILANSER_SELVSTENDIG", + "sporsmalstekst": "selvstendig næringsdrivende", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "CHECKED", + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [ + { + "id": "308950", + "tag": "INNTEKTSKILDE_FRILANSER_SELVSTENDIG_ER_DU_SYKMELDT", + "sporsmalstekst": "Er du sykmeldt fra dette?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308951", + "tag": "INNTEKTSKILDE_ANNET", + "sporsmalstekst": "annet", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + } + ] + } + ] + }, + { + "id": "308952", + "tag": "UTLAND", + "sporsmalstekst": "Har du oppholdt deg utenfor Norge i perioden 2. - 8. september 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308953", + "tag": "PERIODER", + "sporsmalstekst": "Når var du utenfor Norge?", + "undertekst": null, + "svartype": "PERIODER", + "min": "2019-09-02", + "max": "2019-09-08", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "{\"fom\":\"2019-09-03\",\"tom\":\"2019-09-05\"}" + } + ], + "undersporsmal": [] + }, + { + "id": "308954", + "tag": "UTLANDSOPPHOLD_SOKT_SYKEPENGER", + "sporsmalstekst": "Har du søkt om å beholde sykepengene for disse dagene?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "NEI", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308955", + "tag": "IKKE_SOKT_UTENLANDSOPPHOLD_INFORMASJON", + "sporsmalstekst": null, + "undertekst": "

I utgangspunktet kan du bare få sykepenger mens du er i Norge. Du kan likevel søke NAV om å få reise utenlands og beholde sykepengene i en begrenset periode.", + "svartype": "IKKE_RELEVANT", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] + } + ] + }, + { + "id": "308956", + "tag": "UTDANNING", + "sporsmalstekst": "Har du vært under utdanning i løpet av perioden 2. - 8. september 2019?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "308957", + "tag": "UTDANNING_START", + "sporsmalstekst": "Når startet du på utdanningen?", + "undertekst": null, + "svartype": "DATO", + "min": null, + "max": "2019-09-08", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "2019-09-08" + } + ], + "undersporsmal": [] + }, + { + "id": "308958", + "tag": "FULLTIDSSTUDIUM", + "sporsmalstekst": "Er utdanningen et fulltidsstudium?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "NEI" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "308937", + "tag": "ANSVARSERKLARING", + "sporsmalstekst": "Jeg vet at jeg kan miste retten til sykepenger hvis opplysningene jeg gir ikke er riktige eller fullstendige. Jeg vet også at NAV kan holde igjen eller kreve tilbake penger, og at å gi feil opplysninger kan være straffbart. \n", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "308959", + "tag": "BEKREFT_OPPLYSNINGER", + "sporsmalstekst": "Jeg har lest all informasjonen jeg har fått i søknaden og bekrefter at opplysningene jeg har gitt er korrekte.", + "undertekst": null, + "svartype": "CHECKBOX", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + }, + { + "id": "308960", + "tag": "VAER_KLAR_OVER_AT", + "sporsmalstekst": "Viktig å være klar over:", + "undertekst": "

  • Du kan bare få sykepenger hvis det er din egen sykdom eller skade som hindrer deg i å jobbe. Sosiale eller økonomiske problemer gir ikke rett til sykepenger.
  • Du kan miste retten til sykepenger hvis du nekter å opplyse om din egen arbeidsevne, eller hvis du ikke tar imot behandling eller tilrettelegging.
  • Retten til sykepenger gjelder bare inntekt du har mottatt som lønn og betalt skatt av på sykmeldingstidspunktet.
  • NAV kan innhente opplysninger som er nødvendige for å behandle søknaden.
  • Du må melde fra til NAV hvis du satt i varetekt, sonet straff eller var under forvaring i sykmeldingsperioden.
  • Fristen for å søke sykepenger er som hovedregel 3 måneder

Du kan lese mer om rettigheter og plikter på nav.no/sykepenger.

", + "svartype": "IKKE_RELEVANT", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [] + } + ] +} diff --git a/src/test/resources/data/syfosoknader/sykepengerutland.json b/src/test/resources/data/syfosoknader/sykepengerutland.json new file mode 100644 index 0000000..2ce0853 --- /dev/null +++ b/src/test/resources/data/syfosoknader/sykepengerutland.json @@ -0,0 +1,136 @@ +{ + "aktorId": "1556025402895", + "soknadsId": "67ad2c2b-95ea-450e-94b8-efb6a20868df", + "fnr": "16086801683", + "navn": "Karina Mostad", + "soknadstype": "OPPHOLD_UTLAND", + "fom": null, + "tom": null, + "innsendtDato": "2019-09-06", + "sendtArbeidsgiver": null, + "startSykeforlop": null, + "sykmeldingUtskrevet": null, + "arbeidsgiver": null, + "korrigerer": null, + "korrigertAv": null, + "arbeidssituasjon": null, + "soknadPerioder": null, + "sporsmal": [ + { + "id": "280361", + "tag": "PERIODEUTLAND", + "sporsmalstekst": "Når skal du være utenfor Norge?", + "undertekst": null, + "svartype": "PERIODER", + "min": "2019-06-06", + "max": "2020-03-06", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "{\"fom\":\"2019-09-02\",\"tom\":\"2019-09-19\"}" + } + ], + "undersporsmal": [] + }, + { + "id": "280362", + "tag": "LAND", + "sporsmalstekst": "Hvilket land skal du reise til?", + "undertekst": null, + "svartype": "LAND", + "min": null, + "max": "50", + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "Sverige" + }, + { + "verdi": "Kina" + }, + { + "verdi": "Portugal" + } + ], + "undersporsmal": [] + }, + { + "id": "280363", + "tag": "ARBEIDSGIVER", + "sporsmalstekst": "Har du arbeidsgiver?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": "JA", + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [ + { + "id": "280364", + "tag": "SYKMELDINGSGRAD", + "sporsmalstekst": "Er du 100 % sykmeldt?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "JA" + } + ], + "undersporsmal": [] + }, + { + "id": "280365", + "tag": "FERIE", + "sporsmalstekst": "Har du avtalt med arbeidsgiveren din at du skal ha ferie i hele perioden?", + "undertekst": null, + "svartype": "JA_NEI", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "NEI" + } + ], + "undersporsmal": [] + } + ] + }, + { + "id": "280366", + "tag": "BEKREFT_OPPLYSNINGER_UTLAND_INFO", + "sporsmalstekst": "Før du reiser ber vi deg bekrefte:", + "undertekst": "
    \n
  • Jeg har avklart med legen at reisen ikke vil forlenge sykefraværet
  • \n
  • Reisen hindrer ikke planlagt behandling eller avtaler med NAV
  • \n
  • Reisen er avklart med arbeidsgiveren min
", + "svartype": "IKKE_RELEVANT", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [], + "undersporsmal": [ + { + "id": "280367", + "tag": "BEKREFT_OPPLYSNINGER_UTLAND", + "sporsmalstekst": "Jeg bekrefter de tre punktene ovenfor", + "undertekst": null, + "svartype": "CHECKBOX_PANEL", + "min": null, + "max": null, + "kriterieForVisningAvUndersporsmal": null, + "svar": [ + { + "verdi": "CHECKED" + } + ], + "undersporsmal": [] + } + ] + } + ] +} diff --git a/src/test/resources/html/test_template_invalid_fonts.html b/src/test/resources/html/test_template_invalid_fonts.html index b721d62..add82c1 100644 --- a/src/test/resources/html/test_template_invalid_fonts.html +++ b/src/test/resources/html/test_template_invalid_fonts.html @@ -4,11 +4,11 @@ Test template -

This should fail because it's not an embedded font

+

This document shouldn't render fine

diff --git a/templates/dsopkontroll/kontrollforespoersel.hbs b/templates/dsopkontroll/kontrollforespoersel.hbs index 4a70629..b22bca4 100644 --- a/templates/dsopkontroll/kontrollforespoersel.hbs +++ b/templates/dsopkontroll/kontrollforespoersel.hbs @@ -1,7 +1,7 @@ - + Kontroll forespørsel