package com.ruoyi.device.contract; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.time.Duration; import java.time.ZoneId; import java.util.Set; import org.junit.jupiter.api.Test; import com.ruoyi.device.api.DeviceAdapter; import com.ruoyi.device.bts.BtsDevelopmentSimulator; import com.ruoyi.device.bts.BtsDeviceAdapter; import com.ruoyi.device.bts.BtsXmlCodec; import com.ruoyi.device.bts.BtsTcpTransport; import com.ruoyi.device.bts.ScriptedBtsTcpSimulator; import com.ruoyi.device.model.DetectedChannel; import com.ruoyi.device.model.DeviceWorkStatus; import com.ruoyi.device.simulator.BtsSimulatorStateStore; import com.ruoyi.device.simulator.BtsSimulatorStateStore.ChannelState; import com.ruoyi.device.simulator.BtsSimulatorStateStore.DeviceState; import com.ruoyi.device.simulator.BtsSimulatorStateStore.SimulatorState; import com.ruoyi.device.simulator.InMemoryBtsSimulatorStateStore; class SimulatorPublicContractTest { @Test void simulatorIsReusableOutsideTheBtsPackage() throws Exception { try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator(); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1))) { simulator.enqueue("getdevinfo_resp"); assertEquals("getdevinfo_resp", transport.exchange("getdevinfo")); assertEquals(1, simulator.requests().size()); } } @Test void simulatorPreservesRequestEndingWithDelimiterPrefix() throws Exception { try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator(); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1))) { simulator.enqueue("ok"); assertEquals("ok", transport.exchange("payload#")); assertEquals("payload#", simulator.requests().get(0)); } } @Test void developmentSimulatorSupportsRepeatedDiscoveryAndObservation() throws Exception { try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("development-bts", transport, new BtsXmlCodec("development-bts", ZoneId.of("Asia/Hong_Kong")))) { var firstDiscovery = adapter.discoverChannels(); var secondDiscovery = adapter.discoverChannels(); assertEquals(4, firstDiscovery.size()); assertEquals(firstDiscovery, secondDiscovery); assertEquals("开发模拟器", firstDiscovery.get(0).deviceMetadata().manufacturer()); assertEquals("BTS Virtual Cabinet", firstDiscovery.get(0).deviceMetadata().model()); assertEquals(1, firstDiscovery.stream().map(channel -> channel.identity().devid()).distinct().count()); assertEquals(4, firstDiscovery.stream().filter(channel -> channel.available()).count()); Set channels = firstDiscovery.stream() .map(channel -> channel.identity()) .collect(java.util.stream.Collectors.toSet()); var firstObservation = adapter.inquire(channels); var secondObservation = adapter.inquire(channels); assertEquals(4, firstObservation.size()); assertEquals(DeviceWorkStatus.STOPPED, firstObservation.values().iterator().next().workStatus()); assertEquals(firstObservation.keySet(), secondObservation.keySet()); } } @Test void developmentSimulatorPublishesOperatorControlledChannelFacts() throws Exception { try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("dev-sim", transport, new BtsXmlCodec("dev-sim", ZoneId.of("Asia/Hong_Kong")))) { simulator.updateChannel(1, 1, true, DeviceWorkStatus.WORKING); simulator.updateChannel(1, 2, false, DeviceWorkStatus.PAUSED); var discovered = adapter.discoverChannels(); Set available = discovered.stream() .filter(channel -> channel.available()) .map(channel -> channel.identity()) .collect(java.util.stream.Collectors.toSet()); var observed = adapter.inquire(available); assertEquals(4, discovered.size()); assertEquals(3, available.size()); assertEquals(DeviceWorkStatus.WORKING, observed.entrySet().stream() .filter(entry -> entry.getKey().chlid() == 1) .findFirst().orElseThrow().getValue().workStatus()); simulator.updateDevice(1, "127.0.0.2", 4, false); assertEquals(0, adapter.discoverChannels().stream().filter(channel -> channel.available()).count()); assertEquals(4, simulator.snapshot().devices().get(0).channels().size()); assertEquals(3, simulator.snapshot().recentRequests().size()); } } @Test void developmentSimulatorSupportsMultipleConfigurableDevices() throws Exception { try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("dev-sim", transport, new BtsXmlCodec("dev-sim", ZoneId.of("Asia/Hong_Kong")))) { int secondDeviceId = simulator.addDevice("127.0.0.3", 2); simulator.updateDevice(1, "127.0.0.2", 4, true); var discovered = adapter.discoverChannels(); assertEquals(6, discovered.size()); assertEquals(2, discovered.stream().map(channel -> channel.identity().devid()).distinct().count()); assertEquals(4, discovered.stream().filter(channel -> channel.serverIp().equals("127.0.0.2")).count()); assertEquals(2, discovered.stream().filter(channel -> channel.serverIp().equals("127.0.0.3")).count()); simulator.deleteDevice(secondDeviceId); for (int index = 0; index < 3; index++) { var withTombstone = adapter.discoverChannels(); assertEquals(6, withTombstone.size()); assertEquals(4, withTombstone.stream().filter(DetectedChannel::available).count()); } assertEquals(4, adapter.discoverChannels().size()); } } @Test void developmentCollectorsNeverEnterBtsCabinetDiscovery() throws Exception { try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("dev-sim", transport, new BtsXmlCodec("dev-sim", ZoneId.of("Asia/Hong_Kong")))) { assertEquals(4, adapter.discoverChannels().size()); simulator.addCollector("127.0.1.8", 12); assertEquals(4, adapter.discoverChannels().size()); assertEquals(2, simulator.snapshot().collectors().size()); } } @Test void developmentSimulatorRestoresConfigurationNumberingAndStatus() throws Exception { InMemoryBtsSimulatorStateStore store = new InMemoryBtsSimulatorStateStore(); try (BtsDevelopmentSimulator first = new BtsDevelopmentSimulator(0, store)) { first.updateDevice(1, "127.0.0.9", 2, false); first.addDevice("127.0.0.10", 3); first.updateCollector(1, "127.0.1.9", 7, false); } try (BtsDevelopmentSimulator restored = new BtsDevelopmentSimulator(0, store)) { assertEquals(2, restored.snapshot().devices().size()); assertEquals("127.0.0.9", restored.snapshot().devices().get(0).ip()); assertEquals(false, restored.snapshot().devices().get(0).online()); assertEquals(7, restored.snapshot().collectors().get(0).channelCount()); assertEquals(3, restored.addDevice("127.0.0.11", 1)); } } @Test void developmentSimulatorDoesNotExposeWriteThroughFailureInMemory() throws Exception { InMemoryBtsSimulatorStateStore delegate = new InMemoryBtsSimulatorStateStore(); BtsSimulatorStateStore failing = new BtsSimulatorStateStore() { @Override public SimulatorState loadOrInitialize(SimulatorState value) { return delegate.loadOrInitialize(value); } @Override public void save(SimulatorState value) { throw new IllegalStateException("redis unavailable"); } @Override public java.util.List loadRecentRequests() { return delegate.loadRecentRequests(); } @Override public void saveRecentRequests(java.util.List requests) { delegate.saveRecentRequests(requests); } @Override public void reset(SimulatorState value) { delegate.reset(value); } }; try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0, failing)) { assertThrows(IllegalStateException.class, () -> simulator.addDevice("127.0.0.3", 2)); assertEquals(1, simulator.snapshot().devices().size()); } } @Test void developmentSimulatorRestoresUnfinishedTombstones() throws Exception { InMemoryBtsSimulatorStateStore store = new InMemoryBtsSimulatorStateStore(); try (BtsDevelopmentSimulator first = new BtsDevelopmentSimulator(0, store); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", first.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("dev-sim", transport, new BtsXmlCodec("dev-sim", ZoneId.of("Asia/Hong_Kong")))) { first.deleteDevice(1); assertEquals(4, adapter.discoverChannels().size()); } try (BtsDevelopmentSimulator restored = new BtsDevelopmentSimulator(0, store); BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", restored.port(), Duration.ofSeconds(1), Duration.ofSeconds(1)); DeviceAdapter adapter = new BtsDeviceAdapter("dev-sim", transport, new BtsXmlCodec("dev-sim", ZoneId.of("Asia/Hong_Kong")))) { assertEquals(4, adapter.discoverChannels().size()); assertEquals(4, adapter.discoverChannels().size()); assertEquals(0, adapter.discoverChannels().size()); } } @Test void developmentSimulatorRejectsNonContiguousRestoredChannelTopology() { SimulatorState damaged = new SimulatorState(BtsSimulatorStateStore.CURRENT_VERSION, java.util.List.of(new DeviceState(1, "127.0.0.2", true, java.util.List.of(new ChannelState(2, true, DeviceWorkStatus.STOPPED)))), java.util.List.of(), 2, 1, java.util.List.of()); assertThrows(IllegalStateException.class, () -> new BtsDevelopmentSimulator(0, new InMemoryBtsSimulatorStateStore(damaged))); } }