ContratSyncFixturesTest.kt 112 lignes · 4541 octets
package fr.ebii.card2vcf.sync

import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test

/**
 * Décode les fixtures de contrat de synchronisation avec les vrais DTO.
 *
 * Si un champ serveur est renommé, ce test devient rouge immédiatement.
 * Preuve de rougeur (vérifiée) : en renommant `transcription_erreur` → `transcription_error`
 * dans `interaction_note_vocale.json`, l'assertion suivante échoue :
 *   expected:<Modèle ASR indisponible> but was:<null>
 */
class ContratSyncFixturesTest {

    private fun fixture(nom: String): String {
        val stream = javaClass.classLoader!!.getResourceAsStream("contrats/$nom")
            ?: error("Fixture introuvable : contrats/$nom")
        return stream.bufferedReader().readText()
    }

    // ---- sync_pull.json ----

    @Test
    fun syncPull_decodeSyncPullResponse() {
        val json = fixture("sync_pull.json")
        val pull = syncJson.decodeFromString<SyncPullResponse>(json)

        assertTrue("contacts doit contenir au moins un élément", pull.contacts.isNotEmpty())
        assertTrue("interactions doit contenir au moins un élément", pull.interactions.isNotEmpty())
        assertTrue("notes doit contenir au moins un élément", pull.notes.isNotEmpty())
    }

    @Test
    fun syncPull_contactChampsCritiques() {
        val json = fixture("sync_pull.json")
        val pull = syncJson.decodeFromString<SyncPullResponse>(json)

        val contact = pull.contacts.first()
        assertNotNull("id contact ne doit pas être null", contact.id)
        assertNotNull("creeLe contact ne doit pas être null", contact.creeLe)
    }

    /**
     * Contrat de planification : si le serveur renomme un de ces champs, le
     * téléphone cesserait silencieusement d'afficher dates, étiquettes ou
     * sous-tâches. Ce test rend la rupture immédiate.
     */
    @Test
    fun syncPull_planificationDesTaches() {
        val json = fixture("sync_pull.json")
        val pull = syncJson.decodeFromString<SyncPullResponse>(json)

        val projet = pull.projets.first()
        assertTrue("planification doit être transportée", projet.planification)
        assertEquals(5, projet.joursOuvres)
        assertEquals("2026-09-30", projet.echeance)

        val tache = pull.taches.first()
        assertEquals("2026-09-21", tache.debut)
        assertEquals(3, tache.dureeJours)
        assertEquals("contrat", tache.etiquette)
        assertEquals(2, tache.sousTaches.size)
        assertTrue("l'état coché doit survivre au transport", tache.sousTaches.first().fait)
    }

    /** La fin affichée sur le téléphone doit être celle du Gantt web. */
    @Test
    fun syncPull_periodeCalculeeCommeLeServeur() {
        val json = fixture("sync_pull.json")
        val pull = syncJson.decodeFromString<SyncPullResponse>(json)
        val projet = pull.projets.first()
        val tache = pull.taches.first()

        // Lundi 2026-09-21 + 3 jours ouvrés en rythme 5 : mercredi 23.
        val periode = fr.ebii.card2vcf.planning.CalendrierOuvre(projet.joursOuvres)
            .periodeAffichee(java.time.LocalDate.parse(tache.debut!!), tache.dureeJours!!)
        assertEquals("21/09 → 23/09", periode)
    }

    // ---- interaction_note_vocale.json ----

    @Test
    fun interaction_decodeInteractionDto() {
        val json = fixture("interaction_note_vocale.json")
        val dto = syncJson.decodeFromString<InteractionDto>(json)

        assertEquals("note_vocale", dto.typeInteraction)
        assertEquals("vocale_contrat_1.wav", dto.pieceJointe)
        assertEquals("echec", dto.transcription)
        // Champ critique : si transcription_erreur est renommé côté serveur,
        // cette assertion devient rouge : expected:<Modèle ASR indisponible> but was:<null>
        assertEquals("Modèle ASR indisponible", dto.transcriptionErreur)
    }

    // ---- note_projet.json ----

    @Test
    fun noteProjet_decodeNoteProjetDto() {
        val json = fixture("note_projet.json")
        val dto = syncJson.decodeFromString<NoteProjetDto>(json)

        assertEquals("projet-contrat-1", dto.projetId)
        assertEquals("note_contrat_1.wav", dto.audio)
        assertEquals("terminee", dto.transcription)
        // transcription_erreur doit être null pour cette note (transcription réussie)
        assertNull("transcriptionErreur doit être null pour une transcription réussie", dto.transcriptionErreur)
    }
}