package com.battery.device.bts; import static org.junit.jupiter.api.Assertions.assertEquals; import java.time.Duration; import java.time.ZoneId; import java.util.Set; import org.junit.jupiter.api.Test; import com.battery.device.api.DeviceAdapter; import com.battery.device.model.ChannelIdentity; import com.battery.device.model.DeviceConnectionStatus; import com.battery.device.model.DeviceWorkStatus; import com.battery.device.model.ObservationFailureReason; class DeviceObservationSeamContractTest { private static final ChannelIdentity CHANNEL = new ChannelIdentity("lab-bts", 22, 1, 1, 1); @Test void successfulResponseIsOnlineAndCarriesDeviceTimeAndPlan() throws Exception { try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator(); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("lab-bts", transport, new BtsXmlCodec("lab-bts", ZoneId.of("Asia/Hong_Kong")))) { simulator.enqueue("inquire_resp" + "" + ""); var observation = adapter.observe(Set.of(CHANNEL)).get(CHANNEL); assertEquals(DeviceConnectionStatus.ONLINE, observation.connectionStatus()); assertEquals(DeviceWorkStatus.WORKING, observation.workStatus()); assertEquals("2026-09-04T10:00:00Z", observation.deviceTime().toString()); assertEquals(1, observation.plannedSteps().size()); assertEquals("cc", observation.plannedSteps().get(0).stepType()); assertEquals(null, observation.failureReason()); } } @Test void missingChannelResponseIsOfflineAndUnobservable() throws Exception { try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator(); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("lab-bts", transport, new BtsXmlCodec("lab-bts", ZoneId.of("Asia/Hong_Kong")))) { simulator.enqueue("inquire_resp"); var observation = adapter.observe(Set.of(CHANNEL)).get(CHANNEL); assertEquals(DeviceConnectionStatus.OFFLINE, observation.connectionStatus()); assertEquals(DeviceWorkStatus.UNOBSERVABLE, observation.workStatus()); assertEquals(ObservationFailureReason.OFFLINE, observation.failureReason()); } } @Test void timeoutAndParseFailureBecomeStableOfflineReasons() throws Exception { try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator(); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofMillis(50)); DeviceAdapter adapter = new BtsDeviceAdapter("lab-bts", transport, new BtsXmlCodec("lab-bts", ZoneId.of("Asia/Hong_Kong")))) { simulator.enqueueDelayed(Duration.ofMillis(200), "inquire_resp"); assertEquals(ObservationFailureReason.TIMEOUT, adapter.observe(Set.of(CHANNEL)).get(CHANNEL).failureReason()); } try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator(); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("lab-bts", transport, new BtsXmlCodec("lab-bts", ZoneId.of("Asia/Hong_Kong")))) { simulator.enqueue("inquire_resp"); assertEquals(ObservationFailureReason.PARSE_FAILED, adapter.observe(Set.of(CHANNEL)).get(CHANNEL).failureReason()); } } }