You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

337 lines
14 KiB

package com.inspect.nvr.hik.service;
import com.inspect.nvr.hik.domain.HikNvrPtzMode;
import com.inspect.nvr.hik.domain.HikNvrUiActionRequest;
import com.inspect.nvr.hik.domain.HikNvrUiActionResponse;
import com.inspect.nvr.hikVision.utils.jna.HCNetSDK;
import com.inspect.nvr.domain.Infrared.NvrInfo;
import com.inspect.nvr.ivs.mapper.IvsCameraMapper;
import com.inspect.nvr.ivs.pojo.IvsCamera;
import com.inspect.nvr.service.HikCameraService;
import com.inspect.nvr.service.HikLoginService;
import com.inspect.nvr.service.HikLoginSession;
import com.sun.jna.Pointer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class HikNvrUiActionServiceTest {
@Mock
private HikLoginService loginService;
@Mock
private HikCameraService cameraService;
@Mock
private HCNetSDK sdk;
@Mock
private IvsCameraMapper ivsCameraMapper;
private HikNvrUiActionService service;
@Before
public void setUp() {
// 构造完全来自数据库的 NVR 连接对象,服务不再依赖固定页面配置。
NvrInfo databaseNvr = new NvrInfo();
databaseNvr.setNvrName("测试 NVR");
databaseNvr.setNvrIp("192.168.1.249");
databaseNvr.setServerPort(8000);
databaseNvr.setRtspPort(554);
databaseNvr.setHttpPort(80);
databaseNvr.setAccount("admin");
databaseNvr.setPassword("secret");
databaseNvr.setStartChannel(1);
service = spy(new HikNvrUiActionService());
ReflectionTestUtils.setField(service, "boundNvrInfo", databaseNvr);
ReflectionTestUtils.setField(service, "loginService", loginService);
ReflectionTestUtils.setField(service, "cameraService", cameraService);
ReflectionTestUtils.setField(service, "sdk", sdk);
ReflectionTestUtils.setField(service, "ivsCameraMapper", ivsCameraMapper);
// 清理静态通道状态,避免不同测试之间复用上一次 PTZ 动作的停止命令。
Map<?, ?> channelStates = (Map<?, ?>) ReflectionTestUtils.getField(
HikNvrUiActionService.class, "PTZ_CHANNEL_STATES");
if (channelStates != null) {
channelStates.clear();
}
when(loginService.loginSession(any())).thenReturn(
new HikLoginSession(7, 0, 1, 8, 33));
}
@Test
public void callsSdkPresetWithoutIsapiRoundTrip() {
when(sdk.NET_DVR_PTZPreset_Other(
eq(7), eq(40), eq(HCNetSDK.GOTO_PRESET), eq(5))).thenReturn(true);
HikNvrUiActionResponse response = service.preset(HikNvrUiActionRequest.builder()
.channel(8)
.preset(5)
.build());
assertTrue(response.isSuccess());
assertEquals("已调用预置位 5(SDK)", response.getMessage());
assertEquals(Integer.valueOf(40), response.getSdkChannel());
verify(sdk).NET_DVR_PTZPreset_Other(7, 40, HCNetSDK.GOTO_PRESET, 5);
}
@Test
public void sendsPtzThroughSdkWithConfiguredSpeed() {
when(sdk.NET_DVR_PTZControlWithSpeed_Other(
eq(7), eq(40), eq(HCNetSDK.PAN_LEFT), eq(0), eq(6))).thenReturn(true);
HikNvrUiActionResponse response = service.ptz(HikNvrUiActionRequest.builder()
.channel(8)
.action("LEFT")
.speed(6)
.build());
assertTrue(response.isSuccess());
assertEquals(Integer.valueOf(40), response.getSdkChannel());
verify(sdk).NET_DVR_PTZControlWithSpeed_Other(7, 40, HCNetSDK.PAN_LEFT, 0, 6);
}
@Test
public void sendsPointPtzStartAndAutoStopAfterOneSecond() throws Exception {
when(sdk.NET_DVR_PTZControlWithSpeed_Other(
eq(7), eq(40), eq(HCNetSDK.PAN_LEFT), eq(0), eq(6))).thenReturn(true);
when(sdk.NET_DVR_PTZControlWithSpeed_Other(
eq(7), eq(40), eq(HCNetSDK.PAN_LEFT), eq(1), eq(6))).thenReturn(true);
HikNvrUiActionResponse response = service.ptz(
HikNvrUiActionRequest.builder()
.channel(8)
.action("LEFT")
.speed(6)
.build(),
HikNvrPtzMode.POINT);
assertTrue(response.isSuccess());
assertTrue(response.getMessage().contains("1秒后自动停止"));
verify(sdk).NET_DVR_PTZControlWithSpeed_Other(7, 40, HCNetSDK.PAN_LEFT, 0, 6);
verify(sdk, timeout(2500)).NET_DVR_PTZControlWithSpeed_Other(
7, 40, HCNetSDK.PAN_LEFT, 1, 6);
}
@Test
public void explicitStopCancelsPendingPointStop() throws Exception {
when(sdk.NET_DVR_PTZControlWithSpeed_Other(
eq(7), eq(40), eq(HCNetSDK.PAN_LEFT), eq(0), eq(6))).thenReturn(true);
when(sdk.NET_DVR_PTZControlWithSpeed_Other(
eq(7), eq(40), eq(HCNetSDK.PAN_LEFT), eq(1), eq(6))).thenReturn(true);
service.ptz(HikNvrUiActionRequest.builder()
.channel(8)
.action("LEFT")
.speed(6)
.build(), HikNvrPtzMode.POINT);
service.ptz(HikNvrUiActionRequest.builder()
.channel(8)
.action("STOP")
.speed(6)
.build());
Thread.sleep(1200L);
verify(sdk, times(1)).NET_DVR_PTZControlWithSpeed_Other(
7, 40, HCNetSDK.PAN_LEFT, 1, 6);
}
/** 验证光圈动作会映射为海康光圈放大命令并携带设备速度。 */
@Test
public void sendsLensCommandThroughSdkWithMappedCommand() {
when(sdk.NET_DVR_PTZControlWithSpeed_Other(
eq(7), eq(40), eq(HCNetSDK.IRIS_OPEN), eq(0), eq(7))).thenReturn(true);
HikNvrUiActionResponse response = service.ptz(HikNvrUiActionRequest.builder()
.channel(8)
.action("IRIS_OPEN")
.speed(7)
.build());
assertTrue(response.isSuccess());
verify(sdk).NET_DVR_PTZControlWithSpeed_Other(
7, 40, HCNetSDK.IRIS_OPEN, 0, 7);
}
/** 验证停止请求会复用最近一次启动的聚焦命令,而不是使用方向命令占位值。 */
@Test
public void stopsThePreviouslyStartedLensCommand() {
when(sdk.NET_DVR_PTZControlWithSpeed_Other(
eq(7), eq(40), eq(HCNetSDK.FOCUS_NEAR), eq(0), eq(5))).thenReturn(true);
when(sdk.NET_DVR_PTZControlWithSpeed_Other(
eq(7), eq(40), eq(HCNetSDK.FOCUS_NEAR), eq(1), eq(5))).thenReturn(true);
service.ptz(HikNvrUiActionRequest.builder()
.channel(8)
.action("FOCUS_NEAR")
.speed(5)
.build());
HikNvrUiActionResponse response = service.ptz(HikNvrUiActionRequest.builder()
.channel(8)
.action("STOP")
.speed(4)
.build());
assertTrue(response.isSuccess());
verify(sdk).NET_DVR_PTZControlWithSpeed_Other(
7, 40, HCNetSDK.FOCUS_NEAR, 1, 5);
}
/** 验证雨刷打开请求会调用海康辅助设备控制接口。 */
@Test
public void controlsWiperThroughAuxiliarySdkCommand() {
when(sdk.NET_DVR_PTZControl_Other(
eq(7), eq(40), eq(HCNetSDK.WIPER_PWRON), eq(0))).thenReturn(true);
HikNvrUiActionResponse response = service.auxiliaryControl(8, 1, true);
assertTrue(response.isSuccess());
verify(sdk).NET_DVR_PTZControl_Other(7, 40, HCNetSDK.WIPER_PWRON, 0);
}
/** 验证起始位调用会构造远程控制结构体并发送给海康 SDK。 */
@Test
public void callsConfiguredInitialPositionThroughRemoteControl() {
when(sdk.NET_DVR_RemoteControl(
eq(7), eq(HCNetSDK.NET_DVR_PTZ_INITIALPOSITIONCTRL),
any(Pointer.class), anyInt())).thenReturn(true);
HikNvrUiActionResponse response = service.gotoInitialPosition(8);
assertTrue(response.isSuccess());
assertEquals(Integer.valueOf(40), response.getSdkChannel());
verify(sdk).NET_DVR_RemoteControl(
eq(7), eq(HCNetSDK.NET_DVR_PTZ_INITIALPOSITIONCTRL),
any(Pointer.class), anyInt());
}
@Test
public void savesUnnamedPresetThroughSdkWithoutHttpNameSync() {
when(sdk.NET_DVR_PTZPreset_Other(
eq(7), eq(40), eq(HCNetSDK.SET_PRESET), eq(6))).thenReturn(true);
HikNvrUiActionResponse response = service.createPreset(HikNvrUiActionRequest.builder()
.channel(8)
.preset(6)
.build());
assertTrue(response.isSuccess());
assertEquals("已保存预置位 6(SDK)", response.getMessage());
assertEquals(Integer.valueOf(40), response.getSdkChannel());
verify(sdk).NET_DVR_PTZPreset_Other(7, 40, HCNetSDK.SET_PRESET, 6);
}
@Test
public void savesPresetNameThroughCameraSdkAndNvrIsapi() {
when(sdk.NET_DVR_PTZPreset_Other(
eq(7), eq(40), eq(HCNetSDK.SET_PRESET), eq(6))).thenReturn(true);
when(sdk.NET_DVR_SetDVRConfig(
eq(7), eq(HCNetSDK.NET_DVR_SET_PRESET_NAME), eq(40),
any(Pointer.class), anyInt())).thenReturn(true);
doNothing().when(service).setPresetByIsapi(eq(8), eq(6), eq("测试大门"));
HikNvrUiActionResponse response = service.createPreset(HikNvrUiActionRequest.builder()
.channel(8)
.preset(6)
.presetName("测试大门")
.build());
assertTrue(response.isSuccess());
assertEquals("已保存预置位 6(SDK),名称:测试大门", response.getMessage());
verify(sdk).NET_DVR_SetDVRConfig(
eq(7), eq(HCNetSDK.NET_DVR_SET_PRESET_NAME), eq(40),
any(Pointer.class), anyInt());
}
@Test
public void prefersClientDemoDirectCameraTargetWhenConfigured() {
// 直连目标只从当前 NVR 的 ivs_camera 记录读取。
IvsCamera camera = new IvsCamera();
camera.setCode("22078710632508667008#database-domain");
camera.setDevIP("192.168.1.254");
when(ivsCameraMapper.selectByNvrIp("192.168.1.249"))
.thenReturn(java.util.Collections.singletonList(camera));
doReturn(true).when(service).isCameraReachable(any(IvsCamera.class));
when(loginService.loginSession(any(NvrInfo.class))).thenAnswer(invocation -> {
NvrInfo info = invocation.getArgument(0);
if ("192.168.1.254".equals(info.getNvrIp())) {
return new HikLoginSession(9, 1, 1, 0, 0);
}
return new HikLoginSession(7, 0, 1, 8, 33);
});
when(sdk.NET_DVR_PTZPreset_Other(
eq(9), eq(1), eq(HCNetSDK.GOTO_PRESET), eq(5))).thenReturn(true);
HikNvrUiActionResponse response = service.preset(HikNvrUiActionRequest.builder()
.channel(8)
.preset(5)
.build());
assertTrue(response.isSuccess());
assertEquals(Integer.valueOf(1), response.getSdkChannel());
verify(sdk).NET_DVR_PTZPreset_Other(9, 1, HCNetSDK.GOTO_PRESET, 5);
verify(loginService).loginSession(any(NvrInfo.class));
}
@Test
public void decodesPresetNameUsingItsActualEncoding() {
String utf8Name = ReflectionTestUtils.invokeMethod(
service, "decodePresetName", "正确".getBytes(StandardCharsets.UTF_8));
String gbkName = ReflectionTestUtils.invokeMethod(
service, "decodePresetName", "预置点 3".getBytes(Charset.forName("GBK")));
assertEquals("正确", utf8Name);
assertEquals("预置点 3", gbkName);
}
@Test
public void normalizesMixedPresetXmlWithoutChangingUtf8Names() throws Exception {
ByteArrayOutputStream xml = new ByteArrayOutputStream();
xml.write(("<?xml version=\"1.0\" encoding=\"UTF-8\"?><PTZPresets>"
+ "<PTZPreset><id>1</id><presetName>").getBytes(StandardCharsets.UTF_8));
xml.write("正确".getBytes(StandardCharsets.UTF_8));
xml.write(("</presetName></PTZPreset><PTZPreset><id>3</id><presetName>")
.getBytes(StandardCharsets.UTF_8));
xml.write("预置点 3".getBytes(Charset.forName("GBK")));
xml.write("</presetName></PTZPreset></PTZPresets>"
.getBytes(StandardCharsets.UTF_8));
String normalized = ReflectionTestUtils.invokeMethod(
service, "normalizePresetXml", xml.toByteArray());
assertTrue(normalized.contains("<presetName>正确</presetName>"));
assertTrue(normalized.contains("<presetName>预置点 3</presetName>"));
}
@Test
public void buildsPresetNamePayloadAsUtf8() {
String xml = ReflectionTestUtils.invokeMethod(
service, "buildPresetXml", 6, "测试大门");
byte[] wireBytes = xml.getBytes(StandardCharsets.UTF_8);
assertTrue(new String(wireBytes, StandardCharsets.UTF_8)
.contains("<presetName>测试大门</presetName>"));
assertEquals(-1, new String(wireBytes, Charset.forName("GBK"))
.indexOf("<presetName>测试大门</presetName>"));
}
}