| | |
| | | class RedisStateStoreTest |
| | | { |
| | | @Test |
| | | void channelParametersAndPlanSurviveRedisRestartAndOnlineChanges() throws Exception |
| | | { |
| | | var stored = new AtomicReference<Object>(); |
| | | var configuration = new com.battery.device.simulator.BtsSimulatorStateStore.ChannelConfiguration( |
| | | new java.math.BigDecimal("-3.123456"), new java.math.BigDecimal("8.125"), |
| | | new java.math.BigDecimal("7.2"), new java.math.BigDecimal("29.7"), 2, 2, 5, 120, 30, |
| | | List.of(new com.battery.device.simulator.BtsSimulatorStateStore.SimulatorPlanStep("充电", "cc", 60, 4.2), |
| | | new com.battery.device.simulator.BtsSimulatorStateStore.SimulatorPlanStep("放电 <验证>", "dc", 90, 3.0))); |
| | | try (var simulator = new BtsDevelopmentSimulator(0, store(stored))) { |
| | | simulator.configureChannel(1, 1, configuration); |
| | | simulator.updateChannel(1, 1, false, DeviceWorkStatus.PAUSED); |
| | | simulator.setAllDevicesPoweredOn(false); |
| | | simulator.setSoftwareAccessible(false); |
| | | } |
| | | try (var simulator = new BtsDevelopmentSimulator(0, store(stored))) { |
| | | assertEquals(configuration, simulator.snapshot().devices().get(0).channels().get(0).configuration()); |
| | | simulator.setSoftwareAccessible(true); |
| | | simulator.setAllDevicesPoweredOn(true); |
| | | simulator.updateChannel(1, 1, true, DeviceWorkStatus.WORKING); |
| | | var identity = simulator.discoverChannels().get(0).identity(); |
| | | assertEquals(-3.123456, simulator.latestCabinet(identity).currentA()); |
| | | assertEquals(8.125, simulator.latestCabinet(identity).voltageV()); |
| | | assertEquals("放电 <验证>", simulator.testPlan(identity).rows().get(1).stepName()); |
| | | assertEquals("进行中", simulator.testPlan(identity).rows().get(1).executionStatus()); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void oldChannelStateReceivesDefaultConfigurationWithoutResettingIdentity() |
| | | { |
| | | var object = JSON.parseObject(JSON.toJSONString(state(7, "127.0.0.9"))); |
| | | object.getJSONArray("devices").getJSONObject(0).getJSONArray("channels").getJSONObject(0).remove("configuration"); |
| | | var restored = store(new AtomicReference<>(object.toJSONString())).loadOrInitialize(state(1, "127.0.0.2")); |
| | | assertEquals(7, restored.devices().get(0).deviceId()); |
| | | assertEquals("127.0.0.9", restored.devices().get(0).ip()); |
| | | assertEquals(1, restored.devices().get(0).channels().get(0).configuration().steps().size()); |
| | | } |
| | | |
| | | @Test |
| | | void initializesOnlyWhenStateIsAbsentAndRestoresSavedState() |
| | | { |
| | | AtomicReference<Object> stored = new AtomicReference<>(); |
| | |
| | | List.of(new CollectorState(3, "dev-sim-collector-3", "127.0.1.3", |
| | | "02:43:00:00:00:03", "模拟采集器", "开发模拟器", |
| | | "Virtual Collector", 3, true)), |
| | | 3, 4, List.of(new ChannelTombstone(1, "127.0.0.2", 4, 2))); |
| | | 3, 4, List.of(new ChannelTombstone(1, "127.0.0.2", 4, 2)), false, 900L); |
| | | FastJson2JsonRedisSerializer<Object> serializer = new FastJson2JsonRedisSerializer<>(Object.class); |
| | | AtomicReference<Object> stored = new AtomicReference<>( |
| | | serializer.deserialize(serializer.serialize(JSON.toJSONString(expected)))); |
| | |
| | | |
| | | assertEquals(expected, restored); |
| | | assertEquals(DeviceWorkStatus.WORKING, restored.devices().get(0).channels().get(0).workStatus()); |
| | | } |
| | | |
| | | @Test |
| | | void restoresChannelPlanWhenRedisReturnsNestedJsonObjects() |
| | | { |
| | | JSONObject state = JSON.parseObject(JSON.toJSONString(state(1, "127.0.0.2"))); |
| | | JSONObject configuration = state.getJSONArray("devices").getJSONObject(0) |
| | | .getJSONArray("channels").getJSONObject(0).getJSONObject("configuration"); |
| | | configuration.getJSONArray("steps").forEach(item -> ((JSONObject) item).put("@type", JSONObject.class.getName())); |
| | | FastJson2JsonRedisSerializer<Object> serializer = new FastJson2JsonRedisSerializer<>(Object.class); |
| | | Object restoredValue = serializer.deserialize(serializer.serialize(state)); |
| | | |
| | | SimulatorState restored = store(new AtomicReference<>(restoredValue)) |
| | | .loadOrInitialize(state(9, "127.0.0.9")); |
| | | |
| | | assertEquals("恒流充电", restored.devices().get(0).channels().get(0) |
| | | .configuration().steps().get(0).name()); |
| | | } |
| | | |
| | | @Test |
| | |
| | | |
| | | CollectorState collector = restored.collectors().get(0); |
| | | assertEquals(1000, collector.sampleIntervalMs()); |
| | | assertEquals(1L, collector.nextSequence()); |
| | | assertEquals(List.of("S-01", "S-02"), |
| | | collector.channels().stream().map(channel -> channel.channelCode()).toList()); |
| | | assertEquals(0L, collector.historyRecordCount()); |
| | | } |
| | | |
| | | @Test |
| | | void migratesEmbeddedCollectorHistoryToAppendOnlyRedisList() |
| | | { |
| | | AtomicReference<Object> stored = new AtomicReference<>("{\"version\":1,\"devices\":[]," |
| | | + "\"collectors\":[{\"collectorId\":1,\"deviceCode\":\"dev-sim-collector-1\"," |
| | | + "\"ip\":\"127.0.1.2\",\"mac\":\"02:43:00:00:00:01\"," |
| | | + "\"name\":\"模拟采集器\",\"manufacturer\":\"开发模拟器\"," |
| | | + "\"model\":\"Virtual Collector\",\"channelCount\":1,\"online\":true," |
| | | + "\"sampleIntervalMs\":1000,\"nextSequence\":2," |
| | | + "\"lastSampledAt\":\"2026-08-29T05:00:00Z\"," |
| | | + "\"channels\":[{\"channelCode\":\"S-01\",\"metrics\":[{" |
| | | + "\"metricCode\":\"temperature\",\"value\":25.1,\"unit\":\"℃\"}]}]," |
| | | + "\"history\":[{\"adapterCode\":\"dev-sim\"," |
| | | + "\"collectorCode\":\"dev-sim-collector-1\",\"channelCode\":\"S-01\"," |
| | | + "\"sequenceId\":1,\"deviceTime\":\"2026-08-29T05:00:00Z\"," |
| | | + "\"deviceTimeRaw\":\"2026-08-29 13:00:00.000\"," |
| | | + "\"serverReceivedAt\":\"2026-08-29T05:00:00Z\"," |
| | | + "\"metricCode\":\"temperature\",\"value\":25.1,\"unit\":\"℃\"}]," |
| | | + "\"historyFaults\":{\"missingSequences\":[],\"outOfOrder\":false}}]," |
| | | + "\"nextDeviceId\":1,\"nextCollectorId\":2,\"tombstones\":[]}"); |
| | | RedisBtsSimulatorStateStore store = store(stored); |
| | | |
| | | SimulatorState restored = store.loadOrInitialize(state(1, "127.0.0.2")); |
| | | |
| | | assertEquals(1L, restored.collectors().get(0).historyRecordCount()); |
| | | assertEquals(1, store.loadCollectorHistoryPage("dev-sim-collector-1", 0, 1000, false) |
| | | .records().size()); |
| | | assertEquals(true, stored.get().toString().contains("\"history\":[{")); |
| | | store.completeValidatedRestore(restored); |
| | | assertEquals(false, stored.get().toString().contains("\"history\":[{")); |
| | | } |
| | | |
| | | @Test |
| | | void clearsLegacyEmbeddedHistoryForDemoStartup() throws Exception |
| | | { |
| | | AtomicReference<Object> stored = new AtomicReference<>("{\"version\":1,\"devices\":[]," |
| | | + "\"collectors\":[{\"collectorId\":1,\"deviceCode\":\"dev-sim-collector-1\"," |
| | | + "\"ip\":\"127.0.1.2\",\"mac\":\"02:43:00:00:00:01\"," |
| | | + "\"name\":\"模拟采集器\",\"manufacturer\":\"开发模拟器\"," |
| | | + "\"model\":\"Virtual Collector\",\"channelCount\":1,\"online\":true," |
| | | + "\"sampleIntervalMs\":1000,\"nextSequence\":2," |
| | | + "\"lastSampledAt\":\"2026-08-29T05:00:00Z\"," |
| | | + "\"channels\":[{\"channelCode\":\"S-01\",\"metrics\":[{" |
| | | + "\"metricCode\":\"temperature\",\"value\":25.1,\"unit\":\"℃\"}]}]," |
| | | + "\"history\":[{\"adapterCode\":\"dev-sim\"," |
| | | + "\"collectorCode\":\"wrong-collector\",\"channelCode\":\"S-01\"," |
| | | + "\"sequenceId\":1,\"deviceTime\":\"2026-08-29T05:00:00Z\"," |
| | | + "\"deviceTimeRaw\":\"2026-08-29 13:00:00.000\"," |
| | | + "\"serverReceivedAt\":\"2026-08-29T05:00:00Z\"," |
| | | + "\"metricCode\":\"temperature\",\"value\":25.1,\"unit\":\"℃\"}]," |
| | | + "\"historyFaults\":{\"missingSequences\":[],\"outOfOrder\":false}}]," |
| | | + "\"nextDeviceId\":1,\"nextCollectorId\":2,\"tombstones\":[]}"); |
| | | |
| | | try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0, store(stored))) |
| | | { |
| | | assertEquals(0L, simulator.snapshot().collectors().get(0).historyRecordCount()); |
| | | } |
| | | assertEquals(false, stored.get().toString().contains("\"history\":[{")); |
| | | } |
| | | |
| | | @Test |
| | |
| | | |
| | | assertEquals("Unsupported BTS simulator state version: 2", exception.getMessage()); |
| | | assertEquals(true, stored.get().toString().contains("\"version\":2")); |
| | | } |
| | | |
| | | @Test |
| | | void atomicallySavesCollectorDeletionAndRemovesIndependentHistory() |
| | | { |
| | | AtomicReference<Object> stored = new AtomicReference<>(); |
| | | RedisBtsSimulatorStateStore store = store(stored); |
| | | SimulatorState current = state(1, "127.0.0.2"); |
| | | store.loadOrInitialize(current); |
| | | var record = new com.battery.device.simulator.BtsSimulatorStateStore.CollectorMeasurementRecord( |
| | | "dev-sim", "dev-sim-collector-1", "S-01", 1L, |
| | | java.time.Instant.parse("2026-08-29T05:00:00Z"), "2026-08-29 13:00:00.000", |
| | | java.time.Instant.parse("2026-08-29T05:00:00Z"), "temperature", |
| | | new java.math.BigDecimal("25.1"), "℃"); |
| | | store.save(current, List.of( |
| | | new com.battery.device.simulator.BtsSimulatorStateStore.CollectorHistoryAppend( |
| | | "dev-sim-collector-1", List.of(record)))); |
| | | SimulatorState next = state(7, "127.0.0.9"); |
| | | |
| | | store.saveAndDeleteCollectorHistory(next, "dev-sim-collector-1"); |
| | | |
| | | assertEquals(next, store.loadOrInitialize(current)); |
| | | assertEquals(List.of(), store.loadCollectorHistoryPage("dev-sim-collector-1", 0, 1000, false) |
| | | .records()); |
| | | } |
| | | |
| | | @Test |
| | | void readsOnlyRequestedCollectorHistoryRange() |
| | | { |
| | | AtomicReference<Object> stored = new AtomicReference<>(); |
| | | RedisTemplate<Object, Object> redis = redis(stored); |
| | | RedisBtsSimulatorStateStore store = new RedisBtsSimulatorStateStore(redis); |
| | | SimulatorState current = state(1, "127.0.0.2"); |
| | | store.loadOrInitialize(current); |
| | | var record = new com.battery.device.simulator.BtsSimulatorStateStore.CollectorMeasurementRecord( |
| | | "dev-sim", "dev-sim-collector-1", "S-01", 1L, |
| | | java.time.Instant.parse("2026-08-29T05:00:00Z"), "2026-08-29 13:00:00.000", |
| | | java.time.Instant.parse("2026-08-29T05:00:00Z"), "temperature", |
| | | new java.math.BigDecimal("25.1"), "℃"); |
| | | store.save(current, List.of( |
| | | new com.battery.device.simulator.BtsSimulatorStateStore.CollectorHistoryAppend( |
| | | "dev-sim-collector-1", java.util.Collections.nCopies(1010, record)))); |
| | | ListOperations<Object, Object> lists = redis.opsForList(); |
| | | clearInvocations(lists); |
| | | |
| | | var page = store.loadCollectorHistoryPage("dev-sim-collector-1", 1000, 10, false); |
| | | |
| | | assertEquals(1010, page.total()); |
| | | assertEquals(10, page.records().size()); |
| | | assertEquals(false, page.hasMore()); |
| | | verify(lists).range(RedisBtsSimulatorStateStore.HISTORY_KEY_PREFIX + "dev-sim-collector-1", |
| | | 1000L, 1009L); |
| | | } |
| | | |
| | | @Test |