NoteProjetSyncEngineTest.kt
213 lignes · 7376 octets
package fr.ebii.card2vcf.sync import android.content.Context import androidx.room.Room import androidx.test.core.app.ApplicationProvider import fr.ebii.card2vcf.data.CrmDatabase import fr.ebii.card2vcf.data.NoteProjetEntity import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [31]) class NoteProjetSyncEngineTest { private lateinit var db: CrmDatabase private lateinit var api: FakeAilianceApi private lateinit var engine: SyncEngine @Before fun setUp() { val ctx = ApplicationProvider.getApplicationContext<Context>() db = Room.inMemoryDatabaseBuilder(ctx, CrmDatabase::class.java) .allowMainThreadQueries() .build() api = FakeAilianceApi() engine = SyncEngine(api, db) } @After fun tearDown() = db.close() // ---- Tâche 1 : pull avec notes → insérées ---- @Test fun pull_notesInserees() = runBlocking { api.pullResult = AilianceApiClient.ApiResult.Ok( SyncPullResponse( serverTime = "2026-09-15T10:00:00Z", notes = listOf( NoteProjetDto( id = "n1", projetId = "p1", titre = "Réunion kick-off", contenu = "**Objectif** : lancer le projet.", auteur = "alice", creeLe = "2026-09-14T08:00:00Z", majLe = null, ), NoteProjetDto( id = "n2", projetId = "p1", titre = "Suivi hebdo", contenu = "Point de la semaine.", auteur = "bob", creeLe = "2026-09-15T09:00:00Z", ), ), ), ) val result = engine.syncNow() assertEquals(2, db.noteProjetDao().listAll().size) val n1 = db.noteProjetDao().getByServerId("n1") assertNotNull(n1) assertEquals("Réunion kick-off", n1?.titre) assertEquals("**Objectif** : lancer le projet.", n1?.texte) assertEquals("alice", n1?.auteur) assertEquals("p1", n1?.projetServerId) // Notes comptées dans received assertEquals(2, result.received) } // ---- Tâche 2 : majLe plus récent → mise à jour, audioPath local préservé ---- @Test fun pull_noteExistante_majLeRecent_miseAJourEtAudioPathPreserve() = runBlocking { val localAudioPath = "/data/app/audio/n1.wav" db.noteProjetDao().upsert( NoteProjetEntity( serverId = "n1", projetServerId = "p1", titre = "Ancien titre", texte = "Ancien contenu", auteur = "alice", audioPath = localAudioPath, createdAt = parseIsoToEpochMs("2026-09-14T08:00:00Z"), updatedAt = parseIsoToEpochMs("2026-09-14T08:00:00Z"), ), ) api.pullResult = AilianceApiClient.ApiResult.Ok( SyncPullResponse( serverTime = "2026-09-15T10:00:00Z", notes = listOf( NoteProjetDto( id = "n1", projetId = "p1", titre = "Nouveau titre", contenu = "Contenu mis à jour.", auteur = "alice", creeLe = "2026-09-14T08:00:00Z", majLe = "2026-09-15T09:00:00Z", // plus récent que updatedAt local ), ), ), ) engine.syncNow() val updated = db.noteProjetDao().getByServerId("n1") assertNotNull(updated) assertEquals("Nouveau titre", updated?.titre) assertEquals("Contenu mis à jour.", updated?.texte) // audioPath local préservé assertEquals(localAudioPath, updated?.audioPath) } // ---- Tâche 3 : majLe plus ancien → pas d'écrasement ---- @Test fun pull_noteExistante_majLeAncien_pasDecrasement() = runBlocking { val titreOriginal = "Titre original" db.noteProjetDao().upsert( NoteProjetEntity( serverId = "n1", projetServerId = "p1", titre = titreOriginal, texte = "Contenu original", auteur = "alice", createdAt = parseIsoToEpochMs("2026-09-14T08:00:00Z"), updatedAt = parseIsoToEpochMs("2026-09-15T09:00:00Z"), // local plus récent ), ) api.pullResult = AilianceApiClient.ApiResult.Ok( SyncPullResponse( serverTime = "2026-09-15T10:00:00Z", notes = listOf( NoteProjetDto( id = "n1", projetId = "p1", titre = "Titre distant obsolète", contenu = "Contenu distant obsolète", auteur = "alice", creeLe = "2026-09-14T08:00:00Z", majLe = "2026-09-14T10:00:00Z", // plus ancien que updatedAt local ), ), ), ) engine.syncNow() val unchanged = db.noteProjetDao().getByServerId("n1") assertEquals(titreOriginal, unchanged?.titre) } // ---- Tâche 4 : tombstone note → suppression ---- @Test fun pull_tombstoneNote_suppression() = runBlocking { db.noteProjetDao().upsert( NoteProjetEntity( serverId = "n-delete", projetServerId = "p1", titre = "À supprimer", texte = "Sera supprimée.", auteur = "alice", createdAt = parseIsoToEpochMs("2026-09-10T08:00:00Z"), ), ) api.pullResult = AilianceApiClient.ApiResult.Ok( SyncPullResponse( serverTime = "2026-09-15T10:00:00Z", tombstones = listOf( TombstoneDto( entityType = "note", id = "n-delete", supprimeLe = "2026-09-15T09:00:00Z", ), ), ), ) engine.syncNow() assertNull(db.noteProjetDao().getByServerId("n-delete")) } // ---- Tâche 5 : pull sans section notes (ancien serveur) → aucune erreur ---- @Test fun pull_sansSectionNotes_aucuneErreur() = runBlocking { // SyncPullResponse sans `notes` explicite → default emptyList() (compat anciens serveurs) api.pullResult = AilianceApiClient.ApiResult.Ok( SyncPullResponse(serverTime = "2026-09-15T10:00:00Z"), ) val result = engine.syncNow() assertEquals(true, result.success) assertEquals(0, db.noteProjetDao().listAll().size) } }
GitRust