package com.ruoyi.config;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.when;
|
import java.util.List;
|
import java.util.concurrent.atomic.AtomicReference;
|
import org.junit.jupiter.api.Test;
|
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.ValueOperations;
|
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSONObject;
|
import com.ruoyi.device.model.DeviceWorkStatus;
|
import com.ruoyi.device.simulator.BtsSimulatorStateStore.ChannelState;
|
import com.ruoyi.device.simulator.BtsSimulatorStateStore.ChannelTombstone;
|
import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorState;
|
import com.ruoyi.device.simulator.BtsSimulatorStateStore.DeviceState;
|
import com.ruoyi.device.simulator.BtsSimulatorStateStore.SimulatorState;
|
import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore.CabinetCandidate;
|
import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore.ChannelCandidate;
|
import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore.CollectorCandidate;
|
import com.ruoyi.device.model.ChannelIdentity;
|
import com.ruoyi.framework.config.FastJson2JsonRedisSerializer;
|
|
class RedisStateStoreTest
|
{
|
@Test
|
void initializesOnlyWhenStateIsAbsentAndRestoresSavedState()
|
{
|
AtomicReference<Object> stored = new AtomicReference<>();
|
RedisBtsSimulatorStateStore store = store(stored);
|
SimulatorState defaults = state(1, "127.0.0.2");
|
|
assertEquals(defaults, store.loadOrInitialize(defaults));
|
SimulatorState changed = state(7, "127.0.0.9");
|
store.save(changed);
|
|
assertEquals(changed, store.loadOrInitialize(defaults));
|
}
|
|
@Test
|
void restoresNestedSimulatorStateFromRedisSerializer()
|
{
|
SimulatorState expected = new SimulatorState(1,
|
List.of(new DeviceState(2, "127.0.0.3", false,
|
List.of(new ChannelState(1, true, DeviceWorkStatus.WORKING)))),
|
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)));
|
FastJson2JsonRedisSerializer<Object> serializer = new FastJson2JsonRedisSerializer<>(Object.class);
|
AtomicReference<Object> stored = new AtomicReference<>(
|
serializer.deserialize(serializer.serialize(JSON.toJSONString(expected))));
|
|
SimulatorState restored = store(stored).loadOrInitialize(state(1, "127.0.0.2"));
|
|
assertEquals(expected, restored);
|
assertEquals(DeviceWorkStatus.WORKING, restored.devices().get(0).channels().get(0).workStatus());
|
}
|
|
@Test
|
void rejectsIncompatibleVersionWithoutOverwritingIt()
|
{
|
AtomicReference<Object> stored = new AtomicReference<>("{\"version\":2,\"devices\":[],"
|
+ "\"collectors\":[],\"nextDeviceId\":1,\"nextCollectorId\":1,\"tombstones\":[]}");
|
RedisBtsSimulatorStateStore store = store(stored);
|
|
IllegalStateException exception = assertThrows(IllegalStateException.class,
|
() -> store.loadOrInitialize(state(1, "127.0.0.2")));
|
|
assertEquals("Unsupported BTS simulator state version: 2", exception.getMessage());
|
assertEquals(true, stored.get().toString().contains("\"version\":2"));
|
}
|
|
@Test
|
void restoresNestedDiscoveryCandidatesFromJson()
|
{
|
AtomicReference<Object> stored = new AtomicReference<>();
|
RedisDeviceDiscoveryCandidateStore first = new RedisDeviceDiscoveryCandidateStore(redis(stored));
|
java.time.Instant observedAt = java.time.Instant.parse("2026-08-27T00:00:00Z");
|
CabinetCandidate candidate = new CabinetCandidate("dev-sim", null, "dev-sim-22-1", 22, 1,
|
"127.0.0.2", null, "模拟柜", "开发模拟器", "BTS Virtual Cabinet",
|
List.of(new ChannelCandidate(new ChannelIdentity("dev-sim", 22, 1, 1, 1), true,
|
DeviceWorkStatus.WORKING, observedAt)), observedAt, observedAt.plusSeconds(30));
|
first.recordCabinets(List.of(candidate));
|
|
RedisDeviceDiscoveryCandidateStore restored = new RedisDeviceDiscoveryCandidateStore(redis(stored));
|
CabinetCandidate actual = restored.snapshot(observedAt.plusSeconds(1)).cabinets().get(0);
|
|
assertEquals("dev-sim-22-1", actual.deviceCode());
|
assertEquals(DeviceWorkStatus.WORKING, actual.channels().get(0).workStatus());
|
assertEquals(observedAt, actual.channels().get(0).observedAt());
|
}
|
|
@Test
|
void removesDevelopmentCandidatesFromRedisState()
|
{
|
AtomicReference<Object> stored = new AtomicReference<>();
|
RedisDeviceDiscoveryCandidateStore store = new RedisDeviceDiscoveryCandidateStore(redis(stored));
|
java.time.Instant observedAt = java.time.Instant.parse("2026-08-27T00:00:00Z");
|
store.recordCabinets(List.of(new CabinetCandidate("dev-sim", null, "dev-sim-22-1", 22, 1,
|
"127.0.0.2", null, "模拟柜", null, null,
|
List.of(new ChannelCandidate(new ChannelIdentity("dev-sim", 22, 1, 1, 1), true,
|
DeviceWorkStatus.STOPPED, observedAt)), observedAt, observedAt.plusSeconds(30))));
|
|
store.removeAdapter("dev-sim");
|
|
assertEquals(List.of(), store.snapshot(observedAt).cabinets());
|
}
|
|
@Test
|
void restoresDiscoveryCandidatesFromLegacyAutoTypeJson()
|
{
|
AtomicReference<Object> stored = new AtomicReference<>();
|
RedisDeviceDiscoveryCandidateStore first = new RedisDeviceDiscoveryCandidateStore(redis(stored));
|
java.time.Instant observedAt = java.time.Instant.parse("2026-08-27T00:00:00Z");
|
first.recordCabinets(List.of(new CabinetCandidate("dev-sim", null, "dev-sim-22-1", 22, 1,
|
"127.0.0.2", null, "模拟柜", null, null,
|
List.of(new ChannelCandidate(new ChannelIdentity("dev-sim", 22, 1, 1, 1), true,
|
DeviceWorkStatus.STOPPED, observedAt)), observedAt, observedAt.plusSeconds(30))));
|
first.replaceCollectors("dev-sim", List.of(new CollectorCandidate("dev-sim", "dev-sim-collector-1",
|
"127.0.1.2", null, "模拟采集器", null, null, 8, true,
|
observedAt, observedAt.plusSeconds(30))));
|
|
JSONObject legacy = JSON.parseObject(stored.get().toString());
|
legacy.put("@type", "com.ruoyi.config.RedisDeviceDiscoveryCandidateStore$StoredState");
|
JSONObject cabinet = legacy.getJSONArray("cabinets").getJSONObject(0);
|
cabinet.put("@type", JSONObject.class.getName());
|
cabinet.getJSONArray("channels").getJSONObject(0)
|
.put("@type", JSONObject.class.getName());
|
legacy.getJSONArray("collectors").getJSONObject(0)
|
.put("@type", JSONObject.class.getName());
|
FastJson2JsonRedisSerializer<Object> serializer = new FastJson2JsonRedisSerializer<>(Object.class);
|
stored.set(serializer.deserialize(serializer.serialize(legacy)));
|
|
var snapshot = new RedisDeviceDiscoveryCandidateStore(redis(stored)).snapshot(observedAt.plusSeconds(1));
|
CabinetCandidate restored = snapshot.cabinets().get(0);
|
|
assertEquals("dev-sim-22-1", restored.deviceCode());
|
assertEquals(DeviceWorkStatus.STOPPED, restored.channels().get(0).workStatus());
|
assertEquals("dev-sim-collector-1", snapshot.collectors().get(0).deviceCode());
|
}
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
private RedisBtsSimulatorStateStore store(AtomicReference<Object> stored)
|
{
|
return new RedisBtsSimulatorStateStore(redis(stored));
|
}
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
private RedisTemplate<Object, Object> redis(AtomicReference<Object> stored)
|
{
|
RedisTemplate<Object, Object> redis = mock(RedisTemplate.class);
|
ValueOperations<Object, Object> values = mock(ValueOperations.class);
|
when(redis.opsForValue()).thenReturn(values);
|
when(values.get(any())).thenAnswer(invocation -> stored.get());
|
when(values.setIfAbsent(any(), any())).thenAnswer(invocation -> {
|
if (stored.get() != null) return false;
|
stored.set(invocation.getArgument(1));
|
return true;
|
});
|
doAnswer(invocation -> { stored.set(invocation.getArgument(1)); return null; })
|
.when(values).set(any(), any());
|
return redis;
|
}
|
|
private SimulatorState state(int deviceId, String ip)
|
{
|
return new SimulatorState(1,
|
List.of(new DeviceState(deviceId, ip, true,
|
List.of(new ChannelState(1, true, DeviceWorkStatus.STOPPED)))),
|
List.of(), deviceId + 1, 1, List.of());
|
}
|
}
|