From 80ee3669c8f93dee587622bd8e8339c31dc05d0e Mon Sep 17 00:00:00 2001 From: Joakim Taule Kartveit Date: Tue, 20 Jun 2023 14:04:11 +0200 Subject: [PATCH] Use ktfmt for formatting code --- .editorconfig | 97 +- build.gradle.kts | 11 +- src/main/kotlin/no/nav/pdfgen/Bootstrap.kt | 36 +- src/main/kotlin/no/nav/pdfgen/Environment.kt | 68 +- .../kotlin/no/nav/pdfgen/MetricRegistry.kt | 29 +- .../no/nav/pdfgen/api/GeneratePdfApi.kt | 128 +- src/main/kotlin/no/nav/pdfgen/pdf/Create.kt | 53 +- .../kotlin/no/nav/pdfgen/template/Helpers.kt | 151 +- .../no/nav/pdfgen/template/Templates.kt | 46 +- .../kotlin/no/nav/pdfgen/util/FontMetadata.kt | 4 +- src/main/kotlin/no/nav/pdfgen/util/Image.kt | 11 +- src/test/kotlin/no/nav/pdfgen/HelperTest.kt | 1338 ++++++++++------- src/test/kotlin/no/nav/pdfgen/PdfGenITest.kt | 264 ++-- .../kotlin/no/nav/pdfgen/RenderingTest.kt | 71 +- 14 files changed, 1383 insertions(+), 924 deletions(-) diff --git a/.editorconfig b/.editorconfig index a34f3da..40c1763 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,93 @@ -[*.{kt,kts}] -indent_size=4 -insert_final_newline=true -disabled_rules=no-wildcard-imports +# This .editorconfig section approximates ktfmt's formatting rules. You can include it in an +# existing .editorconfig file or use it standalone by copying it to /.editorconfig +# and making sure your editor is set to read settings from .editorconfig files. +# +# It includes editor-specific config options for IntelliJ IDEA. +# +# If any option is wrong, PR are welcome + +[{*.kt,*.kts}] +indent_style = space +insert_final_newline = true +max_line_length = 100 +indent_size = 4 +ij_continuation_indent_size = 4 +ij_java_names_count_to_use_import_on_demand = 9999 +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = false +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_for_chained_calls = true +ij_kotlin_continuation_indent_for_expression_bodies = true +ij_kotlin_continuation_indent_in_argument_lists = true +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = false +ij_kotlin_import_nested_classes = false +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 9999 +ij_kotlin_name_count_to_use_star_import_for_members = 9999 +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e67e1f3..1ad95aa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,10 +16,12 @@ val openHtmlToPdfVersion = "1.0.10" val prometheusVersion = "0.16.0" val junitJupiterVersion = "5.9.3" val verapdfVersion = "1.22.2" +val ktfmtVersion = "0.44" + plugins { kotlin("jvm") version "1.8.22" - id("org.jmailen.kotlinter") version "3.15.0" + id("com.diffplug.spotless") version "6.19.0" id("com.github.johnrengelman.shadow") version "8.1.1" id("com.github.ben-manes.versions") version "0.47.0" } @@ -43,6 +45,13 @@ tasks { withType { manifest.attributes("Main-Class" to "no.nav.pdfgen.BootstrapKt") } + + spotless { + kotlin { ktfmt(ktfmtVersion).kotlinlangStyle() } + check { + dependsOn("spotlessApply") + } + } } repositories { diff --git a/src/main/kotlin/no/nav/pdfgen/Bootstrap.kt b/src/main/kotlin/no/nav/pdfgen/Bootstrap.kt index 0f6e662..3b004eb 100644 --- a/src/main/kotlin/no/nav/pdfgen/Bootstrap.kt +++ b/src/main/kotlin/no/nav/pdfgen/Bootstrap.kt @@ -37,8 +37,7 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.verapdf.gf.foundry.VeraGreenfieldFoundryProvider -val objectMapper: ObjectMapper = ObjectMapper() - .registerKotlinModule() +val objectMapper: ObjectMapper = ObjectMapper().registerKotlinModule() val log: Logger = LoggerFactory.getLogger("pdfgen") @@ -57,14 +56,14 @@ fun initializeApplication(port: Int): ApplicationEngine { XRLog.setLoggerImpl(Slf4jLogger()) return embeddedServer( - Netty, port, + Netty, + port, configure = { - responseWriteTimeoutSeconds = 60 // Increase timeout of Netty to handle large content bodies - } + responseWriteTimeoutSeconds = + 60 // Increase timeout of Netty to handle large content bodies + }, ) { - install(ContentNegotiation) { - jackson {} - } + install(ContentNegotiation) { jackson {} } install(StatusPages) { status(HttpStatusCode.NotFound) { call, _ -> @@ -72,18 +71,14 @@ fun initializeApplication(port: Int): ApplicationEngine { TextContent( messageFor404(templates, call.request.path()), ContentType.Text.Plain.withCharset(Charsets.UTF_8), - HttpStatusCode.NotFound - ) + HttpStatusCode.NotFound, + ), ) } } routing { - get("/internal/is_ready") { - call.respondText("I'm ready") - } - get("/internal/is_alive") { - call.respondText("I'm alive") - } + get("/internal/is_ready") { call.respondText("I'm ready") } + get("/internal/is_alive") { call.respondText("I'm alive") } get("/internal/prometheus") { val names = call.request.queryParameters.getAll("name[]")?.toSet() ?: setOf() call.respondTextWriter(ContentType.parse(TextFormat.CONTENT_TYPE_004)) { @@ -91,7 +86,7 @@ fun initializeApplication(port: Int): ApplicationEngine { runCatching { TextFormat.write004( this@respondTextWriter, - collectorRegistry.filteredMetricFamilySamples(names) + collectorRegistry.filteredMetricFamilySamples(names), ) } } @@ -103,6 +98,7 @@ fun initializeApplication(port: Int): ApplicationEngine { } private fun messageFor404(templates: Map, Template>, path: String) = - "Unkown path '$path'. Known templates:\n" + templates.map { (app, _) -> - "/api/v1/genpdf/%s/%s".format(app.first, app.second) - }.joinToString("\n") + "Unkown path '$path'. Known templates:\n" + + templates + .map { (app, _) -> "/api/v1/genpdf/%s/%s".format(app.first, app.second) } + .joinToString("\n") diff --git a/src/main/kotlin/no/nav/pdfgen/Environment.kt b/src/main/kotlin/no/nav/pdfgen/Environment.kt index bd657a0..1db1394 100644 --- a/src/main/kotlin/no/nav/pdfgen/Environment.kt +++ b/src/main/kotlin/no/nav/pdfgen/Environment.kt @@ -2,13 +2,13 @@ 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 +import no.nav.pdfgen.util.FontMetadata +import org.apache.pdfbox.io.IOUtils val templateRoot: Path = Paths.get("templates/") val imagesRoot: Path = Paths.get("resources/") @@ -17,10 +17,13 @@ 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 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, - val enableHtmlEndpoint: Boolean = System.getenv("ENABLE_HTML_ENDPOINT")?.let { it == "true" } ?: false + val enableHtmlEndpoint: Boolean = + System.getenv("ENABLE_HTML_ENDPOINT")?.let { it == "true" } ?: false, ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -38,32 +41,33 @@ data class Environment( } } -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 +fun loadImages() = + Files.list(imagesRoot) + .filter { + val validExtensions = setOf("jpg", "jpeg", "png", "bmp", "svg") + !Files.isHidden(it) && it.fileName.extension in validExtensions } - val base64string = Base64.getEncoder().encodeToString(Files.readAllBytes(it)) - val base64 = "data:image/$extension;base64,$base64string" - fileName to base64 - } - .toList() - .toMap() + .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() +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/MetricRegistry.kt b/src/main/kotlin/no/nav/pdfgen/MetricRegistry.kt index 448d1ef..822d0de 100644 --- a/src/main/kotlin/no/nav/pdfgen/MetricRegistry.kt +++ b/src/main/kotlin/no/nav/pdfgen/MetricRegistry.kt @@ -2,16 +2,19 @@ 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() -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() -val JSOUP_PARSE_SUMMARY: Summary = Summary.Builder() - .name("jsoup_parse") - .help("Time it takes jsoup to parse the template") - .register() +val HANDLEBARS_RENDERING_SUMMARY: Summary = + Summary.Builder() + .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() +val JSOUP_PARSE_SUMMARY: Summary = + Summary.Builder() + .name("jsoup_parse") + .help("Time it takes jsoup to parse the template") + .register() diff --git a/src/main/kotlin/no/nav/pdfgen/api/GeneratePdfApi.kt b/src/main/kotlin/no/nav/pdfgen/api/GeneratePdfApi.kt index be6c95e..73f5439 100644 --- a/src/main/kotlin/no/nav/pdfgen/api/GeneratePdfApi.kt +++ b/src/main/kotlin/no/nav/pdfgen/api/GeneratePdfApi.kt @@ -17,6 +17,10 @@ import io.ktor.server.routing.Routing import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.route +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Paths import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.logstash.logback.argument.StructuredArguments @@ -25,19 +29,19 @@ import no.nav.pdfgen.pdf.PdfContent import no.nav.pdfgen.pdf.createPDFA import no.nav.pdfgen.template.TemplateMap import no.nav.pdfgen.template.loadTemplates -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 hotTemplates = loadTemplates(env) - createHtml(call, hotTemplates, true) - ?.let { document -> call.respond(PdfContent(document, env)) } - ?: call.respondText("Template or application not found", status = HttpStatusCode.NotFound) + createHtml(call, hotTemplates, true)?.let { document -> + call.respond(PdfContent(document, env)) + } + ?: call.respondText( + "Template or application not found", + status = HttpStatusCode.NotFound + ) } } post("/{applicationName}/{template}") { @@ -45,35 +49,49 @@ fun Routing.setupGeneratePdfApi(env: Environment, templates: TemplateMap) { createHtml(call, templates)?.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) + } + ?: 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 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") + 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() + val timer = + OPENHTMLTOPDF_RENDERING_SUMMARY.labels(applicationName, "convertjpeg").startTimer() when (call.request.contentType()) { - ContentType.Image.JPEG, ContentType.Image.PNG -> { + 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) + 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") + log.info( + "Generated PDF using image for $applicationName om ${timer.observeDuration()}ms" + ) } } if (env.enableHtmlEndpoint) { @@ -81,9 +99,11 @@ fun Routing.setupGeneratePdfApi(env: Environment, templates: TemplateMap) { if (!env.disablePdfGet) { get("/{applicationName}/{template}") { val hotTemplates = loadTemplates(env) - createHtml(call, hotTemplates, true) - ?.let { call.respond(it) } - ?: call.respondText("Template or application not found", status = HttpStatusCode.NotFound) + createHtml(call, hotTemplates, true)?.let { call.respond(it) } + ?: call.respondText( + "Template or application not found", + status = HttpStatusCode.NotFound + ) } } @@ -92,7 +112,11 @@ fun Routing.setupGeneratePdfApi(env: Environment, templates: TemplateMap) { createHtml(call, templates)?.let { call.respond(it) log.info("Done generating HTML in ${System.currentTimeMillis() - startTime}ms") - } ?: call.respondText("Template or application not found", status = HttpStatusCode.NotFound) + } + ?: call.respondText( + "Template or application not found", + status = HttpStatusCode.NotFound + ) } } } @@ -100,48 +124,56 @@ fun Routing.setupGeneratePdfApi(env: Environment, templates: TemplateMap) { private fun hotTemplateData(applicationName: String, template: String): JsonNode { 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 - ) + val data = + objectMapper.readValue( + if (Files.exists(dataFile)) { + Files.readAllBytes(dataFile) + } else { + "{}".toByteArray(Charsets.UTF_8) + }, + JsonNode::class.java, + ) return data } private suspend fun createHtml( call: ApplicationCall, templates: TemplateMap, - useHottemplate: Boolean = false + useHottemplate: Boolean = false, ): String? { val template = call.parameters["template"]!! val applicationName = call.parameters["applicationName"]!! - val jsonNode = if (useHottemplate) hotTemplateData(applicationName, template) else call.receive() + val jsonNode = + if (useHottemplate) hotTemplateData(applicationName, template) else call.receive() log.debug("JSON: {}", objectMapper.writeValueAsString(jsonNode)) return render(applicationName, template, templates, jsonNode) } -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)) +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 - } + /* 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 index de0f9a8..29941ce 100644 --- a/src/main/kotlin/no/nav/pdfgen/pdf/Create.kt +++ b/src/main/kotlin/no/nav/pdfgen/pdf/Create.kt @@ -4,6 +4,10 @@ import com.openhtmltopdf.pdfboxout.PdfRendererBuilder import com.openhtmltopdf.svgsupport.BatikSVGDrawer import io.ktor.http.* import io.ktor.http.content.* +import java.io.* +import java.lang.IllegalArgumentException +import java.util.* +import javax.imageio.ImageIO import no.nav.pdfgen.Environment import no.nav.pdfgen.log import no.nav.pdfgen.util.scale @@ -25,27 +29,32 @@ 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() + val pdf = + ByteArrayOutputStream() .apply { - for (font in env.fonts) { - useFont({ ByteArrayInputStream(font.bytes) }, font.family, font.weight, font.style, font.subset) - } + PdfRendererBuilder() + .apply { + for (font in env.fonts) { + useFont( + { ByteArrayInputStream(font.bytes) }, + font.family, + font.weight, + font.style, + font.subset + ) + } + } + .usePdfAConformance(PdfRendererBuilder.PdfAConformance.PDFA_2_A) + .usePdfUaAccessbility(true) + .useColorProfile(env.colorProfile) + .useSVGDrawer(BatikSVGDrawer()) + .withHtmlContent(html, null) + .toStream(this) + .run() } - .usePdfAConformance(PdfRendererBuilder.PdfAConformance.PDFA_2_A) - .usePdfUaAccessbility(true) - .useColorProfile(env.colorProfile) - .useSVGDrawer(BatikSVGDrawer()) - .withHtmlContent(html, null) - .toStream(this) - .run() - }.toByteArray() + .toByteArray() require(verifyCompliance(pdf)) { "Non-compliant PDF/A :(" } return pdf } @@ -110,12 +119,14 @@ fun createPDFA(imageStream: InputStream, outputStream: OutputStream, env: Enviro } } -private fun verifyCompliance(input: ByteArray, flavour: PDFAFlavour = PDFAFlavour.PDFA_2_A): Boolean { +private fun verifyCompliance( + input: ByteArray, + flavour: PDFAFlavour = PDFAFlavour.PDFA_2_A +): 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 } + 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}") @@ -128,7 +139,7 @@ private fun verifyCompliance(input: ByteArray, flavour: PDFAFlavour = PDFAFlavou class PdfContent( private val html: String, private val env: Environment, - override val contentType: ContentType = ContentType.Application.Pdf + 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 index db1c945..2e0c64c 100644 --- a/src/main/kotlin/no/nav/pdfgen/template/Helpers.kt +++ b/src/main/kotlin/no/nav/pdfgen/template/Helpers.kt @@ -4,24 +4,25 @@ 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.* +import no.nav.pdfgen.Environment +import no.nav.pdfgen.domain.syfosoknader.Periode +import no.nav.pdfgen.domain.syfosoknader.PeriodeMapper val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") -val dateFormatLong: DateTimeFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy") - .withLocale(Locale("no", "NO")) +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 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 { @@ -30,7 +31,7 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { Helper { context, _ -> if (context == null) return@Helper "" formatDate(dateFormat, context) - } + }, ) registerHelper( @@ -38,7 +39,7 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { Helper { context, _ -> if (context == null) return@Helper "" formatDate(datetimeFormat, context) - } + }, ) registerHelper( @@ -46,7 +47,7 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { Helper { context, _ -> if (context == null) return@Helper "" dateFormat.format(DateTimeFormatter.ISO_DATE.parse(context)) - } + }, ) registerHelper( @@ -58,7 +59,7 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { } catch (e: Exception) { dateFormatLong.format(DateTimeFormatter.ISO_DATE.parse(context)) } - } + }, ) registerHelper( @@ -66,9 +67,9 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { Helper { context, options -> ChronoUnit.DAYS.between( LocalDate.from(DateTimeFormatter.ISO_DATE.parse(context)), - LocalDate.from(DateTimeFormatter.ISO_DATE.parse(options.param(0))) + LocalDate.from(DateTimeFormatter.ISO_DATE.parse(options.param(0))), ) - } + }, ) // Expects json-objects of the form { "fom": "2018-05-20", "tom": "2018-05-29" } @@ -79,9 +80,11 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { return@Helper "" } else { val periode: Periode = PeriodeMapper.jsonTilPeriode(context) - return@Helper periode.fom!!.format(dateFormat) + " - " + periode.tom!!.format(dateFormat) + return@Helper periode.fom!!.format(dateFormat) + + " - " + + periode.tom!!.format(dateFormat) } - } + }, ) registerHelper( "insert_at", @@ -90,22 +93,26 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { 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) } - } + .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() - } + 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() - } + if (context?.toString() != options.param(0)?.toString()) options.fn() + else options.inverse() + }, ) registerHelper( @@ -113,7 +120,7 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { Helper> { context, options -> val param = options.param(0) as Comparable if (context > param) options.fn() else options.inverse() - } + }, ) registerHelper( @@ -121,41 +128,39 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { 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] - } + Helper { context, _ -> if (context == null) "" else env.images[context] }, ) registerHelper( "resource", - Helper { context, _ -> - env.resources[context]?.toString(Charsets.UTF_8) ?: "" - } + Helper { context, _ -> env.resources[context]?.toString(Charsets.UTF_8) ?: "" }, ) registerHelper( "capitalize", Helper { context, _ -> context?.lowercase()?.replaceFirstChar { it.uppercase() } ?: "" - } + }, ) registerHelper( "capitalize_names", Helper { context, _ -> - if (context == null) "" else + if (context == null) { + "" + } else Handlebars.SafeString( context .trim() @@ -163,30 +168,24 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { .lowercase() .capitalizeWords(" ") .capitalizeWords("-") - .capitalizeWords("'") + .capitalizeWords("'"), ) - } + }, ) registerHelper( "uppercase", - Helper { context, _ -> - context?.uppercase() ?: "" - } + Helper { context, _ -> context?.uppercase() ?: "" }, ) registerHelper( "inc", - Helper { context, _ -> - context + 1 - } + Helper { context, _ -> context + 1 }, ) registerHelper( "formatComma", - Helper { context, _ -> - context?.toString()?.replace(".", ",") ?: "" - } + Helper { context, _ -> context?.toString()?.replace(".", ",") ?: "" }, ) registerHelper( @@ -197,7 +196,7 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { } else { options.fn() } - } + }, ) registerHelper( @@ -205,17 +204,18 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { 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 + 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( @@ -231,7 +231,7 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { } else { options.inverse() } - } + }, ) registerHelper( @@ -242,15 +242,19 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { val splitNumber = context.toString().split(".") - // we're joining with a non-breaking space since currency values should not be split across several lines - val formattedNumber = splitNumber.first().reversed().chunked(3).joinToString("\u00A0").reversed() + // we're joining with a non-breaking space since currency values should not be split + // across several lines + val formattedNumber = + splitNumber.first().reversed().chunked(3).joinToString("\u00A0").reversed() if (withoutDecimals) { formattedNumber } else { - val decimals = splitNumber.drop(1).firstOrNull()?.let { (it + "0").substring(0, 2) } ?: "00" + val decimals = + splitNumber.drop(1).firstOrNull()?.let { (it + "0").substring(0, 2) } + ?: "00" "$formattedNumber,$decimals" } - } + }, ) registerHelper( @@ -259,11 +263,13 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { val kr = context / 100 val øre = context % 100 - // using .format(locale = Locale("nb")) should also do the trick, but it appears this no longer works + // using .format(locale = Locale("nb")) should also do the trick, but it appears + // this no longer works // so we just reuse the string-based code from above to get the format we want :) - val formattedKr = kr.toString().reversed().chunked(3).joinToString("\u00A0").reversed() + val formattedKr = + kr.toString().reversed().chunked(3).joinToString("\u00A0").reversed() "$formattedKr,%02d".format(øre) - } + }, ) registerHelper( @@ -273,16 +279,17 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { val kr = value / 100 val øre = value % 100 - val formattedKr = kr.toString().reversed().chunked(3).joinToString("\u00A0").reversed() + val formattedKr = + kr.toString().reversed().chunked(3).joinToString("\u00A0").reversed() "$formattedKr,%02d".format(øre) - } + }, ) registerHelper( "is_defined", Helper { context, options -> if (context != null) options.fn() else options.inverse() - } + }, ) registerHelper( @@ -292,17 +299,21 @@ fun registerNavHelpers(handlebars: Handlebars, env: Environment) { "" } else { val santizedText = Handlebars.Utils.escapeExpression(context) - val withLineBreak = santizedText.toString() - .replace("\\r\\n", "
") - .replace("\\n", "
") - .replace("\r\n", "
") - .replace("\n", "
") + 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().replaceFirstChar { it.uppercase() } } + this.split(wordSplitter).joinToString(wordSplitter) { + it.trim().replaceFirstChar { it.uppercase() } + } diff --git a/src/main/kotlin/no/nav/pdfgen/template/Templates.kt b/src/main/kotlin/no/nav/pdfgen/template/Templates.kt index b68a91a..42cb6f9 100644 --- a/src/main/kotlin/no/nav/pdfgen/template/Templates.kt +++ b/src/main/kotlin/no/nav/pdfgen/template/Templates.kt @@ -5,33 +5,33 @@ 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 java.nio.file.Files 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" } +fun setupHandlebars(env: Environment) = + Handlebars(FileTemplateLoader(templateRoot.toFile())).apply { + registerNavHelpers(this, env) + infiniteLoops(true) } - .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 + +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" } } - } - .toList() - .toMap() + .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 index 2aecb76..252f3fb 100644 --- a/src/main/kotlin/no/nav/pdfgen/util/FontMetadata.kt +++ b/src/main/kotlin/no/nav/pdfgen/util/FontMetadata.kt @@ -1,15 +1,15 @@ package no.nav.pdfgen.util import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder -import no.nav.pdfgen.fontsRoot import java.nio.file.Files +import no.nav.pdfgen.fontsRoot data class FontMetadata( val family: String, val path: String, val weight: Int, val style: BaseRendererBuilder.FontStyle, - val subset: Boolean + 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 index d69b8ec..198ee85 100644 --- a/src/main/kotlin/no/nav/pdfgen/util/Image.kt +++ b/src/main/kotlin/no/nav/pdfgen/util/Image.kt @@ -1,10 +1,10 @@ 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 +import org.apache.pdfbox.pdmodel.PDPage +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject data class ImageSize(val width: Float, val height: Float) @@ -13,7 +13,12 @@ fun toPortait(image: BufferedImage): BufferedImage { return image } - val rotateTransform = AffineTransform.getRotateInstance(Math.toRadians(90.0), (image.height / 2f).toDouble(), (image.height / 2f).toDouble()) + 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)) diff --git a/src/test/kotlin/no/nav/pdfgen/HelperTest.kt b/src/test/kotlin/no/nav/pdfgen/HelperTest.kt index 1561d3b..8f2ad66 100644 --- a/src/test/kotlin/no/nav/pdfgen/HelperTest.kt +++ b/src/test/kotlin/no/nav/pdfgen/HelperTest.kt @@ -16,17 +16,15 @@ import org.junit.jupiter.api.assertThrows internal class HelperTest { val jsonNodeFactory = JsonNodeFactory.instance private val env = Environment() - private val handlebars = Handlebars(ClassPathTemplateLoader()).apply { - registerNavHelpers(this, env) - } + private val handlebars = + Handlebars(ClassPathTemplateLoader()).apply { registerNavHelpers(this, env) } private fun jsonContext(jsonNode: JsonNode): Context { println(ObjectMapper().writeValueAsString(jsonNode)) - return Context - .newBuilder(jsonNode) + return Context.newBuilder(jsonNode) .resolver( JsonNodeValueResolver.INSTANCE, - MapValueResolver.INSTANCE + MapValueResolver.INSTANCE, ) .build() } @@ -35,10 +33,8 @@ internal class HelperTest { internal fun `List contains helper a array containing the field fish should result in the string IT_CONTAINS`() { val template = handlebars.compile("helper_templates/contains") - val jsonNode = jsonNodeFactory.objectNode().apply { - putArray("list") - .addObject().put("fish", "test") - } + val jsonNode = + jsonNodeFactory.objectNode().apply { putArray("list").addObject().put("fish", "test") } assertEquals("IT_CONTAINS", template.apply(jsonContext(jsonNode)).trim()) } @@ -47,10 +43,8 @@ internal class HelperTest { internal fun `List contains helper a array containing the field fish, but its a false boolean should result in NO_CONTAINS`() { val template = handlebars.compile("helper_templates/contains") - val jsonNode = jsonNodeFactory.objectNode().apply { - putArray("list") - .addObject().put("fish", false) - } + val jsonNode = + jsonNodeFactory.objectNode().apply { putArray("list").addObject().put("fish", false) } assertEquals("NO_CONTAINS", template.apply(jsonContext(jsonNode)).trim()) } @@ -68,10 +62,10 @@ internal class HelperTest { internal fun `List contains helper a array without the field fish should result in NO_CONTAINS`() { val template = handlebars.compile("helper_templates/contains") - val jsonNode = jsonNodeFactory.objectNode().apply { - putArray("list") - .addObject().put("shark", "something") - } + val jsonNode = + jsonNodeFactory.objectNode().apply { + putArray("list").addObject().put("shark", "something") + } assertEquals("NO_CONTAINS", template.apply(jsonContext(jsonNode)).trim()) } @@ -80,11 +74,10 @@ internal class HelperTest { internal fun `List contains helper a array a null fish field results in IT_CONTAINS`() { val template = handlebars.compile("helper_templates/contains") - val jsonNode = jsonNodeFactory.objectNode().apply { - putArray("list").apply { - addObject().putNull("fish") + val jsonNode = + jsonNodeFactory.objectNode().apply { + putArray("list").apply { addObject().putNull("fish") } } - } assertEquals("NO_CONTAINS", template.apply(jsonContext(jsonNode)).trim()) } @@ -93,12 +86,13 @@ internal class HelperTest { internal fun `List contains helper a array with two nodes, where the second contains the field fish results in IT_CONTAINS`() { val template = handlebars.compile("helper_templates/contains") - val jsonNode = jsonNodeFactory.objectNode().apply { - putArray("list").apply { - addObject().put("shark", "something") - addObject().put("fish", "test") + val jsonNode = + jsonNodeFactory.objectNode().apply { + putArray("list").apply { + addObject().put("shark", "something") + addObject().put("fish", "test") + } } - } assertEquals("IT_CONTAINS", template.apply(jsonContext(jsonNode)).trim()) } @@ -107,514 +101,649 @@ internal class HelperTest { internal fun `List contains helper 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`() { val template = handlebars.compile("helper_templates/contains") - val jsonNode = jsonNodeFactory.objectNode().apply { - putArray("list").apply { - addObject().putNull("fish") - addObject().put("fish", "test") + val jsonNode = + jsonNodeFactory.objectNode().apply { + putArray("list").apply { + addObject().putNull("fish") + addObject().put("fish", "test") + } } - } assertEquals("IT_CONTAINS", template.apply(jsonContext(jsonNode)).trim()) } @Test internal fun `Any operator should result in empty result when a single statement fails`() { - 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") + }, + ) assertEquals("", handlebars.compileInline("{{#any d}}YES{{/any}}").apply(context)) } @Test internal fun `Any operator should result in a YES when a single statement is ok`() { - 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") + }, + ) assertEquals("YES", handlebars.compileInline("{{#any a}}YES{{/any}}").apply(context)) } @Test internal fun `Any operator should result in a YES when one of multiple statements is ok`() { - 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") + }, + ) assertEquals("YES", handlebars.compileInline("{{#any d e f a}}YES{{/any}}").apply(context)) } @Test internal fun `Any operator should result in a YES when the first of multiple statements is ok`() { - 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") + }, + ) assertEquals("YES", handlebars.compileInline("{{#any a d e f}}YES{{/any}}").apply(context)) } @Test internal fun `Any operator should result in empty result when many statements fails`() { - 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") + }, + ) assertEquals("", handlebars.compileInline("{{#any d e f g}}YES{{/any}}").apply(context)) } @Test internal fun `Datetime formatting should format as Norwegian short date and time`() { - 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") + }, + ) - assertEquals("03.03.2020 10:15", handlebars.compileInline("{{ iso_to_nor_datetime timestamp }}").apply(context)) + assertEquals( + "03.03.2020 10:15", + handlebars.compileInline("{{ iso_to_nor_datetime timestamp }}").apply(context) + ) } @Test internal fun `Datetime formatting should format as Norwegian short date`() { - 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") + }, + ) - assertEquals("03.03.2020", handlebars.compileInline("{{ iso_to_nor_date timestamp }}").apply(context)) + assertEquals( + "03.03.2020", + handlebars.compileInline("{{ iso_to_nor_date timestamp }}").apply(context) + ) } @Test internal fun `Datetime formatting should format timestamp as Norwegian long date`() { - 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") + }, + ) - assertEquals("3. oktober 2020", handlebars.compileInline("{{ iso_to_long_date timestampLong }}").apply(context)) + assertEquals( + "3. oktober 2020", + handlebars.compileInline("{{ iso_to_long_date timestampLong }}").apply(context) + ) } @Test internal fun `Datetime formatting should format date as Norwegian long date`() { - 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") + }, + ) - assertEquals("12. februar 2020", handlebars.compileInline("{{ iso_to_long_date date }}").apply(context)) + assertEquals( + "12. februar 2020", + handlebars.compileInline("{{ iso_to_long_date date }}").apply(context) + ) } @Test internal fun `eq should equal self`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#eq an_int an_int }}TRUE{{/eq}}").apply(context)) assertEquals( "TRUE", - handlebars.compileInline("{{#eq string_with_int string_with_int }}TRUE{{/eq}}").apply(context) + handlebars.compileInline("{{#eq an_int an_int }}TRUE{{/eq}}").apply(context) + ) + assertEquals( + "TRUE", + handlebars + .compileInline("{{#eq string_with_int string_with_int }}TRUE{{/eq}}") + .apply(context), ) assertEquals( "TRUE", - handlebars.compileInline("{{#eq string_with_double string_with_double }}TRUE{{/eq}}").apply(context) + handlebars + .compileInline("{{#eq string_with_double string_with_double }}TRUE{{/eq}}") + .apply(context), + ) + assertEquals( + "TRUE", + handlebars.compileInline("{{#eq a_string a_string }}TRUE{{/eq}}").apply(context) + ) + assertEquals( + "TRUE", + handlebars.compileInline("{{#eq a_double a_double }}TRUE{{/eq}}").apply(context) ) - assertEquals("TRUE", handlebars.compileInline("{{#eq a_string a_string }}TRUE{{/eq}}").apply(context)) - assertEquals("TRUE", handlebars.compileInline("{{#eq a_double a_double }}TRUE{{/eq}}").apply(context)) } @Test internal fun `eq should equal self should equal to content`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#eq an_int \"1337\" }}TRUE{{/eq}}").apply(context)) + assertEquals( + "TRUE", + handlebars.compileInline("{{#eq an_int \"1337\" }}TRUE{{/eq}}").apply(context) + ) } @Test internal fun `eq should equal self should equal to content - reverse`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#eq \"1337\" an_int }}TRUE{{/eq}}").apply(context)) + assertEquals( + "TRUE", + handlebars.compileInline("{{#eq \"1337\" an_int }}TRUE{{/eq}}").apply(context) + ) } @Test internal fun `eq should equal self should equal int to string if string has same content`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#eq an_int string_with_int }}TRUE{{/eq}}").apply(context)) + assertEquals( + "TRUE", + handlebars.compileInline("{{#eq an_int string_with_int }}TRUE{{/eq}}").apply(context) + ) } @Test internal fun `eq should equal self should equal int to string if string has same content - reverse`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#eq string_with_int an_int }}TRUE{{/eq}}").apply(context)) + assertEquals( + "TRUE", + handlebars.compileInline("{{#eq string_with_int an_int }}TRUE{{/eq}}").apply(context) + ) } @Test internal fun `eq should equal self should equal double to string if string has same content`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#eq a_double string_with_double }}TRUE{{/eq}}").apply(context)) + assertEquals( + "TRUE", + handlebars + .compileInline("{{#eq a_double string_with_double }}TRUE{{/eq}}") + .apply(context) + ) } @Test internal fun `eq should equal self should equal double to string if string has same content - reverse`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#eq string_with_double a_double}}TRUE{{/eq}}").apply(context)) + assertEquals( + "TRUE", + handlebars + .compileInline("{{#eq string_with_double a_double}}TRUE{{/eq}}") + .apply(context) + ) } @Test internal fun `eq should equal self should not equal if not equal`() { - 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") + }, + ) assertEquals( "FALSE", - handlebars.compileInline("{{#eq an_int a_double}}TRUE{{else}}FALSE{{/eq}}").apply(context) + handlebars + .compileInline("{{#eq an_int a_double}}TRUE{{else}}FALSE{{/eq}}") + .apply(context), ) } @Test internal fun `eq should equal self should not equal if null or empty`() { - 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") + }, + ) assertEquals( "FALSE", - handlebars.compileInline("{{#eq an_int doesnt_exist}}TRUE{{else}}FALSE{{/eq}}").apply(context) + handlebars + .compileInline("{{#eq an_int doesnt_exist}}TRUE{{else}}FALSE{{/eq}}") + .apply(context), + ) + assertEquals( + "FALSE", + handlebars.compileInline("{{#eq an_int null}}TRUE{{else}}FALSE{{/eq}}").apply(context) ) - assertEquals("FALSE", handlebars.compileInline("{{#eq an_int null}}TRUE{{else}}FALSE{{/eq}}").apply(context)) } @Test internal fun `not eq should return false when compared to one self`() { - 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") + }, + ) assertEquals( "FALSE", - handlebars.compileInline("{{#not_eq an_int an_int }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) + handlebars + .compileInline("{{#not_eq an_int an_int }}TRUE{{else}}FALSE{{/not_eq}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#not_eq string_with_int string_with_int }}TRUE{{else}}FALSE{{/not_eq}}") - .apply(context) + handlebars + .compileInline( + "{{#not_eq string_with_int string_with_int }}TRUE{{else}}FALSE{{/not_eq}}" + ) + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#not_eq string_with_double string_with_double }}TRUE{{else}}FALSE{{/not_eq}}") - .apply(context) + handlebars + .compileInline( + "{{#not_eq string_with_double string_with_double }}TRUE{{else}}FALSE{{/not_eq}}" + ) + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#not_eq a_string a_string }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) + handlebars + .compileInline("{{#not_eq a_string a_string }}TRUE{{else}}FALSE{{/not_eq}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#not_eq a_double a_double }}TRUE{{else}}FALSE{{/not_eq}}").apply(context) + handlebars + .compileInline("{{#not_eq a_double a_double }}TRUE{{else}}FALSE{{/not_eq}}") + .apply(context), ) } @Test internal fun `not eq should return true if different content`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#not_eq an_int \"1338\" }}TRUE{{/not_eq}}").apply(context)) + assertEquals( + "TRUE", + handlebars.compileInline("{{#not_eq an_int \"1338\" }}TRUE{{/not_eq}}").apply(context) + ) } @Test internal fun `not eq should return true if different content - reverse`() { - 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") + }, + ) - assertEquals("TRUE", handlebars.compileInline("{{#not_eq \"1338\" an_int }}TRUE{{/not_eq}}").apply(context)) + assertEquals( + "TRUE", + handlebars.compileInline("{{#not_eq \"1338\" an_int }}TRUE{{/not_eq}}").apply(context) + ) } @Test internal fun `not eq should return true if different values`() { - 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") + }, + ) assertEquals( "TRUE", - handlebars.compileInline("{{#not_eq an_int a_double}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) + handlebars + .compileInline("{{#not_eq an_int a_double}}TRUE{{else}}FALSE{{/not_eq}}") + .apply(context), ) } @Test internal fun `not eq should return true compared two null or empty`() { - 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") + }, + ) assertEquals( "TRUE", - handlebars.compileInline("{{#not_eq an_int doesnt_exist}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) + handlebars + .compileInline("{{#not_eq an_int doesnt_exist}}TRUE{{else}}FALSE{{/not_eq}}") + .apply(context), ) assertEquals( "TRUE", - handlebars.compileInline("{{#not_eq an_int null}}TRUE{{else}}FALSE{{/not_eq}}").apply(context) + handlebars + .compileInline("{{#not_eq an_int null}}TRUE{{else}}FALSE{{/not_eq}}") + .apply(context), ) } @Test internal fun `gt - greater than should return false when compared to one self`() { - 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") + }, + ) assertEquals( "FALSE", - handlebars.compileInline("{{#gt small_int small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_int small_int }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#gt small_double small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_double small_double }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#gt a_string a_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt a_string a_string }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) } @Test internal fun `gt - greater than should return true when first param greater than second param`() { - 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") + }, + ) assertEquals( "TRUE", - handlebars.compileInline("{{#gt large_int small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt large_int small_int }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) assertEquals( "TRUE", - handlebars.compileInline("{{#gt large_double small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt large_double small_double }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) assertEquals( "TRUE", - handlebars.compileInline("{{#gt z_string a_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt z_string a_string }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) } @Test internal fun `gt - greater than should return false when first param less than second param`() { - 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") + }, + ) assertEquals( "FALSE", - handlebars.compileInline("{{#gt small_int large_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_int large_int }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#gt small_double large_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_double large_double }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#gt a_string z_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt a_string z_string }}TRUE{{else}}FALSE{{/gt}}") + .apply(context), ) } @Test internal fun `gt - greater than should fail if argument are not of same type`() { - 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") + }, + ) assertThrows { - handlebars.compileInline("{{#gt int_string large_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt int_string large_int }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } assertThrows { - handlebars.compileInline("{{#gt small_double a_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_double a_string }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } assertThrows { - handlebars.compileInline("{{#gt small_double small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_double small_int }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } } @Test internal fun `gt - greater than should fail if comparing two null or empty`() { - 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") + }, + ) assertThrows { - handlebars.compileInline("{{#gt int_string noexists }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt int_string noexists }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } assertThrows { - handlebars.compileInline("{{#gt small_double null }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_double null }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } assertThrows { handlebars.compileInline("{{#gt small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) @@ -623,127 +752,160 @@ internal class HelperTest { @Test internal fun `lt - less than should return false when compared to one self`() { - 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") + }, + ) assertEquals( "FALSE", - handlebars.compileInline("{{#lt small_int small_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt small_int small_int }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#lt small_double small_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt small_double small_double }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#lt a_string a_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt a_string a_string }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) } @Test internal fun `lt - less than should return false when first param greater than second param`() { - 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") + }, + ) assertEquals( "FALSE", - handlebars.compileInline("{{#lt large_int small_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt large_int small_int }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#lt large_double small_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt large_double small_double }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) assertEquals( "FALSE", - handlebars.compileInline("{{#lt z_string a_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt z_string a_string }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) } @Test internal fun `lt - less than should return true when first param less than second param`() { - 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") + }, + ) assertEquals( "TRUE", - handlebars.compileInline("{{#lt small_int large_int }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt small_int large_int }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) assertEquals( "TRUE", - handlebars.compileInline("{{#lt small_double large_double }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt small_double large_double }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) assertEquals( "TRUE", - handlebars.compileInline("{{#lt a_string z_string }}TRUE{{else}}FALSE{{/lt}}").apply(context) + handlebars + .compileInline("{{#lt a_string z_string }}TRUE{{else}}FALSE{{/lt}}") + .apply(context), ) } @Test internal fun `lt - less than should fail if argument are not of same type`() { - 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") + }, + ) assertThrows { - handlebars.compileInline("{{#gt int_string large_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt int_string large_int }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } assertThrows { - handlebars.compileInline("{{#gt small_double a_string }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_double a_string }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } assertThrows { - handlebars.compileInline("{{#gt small_double small_int }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_double small_int }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } } @Test internal fun `lt - less than should fail if comparing two null or empty`() { - 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") + }, + ) assertThrows { - handlebars.compileInline("{{#gt int_string noexists }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt int_string noexists }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } assertThrows { - handlebars.compileInline("{{#gt small_double null }}TRUE{{else}}FALSE{{/gt}}").apply(context) + handlebars + .compileInline("{{#gt small_double null }}TRUE{{else}}FALSE{{/gt}}") + .apply(context) } assertThrows { handlebars.compileInline("{{#gt small_double }}TRUE{{else}}FALSE{{/gt}}").apply(context) @@ -752,269 +914,316 @@ internal class HelperTest { @Test internal fun `Currency formatting should format number as currency`() { - 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) - put("beløp_ganske_liten_integer", 1) - put("beløp_kjempeliten_integer", 0) - put("beløp_liten_integer", 10) - put("beløp_stor_integer", 1000001) - put("beløp_kjempestor_integer", Int.MAX_VALUE) - put("beløp_liten_string", "0") - put("beløp_stor_string", "1000001") - } - ) + 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) + put("beløp_ganske_liten_integer", 1) + put("beløp_kjempeliten_integer", 0) + put("beløp_liten_integer", 10) + put("beløp_stor_integer", 1000001) + put("beløp_kjempestor_integer", Int.MAX_VALUE) + put("beløp_liten_string", "0") + put("beløp_stor_string", "1000001") + }, + ) assertEquals("1 337,69", handlebars.compileInline("{{ currency_no beløp }}").apply(context)) - assertEquals("1 337,60", handlebars.compileInline("{{ currency_no beløp_single_decimal }}").apply(context)) - assertEquals("9 001,00", handlebars.compileInline("{{ currency_no beløp_integer }}").apply(context)) - assertEquals("1 337 420,69", handlebars.compileInline("{{ currency_no beløp_stort }}").apply(context)) + assertEquals( + "1 337,60", + handlebars.compileInline("{{ currency_no beløp_single_decimal }}").apply(context) + ) + assertEquals( + "9 001,00", + handlebars.compileInline("{{ currency_no beløp_integer }}").apply(context) + ) + assertEquals( + "1 337 420,69", + handlebars.compileInline("{{ currency_no beløp_stort }}").apply(context) + ) } @Test internal fun `Currency formatting should format number as currency without decimals`() { - 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) - put("beløp_ganske_liten_integer", 1) - put("beløp_kjempeliten_integer", 0) - put("beløp_liten_integer", 10) - put("beløp_stor_integer", 1000001) - put("beløp_kjempestor_integer", Int.MAX_VALUE) - put("beløp_liten_string", "0") - put("beløp_stor_string", "1000001") - } - ) + 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) + put("beløp_ganske_liten_integer", 1) + put("beløp_kjempeliten_integer", 0) + put("beløp_liten_integer", 10) + put("beløp_stor_integer", 1000001) + put("beløp_kjempestor_integer", Int.MAX_VALUE) + put("beløp_liten_string", "0") + put("beløp_stor_string", "1000001") + }, + ) - assertEquals("1 337", handlebars.compileInline("{{ currency_no beløp true }}").apply(context)) - assertEquals("1 337 420", handlebars.compileInline("{{ currency_no beløp_stort true }}").apply(context)) + assertEquals( + "1 337", + handlebars.compileInline("{{ currency_no beløp true }}").apply(context) + ) + assertEquals( + "1 337 420", + handlebars.compileInline("{{ currency_no beløp_stort true }}").apply(context) + ) } @Test internal fun `Currency formatting should format integer to currency`() { - 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) - put("beløp_ganske_liten_integer", 1) - put("beløp_kjempeliten_integer", 0) - put("beløp_liten_integer", 10) - put("beløp_stor_integer", 1000001) - put("beløp_kjempestor_integer", Int.MAX_VALUE) - put("beløp_liten_string", "0") - put("beløp_stor_string", "1000001") - } - ) + 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) + put("beløp_ganske_liten_integer", 1) + put("beløp_kjempeliten_integer", 0) + put("beløp_liten_integer", 10) + put("beløp_stor_integer", 1000001) + put("beløp_kjempestor_integer", Int.MAX_VALUE) + put("beløp_liten_string", "0") + put("beløp_stor_string", "1000001") + }, + ) - assertEquals("90,01", handlebars.compileInline("{{ int_as_currency_no beløp_integer }}").apply(context)) - assertEquals("0,10", handlebars.compileInline("{{ int_as_currency_no beløp_liten_integer }}").apply(context)) + assertEquals( + "90,01", + handlebars.compileInline("{{ int_as_currency_no beløp_integer }}").apply(context) + ) + assertEquals( + "0,10", + handlebars.compileInline("{{ int_as_currency_no beløp_liten_integer }}").apply(context) + ) assertEquals( "0,00", - handlebars.compileInline("{{ int_as_currency_no beløp_kjempeliten_integer }}").apply(context) + handlebars + .compileInline("{{ int_as_currency_no beløp_kjempeliten_integer }}") + .apply(context), ) assertEquals( "0,01", - handlebars.compileInline("{{ int_as_currency_no beløp_ganske_liten_integer }}").apply(context) + handlebars + .compileInline("{{ int_as_currency_no beløp_ganske_liten_integer }}") + .apply(context), ) assertEquals( "10 000,01", - handlebars.compileInline("{{ int_as_currency_no beløp_stor_integer }}").apply(context) + handlebars.compileInline("{{ int_as_currency_no beløp_stor_integer }}").apply(context), ) assertEquals( "21 474 836,47", - handlebars.compileInline("{{ int_as_currency_no beløp_kjempestor_integer }}").apply(context) + handlebars + .compileInline("{{ int_as_currency_no beløp_kjempestor_integer }}") + .apply(context), ) } @Test internal fun `Currency formatting should format string to currency`() { - 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) - put("beløp_ganske_liten_integer", 1) - put("beløp_kjempeliten_integer", 0) - put("beløp_liten_integer", 10) - put("beløp_stor_integer", 1000001) - put("beløp_kjempestor_integer", Int.MAX_VALUE) - put("beløp_liten_string", "0") - put("beløp_stor_string", "1000001") - } - ) + 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) + put("beløp_ganske_liten_integer", 1) + put("beløp_kjempeliten_integer", 0) + put("beløp_liten_integer", 10) + put("beløp_stor_integer", 1000001) + put("beløp_kjempestor_integer", Int.MAX_VALUE) + put("beløp_liten_string", "0") + put("beløp_stor_string", "1000001") + }, + ) - assertEquals("0,00", handlebars.compileInline("{{ string_as_currency_no beløp_liten_string }}").apply(context)) + assertEquals( + "0,00", + handlebars + .compileInline("{{ string_as_currency_no beløp_liten_string }}") + .apply(context) + ) assertEquals( "10 000,01", - handlebars.compileInline("{{ string_as_currency_no beløp_stor_string }}").apply(context) + handlebars + .compileInline("{{ string_as_currency_no beløp_stor_string }}") + .apply(context), ) } @Test internal fun `Is defined should output IS DEFINED if someProperty is defined`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - put("someProperty", false) - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { put("someProperty", false) }, + ) assertEquals( "IS DEFINED", - handlebars.compileInline("{{#is_defined someProperty }}IS DEFINED{{/is_defined }}").apply(context) + handlebars + .compileInline("{{#is_defined someProperty }}IS DEFINED{{/is_defined }}") + .apply(context), ) } @Test internal fun `Is defined should output empty string if someOtherProperty is not defined`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - put("someProperty", false) - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { put("someProperty", false) }, + ) assertEquals( "", - handlebars.compileInline("{{#is_defined someOtherProperty }}IS DEFINED{{/is_defined }}").apply(context) + handlebars + .compileInline("{{#is_defined someOtherProperty }}IS DEFINED{{/is_defined }}") + .apply(context), ) } @Test internal fun `contains_all should find single param that matches`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - putArray("myList") - .add("FIRST_VAL") - .add("SECOND_VAL") - .add("THIRD_VAL") - putArray("emptyList") - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { + putArray("myList").add("FIRST_VAL").add("SECOND_VAL").add("THIRD_VAL") + putArray("emptyList") + }, + ) assertEquals( "FOUND!", - handlebars.compileInline("{{#contains_all myList \"FIRST_VAL\"}}FOUND!{{else}}NOTHING!{{/contains_all }}") - .apply(context) + handlebars + .compileInline( + "{{#contains_all myList \"FIRST_VAL\"}}FOUND!{{else}}NOTHING!{{/contains_all }}" + ) + .apply(context), ) } @Test internal fun `contains_all should find all values without order`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - putArray("myList") - .add("FIRST_VAL") - .add("SECOND_VAL") - .add("THIRD_VAL") - putArray("emptyList") - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { + putArray("myList").add("FIRST_VAL").add("SECOND_VAL").add("THIRD_VAL") + putArray("emptyList") + }, + ) assertEquals( "FOUND!", - handlebars.compileInline("""{{#contains_all myList "FIRST_VAL" "THIRD_VAL" "SECOND_VAL"}}FOUND!{{else}}NOTHING!{{/contains_all }}""") - .apply(context) + handlebars + .compileInline( + """{{#contains_all myList "FIRST_VAL" "THIRD_VAL" "SECOND_VAL"}}FOUND!{{else}}NOTHING!{{/contains_all }}""" + ) + .apply(context), ) } @Test internal fun `contains_all should not find if at least one did not match`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - putArray("myList") - .add("FIRST_VAL") - .add("SECOND_VAL") - .add("THIRD_VAL") - putArray("emptyList") - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { + putArray("myList").add("FIRST_VAL").add("SECOND_VAL").add("THIRD_VAL") + putArray("emptyList") + }, + ) assertEquals( "NOTHING!", - handlebars.compileInline("""{{#contains_all myList "FIRST_VAL" "THIRD_VAL" "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""") - .apply(context) + handlebars + .compileInline( + """{{#contains_all myList "FIRST_VAL" "THIRD_VAL" "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""" + ) + .apply(context), ) } @Test internal fun `contains_all should not find if empty parameter`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - putArray("myList") - .add("FIRST_VAL") - .add("SECOND_VAL") - .add("THIRD_VAL") - putArray("emptyList") - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { + putArray("myList").add("FIRST_VAL").add("SECOND_VAL").add("THIRD_VAL") + putArray("emptyList") + }, + ) assertEquals( "NOTHING!", - handlebars.compileInline("""{{#contains_all myList ""}}FOUND!{{else}}NOTHING!{{/contains_all }}""") - .apply(context) + handlebars + .compileInline( + """{{#contains_all myList ""}}FOUND!{{else}}NOTHING!{{/contains_all }}""" + ) + .apply(context), ) } @Test internal fun `contains_all should not find if no parameter`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - putArray("myList") - .add("FIRST_VAL") - .add("SECOND_VAL") - .add("THIRD_VAL") - putArray("emptyList") - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { + putArray("myList").add("FIRST_VAL").add("SECOND_VAL").add("THIRD_VAL") + putArray("emptyList") + }, + ) assertEquals( "NOTHING!", - handlebars.compileInline("""{{#contains_all myList}}FOUND!{{else}}NOTHING!{{/contains_all }}""") - .apply(context) + handlebars + .compileInline( + """{{#contains_all myList}}FOUND!{{else}}NOTHING!{{/contains_all }}""" + ) + .apply(context), ) } @Test internal fun `contains_all should not fail if list is empty`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - putArray("myList") - .add("FIRST_VAL") - .add("SECOND_VAL") - .add("THIRD_VAL") - putArray("emptyList") - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { + putArray("myList").add("FIRST_VAL").add("SECOND_VAL").add("THIRD_VAL") + putArray("emptyList") + }, + ) assertEquals( "NOTHING!", - handlebars.compileInline("""{{#contains_all emptyList "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""") - .apply(context) + handlebars + .compileInline( + """{{#contains_all emptyList "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""" + ) + .apply(context), ) } @Test internal fun `contains_all should throw exception if unknown list`() { - val context = jsonContext( - jsonNodeFactory.objectNode().apply { - putArray("myList") - .add("FIRST_VAL") - .add("SECOND_VAL") - .add("THIRD_VAL") - putArray("emptyList") - } - ) + val context = + jsonContext( + jsonNodeFactory.objectNode().apply { + putArray("myList").add("FIRST_VAL").add("SECOND_VAL").add("THIRD_VAL") + putArray("emptyList") + }, + ) assertThrows { - handlebars.compileInline("""{{#contains_all dontexist "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""") + handlebars + .compileInline( + """{{#contains_all dontexist "UNKNOWN"}}FOUND!{{else}}NOTHING!{{/contains_all }}""" + ) .apply(context) } } @@ -1025,7 +1234,7 @@ internal class HelperTest { assertEquals( "Brage Bruker Olsen", - handlebars.compileInline("{{capitalize_names \"BRAGE BRUKER OLSEN\"}}").apply(context) + handlebars.compileInline("{{capitalize_names \"BRAGE BRUKER OLSEN\"}}").apply(context), ) } @@ -1035,7 +1244,7 @@ internal class HelperTest { assertEquals( "Brage Bruker Olsen", - handlebars.compileInline("{{capitalize_names \"brage bruker olsen\"}}").apply(context) + handlebars.compileInline("{{capitalize_names \"brage bruker olsen\"}}").apply(context), ) } @@ -1045,7 +1254,7 @@ internal class HelperTest { assertEquals( "Brage Bruker Olsen", - handlebars.compileInline("{{capitalize_names \"BRage BRUker OLSEn\"}}").apply(context) + handlebars.compileInline("{{capitalize_names \"BRage BRUker OLSEn\"}}").apply(context), ) } @@ -1055,7 +1264,7 @@ internal class HelperTest { assertEquals( "Brage Bruker Olsen", - handlebars.compileInline("{{capitalize_names \"brAGE bruKer oLsEn\"}}").apply(context) + handlebars.compileInline("{{capitalize_names \"brAGE bruKer oLsEn\"}}").apply(context), ) } @@ -1065,7 +1274,9 @@ internal class HelperTest { assertEquals( "Brage Bruker Olsen", - handlebars.compileInline("{{capitalize_names \" BRAGE BRUKER OLSEN \"}}").apply(context) + handlebars + .compileInline("{{capitalize_names \" BRAGE BRUKER OLSEN \"}}") + .apply(context), ) } @@ -1075,7 +1286,9 @@ internal class HelperTest { assertEquals( "Brage-Bruker Olsen", - handlebars.compileInline("{{capitalize_names \" BRAGE-BRUKER OLSEN \"}}").apply(context) + handlebars + .compileInline("{{capitalize_names \" BRAGE-BRUKER OLSEN \"}}") + .apply(context), ) } @@ -1085,7 +1298,9 @@ internal class HelperTest { assertEquals( "Brage-Bruker Olsen", - handlebars.compileInline("{{capitalize_names \" BRAGE - BRUKER OLSEN \"}}").apply(context) + handlebars + .compileInline("{{capitalize_names \" BRAGE - BRUKER OLSEN \"}}") + .apply(context), ) } @@ -1093,7 +1308,10 @@ internal class HelperTest { internal fun `Capitilize all should capitalize names splitted by apostrophe`() { val context = jsonContext(jsonNodeFactory.objectNode()) - assertEquals("O'Shea Olsen", handlebars.compileInline("{{capitalize_names \" O'SHEA OLSEN \"}}").apply(context)) + assertEquals( + "O'Shea Olsen", + handlebars.compileInline("{{capitalize_names \" O'SHEA OLSEN \"}}").apply(context) + ) } @Test @@ -1102,7 +1320,7 @@ internal class HelperTest { assertEquals( "Brage Bruker Olsen", - handlebars.compileInline("{{capitalize_names \"Brage Bruker Olsen\"}}").apply(context) + handlebars.compileInline("{{capitalize_names \"Brage Bruker Olsen\"}}").apply(context), ) } @@ -1110,7 +1328,10 @@ internal class HelperTest { internal fun `Capitilize all should do nothing if already capitilized - single word`() { val context = jsonContext(jsonNodeFactory.objectNode()) - assertEquals("Brage", handlebars.compileInline("{{capitalize_names \"Brage\"}}").apply(context)) + assertEquals( + "Brage", + handlebars.compileInline("{{capitalize_names \"Brage\"}}").apply(context) + ) } @Test @@ -1118,7 +1339,7 @@ internal class HelperTest { val context = jsonContext(jsonNodeFactory.objectNode()) assertEquals( "BRAGE BRUKER OLSEN", - handlebars.compileInline("{{uppercase \"brage bruker olsen\"}}").apply(context) + handlebars.compileInline("{{uppercase \"brage bruker olsen\"}}").apply(context), ) } @@ -1127,14 +1348,17 @@ internal class HelperTest { val context = jsonContext(jsonNodeFactory.objectNode()) assertEquals( "BRAGE BRUKER OLSEN", - handlebars.compileInline("{{uppercase \"Brage Bruker Olsen\"}}").apply(context) + handlebars.compileInline("{{uppercase \"Brage Bruker Olsen\"}}").apply(context), ) } @Test internal fun `uppercase should uppercase strings with numbers`() { val context = jsonContext(jsonNodeFactory.objectNode()) - assertEquals("0553 OSLO", handlebars.compileInline("{{uppercase \"0553 Oslo\"}}").apply(context)) + assertEquals( + "0553 OSLO", + handlebars.compileInline("{{uppercase \"0553 Oslo\"}}").apply(context) + ) } @Test @@ -1142,7 +1366,9 @@ internal class HelperTest { val context = jsonContext(jsonNodeFactory.objectNode()) assertEquals( "I pitty the fool
Who doesn't br", - handlebars.compileInline("{{breaklines \"I pitty the fool \\r\\n Who doesn't br\"}}").apply(context) + handlebars + .compileInline("{{breaklines \"I pitty the fool \\r\\n Who doesn't br\"}}") + .apply(context), ) } @@ -1151,7 +1377,9 @@ internal class HelperTest { val context = jsonContext(jsonNodeFactory.objectNode()) assertEquals( "I pitty the fool
Who doesn't br", - handlebars.compileInline("{{breaklines \"I pitty the fool \\n Who doesn't br\"}}").apply(context) + handlebars + .compileInline("{{breaklines \"I pitty the fool \\n Who doesn't br\"}}") + .apply(context), ) } } diff --git a/src/test/kotlin/no/nav/pdfgen/PdfGenITest.kt b/src/test/kotlin/no/nav/pdfgen/PdfGenITest.kt index 0e67ebd..4657381 100644 --- a/src/test/kotlin/no/nav/pdfgen/PdfGenITest.kt +++ b/src/test/kotlin/no/nav/pdfgen/PdfGenITest.kt @@ -12,6 +12,9 @@ import io.ktor.http.ContentType import io.ktor.http.content.ByteArrayContent import io.ktor.http.content.TextContent import io.ktor.http.isSuccess +import java.nio.file.Files +import java.nio.file.Paths +import java.util.concurrent.Executors import kotlinx.coroutines.* import no.nav.pdfgen.template.loadTemplates import org.apache.pdfbox.io.IOUtils @@ -20,16 +23,11 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test -import java.nio.file.Files -import java.nio.file.Paths -import java.util.concurrent.Executors internal class PdfGenITest { private val applicationPort = getRandomPort() private val application = initializeApplication(applicationPort) - private val client = HttpClient(CIO) { - expectSuccess = false - } + private val client = HttpClient(CIO) { expectSuccess = false } private val env = Environment() private val templates = loadTemplates(env) private val timeoutSeconds: Long = 10 @@ -43,48 +41,66 @@ internal class PdfGenITest { internal fun post_to_api_v1_genpdf_applicationName_templateName() { application.start() // api path /api/v1/genpdf/{applicationName}/{templateName} - templates.map { it.key }.forEach { - val (applicationName, templateName) = it - println("With $templateName for $applicationName results in a valid PDF") - val json = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json") - ?.readBytes()?.toString(Charsets.UTF_8) ?: "{}" + templates + .map { it.key } + .forEach { + val (applicationName, templateName) = it + println("With $templateName for $applicationName results in a valid PDF") + val json = + javaClass + .getResourceAsStream("/data/$applicationName/$templateName.json") + ?.readBytes() + ?.toString(Charsets.UTF_8) + ?: "{}" - val response = runBlocking { - client.post("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { - setBody(TextContent(json, contentType = ContentType.Application.Json)) - } + val response = + runBlocking { + client.post( + "http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName" + ) { + setBody(TextContent(json, contentType = ContentType.Application.Json)) + } + } + assertEquals(true, response.status.isSuccess()) + val bytes = runBlocking { response.readBytes() } + assertNotEquals(null, bytes) + // Load the document in pdfbox to ensure it's valid + val document = PDDocument.load(bytes) + assertNotEquals(null, document) + assertEquals(true, document.pages.count > 0) + println(document.documentInformation.title) + document.close() } - assertEquals(true, response.status.isSuccess()) - val bytes = runBlocking { response.readBytes() } - assertNotEquals(null, bytes) - // Load the document in pdfbox to ensure it's valid - val document = PDDocument.load(bytes) - assertNotEquals(null, document) - assertEquals(true, document.pages.count > 0) - println(document.documentInformation.title) - document.close() - } } @Test internal fun `Generate sample PDFs using test data`() { application.start() - templates.map { it.key }.forEach { - val (applicationName, templateName) = it - println("$templateName for $applicationName generates a sample PDF") - val json = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json") - ?.readBytes()?.toString(Charsets.UTF_8) ?: "{}" + templates + .map { it.key } + .forEach { + val (applicationName, templateName) = it + println("$templateName for $applicationName generates a sample PDF") + val json = + javaClass + .getResourceAsStream("/data/$applicationName/$templateName.json") + ?.readBytes() + ?.toString(Charsets.UTF_8) + ?: "{}" - val response = runBlocking { - client.post("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { - setBody(TextContent(json, contentType = ContentType.Application.Json)) - } + val response = + runBlocking { + client.post( + "http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName" + ) { + setBody(TextContent(json, contentType = ContentType.Application.Json)) + } + } + assertEquals(true, response.status.isSuccess()) + val bytes = runBlocking { response.readBytes() } + assertNotEquals(null, bytes) + Files.write(Paths.get("build", "${it.first}-${it.second}.pdf"), bytes) } - assertEquals(true, response.status.isSuccess()) - val bytes = runBlocking { response.readBytes() } - assertNotEquals(null, bytes) - Files.write(Paths.get("build", "${it.first}-${it.second}.pdf"), bytes) - } } @Test @@ -115,36 +131,39 @@ internal class PdfGenITest { } response.status.isSuccess() shouldBeEqualTo false } - */ + */ } @Test internal fun `Using the image convert endpoint Should render a document using input image`() { application.start() mapOf( - ByteArrayContent(testJpg, ContentType.Image.JPEG) to "jpg.pdf", - ByteArrayContent(testPng, ContentType.Image.PNG) to "png.pdf" - ).forEach { (payload, outputFile) -> - - println("Should render a document using input image, $outputFile") - runBlocking { + ByteArrayContent(testJpg, ContentType.Image.JPEG) to "jpg.pdf", + ByteArrayContent(testPng, ContentType.Image.PNG) to "png.pdf", + ) + .forEach { (payload, outputFile) -> + println("Should render a document using input image, $outputFile") runBlocking { - client.preparePost("http://localhost:$applicationPort/api/v1/genpdf/image/integration-test") { - setBody(payload) - } - }.execute { response -> - assertEquals(true, response.status.isSuccess()) - val bytes = response.readBytes() - assertEquals(false, bytes.isEmpty()) - Files.write(Paths.get("build", outputFile), bytes) - // Load the document in pdfbox to ensure its valid - val document = PDDocument.load(bytes) - assertNotEquals(null, document) - assertEquals(true, document.pages.count > 0) - document.close() + runBlocking { + client.preparePost( + "http://localhost:$applicationPort/api/v1/genpdf/image/integration-test" + ) { + setBody(payload) + } + } + .execute { response -> + assertEquals(true, response.status.isSuccess()) + val bytes = response.readBytes() + assertEquals(false, bytes.isEmpty()) + Files.write(Paths.get("build", outputFile), bytes) + // Load the document in pdfbox to ensure its valid + val document = PDDocument.load(bytes) + assertNotEquals(null, document) + assertEquals(true, document.pages.count > 0) + document.close() + } } } - } } @Test @@ -152,12 +171,15 @@ internal class PdfGenITest { application.start() runBlocking { runBlocking { - client.config { expectSuccess = false }.preparePost("http://localhost:$applicationPort/whodis") - }.execute { response -> - assertEquals(404, response.status.value) - val text = runBlocking { response.bodyAsText() } - assertEquals(true, text.contains("Known templates:\n/api/v1/genpdf")) - } + client + .config { expectSuccess = false } + .preparePost("http://localhost:$applicationPort/whodis") + } + .execute { response -> + assertEquals(404, response.status.value) + val text = runBlocking { response.bodyAsText() } + assertEquals(true, text.contains("Known templates:\n/api/v1/genpdf")) + } } } @@ -167,28 +189,50 @@ internal class PdfGenITest { val passes = 20 val context = Executors.newFixedThreadPool(8).asCoroutineDispatcher() - templates.map { it.key }.forEach { (applicationName, templateName) -> - println("$templateName for $applicationName performs fine") - val startTime = System.currentTimeMillis() - runBlocking(context) { - val tasks = (1..passes).map { - launch { - val json = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json")?.use { - IOUtils.toByteArray(it).toString(Charsets.UTF_8) - } ?: "{}" + templates + .map { it.key } + .forEach { (applicationName, templateName) -> + println("$templateName for $applicationName performs fine") + val startTime = System.currentTimeMillis() + runBlocking(context) { + val tasks = + (1..passes) + .map { + launch { + val json = + javaClass + .getResourceAsStream( + "/data/$applicationName/$templateName.json" + ) + ?.use { + IOUtils.toByteArray(it).toString(Charsets.UTF_8) + } + ?: "{}" - val response = - client.preparePost("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { - setBody(TextContent(json, contentType = ContentType.Application.Json)) - }.execute() - assertNotEquals(null, response.readBytes()) - assertEquals(true, response.status.isSuccess()) - } - }.toList() - tasks.forEach { it.join() } + val response = + client + .preparePost( + "http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName" + ) { + setBody( + TextContent( + json, + contentType = ContentType.Application.Json + ) + ) + } + .execute() + assertNotEquals(null, response.readBytes()) + assertEquals(true, response.status.isSuccess()) + } + } + .toList() + tasks.forEach { it.join() } + } + println( + "Multiple-threads performance testing $templateName for $applicationName took ${System.currentTimeMillis() - startTime}ms" + ) } - println("Multiple-threads performance testing $templateName for $applicationName took ${System.currentTimeMillis() - startTime}ms") - } } @Test @@ -196,25 +240,39 @@ internal class PdfGenITest { application.start() val passes = 40 - templates.map { it.key }.forEach { (applicationName, templateName) -> - - println("$templateName for $applicationName performs fine with single-thread load") - val startTime = System.currentTimeMillis() - runBlocking { - for (i in 1..passes) { - val json = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json")?.use { - IOUtils.toByteArray(it).toString(Charsets.UTF_8) - } ?: "{}" + templates + .map { it.key } + .forEach { (applicationName, templateName) -> + println("$templateName for $applicationName performs fine with single-thread load") + val startTime = System.currentTimeMillis() + runBlocking { + for (i in 1..passes) { + val json = + javaClass + .getResourceAsStream("/data/$applicationName/$templateName.json") + ?.use { IOUtils.toByteArray(it).toString(Charsets.UTF_8) } + ?: "{}" - val response = - client.preparePost("http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName") { - setBody(TextContent(json, contentType = ContentType.Application.Json)) - }.execute() - assertNotEquals(null, response.readBytes()) - assertEquals(true, response.status.isSuccess()) + val response = + client + .preparePost( + "http://localhost:$applicationPort/api/v1/genpdf/$applicationName/$templateName" + ) { + setBody( + TextContent( + json, + contentType = ContentType.Application.Json + ) + ) + } + .execute() + assertNotEquals(null, response.readBytes()) + assertEquals(true, response.status.isSuccess()) + } } + println( + "Single-thread performance testing $templateName for $applicationName took ${System.currentTimeMillis() - startTime}ms" + ) } - println("Single-thread performance testing $templateName for $applicationName took ${System.currentTimeMillis() - startTime}ms") - } } } diff --git a/src/test/kotlin/no/nav/pdfgen/RenderingTest.kt b/src/test/kotlin/no/nav/pdfgen/RenderingTest.kt index cc670ab..a6eb130 100644 --- a/src/test/kotlin/no/nav/pdfgen/RenderingTest.kt +++ b/src/test/kotlin/no/nav/pdfgen/RenderingTest.kt @@ -2,6 +2,7 @@ package no.nav.pdfgen import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper +import java.io.ByteArrayInputStream import no.nav.pdfgen.api.render import no.nav.pdfgen.pdf.createPDFA import no.nav.pdfgen.template.loadTemplates @@ -12,7 +13,6 @@ import org.verapdf.gf.foundry.VeraGreenfieldFoundryProvider import org.verapdf.pdfa.Foundries import org.verapdf.pdfa.flavours.PDFAFlavour import org.verapdf.pdfa.results.TestAssertion -import java.io.ByteArrayInputStream internal class RenderingTest { private val env = Environment() @@ -26,14 +26,20 @@ internal class RenderingTest { @Test internal fun `All pdfs should render with default values`() { - templates.map { it.key }.forEach { it -> - val (applicationName, templateName) = it - val node = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json")?.use { that -> - objectMapper.readValue(that, JsonNode::class.java) - } ?: objectMapper.createObjectNode() - println("Renders the template $templateName for application $applicationName without exceptions") - render(applicationName, templateName, templates, node) - } + templates + .map { it.key } + .forEach { it -> + val (applicationName, templateName) = it + val node = + javaClass + .getResourceAsStream("/data/$applicationName/$templateName.json") + ?.use { that -> objectMapper.readValue(that, JsonNode::class.java) } + ?: objectMapper.createObjectNode() + println( + "Renders the template $templateName for application $applicationName without exceptions" + ) + render(applicationName, templateName, templates, node) + } } @Test @@ -42,27 +48,34 @@ internal class RenderingTest { val pdfaFlavour = PDFAFlavour.PDFA_2_U val validator = Foundries.defaultInstance().createValidator(pdfaFlavour, false) - templates.map { it.key }.filterNot(blackList::contains).forEach { - val (applicationName, templateName) = it - val node = javaClass.getResourceAsStream("/data/$applicationName/$templateName.json")?.use { that -> - objectMapper.readValue(that, JsonNode::class.java) - } ?: objectMapper.createObjectNode() - println("Renders the template $templateName for application $applicationName to a PDF/A compliant document") - val doc = render(applicationName, templateName, templates, node) - val pdf = createPDFA(doc!!, env) - Foundries.defaultInstance().createParser(ByteArrayInputStream(pdf)).use { that -> - val validationResult = validator.validate(that) - validationResult.testAssertions - .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}") - } - assertEquals(true, validationResult.isCompliant) + templates + .map { it.key } + .filterNot(blackList::contains) + .forEach { + val (applicationName, templateName) = it + val node = + javaClass + .getResourceAsStream("/data/$applicationName/$templateName.json") + ?.use { that -> objectMapper.readValue(that, JsonNode::class.java) } + ?: objectMapper.createObjectNode() + println( + "Renders the template $templateName for application $applicationName to a PDF/A compliant document" + ) + val doc = render(applicationName, templateName, templates, node) + val pdf = createPDFA(doc!!, env) + Foundries.defaultInstance().createParser(ByteArrayInputStream(pdf)).use { that -> + val validationResult = validator.validate(that) + validationResult.testAssertions + .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}") + } + assertEquals(true, validationResult.isCompliant) + } } - } } @Test