| | |
| | | package com.ruoyi.device.contract; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import java.math.BigDecimal; |
| | | import java.time.Duration; |
| | | import java.time.Instant; |
| | | import java.time.ZoneId; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import org.junit.jupiter.api.Test; |
| | | import com.ruoyi.device.api.DeviceAdapter; |
| | |
| | | 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.CollectorHistoryFaults; |
| | | import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorMetricState; |
| | | import com.ruoyi.device.simulator.BtsSimulatorStateStore.DeviceState; |
| | | import com.ruoyi.device.simulator.BtsSimulatorStateStore.SimulatorState; |
| | | import com.ruoyi.device.simulator.InMemoryBtsSimulatorStateStore; |
| | |
| | | class SimulatorPublicContractTest |
| | | { |
| | | @Test |
| | | void collectorGeneratesIndependentRealtimeAndHistoricalMeasurements() throws Exception |
| | | { |
| | | Instant startedAt = Instant.parse("2026-08-29T01:00:00Z"); |
| | | try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0)) |
| | | { |
| | | simulator.configureCollectorSampling(1, 200, CollectorHistoryFaults.none()); |
| | | simulator.configureCollectorChannel(1, "S-01", List.of( |
| | | new CollectorMetricState("temperature", new BigDecimal("26.5"), "℃"), |
| | | new CollectorMetricState("positive_potential", new BigDecimal("3.801"), "V"), |
| | | new CollectorMetricState("negative_potential", new BigDecimal("-0.120"), "V"), |
| | | new CollectorMetricState("pressure", new BigDecimal("101.2"), "kPa"))); |
| | | |
| | | simulator.advanceCollectors(startedAt); |
| | | simulator.advanceCollectors(startedAt.plusMillis(400)); |
| | | |
| | | var realtime = simulator.snapshot().collectors().get(0).latestMeasurements(); |
| | | var history = simulator.collectorHistory(1, 0, 1000); |
| | | assertEquals(13, realtime.size()); |
| | | assertTrue(realtime.stream().allMatch(item -> item.sequenceId() == 3)); |
| | | assertTrue(realtime.stream().anyMatch(item -> item.channelCode().equals("S-01") |
| | | && item.metricCode().equals("pressure") && item.unit().equals("kPa"))); |
| | | assertEquals(39, history.total()); |
| | | assertEquals("dev-sim", history.records().get(0).adapterCode()); |
| | | assertEquals("dev-sim-collector-1", history.records().get(0).collectorCode()); |
| | | assertEquals(startedAt, history.records().get(0).deviceTime()); |
| | | assertTrue(history.records().get(0).deviceTimeRaw().startsWith("2026-08-29 09:00:00")); |
| | | assertEquals(startedAt, history.records().get(0).serverReceivedAt()); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void collectorHistorySupportsPagingAndRepeatableFaultScenarios() throws Exception |
| | | { |
| | | Instant startedAt = Instant.parse("2026-08-29T02:00:00Z"); |
| | | try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0)) |
| | | { |
| | | simulator.configureCollectorSampling(1, 100, |
| | | new CollectorHistoryFaults(List.of(2L), 1L, 3L, true)); |
| | | simulator.advanceCollectors(startedAt); |
| | | simulator.advanceCollectors(startedAt.plusMillis(200)); |
| | | |
| | | var first = simulator.collectorHistory(1, 0, 17); |
| | | var second = simulator.collectorHistory(1, first.nextOffset(), 1000); |
| | | List<com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorMeasurementRecord> records = |
| | | new java.util.ArrayList<>(first.records()); |
| | | records.addAll(second.records()); |
| | | |
| | | assertEquals(48, first.total()); |
| | | assertTrue(first.hasMore()); |
| | | assertFalse(second.hasMore()); |
| | | assertTrue(records.stream().noneMatch(item -> item.sequenceId() == 2)); |
| | | assertEquals(24, records.stream().filter(item -> item.sequenceId() == 1).count()); |
| | | assertEquals(2, records.stream().filter(item -> item.sequenceId() == 3 |
| | | && item.channelCode().equals("S-01") && item.metricCode().equals("temperature")).count()); |
| | | assertTrue(records.get(0).sequenceId() >= records.get(records.size() - 1).sequenceId()); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void collectorStopsWhileOfflineAndResumesFromPersistedSequence() throws Exception |
| | | { |
| | | InMemoryBtsSimulatorStateStore store = new InMemoryBtsSimulatorStateStore(); |
| | | Instant now = Instant.now(); |
| | | try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0, store)) |
| | | { |
| | | simulator.advanceCollectors(now); |
| | | simulator.updateCollector(1, "127.0.1.2", 4, false); |
| | | simulator.advanceCollectors(now.plusSeconds(10)); |
| | | assertEquals(1, simulator.snapshot().collectors().get(0).latestMeasurements().get(0).sequenceId()); |
| | | } |
| | | |
| | | try (BtsDevelopmentSimulator restored = new BtsDevelopmentSimulator(0, store)) |
| | | { |
| | | restored.updateCollector(1, "127.0.1.2", 4, true); |
| | | restored.advanceCollectors(Instant.now().plusSeconds(2)); |
| | | assertTrue(restored.snapshot().collectors().get(0).latestMeasurements().get(0).sequenceId() >= 2); |
| | | assertEquals(List.of("S-01", "S-02", "S-03", "S-04"), |
| | | restored.snapshot().collectors().get(0).channels().stream() |
| | | .map(channel -> channel.channelCode()).toList()); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void simulatorIsReusableOutsideTheBtsPackage() throws Exception |
| | | { |
| | | try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator(); |