AilianceApiClientTest.kt
361 lignes · 13030 octets
package fr.ebii.card2vcf.sync import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test class AilianceApiClientTest { private lateinit var server: MockWebServer private lateinit var client: AilianceApiClient @Before fun setUp() { server = MockWebServer() server.start() client = AilianceApiClient( baseUrl = server.url("/").toString().trimEnd('/'), apiKey = "test-bearer-key", ) } @After fun tearDown() { server.shutdown() } @Test fun authCle200ParsesResponse() { server.enqueue( MockResponse() .setResponseCode(200) .setBody( """ { "nom": "alice", "cle_chiffree": "Y2lw", "sel": "c2Vs", "kdf": "argon2id", "cipher": "xchacha20poly1305" } """.trimIndent(), ), ) val result = client.authCle("alice", "secret") assertTrue(result is AilianceApiClient.ApiResult.Ok) val ok = result as AilianceApiClient.ApiResult.Ok assertEquals("alice", ok.value.nom) assertEquals("Y2lw", ok.value.cleChiffree) assertEquals("c2Vs", ok.value.sel) assertEquals("argon2id", ok.value.kdf) assertEquals("xchacha20poly1305", ok.value.cipher) val request = server.takeRequest() assertEquals("POST", request.method) assertEquals("/api/auth/cle", request.path) assertTrue(request.getHeader("Authorization") == null) } @Test fun authCle401ReturnsErr() { server.enqueue( MockResponse() .setResponseCode(401) .setBody("""{"message":"Identifiants invalides."}"""), ) val result = client.authCle("alice", "wrong") assertTrue(result is AilianceApiClient.ApiResult.Err) val err = result as AilianceApiClient.ApiResult.Err assertEquals(401, err.code) assertEquals("Identifiants invalides.", err.message) } @Test fun syncStatus200ParsesTotalAndChanges() { server.enqueue( MockResponse() .setResponseCode(200) .setBody( """ { "server_time": "2026-07-22T12:00:00Z", "changes": { "contacts": 2, "entreprises": 0, "projets": 1, "taches": 3, "interactions": 1, "tombstones": 1 }, "total": 8 } """.trimIndent(), ), ) val result = client.syncStatus("1970-01-01T00:00:00Z") assertTrue(result is AilianceApiClient.ApiResult.Ok) val ok = result as AilianceApiClient.ApiResult.Ok assertEquals("2026-07-22T12:00:00Z", ok.value.serverTime) assertEquals(8, ok.value.total) assertEquals(2, ok.value.changes.contacts) assertEquals(1, ok.value.changes.tombstones) val request = server.takeRequest() assertEquals("GET", request.method) assertEquals("/api/sync/status?since=1970-01-01T00%3A00%3A00Z", request.path) assertEquals("Bearer test-bearer-key", request.getHeader("Authorization")) } @Test fun syncPull200ParsesContactsAndTombstones() { server.enqueue( MockResponse() .setResponseCode(200) .setBody( """ { "server_time": "2026-07-22T12:05:00Z", "contacts": [ { "id": "c1", "prenom": "Jean", "nom": "Dupont", "cree_le": "2026-07-20T10:00:00Z", "mis_a_jour_le": "2026-07-21T08:00:00Z" } ], "entreprises": [], "projets": [], "taches": [ { "projet_id": "p1", "id": "t1", "titre": "Appeler client", "colonne_id": "col-a-faire", "ordre": 0, "cree_le": "2026-07-21T09:00:00Z", "assigne_a": "alice" } ], "interactions": [], "tombstones": [ { "type": "contact", "id": "c-old", "supprime_le": "2026-07-22T11:00:00Z" } ], "workflows": [] } """.trimIndent(), ), ) val result = client.syncPull("2026-07-20T00:00:00Z") assertTrue(result is AilianceApiClient.ApiResult.Ok) val ok = result as AilianceApiClient.ApiResult.Ok assertEquals(1, ok.value.contacts.size) assertEquals("c1", ok.value.contacts[0].id) assertEquals("Jean", ok.value.contacts[0].prenom) assertEquals(1, ok.value.taches.size) assertEquals("p1", ok.value.taches[0].projetId) assertEquals("alice", ok.value.taches[0].assigneA) assertEquals(1, ok.value.tombstones.size) assertEquals("contact", ok.value.tombstones[0].entityType) assertEquals("c-old", ok.value.tombstones[0].id) } @Test fun syncPull200ParsesAgendaFields() { server.enqueue( MockResponse() .setResponseCode(200) .setBody( """ { "server_time": "2026-07-22T12:05:00Z", "contacts": [], "entreprises": [], "projets": [], "taches": [], "interactions": [], "rdv": [ { "id": "rdv1", "titre": "Point client", "utilisateur": "alice", "debut": "2026-07-22T09:00:00Z", "fin": "2026-07-22T10:00:00Z", "cree_par": "alice", "cree_le": "2026-07-20T08:00:00Z" } ], "reservations": [ { "id": "resa1", "cible": {"type": "salle", "id": "salle-a"}, "debut": "2026-07-22T09:00:00Z", "fin": "2026-07-22T10:00:00Z", "motif": "Réunion", "statut": "active", "reserve_par": "alice", "cree_le": "2026-07-20T08:00:00Z" } ], "indisponibilites": [ { "id": "indispo1", "cible": {"type": "vehicule", "id": "vehicule-a"}, "nature": "entretien", "debut": "2026-07-22T09:00:00Z", "fin": "2026-07-22T10:00:00Z", "cree_par": "alice", "cree_le": "2026-07-20T08:00:00Z" } ], "tombstones": [], "workflows": [] } """.trimIndent(), ), ) val result = client.syncPull("2026-07-20T00:00:00Z") assertTrue(result is AilianceApiClient.ApiResult.Ok) val ok = result as AilianceApiClient.ApiResult.Ok assertEquals(1, ok.value.rdv.size) assertEquals("Point client", ok.value.rdv[0].titre) assertEquals(1, ok.value.reservations.size) assertEquals("salle", ok.value.reservations[0].cible.type) assertEquals("salle-a", ok.value.reservations[0].cible.id) assertEquals(1, ok.value.indisponibilites.size) assertEquals("entretien", ok.value.indisponibilites[0].nature) } @Test fun createReservation409ReturnsErrWithCode() { server.enqueue( MockResponse() .setResponseCode(409) .setBody("""{"message":"Réservation refusée : période en conflit.","reclamable":true}"""), ) val result = client.createReservation( "salle", "salle-a", """{"debut":"2026-07-22T09:00:00Z","fin":"2026-07-22T10:00:00Z","motif":"Réunion"}""", ) assertTrue(result is AilianceApiClient.ApiResult.Err) val err = result as AilianceApiClient.ApiResult.Err assertEquals(409, err.code) assertEquals("Réservation refusée : période en conflit.", err.message) val request = server.takeRequest() assertEquals("POST", request.method) assertEquals("/api/ressources/salle/salle-a/reservations", request.path) } @Test fun getRessourcesCatalogue200ParsesCatalogue() { server.enqueue( MockResponse() .setResponseCode(200) .setBody( """ { "salles": [{"id": "salle-a", "nom": "Salle A", "actif": true}], "materiels": [], "vehicules": [{"id": "vehicule-a", "nom": "Véhicule A", "actif": false}] } """.trimIndent(), ), ) val result = client.getRessourcesCatalogue() assertTrue(result is AilianceApiClient.ApiResult.Ok) val ok = result as AilianceApiClient.ApiResult.Ok assertEquals(1, ok.value.salles.size) assertEquals("Salle A", ok.value.salles[0].nom) assertTrue(ok.value.salles[0].actif) assertEquals(0, ok.value.materiels.size) assertEquals(1, ok.value.vehicules.size) assertTrue(!ok.value.vehicules[0].actif) val request = server.takeRequest() assertEquals("GET", request.method) assertEquals("/api/ressources", request.path) } @Test fun protectedEndpointWithoutBearerReturns401() { val unauthenticated = AilianceApiClient( baseUrl = server.url("/").toString().trimEnd('/'), apiKey = null, ) server.enqueue( MockResponse() .setResponseCode(401) .setBody("""{"message":"Non autorisé."}"""), ) val result = unauthenticated.syncStatus("1970-01-01T00:00:00Z") assertTrue(result is AilianceApiClient.ApiResult.Err) val err = result as AilianceApiClient.ApiResult.Err assertEquals(401, err.code) val request = server.takeRequest() assertTrue(request.getHeader("Authorization") == null) } @Test fun uploadContactCarte_sendsMultipartWithBearer() { server.enqueue( MockResponse() .setResponseCode(200) .setBody("""{"id":"c1","carte_visite":"carte.jpg","cree_le":"2026-07-22T00:00:00Z"}"""), ) val result = client.uploadContactCarte("c1", byteArrayOf(1, 2, 3), "card.jpg") assertTrue(result is AilianceApiClient.ApiResult.Ok) val request = server.takeRequest() assertEquals("POST", request.method) assertEquals("/api/contacts/c1/carte", request.path) assertEquals("Bearer test-bearer-key", request.getHeader("Authorization")) val body = request.body.readUtf8() assertTrue(body.contains("filename=\"card.jpg\"")) assertTrue(request.getHeader("Content-Type")!!.startsWith("multipart/form-data")) } @Test fun downloadContactCarte_returnsBytes() { server.enqueue( MockResponse() .setResponseCode(200) .setHeader("Content-Type", "image/jpeg") .setBody(okio.Buffer().write(byteArrayOf(10, 20, 30))), ) val result = client.downloadContactCarte("c1") assertTrue(result is AilianceApiClient.ApiResult.Ok) assertTrue((result as AilianceApiClient.ApiResult.Ok).value.contentEquals(byteArrayOf(10, 20, 30))) val request = server.takeRequest() assertEquals("GET", request.method) assertEquals("/api/contacts/c1/carte", request.path) } }
GitRust