diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/Event.kt b/lib/src/main/kotlin/at/bitfire/ical4android/Event.kt index 673cc46e..3517c0c3 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/Event.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/Event.kt @@ -243,7 +243,7 @@ data class Event( get() = Logger.getLogger(Event::class.java.name) /** - * Parses an iCalendar resource, applies [at.bitfire.ical4android.validation.ICalPreprocessor] + * Parses an iCalendar resource, applies [at.bitfire.synctools.icalendar.validation.ICalPreprocessor] * and [EventValidator] to increase compatibility and extracts the VEVENTs. * * @param reader where the iCalendar is read from diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt b/lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt index d33c6f5e..a4c8609f 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt @@ -7,10 +7,10 @@ package at.bitfire.ical4android import at.bitfire.ical4android.ICalendar.Companion.CALENDAR_NAME -import at.bitfire.ical4android.validation.ICalPreprocessor import at.bitfire.synctools.BuildConfig import at.bitfire.synctools.exception.InvalidRemoteResourceException import at.bitfire.synctools.icalendar.ICalendarParser +import at.bitfire.synctools.icalendar.validation.ICalPreprocessor import net.fortuna.ical4j.data.CalendarBuilder import net.fortuna.ical4j.data.ParserException import net.fortuna.ical4j.model.Calendar diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/Task.kt b/lib/src/main/kotlin/at/bitfire/ical4android/Task.kt index dbff29d8..c27ad76b 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/Task.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/Task.kt @@ -100,7 +100,7 @@ data class Task( get() = Logger.getLogger(Task::class.java.name) /** - * Parses an iCalendar resource, applies [at.bitfire.ical4android.validation.ICalPreprocessor] to increase compatibility + * Parses an iCalendar resource, applies [at.bitfire.synctools.icalendar.validation.ICalPreprocessor] to increase compatibility * and extracts the VTODOs. * * @param reader where the iCalendar is taken from diff --git a/lib/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarParser.kt b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarParser.kt index 1e5a378f..72f24f79 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarParser.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarParser.kt @@ -6,8 +6,8 @@ package at.bitfire.synctools.icalendar -import at.bitfire.ical4android.validation.ICalPreprocessor import at.bitfire.synctools.exception.InvalidRemoteResourceException +import at.bitfire.synctools.icalendar.validation.ICalPreprocessor import net.fortuna.ical4j.data.CalendarBuilder import net.fortuna.ical4j.data.CalendarParserFactory import net.fortuna.ical4j.data.ContentHandlerContext @@ -20,8 +20,12 @@ import java.util.logging.Logger /** * Custom iCalendar parser that applies error correction using [ICalPreprocessor]. + * + * @param preprocessor pre-processor to use */ -class ICalendarParser { +class ICalendarParser( + private val preprocessor: ICalPreprocessor = ICalPreprocessor() +) { private val logger get() = Logger.getLogger(javaClass.name) @@ -39,7 +43,7 @@ class ICalendarParser { */ fun parse(reader: Reader): Calendar { // preprocess stream to work around problems that prevent parsing and thus can't be fixed later - val preprocessed = ICalPreprocessor.preprocessStream(reader) + val preprocessed = preprocessor.preprocessStream(reader) // parse stream, ignoring invalid properties (if possible) val calendar: Calendar @@ -57,7 +61,7 @@ class ICalendarParser { // Pre-process calendar for increased compatibility (fixes some common errors) try { - ICalPreprocessor.preprocessCalendar(calendar) + preprocessor.preprocessCalendar(calendar) } catch (e: Exception) { logger.log(Level.WARNING, "Couldn't pre-process iCalendar", e) } diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/validation/FixInvalidDayOffsetPreprocessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidDayOffsetPreprocessor.kt similarity index 93% rename from lib/src/main/kotlin/at/bitfire/ical4android/validation/FixInvalidDayOffsetPreprocessor.kt rename to lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidDayOffsetPreprocessor.kt index 268663bc..63cd9d00 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/validation/FixInvalidDayOffsetPreprocessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidDayOffsetPreprocessor.kt @@ -4,13 +4,13 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.ical4android.validation +package at.bitfire.synctools.icalendar.validation /** * Fixes durations with day offsets with the 'T' prefix. * See also https://github.com/bitfireAT/ical4android/issues/77 */ -object FixInvalidDayOffsetPreprocessor : StreamPreprocessor() { +class FixInvalidDayOffsetPreprocessor : StreamPreprocessor() { override fun regexpForProblem() = Regex( // Examples: diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/validation/FixInvalidUtcOffsetPreprocessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidUtcOffsetPreprocessor.kt similarity index 84% rename from lib/src/main/kotlin/at/bitfire/ical4android/validation/FixInvalidUtcOffsetPreprocessor.kt rename to lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidUtcOffsetPreprocessor.kt index 8acdb112..89698f09 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/validation/FixInvalidUtcOffsetPreprocessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidUtcOffsetPreprocessor.kt @@ -4,9 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.ical4android.validation +package at.bitfire.synctools.icalendar.validation -import at.bitfire.ical4android.validation.FixInvalidUtcOffsetPreprocessor.TZOFFSET_REGEXP import java.util.logging.Level import java.util.logging.Logger @@ -17,7 +16,7 @@ import java.util.logging.Logger * Rewrites values of all TZOFFSETFROM and TZOFFSETTO properties which match [TZOFFSET_REGEXP] * so that an hour value of 00 is inserted. */ -object FixInvalidUtcOffsetPreprocessor: StreamPreprocessor() { +class FixInvalidUtcOffsetPreprocessor: StreamPreprocessor() { private val logger get() = Logger.getLogger(javaClass.name) diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/validation/ICalPreprocessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/ICalPreprocessor.kt similarity index 89% rename from lib/src/main/kotlin/at/bitfire/ical4android/validation/ICalPreprocessor.kt rename to lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/ICalPreprocessor.kt index 0a306b76..8c6e17ab 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/validation/ICalPreprocessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/ICalPreprocessor.kt @@ -4,8 +4,9 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.ical4android.validation +package at.bitfire.synctools.icalendar.validation +import androidx.annotation.VisibleForTesting import net.fortuna.ical4j.model.Calendar import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.transform.rfc5545.CreatedPropertyRule @@ -24,7 +25,7 @@ import java.util.logging.Logger * (like "W. Europe Standard Time" to an Android-friendly name like "Europe/Vienna") * */ -object ICalPreprocessor { +class ICalPreprocessor { private val propertyRules = arrayOf( CreatedPropertyRule(), // make sure CREATED is UTC @@ -33,9 +34,10 @@ object ICalPreprocessor { DateListPropertyRule() // ... by the ical4j VTIMEZONE with the same TZID! ) - private val streamPreprocessors = arrayOf( - FixInvalidUtcOffsetPreprocessor, // fix things like TZOFFSET(FROM,TO):+5730 - FixInvalidDayOffsetPreprocessor // fix things like DURATION:PT2D + @VisibleForTesting + internal val streamPreprocessors = arrayOf( + FixInvalidUtcOffsetPreprocessor(), // fix things like TZOFFSET(FROM,TO):+5730 + FixInvalidDayOffsetPreprocessor() // fix things like DURATION:PT2D ) /** diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/validation/StreamPreprocessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/StreamPreprocessor.kt similarity index 97% rename from lib/src/main/kotlin/at/bitfire/ical4android/validation/StreamPreprocessor.kt rename to lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/StreamPreprocessor.kt index e11044d8..53e5cc87 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/validation/StreamPreprocessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/icalendar/validation/StreamPreprocessor.kt @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.ical4android.validation +package at.bitfire.synctools.icalendar.validation import java.io.IOException import java.io.Reader diff --git a/lib/src/test/kotlin/at/bitfire/synctools/icalendar/ICalendarParserTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/icalendar/ICalendarParserTest.kt index c4df7d84..6ff784a3 100644 --- a/lib/src/test/kotlin/at/bitfire/synctools/icalendar/ICalendarParserTest.kt +++ b/lib/src/test/kotlin/at/bitfire/synctools/icalendar/ICalendarParserTest.kt @@ -6,13 +6,18 @@ package at.bitfire.synctools.icalendar -import at.bitfire.ical4android.validation.ICalPreprocessor import at.bitfire.synctools.exception.InvalidRemoteResourceException +import at.bitfire.synctools.icalendar.validation.ICalPreprocessor +import io.mockk.every +import io.mockk.impl.annotations.InjectMockKs +import io.mockk.impl.annotations.RelaxedMockK import io.mockk.junit4.MockKRule -import io.mockk.mockkObject +import io.mockk.slot import io.mockk.verify +import org.junit.Before import org.junit.Rule import org.junit.Test +import java.io.Reader import java.io.StringReader class ICalendarParserTest { @@ -20,24 +25,35 @@ class ICalendarParserTest { @get:Rule val mockkRule = MockKRule(this) + @RelaxedMockK + lateinit var preprocessor: ICalPreprocessor + + @InjectMockKs + lateinit var parser: ICalendarParser + + @Before + fun setUp() { + val reader = slot() + every { preprocessor.preprocessStream(capture(reader)) } answers { reader.captured } + } + + @Test fun testParse_AppliesPreProcessing() { - mockkObject(ICalPreprocessor) - val reader = StringReader( "BEGIN:VCALENDAR\r\n" + "BEGIN:VEVENT\r\n" + "END:VEVENT\r\n" + "END:VCALENDAR\r\n" ) - val cal = ICalendarParser().parse(reader) + val cal = parser.parse(reader) verify(exactly = 1) { // verify preprocessing was applied to stream - ICalPreprocessor.preprocessStream(any()) + preprocessor.preprocessStream(any()) // verify preprocessing was applied to resulting calendar - ICalPreprocessor.preprocessCalendar(cal) + preprocessor.preprocessCalendar(cal) } } @@ -50,13 +66,14 @@ class ICalendarParserTest { "END:VEVENT\r\n" + "END:VCALENDAR\r\n" ) - ICalendarParser().parse(reader) + parser.parse(reader) + // no exception called } @Test(expected = InvalidRemoteResourceException::class) fun testParse_ThrowsExceptionOnInvalidInput() { val reader = StringReader("invalid") - ICalendarParser().parse(reader) + parser.parse(reader) } } \ No newline at end of file diff --git a/lib/src/test/kotlin/at/bitfire/ical4android/validation/FixInvalidDayOffsetPreprocessorTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidDayOffsetPreprocessorTest.kt similarity index 89% rename from lib/src/test/kotlin/at/bitfire/ical4android/validation/FixInvalidDayOffsetPreprocessorTest.kt rename to lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidDayOffsetPreprocessorTest.kt index 0293902a..893a0133 100644 --- a/lib/src/test/kotlin/at/bitfire/ical4android/validation/FixInvalidDayOffsetPreprocessorTest.kt +++ b/lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidDayOffsetPreprocessorTest.kt @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.ical4android.validation +package at.bitfire.synctools.icalendar.validation import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -13,9 +13,11 @@ import org.junit.Test import java.time.Duration class FixInvalidDayOffsetPreprocessorTest { + + private val processor = FixInvalidDayOffsetPreprocessor() /** - * Calls [FixInvalidDayOffsetPreprocessor.fixString] and asserts the result is equal to [expected]. + * Calls `processor.fixString` and asserts the result is equal to [expected]. * * @param expected The expected result * @param testValue The value to test @@ -23,7 +25,7 @@ class FixInvalidDayOffsetPreprocessorTest { */ private fun assertFixedEquals(expected: String, testValue: String, parseDuration: Boolean = true) { // Fix the duration string - val fixed = FixInvalidDayOffsetPreprocessor.fixString(testValue) + val fixed = processor.fixString(testValue) // Test the duration can now be parsed if (parseDuration) @@ -40,7 +42,7 @@ class FixInvalidDayOffsetPreprocessorTest { fun test_FixString_NoOccurrence() { assertEquals( "Some String", - FixInvalidDayOffsetPreprocessor.fixString("Some String"), + processor.fixString("Some String"), ) } @@ -99,14 +101,14 @@ class FixInvalidDayOffsetPreprocessorTest { @Test fun test_RegexpForProblem_DayOffsetTo_Invalid() { - val regex = FixInvalidDayOffsetPreprocessor.regexpForProblem() + val regex = processor.regexpForProblem() assertTrue(regex.matches("DURATION:PT2D")) assertTrue(regex.matches("TRIGGER:PT1D")) } @Test fun test_RegexpForProblem_DayOffsetTo_Valid() { - val regex = FixInvalidDayOffsetPreprocessor.regexpForProblem() + val regex = processor.regexpForProblem() assertFalse(regex.matches("DURATION:-PT12H")) assertFalse(regex.matches("TRIGGER:-PT15M")) } diff --git a/lib/src/test/kotlin/at/bitfire/ical4android/validation/FixInvalidUtcOffsetPreprocessorTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidUtcOffsetPreprocessorTest.kt similarity index 67% rename from lib/src/test/kotlin/at/bitfire/ical4android/validation/FixInvalidUtcOffsetPreprocessorTest.kt rename to lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidUtcOffsetPreprocessorTest.kt index f280b479..9b86c27d 100644 --- a/lib/src/test/kotlin/at/bitfire/ical4android/validation/FixInvalidUtcOffsetPreprocessorTest.kt +++ b/lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/FixInvalidUtcOffsetPreprocessorTest.kt @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.ical4android.validation +package at.bitfire.synctools.icalendar.validation import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -12,48 +12,50 @@ import org.junit.Assert.assertTrue import org.junit.Test class FixInvalidUtcOffsetPreprocessorTest { + + private val processor = FixInvalidUtcOffsetPreprocessor() @Test fun test_FixString_NoOccurrence() { assertEquals( "Some String", - FixInvalidUtcOffsetPreprocessor.fixString("Some String")) + processor.fixString("Some String")) } @Test fun test_FixString_TzOffsetFrom_Invalid() { assertEquals("TZOFFSETFROM:+005730", - FixInvalidUtcOffsetPreprocessor.fixString("TZOFFSETFROM:+5730")) + processor.fixString("TZOFFSETFROM:+5730")) } @Test fun test_FixString_TzOffsetFrom_Valid() { assertEquals("TZOFFSETFROM:+005730", - FixInvalidUtcOffsetPreprocessor.fixString("TZOFFSETFROM:+005730")) + processor.fixString("TZOFFSETFROM:+005730")) } @Test fun test_FixString_TzOffsetTo_Invalid() { assertEquals("TZOFFSETTO:+005730", - FixInvalidUtcOffsetPreprocessor.fixString("TZOFFSETTO:+5730")) + processor.fixString("TZOFFSETTO:+5730")) } @Test fun test_FixString_TzOffsetTo_Valid() { assertEquals("TZOFFSETTO:+005730", - FixInvalidUtcOffsetPreprocessor.fixString("TZOFFSETTO:+005730")) + processor.fixString("TZOFFSETTO:+005730")) } @Test fun test_RegexpForProblem_TzOffsetTo_Invalid() { - val regex = FixInvalidUtcOffsetPreprocessor.regexpForProblem() + val regex = processor.regexpForProblem() assertTrue(regex.matches("TZOFFSETTO:+5730")) } @Test fun test_RegexpForProblem_TzOffsetTo_Valid() { - val regex = FixInvalidUtcOffsetPreprocessor.regexpForProblem() + val regex = processor.regexpForProblem() assertFalse(regex.matches("TZOFFSETTO:+005730")) } diff --git a/lib/src/androidTest/kotlin/at/bitfire/ical4android/ICalPreprocessorTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/ICalPreprocessorTest.kt similarity index 53% rename from lib/src/androidTest/kotlin/at/bitfire/ical4android/ICalPreprocessorTest.kt rename to lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/ICalPreprocessorTest.kt index 71a435e5..dab87525 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/ical4android/ICalPreprocessorTest.kt +++ b/lib/src/test/kotlin/at/bitfire/synctools/icalendar/validation/ICalPreprocessorTest.kt @@ -4,35 +4,43 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.ical4android +package at.bitfire.synctools.icalendar.validation -import androidx.test.filters.SdkSuppress -import at.bitfire.ical4android.validation.FixInvalidDayOffsetPreprocessor -import at.bitfire.ical4android.validation.FixInvalidUtcOffsetPreprocessor -import at.bitfire.ical4android.validation.ICalPreprocessor +import io.mockk.junit4.MockKRule import io.mockk.mockkObject import io.mockk.verify import net.fortuna.ical4j.data.CalendarBuilder import net.fortuna.ical4j.model.Component import net.fortuna.ical4j.model.component.VEvent import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test import java.io.InputStreamReader import java.io.StringReader class ICalPreprocessorTest { + @get:Rule + val mockkRule = MockKRule(this) + + val processor = ICalPreprocessor() + + @Test - @SdkSuppress(minSdkVersion = 28) fun testPreprocessStream_appliesStreamProcessors() { - // Can only run on API Level 28 or newer because mockkObject doesn't support Android < P - mockkObject(FixInvalidDayOffsetPreprocessor, FixInvalidUtcOffsetPreprocessor) { - ICalPreprocessor.preprocessStream(StringReader("")) - - // verify that the required stream processors have been called - verify { - FixInvalidDayOffsetPreprocessor.preprocess(any()) - FixInvalidUtcOffsetPreprocessor.preprocess(any()) + val preprocessors = processor.streamPreprocessors + assertTrue(preprocessors.isNotEmpty()) + processor.streamPreprocessors.forEach { + mockkObject(it) + } + + processor.preprocessStream(StringReader("")) + + // verify that the required stream processors have been called + verify { + processor.streamPreprocessors.forEach { + it.preprocess(any()) } } } @@ -40,13 +48,13 @@ class ICalPreprocessorTest { @Test fun testPreprocessCalendar_MsTimeZones() { - javaClass.classLoader!!.getResourceAsStream("events/outlook1.ics").use { stream -> + javaClass.getResourceAsStream("/events/outlook1.ics").use { stream -> val reader = InputStreamReader(stream, Charsets.UTF_8) val calendar = CalendarBuilder().build(reader) val vEvent = calendar.getComponent(Component.VEVENT) as VEvent assertEquals("W. Europe Standard Time", vEvent.startDate.timeZone.id) - ICalPreprocessor.preprocessCalendar(calendar) + processor.preprocessCalendar(calendar) assertEquals("Europe/Vienna", vEvent.startDate.timeZone.id) } } diff --git a/lib/src/test/resources/events b/lib/src/test/resources/events new file mode 120000 index 00000000..48d7a15a --- /dev/null +++ b/lib/src/test/resources/events @@ -0,0 +1 @@ +../../androidTest/resources/events \ No newline at end of file