package pw.binom.agentik.desktop.session import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.io.TempDir import pw.binom.agentik.desktop.settings.AgentConfig import pw.binom.agentik.desktop.settings.SettingsRepository import pw.binom.agentik.desktop.settings.ThemeId import java.nio.file.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.test.assertTrue /** * Тесты [AgentRegistry]: persist настроек и переходы статусов. Подключение * подменено [AgentConnector], который всегда падает — так проверяем * деградацию в [AgentStatus.Offline] без сети. Успешное [AgentStatus.Online] * потребовало бы настоящего [AgentConnection] (его конструктор приватный), * поэтому проверяется косвенно — см. подключение к реальному серверу вручную. */ class AgentRegistryTest { @TempDir lateinit var dir: Path private fun registry(scope: CoroutineScope) = AgentRegistry( settingsRepo = SettingsRepository.inDirectory(dir), scope = scope, connector = { _, _ -> throw RuntimeException("нет сети") }, ) @Test fun `add agent persists config and goes offline on failure`() = runBlocking { val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val reg = registry(scope) try { reg.load() val cfg = AgentConfig.create("http://192.168.1.5:8080/agentik", nameOverride = "Тестовый") val status = reg.addAgent(cfg) assertTrue(status is AgentStatus.Offline) assertEquals(1, reg.settings.value.agents.size) assertNull(reg.connection(cfg.id)) // persist переживает переоткрытие val reopened = SettingsRepository.inDirectory(dir).load() assertEquals(listOf(cfg), reopened.agents) } finally { reg.close() scope.cancel() } } @Test fun `update agent persists new baseUrl`() = runBlocking { val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val reg = registry(scope) try { reg.load() val cfg = AgentConfig.create("http://old:8080/agentik") reg.addAgent(cfg) reg.updateAgent(cfg.copy(baseUrl = "http://new:9090/agentik")) assertEquals("http://new:9090/agentik", reg.settings.value.agents.single().baseUrl) assertEquals("http://new:9090/agentik", SettingsRepository.inDirectory(dir).load().agents.single().baseUrl) } finally { reg.close() scope.cancel() } } @Test fun `remove agent drops it from settings and statuses`() = runBlocking { val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val reg = registry(scope) try { reg.load() val cfg = AgentConfig.create("http://host:8080/agentik") reg.addAgent(cfg) reg.removeAgent(cfg.id) assertTrue(reg.settings.value.agents.isEmpty()) assertNull(reg.statuses.value[cfg.id]) assertTrue(SettingsRepository.inDirectory(dir).load().agents.isEmpty()) } finally { reg.close() scope.cancel() } } @Test fun `updateSettings persists theme`() = runBlocking { val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val reg = registry(scope) try { reg.load() reg.updateSettings { it.copy(themeId = ThemeId.LIGHT) } assertEquals(ThemeId.LIGHT, reg.settings.value.themeId) assertEquals(ThemeId.LIGHT, SettingsRepository.inDirectory(dir).load().themeId) } finally { reg.close() scope.cancel() } } @Test fun `update nonexistent agent is offline and does not add it`() = runBlocking { val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val reg = registry(scope) try { reg.load() val status = reg.updateAgent(AgentConfig.create("http://host:8080/agentik")) assertTrue(status is AgentStatus.Offline) assertTrue(reg.settings.value.agents.isEmpty()) } finally { reg.close() scope.cancel() } } }