android-buschauffeurswekker/app/src/test/java/nl/eddydevink/buschauffeurswekker/caldav/IcalTest.kt

138 lines
No EOL
4.9 KiB
Kotlin

package nl.eddydevink.buschauffeurswekker.caldav
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class IcalTest {
private val amsterdam = ZoneId.of("Europe/Amsterdam")
@Test
fun `extractCalendarBlocks haalt blokken met elk namespace-prefix`() {
val xml = """
<d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
<d:response>
<d:href>/remote.php/dav/calendars/eddy/ebs_diensten-1/abc.ics</d:href>
<d:propstat><d:prop>
<c:calendar-data>BEGIN:VCALENDAR${'\n'}X:een</c:calendar-data>
</d:prop></d:propstat>
</d:response>
<d:response>
<d:propstat><d:prop>
<calendar-data>BEGIN:VCALENDAR${'\n'}X:twee</calendar-data>
</d:prop></d:propstat>
</d:response>
<d:response>
<d:propstat><d:prop>
<cal:calendar-data>BEGIN:VCALENDAR${'\n'}X:drie</cal:calendar-data>
</d:prop></d:propstat>
</d:response>
</d:multistatus>
""".trimIndent()
val blocks = Ical.extractCalendarBlocks(xml)
assertEquals(3, blocks.size)
assertEquals("BEGIN:VCALENDAR${'\n'}X:drie", blocks[2])
}
@Test
fun `DTSTART met TZID lost op naar lokale tijd in die zone`() {
val block = buildVEvent(
summary = "1234",
dtstart = "DTSTART;TZID=Europe/Amsterdam:20261015T050000",
)
val event = Ical.parseVEvent(block, amsterdam)!!
assertEquals(LocalDateTime.of(2026, 10, 15, 5, 0), event.start.toLocalDateTime())
assertEquals(amsterdam, event.start.zone)
assertEquals("1234", event.summary)
}
@Test
fun `DTSTART in UTC wordt omgerekend naar de lokale zone`() {
// 05:00 UTC op 15 juni = 07:00 zomertijd Amsterdam
val block = buildVEvent(
summary = "1234",
dtstart = "DTSTART:20260615T050000Z",
)
val event = Ical.parseVEvent(block, amsterdam)!!
val local = event.start.withZoneSameInstant(amsterdam)
assertEquals(LocalDateTime.of(2026, 6, 15, 7, 0), local.toLocalDateTime())
}
@Test
fun `all-day-event geeft geen diensttijd`() {
val block = buildVEvent(
summary = "vrij",
dtstart = "DTSTART;VALUE=DATE:20261015",
)
assertNull(Ical.parseVEvent(block, amsterdam))
}
@Test
fun `geannuleerd event wordt overgeslagen`() {
val block = buildVEvent(
summary = "1234",
dtstart = "DTSTART;TZID=Europe/Amsterdam:20261015T050000",
extra = "STATUS:CANCELLED",
)
assertNull(Ical.parseVEvent(block, amsterdam))
}
@Test
fun `eerste event is de vroegste aanvang`() {
val vcalendar = """
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud//NEXTCLOUD//NL
BEGIN:VEVENT
UID:laat
SUMMARY:2222
DTSTART;TZID=Europe/Amsterdam:20261015T140000
DTEND;TZID=Europe/Amsterdam:20261015T210000
END:VEVENT
BEGIN:VEVENT
UID:vroeg
SUMMARY:1111
DTSTART;TZID=Europe/Amsterdam:20261015T043000
DTEND;TZID=Europe/Amsterdam:20261015T120000
END:VEVENT
END:VCALENDAR
""".trimIndent()
val event = Ical.firstTimedEvent(vcalendar)!!
assertEquals("1111", event.summary)
assertEquals(LocalDateTime.of(2026, 10, 15, 4, 30), event.start.toLocalDateTime())
}
@Test
fun `unfold voegt gevouwen regels samen`() {
// RFC 5545: CRLF + spatie/tab na de break is het vouwteken en wordt verwijderd.
val lines = Ical.unfold("SUMMARY:een \r\n lange samenvatting\r\nDTSTART:20260615T050000")
assertEquals("SUMMARY:een lange samenvatting", lines[0])
assertEquals("DTSTART:20260615T050000", lines[1])
}
@Test
fun `XML-ampex wordt ontsnapt in calendar-data`() {
val xml = ("<c:calendar-data>" +
"BEGIN:VCALENDAR\nBEGIN:VEVENT\nSUMMARY:Dienst &amp; meer\n" +
"DTSTART;TZID=Europe/Amsterdam:20261015T050000\nEND:VEVENT\nEND:VCALENDAR" +
"</c:calendar-data>")
val block = Ical.extractCalendarBlocks(xml).single()
val event = Ical.parseVEvent(block, amsterdam)!!
assertEquals("Dienst & meer", event.summary)
}
private fun buildVEvent(summary: String, dtstart: String, extra: String = ""): String {
val extraLine = if (extra.isBlank()) "" else "$extra\n"
return """
BEGIN:VEVENT
UID:test-1
SUMMARY:$summary
$dtstart
${extraLine}END:VEVENT
""".trimIndent() + "\n"
}
}