From f74e20b7f71a8453052fbb30bd77919ce672b664 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 26 Jun 2026 21:03:13 +0200 Subject: [PATCH] Fix bug in `JtxContract.getXPropertyListFromJson()` Also add tests for functions to convert to/from JSON. --- app/build.gradle.kts | 1 + .../at/techbee/jtx/contract/JtxContract.kt | 35 +-- .../techbee/jtx/contract/JtxContractTest.kt | 254 ++++++++++++++++++ gradle/libs.versions.toml | 2 + 4 files changed, 276 insertions(+), 16 deletions(-) create mode 100644 app/src/test/java/at/techbee/jtx/contract/JtxContractTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 784c9a7b7..d73009249 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -250,6 +250,7 @@ dependencies { // Testing testImplementation(libs.junit) + testImplementation(libs.robolectric) testImplementation(libs.room.testing) testImplementation(libs.androidx.arch.core.testing) testImplementation(libs.androidx.test.core) diff --git a/app/src/main/java/at/techbee/jtx/contract/JtxContract.kt b/app/src/main/java/at/techbee/jtx/contract/JtxContract.kt index 8cea80703..90c668fc2 100644 --- a/app/src/main/java/at/techbee/jtx/contract/JtxContract.kt +++ b/app/src/main/java/at/techbee/jtx/contract/JtxContract.kt @@ -97,29 +97,32 @@ object JtxContract { * This function takes a string and tries to parse it to a list of XProperty. * This is the counterpart of getJsonStringFromXProperties(...) * @param [string] that should be parsed - * @return The list of XProperty parsed from the string + * @return A PropertyList containing the XProperty instances parsed from the string */ fun getXPropertyListFromJson(string: String): PropertyList { - val propertyList = PropertyList() - - if (string.isBlank()) - return propertyList - - try { - val jsonObject = JSONObject(string) - for (i in 0 until jsonObject.length()) { - val names = jsonObject.names() ?: break - val propertyName = names[i]?.toString() ?: break - val propertyValue = jsonObject.getString(propertyName).toString() - if (propertyName.isNotBlank() && propertyValue.isNotBlank()) { - val prop = XProperty(propertyName, propertyValue) - propertyList.add(prop) + if (string.isBlank()) { + return PropertyList() + } + + return try { + val properties = buildList { + val jsonObject = JSONObject(string) + for (i in 0 until jsonObject.length()) { + val names = jsonObject.names() ?: break + val propertyName = names[i]?.toString() ?: break + val propertyValue = jsonObject.getString(propertyName).toString() + if (propertyName.isNotBlank() && propertyValue.isNotBlank()) { + val prop = XProperty(propertyName, propertyValue) + add(prop) + } } } + + PropertyList(properties) } catch (e: NullPointerException) { logger.log(Level.WARNING, "Error parsing x-property-list $string", e) + PropertyList() } - return propertyList } diff --git a/app/src/test/java/at/techbee/jtx/contract/JtxContractTest.kt b/app/src/test/java/at/techbee/jtx/contract/JtxContractTest.kt new file mode 100644 index 000000000..c998d7746 --- /dev/null +++ b/app/src/test/java/at/techbee/jtx/contract/JtxContractTest.kt @@ -0,0 +1,254 @@ +package at.techbee.jtx.contract + +import net.fortuna.ical4j.model.Parameter +import net.fortuna.ical4j.model.ParameterList +import net.fortuna.ical4j.model.Property +import net.fortuna.ical4j.model.PropertyList +import net.fortuna.ical4j.model.parameter.XParameter +import net.fortuna.ical4j.model.property.XProperty +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +// Can be removed once Robolectric supports SDK 37 +@Config(sdk = [36]) +class JtxContractTest { + + // region getXPropertyListFromJson() tests + + @Test + fun `getXPropertyListFromJson with single property`() { + val input = """{"X-PROPERTY":"value"}""" + + val result = JtxContract.getXPropertyListFromJson(input) + + assertEquals(propertyListOf(XProperty("X-PROPERTY", "value")), result) + } + + @Test + fun `getXPropertyListFromJson with multiple property`() { + val input = """{"X-PROPERTY-1":"value1","X-PROPERTY-2":"value2","X-PROPERTY-3":"value3"}""" + + val result = JtxContract.getXPropertyListFromJson(input) + + assertEquals( + propertyListOf( + XProperty("X-PROPERTY-1", "value1"), + XProperty("X-PROPERTY-2", "value2"), + XProperty("X-PROPERTY-3", "value3"), + ), + result + ) + } + + @Test + fun `getXPropertyListFromJson with blank property name`() { + val input = """{" ":"value"}""" + + val result = JtxContract.getXPropertyListFromJson(input) + + assertEquals(PropertyList(), result) + } + + @Test + fun `getXPropertyListFromJson with blank property value`() { + val input = """{"X-PROPERTY":" "}""" + + val result = JtxContract.getXPropertyListFromJson(input) + + assertEquals(PropertyList(), result) + } + + @Test + fun `getXPropertyListFromJson without property`() { + val input = "{}" + + val result = JtxContract.getXPropertyListFromJson(input) + + assertEquals(PropertyList(), result) + } + + @Test + fun `getXPropertyListFromJson with empty string`() { + val input = "" + + val result = JtxContract.getXPropertyListFromJson(input) + + assertEquals(PropertyList(), result) + } + + @Test + fun `getXPropertyListFromJson with blank string`() { + val input = " " + + val result = JtxContract.getXPropertyListFromJson(input) + + assertEquals(PropertyList(), result) + } + + // endregion + + // region getJsonStringFromXProperties() tests + + @Test + fun `getJsonStringFromXProperties with single property`() { + val input = propertyListOf(XProperty("X-PROPERTY", "value")) + + val result = JtxContract.getJsonStringFromXProperties(input) + + assertEquals("""{"X-PROPERTY":"value"}""", result) + } + + @Test + fun `getJsonStringFromXProperties with multiple properties`() { + val input = propertyListOf( + XProperty("X-PROPERTY-1", "value1"), + XProperty("X-PROPERTY-2", "value2"), + XProperty("X-PROPERTY-3", "value3"), + ) + + val result = JtxContract.getJsonStringFromXProperties(input) + + assertEquals( + """{"X-PROPERTY-1":"value1","X-PROPERTY-2":"value2","X-PROPERTY-3":"value3"}""", + result + ) + } + + @Test + fun `getJsonStringFromXProperties with empty PropertyList`() { + val input = PropertyList() + + val result = JtxContract.getJsonStringFromXProperties(input) + + assertNull(result) + } + + @Test + fun `getJsonStringFromXProperties with null argument`() { + val input = null + + val result = JtxContract.getJsonStringFromXProperties(input) + + assertNull(result) + } + + // endregion + + // region getXParametersFromJson() tests + + @Test + fun `getXParametersFromJson with single parameter`() { + val input = """{"X-PARAMETER":"value"}""" + + val result = JtxContract.getXParametersFromJson(input) + + assertEquals(listOf(XParameter("X-PARAMETER", "value")), result) + } + + @Test + fun `getXParametersFromJson with multiple parameters`() { + val input = """ + { + "X-PARAMETER-1": "value1", + "X-PARAMETER-2": "value2", + "X-PARAMETER-3": "value3" + } + """.trimIndent() + + val result = JtxContract.getXParametersFromJson(input) + + assertEquals( + listOf( + XParameter("X-PARAMETER-1", "value1"), + XParameter("X-PARAMETER-2", "value2"), + XParameter("X-PARAMETER-3", "value3"), + ), + result + ) + } + + @Test + fun `getXParametersFromJson with blank property name`() { + val input = """{" ":"value"}""" + + val result = JtxContract.getXParametersFromJson(input) + + assertEquals(emptyList(), result) + } + + @Test + fun `getXParametersFromJson with blank property value`() { + val input = """{"X-PARAMETER":" "}""" + + val result = JtxContract.getXParametersFromJson(input) + + assertEquals(emptyList(), result) + } + + @Test + fun `getXParametersFromJson without property`() { + val input = "{}" + + val result = JtxContract.getXParametersFromJson(input) + + assertEquals(emptyList(), result) + } + + // endregion + + // region getJsonStringFromXParameters() tests + + @Test + fun `getJsonStringFromXParameters with single property`() { + val input = parameterListOf(XParameter("X-PARAMETER", "value")) + + val result = JtxContract.getJsonStringFromXParameters(input) + + assertEquals("""{"X-PARAMETER":"value"}""", result) + } + + @Test + fun `getJsonStringFromXParameters with multiple properties`() { + val input = parameterListOf( + XParameter("X-PARAMETER-1", "value1"), + XParameter("X-PARAMETER-2", "value2"), + XParameter("X-PARAMETER-3", "value3"), + ) + + val result = JtxContract.getJsonStringFromXParameters(input) + + assertEquals( + """{"X-PARAMETER-1":"value1","X-PARAMETER-2":"value2","X-PARAMETER-3":"value3"}""", + result + ) + } + + @Test + fun `getJsonStringFromXParameters with empty ParameterList`() { + val input = ParameterList() + + val result = JtxContract.getJsonStringFromXParameters(input) + + assertNull(result) + } + + @Test + fun `getJsonStringFromXParameters with null argument`() { + val input = null + + val result = JtxContract.getJsonStringFromXParameters(input) + + assertNull(result) + } + + // endregion +} + +private fun propertyListOf(vararg properties: Property) = PropertyList(properties.toList()) + +private fun parameterListOf(vararg parameters: Parameter) = ParameterList(parameters.toList()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2a2dfbd18..3ce1cc3d4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,6 +43,7 @@ profileinstaller = "1.4.1" reorderable = "3.1.0" synctools = "b5d43f5712" uiTextGoogleFonts = "1.11.3" +robolectric = "4.16.1" room = "2.8.4" volley = "1.2.1" ktor = "3.5.0" @@ -107,6 +108,7 @@ osmdroid-android = { module = "org.osmdroid:osmdroid-android", version.ref = "os play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } play-services-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "playServicesMaps" } reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" } +robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } room-base = { module = "androidx.room:room-ktx", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }