CalendarBridgeTest.kt
205 lignes · 7501 octets
package fr.ebii.card2vcf.sync import android.content.ContentProvider import android.content.ContentResolver import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.UriMatcher import android.content.pm.ProviderInfo import android.database.Cursor import android.database.MatrixCursor import android.net.Uri import android.provider.CalendarContract import androidx.test.core.app.ApplicationProvider import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config /** * Couvre [CalendarBridge] au-delà des fonctions pures déjà testées dans * `CalendarServerIdCodecTest` : `ensureLocalCalendar`/`listEvents`/`upsertEvent`/`deleteEvent` * via un `ContentProvider` en mémoire ([FakeCalendarProvider]), le vrai fournisseur Agenda * n'étant pas embarqué dans `android-all` sous Robolectric. */ @RunWith(RobolectricTestRunner::class) @Config(sdk = [31]) class CalendarBridgeTest { private lateinit var bridge: CalendarBridge @Before fun setUp() { val providerInfo = ProviderInfo().apply { authority = CalendarContract.AUTHORITY } Robolectric.buildContentProvider(FakeCalendarProvider::class.java).create(providerInfo) val resolver = ApplicationProvider.getApplicationContext<Context>().contentResolver bridge = CalendarBridge(resolver) } @Test fun ensureLocalCalendarCreatesThenReusesSameCalendar() { val firstId = bridge.ensureLocalCalendar("Card2vcf — Mes RDV") val secondId = bridge.ensureLocalCalendar("Card2vcf — Mes RDV") assertEquals(firstId, secondId) } @Test fun ensureLocalCalendarCreatesDistinctCalendarsForDistinctNames() { val rdvId = bridge.ensureLocalCalendar("Card2vcf — Mes RDV") val salleId = bridge.ensureLocalCalendar("Card2vcf — Salle A") assertTrue(rdvId != salleId) } @Test fun listEventsOnEmptyCalendarIsEmpty() { val calendarId = bridge.ensureLocalCalendar("Card2vcf — Mes RDV") assertTrue(bridge.listEvents(calendarId).isEmpty()) } @Test fun upsertEventInsertsThenListEventsReturnsIt() { val calendarId = bridge.ensureLocalCalendar("Card2vcf — Mes RDV") val snapshot = CalendarEventSnapshot( title = "RDV client", debutMs = 1_000L, finMs = 2_000L, serverId = "rdv-42", descriptionBody = "notes libres", ) val eventId = bridge.upsertEvent(calendarId, snapshot) assertEquals(listOf(snapshot.copy(eventId = eventId)), bridge.listEvents(calendarId)) } @Test fun upsertEventWithExistingEventIdUpdatesInPlace() { val calendarId = bridge.ensureLocalCalendar("Card2vcf — Mes RDV") val eventId = bridge.upsertEvent( calendarId, CalendarEventSnapshot(title = "RDV client", debutMs = 1_000L, finMs = 2_000L, serverId = "rdv-42"), ) val updatedId = bridge.upsertEvent( calendarId, CalendarEventSnapshot( title = "RDV client (déplacé)", debutMs = 3_000L, finMs = 4_000L, eventId = eventId, serverId = "rdv-42", ), ) assertEquals(eventId, updatedId) val events = bridge.listEvents(calendarId) assertEquals(1, events.size) assertEquals("RDV client (déplacé)", events.single().title) assertEquals(3_000L, events.single().debutMs) } @Test fun deleteEventRemovesItFromListEvents() { val calendarId = bridge.ensureLocalCalendar("Card2vcf — Mes RDV") val eventId = bridge.upsertEvent( calendarId, CalendarEventSnapshot(title = "RDV client", debutMs = 1_000L, finMs = 2_000L), ) bridge.deleteEvent(eventId) assertTrue(bridge.listEvents(calendarId).isEmpty()) } } /** * Fournisseur Agenda minimal en mémoire, pour tester [CalendarBridge] sous Robolectric * (le vrai `CalendarProvider2` de l'app Agenda n'est pas embarqué dans `android-all`). * Ne supporte que les projections/sélections utilisées par [CalendarBridge]. */ class FakeCalendarProvider : ContentProvider() { private val calendars = mutableMapOf<Long, ContentValues>() private val events = mutableMapOf<Long, ContentValues>() private var nextId = 1L override fun onCreate(): Boolean = true override fun getType(uri: Uri): String? = null override fun insert(uri: Uri, values: ContentValues?): Uri { val id = nextId++ val row = ContentValues(values).apply { put("_id", id) } when (matcher.match(uri)) { CALENDARS -> calendars[id] = row EVENTS -> events[id] = row else -> error("Uri non supportée par FakeCalendarProvider : $uri") } return ContentUris.withAppendedId(baseUriFor(matcher.match(uri)), id) } override fun query( uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?, ): Cursor { val table = when (matcher.match(uri)) { CALENDARS -> calendars EVENTS -> events else -> error("Uri non supportée par FakeCalendarProvider : $uri") } val columns = projection ?: arrayOf("_id") val cursor = MatrixCursor(columns) table.toSortedMap().values .filter { matchesSelection(it, selection, selectionArgs) } .forEach { row -> cursor.addRow(columns.map { column -> row.get(column) }) } return cursor } override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int { val id = ContentUris.parseId(uri) val row = events[id] ?: return 0 values?.let { row.putAll(it) } return 1 } override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { val id = ContentUris.parseId(uri) return if (events.remove(id) != null) 1 else 0 } private fun baseUriFor(matchCode: Int): Uri = when (matchCode) { CALENDARS -> Uri.parse("content://${CalendarContract.AUTHORITY}/calendars") EVENTS -> Uri.parse("content://${CalendarContract.AUTHORITY}/events") else -> error("Code de correspondance inconnu : $matchCode") } private fun matchesSelection(row: ContentValues, selection: String?, args: Array<out String>?): Boolean { if (selection.isNullOrBlank()) return true return selection.split(" AND ").withIndex().all { (index, condition) -> val column = condition.substringBefore(" = ?").trim() row.get(column)?.toString() == args?.getOrNull(index) } } private companion object { const val CALENDARS = 1 const val EVENTS = 2 val matcher = UriMatcher(UriMatcher.NO_MATCH).apply { addURI(CalendarContract.AUTHORITY, "calendars", CALENDARS) addURI(CalendarContract.AUTHORITY, "calendars/#", CALENDARS) addURI(CalendarContract.AUTHORITY, "events", EVENTS) addURI(CalendarContract.AUTHORITY, "events/#", EVENTS) } } }
GitRust