138 lines
No EOL
4.9 KiB
Kotlin
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 & 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"
|
|
}
|
|
} |