From bb99796140808a2f47a0bc433892f6cecda2779f Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 30 Oct 2026 22:23:15 +0100 Subject: [PATCH] Verify monitoring enable, disable and endpoint validation contracts --- doc/tasks.md | 12 ++-- .../utun/chat/data/MonitoringSettingsTest.kt | 56 +++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 tools/chatgui-android/app/src/test/java/com/utun/chat/data/MonitoringSettingsTest.kt diff --git a/doc/tasks.md b/doc/tasks.md index fd678278f..8a9d86c69 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -4,11 +4,13 @@ ## Текущие задачи -[ ] **Android Dev: IP/порт мониторинга сохраняются, но не применяются к ядру** — 30.10.2026. - `control.bind_ip`/`control.port` есть в DataStore и SettingsScreen, однако `ConfigProvider.buildConfigFromServers` - не формирует секцию `[control]`. Параметр controlPort в NativeLib.start/restart доходит только до stub-init - callbacks, а `instance_lite` запускается по INI. Проверить ожидаемый режим мониторинга, подключить настройки - к реальному control server и проверить bind/перезапуск. Найдено при переименовании Dev-настроек. +[+] **Android Dev: включение мониторинга и применение IP/порта к ядру** — 30.10.2026. + Добавлен выключенный по умолчанию checkbox. При включении сохраняется единый endpoint IPv4/port, + генератор записывает `[control] ip/port/allow` для TCP-монитора на выбранном адресе. Runtime применяется + общим restart при выходе из настроек. При выключении поля скрыты, IP/порт удаляются из DataStore, + секция удаляется из INI также при остановленном ядре. Старые endpoint без enabled монитор не включают. + Некорректный/незавершённый ввод не заменяет сохранённый endpoint. MonitoringSettingsTest:4/4 PASS; + native test_config_parser/test_control_server PASS, C-ядро и Android APK собраны. [ ] **Обновить три теста звонка под стартовую пачку из четырёх кадров** — 30.10.2026. Общий прогон после добавления графиков:229/232 PASS. `test_call_admission` зависает в phase4, diff --git a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/MonitoringSettingsTest.kt b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/MonitoringSettingsTest.kt new file mode 100644 index 000000000..19655c8b0 --- /dev/null +++ b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/MonitoringSettingsTest.kt @@ -0,0 +1,56 @@ +package com.utun.chat.data + +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.mutablePreferencesOf +import androidx.datastore.preferences.core.stringPreferencesKey +import org.junit.Assert.* +import org.junit.Test + +class MonitoringSettingsTest { + private val enabled = booleanPreferencesKey("control_enabled") + private val ip = stringPreferencesKey("control_bind_ip") + private val port = intPreferencesKey("control_port") + private fun config(settings: MonitoringSettings) = buildString { settings.appendConfig(this) } + + @Test fun defaultIsOffEvenWithLegacyEndpoint() { + val prefs = mutablePreferencesOf(ip to "10.20.30.40", port to 12345) + assertEquals(MonitoringSettings(), MonitoringSettings.read(prefs)) + assertEquals("", config(MonitoringSettings.read(prefs))) + } + + @Test fun enabledEndpointSurvivesReloadAndGeneratesNativeConfig() { + val prefs = mutablePreferencesOf() + val settings = MonitoringSettings(true, "192.168.1.12", 12345) + settings.writeTo(prefs) + assertEquals(settings, MonitoringSettings.read(prefs)) + assertEquals("[control]\nip=192.168.1.12\nport=12345\nallow=0.0.0.0/0\n\n", config(MonitoringSettings.read(prefs))) + } + + @Test fun disablingRemovesEndpointAndDoesNotRestoreItOnNextEnable() { + val prefs = mutablePreferencesOf() + MonitoringSettings(true, "10.20.30.40", 12345).writeTo(prefs) + MonitoringSettings(false, "10.20.30.40", 12345).writeTo(prefs) + assertEquals(false, prefs[enabled]); assertNull(prefs[ip]); assertNull(prefs[port]) + val defaults = MonitoringSettings.read(prefs) + assertEquals("", config(defaults)) + assertEquals("127.0.0.1", defaults.ip); assertEquals(9999, defaults.port) + defaults.copy(enabled = true).writeTo(prefs) + assertEquals(MonitoringSettings(true), MonitoringSettings.read(prefs)) + } + + @Test fun invalidEndpointsCannotPartiallyOverwritePreferences() { + val prefs = mutablePreferencesOf() + val good = MonitoringSettings(true) + good.writeTo(prefs) + val invalid = listOf("localhost", "::1", "127.1", "127.0.0.256", "01.0.0.1", "127.0.0.1\n[global]") + .map { MonitoringSettings(true, it) } + listOf(0, -1, 65536).map { MonitoringSettings(true, port = it) } + for (settings in invalid) { + assertFalse(settings.valid) + try { settings.writeTo(prefs); fail("Invalid endpoint saved: $settings") } catch (_: IllegalArgumentException) { } + assertEquals(good, MonitoringSettings.read(prefs)) + } + assertTrue(MonitoringSettings(true, port = 1).valid) + assertTrue(MonitoringSettings(true, port = 65535).valid) + } +}