test: pin ModemPreset->channel-name contract; drop vacuous name check

Add ChannelPresetNameTest (commonTest, so it gates every PR) pinning
every ModemPreset to its exact firmware-canonical channel name from
DisplayFormatters::getModemPresetDisplayName(preset, useShortName=false).
The name is interop-critical (it feeds the channel hash, channel
number/frequency and MQTT topic), so the test:
- locks the mapping against accidental edits (e.g. "LongMod" ->
  "LongModerate"),
- asserts set-completeness vs ModemPreset.entries, so a new preset that
  was added to Channel.name's `when` but not pinned here fails loudly
  (also catches someone silencing the intentional compile tripwire with
  an `else`),
- covers the deprecated VERY_LONG_SLOW, which still has a real name.

This backstops Channel.name's exhaustive `when` without weakening it.
The numeric on-air anchors (hash/channelNum/radioFreq) stay in
ChannelTest. Remove the old androidDeviceTest allModemPresetsHaveValidNames,
which only asserted name != "Invalid" — a value the no-else `when` can
never produce, so it gave the illusion of a name guard while testing
nothing, and ran only on-device rather than on every PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
James Rich
2026-06-13 11:53:34 -05:00
parent 0a03b1e2e4
commit 4aba9cd3e9
2 changed files with 82 additions and 15 deletions

View File

@@ -24,7 +24,6 @@ import org.meshtastic.core.model.util.CHANNEL_URL_PREFIX
import org.meshtastic.core.model.util.getChannelUrl
import org.meshtastic.core.model.util.toChannelSet
import org.meshtastic.proto.ChannelSet
import org.meshtastic.proto.Config
@RunWith(AndroidJUnit4::class)
class ChannelTest {
@@ -64,18 +63,4 @@ class ChannelTest {
Assert.assertEquals(906.875f, ch.radioFreq)
}
@Test
fun allModemPresetsHaveValidNames() {
Config.LoRaConfig.ModemPreset.entries.forEach { preset ->
// Skip UNRECOGNIZED if it exists (Wire generates it sometimes) or generic UNSET values if applicable
if (preset.name == "UNSET" || preset.name == "UNRECOGNIZED") return@forEach
val loraConfig = Channel.default.loraConfig.copy(use_preset = true, modem_preset = preset)
val channel = Channel(loraConfig = loraConfig)
// We want to ensure it is NOT "Invalid"
Assert.assertNotEquals("Preset ${preset.name} should typically have a valid name", "Invalid", channel.name)
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model
import org.meshtastic.proto.Config.LoRaConfig.ModemPreset
import kotlin.test.Test
import kotlin.test.assertEquals
/**
* Contract test pinning the preset -> channel-name mapping in [Channel.name].
*
* This name is INTEROP-CRITICAL: for a channel with an empty name and `use_preset = true`, the name is hashed into the
* channel hash, the channel number / radio frequency, and the MQTT topic, so it must byte-match the firmware. Source of
* truth is firmware `DisplayFormatters::getModemPresetDisplayName(preset, useShortName = false)`. Two names are
* deliberately abbreviated (LONG_MODERATE -> "LongMod", VERY_LONG_SLOW -> "VLongSlow") and must NOT be auto-derived
* from the enum name.
*
* When firmware adds a preset, [Channel.name]'s exhaustive `when` fails to compile first (by design). This test is the
* backstop: it forces the new branch to carry the EXACT firmware name, guards the mapping against accidental edits, and
* fails if a preset is left unpinned (e.g. someone silenced the compile error with an `else`). It deliberately covers
* the deprecated VERY_LONG_SLOW, which still has a real firmware name. The numeric anchors (hash/channelNum/radioFreq)
* live in ChannelTest and are the genuine on-air interop guard — keep both.
*/
class ChannelPresetNameTest {
// Firmware-canonical names: DisplayFormatters::getModemPresetDisplayName(preset, useShortName = false).
private val expectedNames =
mapOf(
ModemPreset.SHORT_TURBO to "ShortTurbo",
ModemPreset.SHORT_FAST to "ShortFast",
ModemPreset.SHORT_SLOW to "ShortSlow",
ModemPreset.MEDIUM_FAST to "MediumFast",
ModemPreset.MEDIUM_SLOW to "MediumSlow",
ModemPreset.LONG_FAST to "LongFast",
ModemPreset.LONG_SLOW to "LongSlow",
ModemPreset.LONG_MODERATE to "LongMod",
ModemPreset.VERY_LONG_SLOW to "VLongSlow",
ModemPreset.LONG_TURBO to "LongTurbo",
ModemPreset.LITE_FAST to "LiteFast",
ModemPreset.LITE_SLOW to "LiteSlow",
ModemPreset.NARROW_FAST to "NarrowFast",
ModemPreset.NARROW_SLOW to "NarrowSlow",
ModemPreset.TINY_FAST to "TinyFast",
ModemPreset.TINY_SLOW to "TinySlow",
)
private fun presetChannelName(preset: ModemPreset): String =
Channel(loraConfig = Channel.default.loraConfig.copy(use_preset = true, modem_preset = preset)).name
@Test
fun every_preset_maps_to_its_exact_firmware_name() {
expectedNames.forEach { (preset, expected) ->
assertEquals(expected, presetChannelName(preset), "Channel name for $preset must match firmware exactly")
}
}
@Test
fun every_ModemPreset_is_pinned() {
val protoPresets = ModemPreset.entries.filter { it.name != "UNSET" && it.name != "UNRECOGNIZED" }.toSet()
assertEquals(
protoPresets,
expectedNames.keys,
"Every ModemPreset must be pinned to its firmware name here. A new preset was added to the protos " +
"(and to Channel.name's `when`) but not recorded in this contract — add it with its exact " +
"DisplayFormatters::getModemPresetDisplayName string.",
)
}
}