|
| 1 | +package com.mg4.control.api |
| 2 | + |
| 3 | +import org.junit.Assert.assertEquals |
| 4 | +import org.junit.Assert.assertNull |
| 5 | +import org.junit.Assert.assertTrue |
| 6 | +import org.junit.Test |
| 7 | + |
| 8 | +/** |
| 9 | + * Cycle NEXT/PREV de l'API externe — logique pure, aucun accès véhicule ni Android. |
| 10 | + * |
| 11 | + * Ce qui est réellement à risque ici, c'est le rebouclage : Kotlin rend `(-1) % 4 == -1`, donc |
| 12 | + * un modulo naïf sortirait des bornes sur PREV au lieu de revenir au maximum. |
| 13 | + */ |
| 14 | +class ExternalApiCycleTest { |
| 15 | + |
| 16 | + // ── Vocabulaire ────────────────────────────────────────────────────────── |
| 17 | + |
| 18 | + @Test |
| 19 | + fun `NEXT et TOGGLE avancent, PREV recule`() { |
| 20 | + assertEquals(1, ExternalApi.cycleDirection("NEXT")) |
| 21 | + assertEquals(1, ExternalApi.cycleDirection("TOGGLE")) |
| 22 | + assertEquals(-1, ExternalApi.cycleDirection("PREV")) |
| 23 | + } |
| 24 | + |
| 25 | + @Test |
| 26 | + fun `la casse et les espaces sont tolerés`() { |
| 27 | + // Tasker et KeyMapper laissent facilement passer une espace en fin de champ. |
| 28 | + assertEquals(1, ExternalApi.cycleDirection(" next ")) |
| 29 | + assertEquals(-1, ExternalApi.cycleDirection("Prev")) |
| 30 | + } |
| 31 | + |
| 32 | + @Test |
| 33 | + fun `une consigne ordinaire n est pas un cycle`() { |
| 34 | + // Sans quoi une valeur legitime serait detournee en cycle. |
| 35 | + assertNull(ExternalApi.cycleDirection("2")) |
| 36 | + assertNull(ExternalApi.cycleDirection("AUTO")) |
| 37 | + assertNull(ExternalApi.cycleDirection("")) |
| 38 | + } |
| 39 | + |
| 40 | + // ── Rebouclage ─────────────────────────────────────────────────────────── |
| 41 | + |
| 42 | + @Test |
| 43 | + fun `NEXT avance d un cran`() { |
| 44 | + assertEquals(2, ExternalApi.cycleStep(1, 0, 3, 1)) |
| 45 | + } |
| 46 | + |
| 47 | + @Test |
| 48 | + fun `NEXT reboucle du maximum vers le minimum`() { |
| 49 | + assertEquals(0, ExternalApi.cycleStep(3, 0, 3, 1)) |
| 50 | + } |
| 51 | + |
| 52 | + @Test |
| 53 | + fun `PREV reboucle du minimum vers le maximum`() { |
| 54 | + // Le cas qui casse avec un modulo naïf : (-1 % 4) vaut -1 en Kotlin. |
| 55 | + assertEquals(3, ExternalApi.cycleStep(0, 0, 3, -1)) |
| 56 | + } |
| 57 | + |
| 58 | + @Test |
| 59 | + fun `PREV recule d un cran`() { |
| 60 | + assertEquals(2, ExternalApi.cycleStep(3, 0, 3, -1)) |
| 61 | + } |
| 62 | + |
| 63 | + @Test |
| 64 | + fun `bornes ne commencant pas a zero - ventilation 1 a 10`() { |
| 65 | + assertEquals(2, ExternalApi.cycleStep(1, 1, 10, 1)) |
| 66 | + assertEquals(1, ExternalApi.cycleStep(10, 1, 10, 1)) |
| 67 | + assertEquals(10, ExternalApi.cycleStep(1, 1, 10, -1)) |
| 68 | + } |
| 69 | + |
| 70 | + @Test |
| 71 | + fun `bornes de temperature 16 a 32`() { |
| 72 | + assertEquals(17, ExternalApi.cycleStep(16, 16, 32, 1)) |
| 73 | + assertEquals(16, ExternalApi.cycleStep(32, 16, 32, 1)) |
| 74 | + assertEquals(32, ExternalApi.cycleStep(16, 16, 32, -1)) |
| 75 | + } |
| 76 | + |
| 77 | + @Test |
| 78 | + fun `une valeur hors bornes retombe dans les bornes`() { |
| 79 | + // Une propriete peut rendre une valeur inattendue ; le cycle ne doit pas la propager. |
| 80 | + val r = ExternalApi.cycleStep(99, 0, 3, 1) |
| 81 | + assertTrue("resultat hors bornes : $r", r in 0..3) |
| 82 | + } |
| 83 | + |
| 84 | + @Test |
| 85 | + fun `plage degeneree ne boucle pas dans le vide`() { |
| 86 | + // max <= min : une division par zero serait le pire resultat possible ici. |
| 87 | + assertEquals(5, ExternalApi.cycleStep(5, 5, 5, 1)) |
| 88 | + assertEquals(5, ExternalApi.cycleStep(9, 5, 4, -1)) |
| 89 | + } |
| 90 | + |
| 91 | + // ── Liste blanche ──────────────────────────────────────────────────────── |
| 92 | + |
| 93 | + @Test |
| 94 | + fun `les cles non cyclables restent exclues`() { |
| 95 | + // Exclusions raisonnees, pas des oublis : voir la doc de CYCLABLE_KEYS. |
| 96 | + assertTrue(ExternalApi.SET_DRIVE_MODE !in ExternalApi.CYCLABLE_KEYS) |
| 97 | + assertTrue(ExternalApi.SET_REGEN !in ExternalApi.CYCLABLE_KEYS) |
| 98 | + assertTrue(ExternalApi.SET_PROFILE !in ExternalApi.CYCLABLE_KEYS) |
| 99 | + } |
| 100 | + |
| 101 | + @Test |
| 102 | + fun `les onze cles annoncees sont cyclables`() { |
| 103 | + listOf( |
| 104 | + ExternalApi.SET_SEAT_HEAT_LEFT, ExternalApi.SET_SEAT_HEAT_RIGHT, |
| 105 | + ExternalApi.SET_STEERING_HEAT, ExternalApi.SET_HVAC_POWER, |
| 106 | + ExternalApi.SET_HVAC_AC, ExternalApi.SET_HVAC_AUTO, ExternalApi.SET_HVAC_TEMP, |
| 107 | + ExternalApi.SET_HVAC_FAN, ExternalApi.SET_HVAC_RECIRC, |
| 108 | + ExternalApi.SET_DEFROST_FRONT, ExternalApi.SET_DEFROST_REAR |
| 109 | + ).forEach { assertTrue("$it devrait etre cyclable", it in ExternalApi.CYCLABLE_KEYS) } |
| 110 | + assertEquals(11, ExternalApi.CYCLABLE_KEYS.size) |
| 111 | + } |
| 112 | + |
| 113 | + @Test |
| 114 | + fun `aucune cle cyclable n est une action bloquee`() { |
| 115 | + // Le cycle ne doit surtout pas rouvrir une porte fermee par BLOCKED_ACTIONS. |
| 116 | + ExternalApi.CYCLABLE_KEYS.forEach { |
| 117 | + assertTrue("$it est bloquee", it.uppercase() !in ExternalApi.BLOCKED_ACTIONS) |
| 118 | + } |
| 119 | + } |
| 120 | +} |
0 commit comments