package com.battery.business.service.impl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.ruoyi.common.exception.ServiceException;
import com.battery.business.domain.TCollector;
import com.battery.business.domain.TDeviceAdapter;
import com.battery.business.mapper.TCollectorMapper;
import com.battery.business.mapper.TDeviceAdapterMapper;
/**
* 采集器 Service 单元测试。
*
*
采集器按 ADR-0064 不建独立设备模型:登记不再依赖发现候选(真实来源不产出采集器候选),
* 由操作员手工登记来源身份,在线状态取所链接来源实例的连接结果。
*/
@ExtendWith(MockitoExtension.class)
class TCollectorServiceImplTest
{
@Mock
private TCollectorMapper collectorMapper;
@Mock
private TDeviceAdapterMapper adapterMapper;
private TCollectorServiceImpl collectorService;
private TCollector collector;
@BeforeEach
void setUp()
{
collectorService = new TCollectorServiceImpl(collectorMapper, adapterMapper);
collector = new TCollector();
collector.setCollectorId(1L);
collector.setCollectorCode("COL-001");
collector.setIpAddress("192.168.1.10");
collector.setMacAddress("AA:BB:CC:DD:EE:FF");
collector.setCollectorName("一号采集器");
collector.setChannelCount(8);
collector.setOnlineStatus("0");
collector.setTestStatus("0");
collector.setStatus("0");
}
@Test
void insertCollectorSqlShouldNotWriteUpdateAuditFields() throws IOException
{
try (InputStream input = getClass().getResourceAsStream(
"/mapper/battery/TCollectorMapper.xml"))
{
assertNotNull(input);
String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
int insertStart = mapperXml.indexOf("", insertStart);
String insertSql = mapperXml.substring(insertStart, insertEnd);
assertFalse(insertSql.contains("update_by"));
assertFalse(insertSql.contains("update_time"));
}
}
/** 在线事实只有一个来源:所链接来源实例的连接结果;登记表里的存储列不再充当这个事实。 */
@Test
void onlineStatusSqlShouldDeriveFromLinkedAdapter() throws IOException
{
try (InputStream input = getClass().getResourceAsStream(
"/mapper/battery/TCollectorMapper.xml"))
{
assertNotNull(input);
String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
int fragmentStart = mapperXml.indexOf("= 0, "在线状态的派生表达式应当是一个共用片段");
String fragment = mapperXml.substring(fragmentStart, mapperXml.indexOf("", fragmentStart));
assertTrue(fragment.contains("from t_device_adapter adapter_fact"), fragment);
// 存储列不再参与:只有派生的那个表达式。
assertFalse(mapperXml.contains("upper(c.online_status)"));
// SELECT 与 WHERE 各用一次(WHERE 里不能用别名,所以走同一个片段)。
assertEquals(2, mapperXml.split("", -1).length - 1);
}
}
/** 登记不再要求发现候选:手工填写的来源身份即可落库。 */
@Test
void insertCollectorShouldNotRequireADiscoveryCandidate()
{
collector.setCollectorId(null);
when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1);
assertEquals(1, collectorService.insertCollector(collector));
verify(collectorMapper).insertCollector(any(TCollector.class));
}
@Test
void insertCollectorShouldAcceptCollectorWithoutIpAndMac()
{
collector.setCollectorId(null);
collector.setIpAddress(null);
collector.setMacAddress(null);
when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1);
assertEquals(1, collectorService.insertCollector(collector));
ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class);
verify(collectorMapper).insertCollector(captor.capture());
assertNull(captor.getValue().getIpAddress());
assertNull(captor.getValue().getMacAddress());
}
@Test
void insertCollectorShouldNormalizeFieldsAndInitializeStatus()
{
collector.setCollectorId(null);
collector.setCollectorCode(" COL-001 ");
collector.setMacAddress("aa-bb-cc-dd-ee-ff");
when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1);
int rows = collectorService.insertCollector(collector);
ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class);
verify(collectorMapper).insertCollector(captor.capture());
TCollector saved = captor.getValue();
assertEquals(1, rows);
assertEquals("COL-001", saved.getCollectorCode());
assertEquals("AA:BB:CC:DD:EE:FF", saved.getMacAddress());
assertEquals("0", saved.getOnlineStatus());
assertEquals("0", saved.getTestStatus());
assertEquals("0", saved.getStatus());
assertEquals("0", saved.getDelFlag());
}
@Test
void insertCollectorShouldRejectMalformedIpAndMacInsteadOfGuessing()
{
collector.setCollectorId(null);
collector.setIpAddress("invalid-host");
collector.setMacAddress("AA:BB:CC:DD:EE:FF");
ServiceException ip = assertThrows(ServiceException.class,
() -> collectorService.insertCollector(collector));
assertEquals("IP地址格式不正确", ip.getMessage());
collector.setIpAddress("192.168.1.10");
collector.setMacAddress("invalid-mac");
ServiceException mac = assertThrows(ServiceException.class,
() -> collectorService.insertCollector(collector));
assertEquals("MAC地址格式不正确", mac.getMessage());
verify(collectorMapper, never()).insertCollector(any(TCollector.class));
}
@Test
void insertCollectorShouldRejectUnknownLinkedSource()
{
collector.setCollectorId(null);
collector.setAdapterId(9L);
when(adapterMapper.selectDeviceAdapterById(9L)).thenReturn(null);
ServiceException exception = assertThrows(ServiceException.class,
() -> collectorService.insertCollector(collector));
assertEquals("所链接的数据来源不存在", exception.getMessage());
verify(collectorMapper, never()).insertCollector(any(TCollector.class));
}
/** 链接到来源实例后,登记保存该链接:采集器的辅助测量由它提供。 */
@Test
void insertCollectorShouldPersistLinkedSource()
{
collector.setCollectorId(null);
collector.setAdapterId(3L);
when(adapterMapper.selectDeviceAdapterById(3L)).thenReturn(new TDeviceAdapter());
when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1);
assertEquals(1, collectorService.insertCollector(collector));
ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class);
verify(collectorMapper).insertCollector(captor.capture());
assertEquals(Long.valueOf(3L), captor.getValue().getAdapterId());
}
@Test
void insertCollectorShouldRetainRequestedDisabledStatus()
{
collector.setCollectorId(null);
collector.setStatus("1");
when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1);
assertEquals(1, collectorService.insertCollector(collector));
ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class);
verify(collectorMapper).insertCollector(captor.capture());
assertEquals("1", captor.getValue().getStatus());
}
@Test
void insertCollectorShouldDefaultToEnabledWhenStatusIsMissing()
{
collector.setCollectorId(null);
collector.setStatus(null);
when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1);
assertEquals(1, collectorService.insertCollector(collector));
ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class);
verify(collectorMapper).insertCollector(captor.capture());
assertEquals("0", captor.getValue().getStatus());
}
@Test
void insertCollectorShouldRejectDuplicateCode()
{
collector.setCollectorId(null);
TCollector existing = new TCollector();
existing.setCollectorId(2L);
when(collectorMapper.selectCollectorByCode("COL-001")).thenReturn(existing);
ServiceException exception = assertThrows(ServiceException.class,
() -> collectorService.insertCollector(collector));
assertEquals("设备编码已存在", exception.getMessage());
verify(collectorMapper, never()).insertCollector(any(TCollector.class));
}
@Test
void updateCollectorShouldAllowCurrentUniqueValues()
{
when(collectorMapper.selectCollectorById(1L)).thenReturn(collector);
when(collectorMapper.selectCollectorByCode("COL-001")).thenReturn(collector);
when(collectorMapper.selectCollectorByMacAddress("AA:BB:CC:DD:EE:FF")).thenReturn(collector);
when(collectorMapper.updateCollector(any(TCollector.class))).thenReturn(1);
assertEquals(1, collectorService.updateCollector(collector));
verify(collectorMapper).updateCollector(collector);
}
/** 编辑只提交可改字段:未提交的 IP/MAC 不能被当成"清空"。 */
@Test
void updateCollectorShouldKeepUnsubmittedIpAndMac()
{
when(collectorMapper.selectCollectorById(1L)).thenReturn(collector);
when(collectorMapper.selectCollectorByCode("COL-001")).thenReturn(collector);
when(collectorMapper.selectCollectorByMacAddress("AA:BB:CC:DD:EE:FF")).thenReturn(collector);
when(collectorMapper.updateCollector(any(TCollector.class))).thenReturn(1);
TCollector edit = new TCollector();
edit.setCollectorId(1L);
edit.setCollectorName("改名后");
assertEquals(1, collectorService.updateCollector(edit));
ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class);
verify(collectorMapper).updateCollector(captor.capture());
assertEquals("192.168.1.10", captor.getValue().getIpAddress());
assertEquals("AA:BB:CC:DD:EE:FF", captor.getValue().getMacAddress());
assertEquals("改名后", captor.getValue().getCollectorName());
}
@Test
void updateCollectorShouldRejectDisablingTestingCollector()
{
collector.setStatus("1");
collector.setTestStatus("1");
when(collectorMapper.selectCollectorById(1L)).thenReturn(collector);
ServiceException exception = assertThrows(ServiceException.class,
() -> collectorService.updateCollector(collector));
assertEquals("采集器正在测试,不能禁用", exception.getMessage());
verify(collectorMapper, never()).updateCollector(any(TCollector.class));
}
@Test
void updateCollectorStatusShouldRejectDisablingTestingCollector()
{
collector.setTestStatus("1");
when(collectorMapper.selectCollectorById(1L)).thenReturn(collector);
TCollector command = statusCommand("1");
ServiceException exception = assertThrows(ServiceException.class,
() -> collectorService.updateCollectorStatus(command));
assertEquals("采集器正在测试,不能禁用", exception.getMessage());
verify(collectorMapper, never()).updateCollectorStatus(any(TCollector.class));
}
@Test
void updateCollectorStatusShouldAllowEnablingTestingCollector()
{
collector.setTestStatus("1");
collector.setStatus("1");
when(collectorMapper.selectCollectorById(1L)).thenReturn(collector);
when(collectorMapper.updateCollectorStatus(any(TCollector.class))).thenReturn(1);
TCollector command = statusCommand("0");
assertEquals(1, collectorService.updateCollectorStatus(command));
verify(collectorMapper).updateCollectorStatus(command);
}
@Test
void updateCollectorStatusShouldBeIdempotent()
{
when(collectorMapper.selectCollectorById(1L)).thenReturn(collector);
TCollector command = statusCommand("0");
assertEquals(1, collectorService.updateCollectorStatus(command));
verify(collectorMapper, never()).updateCollectorStatus(any(TCollector.class));
}
@Test
void deleteCollectorByIdsShouldRejectTestingCollectorBeforeAnyDelete()
{
TCollector idle = new TCollector();
idle.setCollectorId(1L);
idle.setTestStatus("0");
TCollector testing = new TCollector();
testing.setCollectorId(2L);
testing.setTestStatus("1");
when(collectorMapper.selectCollectorById(1L)).thenReturn(idle);
when(collectorMapper.selectCollectorById(2L)).thenReturn(testing);
ServiceException exception = assertThrows(ServiceException.class,
() -> collectorService.deleteCollectorByIds(new Long[] { 1L, 2L }, "admin"));
assertEquals("采集器正在测试,不能删除", exception.getMessage());
verify(collectorMapper, never()).deleteCollectorById(anyLong(), any(String.class));
}
private TCollector statusCommand(String status)
{
TCollector command = new TCollector();
command.setCollectorId(1L);
command.setStatus(status);
command.setUpdateBy("admin");
return command;
}
}