From b2316b0029c52840e52c5c80dadb5d3a01944cf1 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 31 May 2023 22:13:31 +0200 Subject: [PATCH] split up app/lib --- app/build.gradle.kts | 148 ++++++++++++++++++ app/detekt-baseline.xml | 8 + app/pom.xml | 57 +++++++ .../net/thauvin/erik/urlencoder/UrlEncoder.kt | 106 +++++++++++++ .../thauvin/erik/urlencoder/UrlEncoderTest.kt | 66 -------- lib/build.gradle.kts | 55 ++++--- lib/detekt-baseline.xml | 17 +- lib/pom.xml | 6 +- .../{UrlEncoder.kt => UrlEncoderUtil.kt} | 49 +----- .../erik/urlencoder/UrlEncoderUtilTest.kt | 114 ++++++++++++++ settings.gradle.kts | 3 + 11 files changed, 477 insertions(+), 152 deletions(-) create mode 100644 app/build.gradle.kts create mode 100644 app/detekt-baseline.xml create mode 100644 app/pom.xml create mode 100644 app/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt rename {lib => app}/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt (64%) rename lib/src/main/kotlin/net/thauvin/erik/urlencoder/{UrlEncoder.kt => UrlEncoderUtil.kt} (80%) create mode 100644 lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtilTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..9b2a918 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,148 @@ +/* + * Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com) + * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import buildsrc.utils.Rife2TestListener +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + +plugins { + id("application") + id("com.github.ben-manes.versions") + id("io.gitlab.arturbosch.detekt") + id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.kotlinx.kover") + + buildsrc.conventions.publishing + buildsrc.conventions.sonarqube +} + +val mavenName = "UrlEncoder" +val deployDir = project.layout.projectDirectory.dir("deploy") +val gitHub = "ethauvin/${rootProject.name}" +val mavenUrl = "https://github.com/$gitHub" +val publicationName = "mavenJava" +val myClassName = "$group.${rootProject.name}.$mavenName" + +dependencies { + implementation(projects.lib) +// testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") + testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") +} + +base { + archivesName.set(rootProject.name) +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + withSourcesJar() +} + +application { + mainClass.set(myClassName) +} + + +tasks { + jar { + manifest { + attributes["Main-Class"] = myClassName + } + } + + val fatJar = register("fatJar") { + group = "build" + dependsOn.addAll(listOf("compileJava", "compileKotlin", "processResources")) + archiveClassifier.set("all") + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { attributes(mapOf("Main-Class" to application.mainClass)) } + val sourcesMain = sourceSets.main.get() + val contents = configurations.runtimeClasspath.get() + .map { if (it.isDirectory) it else zipTree(it) } + sourcesMain.output + from(contents) + } + + build { + dependsOn(fatJar) + } + + withType().configureEach { + kotlinOptions.jvmTarget = java.targetCompatibility.toString() + } + + test { + addTestListener(Rife2TestListener(project.properties["testsBadgeApiKey"]?.toString())) + } + + withType().configureEach { + useJUnitPlatform() + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } + } + + withType().configureEach { + destination = file("$projectDir/pom.xml") + } + + clean { + delete(deployDir) + } + + withType().configureEach { + dokkaSourceSets { + named("main") { + moduleName.set("UrlEncoder Application") + } + } + } + + val copyToDeploy by registering(Sync::class) { + from(configurations.runtimeClasspath) { + exclude("annotations-*.jar") + } + from(jar) + into(deployDir) + } + + register("deploy") { + description = "Copies all needed files to the 'deploy' directory." + group = PublishingPlugin.PUBLISH_TASK_GROUP + dependsOn(build, jar) + outputs.dir(deployDir) + inputs.files(copyToDeploy) + } + + "sonar" { + dependsOn(koverReport) + } +} + +publishing { + publications { + create("mavenJava") { + from(components["java"]) + artifactId = rootProject.name + artifact(tasks.javadocJar) + } + } +} diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml new file mode 100644 index 0000000..fd4e62e --- /dev/null +++ b/app/detekt-baseline.xml @@ -0,0 +1,8 @@ + + + + + ComplexCondition:UrlEncoder.kt$UrlEncoder$hasOption && args.size == 2 || !hasOption && args.size == 1 + MaxLineLength:UrlEncoder.kt$UrlEncoder$* + + diff --git a/app/pom.xml b/app/pom.xml new file mode 100644 index 0000000..57aff04 --- /dev/null +++ b/app/pom.xml @@ -0,0 +1,57 @@ + + + + + + + + 4.0.0 + urlencoder + urlencoder + unspecified + UrlEncoder for Kotlin + https://github.com/ethauvin/urlencoder + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + gbevin + Geert Bevin + gbevin@uwyn.com + https://github.com/gbevin + + + ethauvin + Erik C. Thauvin + erik@thauvin.net + https://erik.thauvin.net/ + + + + scm:git://github.com/ethauvin/urlencoder.git + scm:git@github.com:ethauvin/urlencoder.git + https://github.com/ethauvin/urlencoder + + + GitHub + https://github.com/ethauvin/urlencoder/issues + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + 1.8.0 + compile + + + urlencoder + urlencoder-lib + unspecified + runtime + + + diff --git a/app/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt b/app/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt new file mode 100644 index 0000000..902bc45 --- /dev/null +++ b/app/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com) + * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.thauvin.erik.urlencoder + +import kotlin.system.exitProcess + +/** + * Most defensive approach to URL encoding and decoding. + * + * - Rules determined by combining the unreserved character set from + * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#page-13) with the percent-encode set from + * [application/x-www-form-urlencoded](https://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set). + * + * - Both specs above support percent decoding of two hexadecimal digits to a binary octet, however their unreserved + * set of characters differs and `application/x-www-form-urlencoded` adds conversion of space to `+`, which has the + * potential to be misunderstood. + * + * - This library encodes with rules that will be decoded correctly in either case. + * + * @author Geert Bevin (gbevin(remove) at uwyn dot com) + * @author Erik C. Thauvin (erik@thauvin.net) + **/ +object UrlEncoder { + + internal val usage = + "Usage : java -jar urlencoder-*all.jar [-ed] text" + System.lineSeparator() + + "Encode and decode URL components defensively." + System.lineSeparator() + + " -e encode (default) " + System.lineSeparator() + + " -d decode" + + /** + * Encodes and decodes URLs from the command line. + * + * - `java -jar urlencoder-*all.jar [-ed] text` + */ + @JvmStatic + fun main(args: Array) { + try { + val result = processMain(args) + if (result.status == 1) { + System.err.println(result.output) + } else { + println(result.output) + } + exitProcess(result.status) + } catch (e: IllegalArgumentException) { + System.err.println("${UrlEncoder::class.java.simpleName}: ${e.message}") + exitProcess(1) + } + } + + internal data class MainResult(var output: String = usage, var status: Int = 1) + + internal fun processMain(args: Array): MainResult { + val result = MainResult() + if (args.isNotEmpty() && args[0].isNotEmpty()) { + val hasDecode = (args[0] == "-d") + val hasOption = (hasDecode || args[0] == "-e") + if (hasOption && args.size == 2 || !hasOption && args.size == 1) { + val arg = if (hasOption) args[1] else args[0] + if (hasDecode) { + result.output = decode(arg) + } else { + result.output = UrlEncoderUtil.encode(arg) + } + result.status = 0 + } + } + return result + } + + /** + * Transforms a provided [String] into a new string, containing decoded URL characters in the UTF-8 + * encoding. + */ + @JvmStatic + @JvmOverloads + fun decode(source: String, plusToSpace: Boolean = false): String = + // delegate to UrlEncoderFunctions for backwards compatibility + UrlEncoderUtil.decode(source, plusToSpace) + + /** + * Transforms a provided [String] object into a new string, containing only valid URL characters in the UTF-8 + * encoding. + * + * - Letters, numbers, unreserved (`_-!.'()*`) and allowed characters are left intact. + */ + @JvmStatic + @JvmOverloads + fun encode(source: String, allow: String = "", spaceToPlus: Boolean = false): String = + UrlEncoderUtil.encode(source, allow, spaceToPlus) +} diff --git a/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt b/app/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt similarity index 64% rename from lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt rename to app/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt index 8439010..08e78b7 100644 --- a/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt +++ b/app/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt @@ -17,14 +17,10 @@ package net.thauvin.erik.urlencoder -import net.thauvin.erik.urlencoder.UrlEncoder.decode -import net.thauvin.erik.urlencoder.UrlEncoder.encode import net.thauvin.erik.urlencoder.UrlEncoder.processMain import net.thauvin.erik.urlencoder.UrlEncoder.usage import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertSame import org.junit.jupiter.api.Assertions.assertThrows -import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments @@ -34,7 +30,6 @@ import org.junit.jupiter.params.provider.ValueSource import java.util.stream.Stream class UrlEncoderTest { - private val same = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVQXYZ0123456789-_." companion object { @JvmStatic @@ -54,67 +49,6 @@ class UrlEncoderTest { ) } - @ParameterizedTest(name = "decode({0}) should be {1}") - @MethodSource("validMap") - fun `Decode URL`(expected: String, source: String) { - assertEquals(expected, decode(source)) - } - - @ParameterizedTest(name = "decode({0})") - @MethodSource("invalid") - fun `Decode with Exception`(source: String) { - assertThrows(IllegalArgumentException::class.java, { decode(source) }, "decode($source)") - } - - @Test - fun `Decode when None needed`() { - assertSame(same, decode(same)) - assertEquals("", decode(""), "decode('')") - assertEquals(" ", decode(" "), "decode(' ')") - } - - @Test - fun `Decode with Plus to Space`() { - assertEquals("foo bar", decode("foo+bar", true)) - assertEquals("foo bar foo", decode("foo+bar++foo", true)) - assertEquals("foo bar foo", decode("foo+%20bar%20+foo", true)) - assertEquals("foo + bar", decode("foo+%2B+bar", plusToSpace = true)) - assertEquals("foo+bar", decode("foo%2Bbar", plusToSpace = true)) - } - - @ParameterizedTest(name = "encode({0}) should be {1}") - @MethodSource("validMap") - fun `Encode URL`(source: String, expected: String) { - assertEquals(expected, encode(source)) - } - - @Test - fun `Encode Empty or Blank`() { - assertTrue(encode("", allow = "").isEmpty(), "encode('','')") - assertEquals("", encode(""), "encode('')") - assertEquals("%20", encode(" "), "encode(' ')") - } - - @Test - fun `Encode when None needed`() { - assertSame(same, encode(same)) - assertSame(same, encode(same, allow = ""), "with empty allow") - } - - @Test - fun `Encode with Allow Arg`() { - assertEquals("?test=a%20test", encode("?test=a test", allow = "=?"), "encode(x, =?)") - assertEquals("aaa", encode("aaa", "a"), "encode(aaa, a)") - assertEquals(" ", encode(" ", " "), "encode(' ', ' ')") - } - - @Test - fun `Encode with Space to Plus`() { - assertEquals("foo+bar", encode("foo bar", spaceToPlus = true)) - assertEquals("foo+bar++foo", encode("foo bar foo", spaceToPlus = true)) - assertEquals("foo bar", encode("foo bar", " ", true)) - } - @ParameterizedTest(name = "processMain(-d {1}) should be {0}") @MethodSource("validMap") fun `Main Decode`(expected: String, source: String) { diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index eb0edae..7089640 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -1,17 +1,20 @@ +import buildsrc.utils.Rife2TestListener +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { buildsrc.conventions.lang.`kotlin-jvm` buildsrc.conventions.publishing - id("application") id("com.github.ben-manes.versions") } description = "A simple defensive library to encode/decode URL components" val deployDir = project.layout.projectDirectory.dir("deploy") -val urlEncoderMainClass = "net.thauvin.erik.urlencoder.UrlEncoder" +val mainClassName = "net.thauvin.erik.urlencoder.UrlEncoder" dependencies { // testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") @@ -22,37 +25,37 @@ base { archivesName.set(rootProject.name) } -application { - mainClass.set(urlEncoderMainClass) +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + withSourcesJar() } tasks { - jar { - manifest { - attributes["Main-Class"] = urlEncoderMainClass + withType().configureEach { + kotlinOptions.jvmTarget = java.targetCompatibility.toString() + } + + test { + addTestListener(Rife2TestListener(project.properties["testsBadgeApiKey"]?.toString())) + } + + withType().configureEach { + useJUnitPlatform() + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) } } - val fatJar by registering(Jar::class) { - group = LifecycleBasePlugin.BUILD_GROUP - dependsOn.addAll(listOf("compileJava", "compileKotlin", "processResources")) - archiveClassifier.set("all") - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - manifest { attributes(mapOf("Main-Class" to application.mainClass)) } - val sourcesMain = sourceSets.main.get() - val contents = configurations.runtimeClasspath.get() - .map { if (it.isDirectory) it else zipTree(it) } + sourcesMain.output - from(contents) - } - - build { - dependsOn(fatJar) - } - withType().configureEach { destination = file("$projectDir/pom.xml") } + clean { + delete(deployDir) + } + withType().configureEach { dokkaSourceSets { named("main") { @@ -77,8 +80,8 @@ tasks { dependsOn(build, copyToDeploy) } - clean { - delete(deployDir) + "sonar" { + dependsOn(koverReport) } } @@ -86,7 +89,7 @@ publishing { publications { create("mavenJava") { from(components["java"]) - artifactId = rootProject.name + artifactId = "${rootProject.name}-lib" artifact(tasks.javadocJar) } } diff --git a/lib/detekt-baseline.xml b/lib/detekt-baseline.xml index 700d787..2562c74 100644 --- a/lib/detekt-baseline.xml +++ b/lib/detekt-baseline.xml @@ -2,14 +2,13 @@ - ComplexCondition:UrlEncoder.kt$UrlEncoder$hasOption && args.size == 2 || !hasOption && args.size == 1 - MagicNumber:UrlEncoder.kt$UrlEncoder$0x80 - MagicNumber:UrlEncoder.kt$UrlEncoder$0xFF - MagicNumber:UrlEncoder.kt$UrlEncoder$16 - MagicNumber:UrlEncoder.kt$UrlEncoder$3 - MagicNumber:UrlEncoder.kt$UrlEncoder$4 - MaxLineLength:UrlEncoder.kt$UrlEncoder$* - NestedBlockDepth:UrlEncoder.kt$UrlEncoder$@JvmStatic @JvmOverloads fun decode(source: String, plusToSpace: Boolean = false): String - NestedBlockDepth:UrlEncoder.kt$UrlEncoder$@JvmStatic @JvmOverloads fun encode(source: String, allow: String = "", spaceToPlus: Boolean = false): String + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$0x80 + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$0xFF + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$16 + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$3 + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$4 + MaxLineLength:UrlEncoderUtil.kt$UrlEncoderUtil$* + NestedBlockDepth:UrlEncoderUtil.kt$UrlEncoderUtil$@JvmStatic @JvmOverloads fun decode(source: String, plusToSpace: Boolean = false): String + NestedBlockDepth:UrlEncoderUtil.kt$UrlEncoderUtil$@JvmStatic @JvmOverloads fun encode(source: String, allow: String = "", spaceToPlus: Boolean = false): String diff --git a/lib/pom.xml b/lib/pom.xml index 8af3e06..9e38022 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -6,9 +6,9 @@ 4.0.0 - net.thauvin.erik - urlencoder - 1.3.1-SNAPSHOT + urlencoder + urlencoder-lib + unspecified UrlEncoder for Kotlin A simple defensive library to encode/decode URL components https://github.com/ethauvin/urlencoder diff --git a/lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt b/lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtil.kt similarity index 80% rename from lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt rename to lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtil.kt index 8636b28..0bea939 100644 --- a/lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt +++ b/lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtil.kt @@ -19,7 +19,6 @@ package net.thauvin.erik.urlencoder import java.nio.charset.StandardCharsets import java.util.BitSet -import kotlin.system.exitProcess /** * Most defensive approach to URL encoding and decoding. @@ -37,13 +36,8 @@ import kotlin.system.exitProcess * @author Geert Bevin (gbevin(remove) at uwyn dot com) * @author Erik C. Thauvin (erik@thauvin.net) **/ -object UrlEncoder { +object UrlEncoderUtil { private val hexDigits = "0123456789ABCDEF".toCharArray() - internal val usage = - "Usage : java -jar urlencoder-*all.jar [-ed] text" + System.lineSeparator() + - "Encode and decode URL components defensively." + System.lineSeparator() + - " -e encode (default) " + System.lineSeparator() + - " -d decode" // see https://www.rfc-editor.org/rfc/rfc3986#page-13 // and https://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set @@ -196,45 +190,4 @@ object UrlEncoder { return out?.toString() ?: source } - - /** - * Encodes and decodes URLs from the command line. - * - * - `java -jar urlencoder-*all.jar [-ed] text` - */ - @JvmStatic - fun main(args: Array) { - try { - val result = processMain(args) - if (result.status == 1) { - System.err.println(result.output) - } else { - println(result.output) - } - exitProcess(result.status) - } catch (e: IllegalArgumentException) { - System.err.println("${UrlEncoder::class.java.simpleName}: ${e.message}") - exitProcess(1) - } - } - - internal data class MainResult(var output: String = usage, var status: Int = 1) - - internal fun processMain(args: Array): MainResult { - val result = MainResult() - if (args.isNotEmpty() && args[0].isNotEmpty()) { - val hasDecode = (args[0] == "-d") - val hasOption = (hasDecode || args[0] == "-e") - if (hasOption && args.size == 2 || !hasOption && args.size == 1) { - val arg = if (hasOption) args[1] else args[0] - if (hasDecode) { - result.output = decode(arg) - } else { - result.output = encode(arg) - } - result.status = 0 - } - } - return result - } } diff --git a/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtilTest.kt b/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtilTest.kt new file mode 100644 index 0000000..20d8e4d --- /dev/null +++ b/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtilTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com) + * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.thauvin.erik.urlencoder + +import net.thauvin.erik.urlencoder.UrlEncoderUtil.decode +import net.thauvin.erik.urlencoder.UrlEncoderUtil.encode +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertSame +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +class UrlEncoderUtilTest { + private val same = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVQXYZ0123456789-_." + + companion object { + @JvmStatic + fun invalid() = arrayOf("sdkjfh%", "sdkjfh%6", "sdkjfh%xx", "sdfjfh%-1") + + @JvmStatic + fun validMap(): Stream = Stream.of( + arguments("a test &", "a%20test%20%26"), + arguments( + "!abcdefghijklmnopqrstuvwxyz%%ABCDEFGHIJKLMNOPQRSTUVQXYZ0123456789-_.~=", + "%21abcdefghijklmnopqrstuvwxyz%25%25ABCDEFGHIJKLMNOPQRSTUVQXYZ0123456789-_.%7E%3D" + ), + arguments("%#okékÉȢ smile!😁", "%25%23ok%C3%A9k%C3%89%C8%A2%20smile%21%F0%9F%98%81"), + arguments( + "\uD808\uDC00\uD809\uDD00\uD808\uDF00\uD808\uDD00", "%F0%92%80%80%F0%92%94%80%F0%92%8C%80%F0%92%84%80" + ) + ) + } + + @ParameterizedTest(name = "decode({0}) should be {1}") + @MethodSource("validMap") + fun `Decode URL`(expected: String, source: String) { + assertEquals(expected, decode(source)) + } + + @ParameterizedTest(name = "decode({0})") + @MethodSource("invalid") + fun `Decode with Exception`(source: String) { + assertThrows(IllegalArgumentException::class.java, { decode(source) }, "decode($source)") + } + + @Test + fun `Decode when None needed`() { + assertSame(same, decode(same)) + assertEquals("", decode(""), "decode('')") + assertEquals(" ", decode(" "), "decode(' ')") + } + + @Test + fun `Decode with Plus to Space`() { + assertEquals("foo bar", decode("foo+bar", true)) + assertEquals("foo bar foo", decode("foo+bar++foo", true)) + assertEquals("foo bar foo", decode("foo+%20bar%20+foo", true)) + assertEquals("foo + bar", decode("foo+%2B+bar", plusToSpace = true)) + assertEquals("foo+bar", decode("foo%2Bbar", plusToSpace = true)) + } + + @ParameterizedTest(name = "encode({0}) should be {1}") + @MethodSource("validMap") + fun `Encode URL`(source: String, expected: String) { + assertEquals(expected, encode(source)) + } + + @Test + fun `Encode Empty or Blank`() { + assertTrue(encode("", allow = "").isEmpty(), "encode('','')") + assertEquals("", encode(""), "encode('')") + assertEquals("%20", encode(" "), "encode(' ')") + } + + @Test + fun `Encode when None needed`() { + assertSame(same, encode(same)) + assertSame(same, encode(same, allow = ""), "with empty allow") + } + + @Test + fun `Encode with Allow Arg`() { + assertEquals("?test=a%20test", encode("?test=a test", allow = "=?"), "encode(x, =?)") + assertEquals("aaa", encode("aaa", "a"), "encode(aaa, a)") + assertEquals(" ", encode(" ", " "), "encode(' ', ' ')") + } + + @Test + fun `Encode with Space to Plus`() { + assertEquals("foo+bar", encode("foo bar", spaceToPlus = true)) + assertEquals("foo+bar++foo", encode("foo bar foo", spaceToPlus = true)) + assertEquals("foo bar", encode("foo bar", " ", true)) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e75db1d..86af703 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,9 @@ dependencyResolutionManagement { } } +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + include( + ":app", ":lib", )