package com.ruoyi.app.user.service; import com.ruoyi.system.domain.InfoUserDevice; import com.ruoyi.system.mapper.InfoUserDeviceMapper; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; /** * DeviceTrustService 单元测试(029-device-trust-phone-bind,TDD 先行)。 */ class DeviceTrustServiceTest { private final InfoUserDeviceMapper mapper = mock(InfoUserDeviceMapper.class); private final DeviceTrustService service = new DeviceTrustService(mapper); // ---------- isValidDeviceId ---------- @Test void nullOrEmptyOrOversizeDeviceIdIsInvalid() { assertFalse(DeviceTrustService.isValidDeviceId(null)); assertFalse(DeviceTrustService.isValidDeviceId("")); assertFalse(DeviceTrustService.isValidDeviceId("x".repeat(65))); assertTrue(DeviceTrustService.isValidDeviceId("x".repeat(64))); assertTrue(DeviceTrustService.isValidDeviceId("dev-uuid-001")); } // ---------- recordLogin:首次 insert ---------- @Test void firstVerifiedLoginInsertsNewTrustRowWithBothTimestamps() { when(mapper.selectById("dev-1")).thenReturn(null); service.recordLogin("dev-1", "0987654321", 100L); verify(mapper).insert(any(InfoUserDevice.class)); verify(mapper, never()).updateById(any(InfoUserDevice.class)); org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(InfoUserDevice.class); verify(mapper).insert(captor.capture()); InfoUserDevice saved = captor.getValue(); assertEquals("dev-1", saved.getDeviceId()); assertEquals("0987654321", saved.getPhone()); assertEquals(100L, saved.getUserId()); assertTrue(saved.getCreateTime() != null && saved.getUpdateTime() != null); } // ---------- recordLogin:同设备换号覆盖 ---------- @Test void laterLoginWithDifferentPhoneOverwritesTrustRow() { InfoUserDevice exist = new InfoUserDevice(); exist.setDeviceId("dev-1"); exist.setPhone("0980000001"); exist.setUserId(100L); when(mapper.selectById("dev-1")).thenReturn(exist); service.recordLogin("dev-1", "0987654321", 200L); verify(mapper).updateById(exist); verify(mapper, never()).insert(any(InfoUserDevice.class)); assertEquals("0987654321", exist.getPhone()); assertEquals(200L, exist.getUserId()); assertTrue(exist.getUpdateTime() != null); } // ---------- recordLogin:非法入参静默跳过 ---------- @Test void invalidDeviceIdOrMissingPhoneOrUserSkipsSilently() { service.recordLogin(null, "0987654321", 100L); service.recordLogin("", "0987654321", 100L); service.recordLogin("x".repeat(65), "0987654321", 100L); service.recordLogin("dev-1", null, 100L); service.recordLogin("dev-1", "0987654321", null); verifyNoInteractions(mapper); } // ---------- getTrust / getTrustedPhone ---------- @Test void getTrustReturnsNullForInvalidDeviceIdWithoutQuery() { assertNull(service.getTrust(null)); assertNull(service.getTrust("")); verifyNoInteractions(mapper); } @Test void getTrustedPhoneReturnsRowPhoneOrNull() { InfoUserDevice row = new InfoUserDevice(); row.setDeviceId("dev-1"); row.setPhone("0987654321"); when(mapper.selectById("dev-1")).thenReturn(row); when(mapper.selectById("dev-2")).thenReturn(null); assertEquals("0987654321", service.getTrustedPhone("dev-1")); assertNull(service.getTrustedPhone("dev-2")); verify(mapper).selectById("dev-1"); verify(mapper).selectById("dev-2"); } // ---------- maskPhone(与现有日志脱敏规则一致:前3后4) ---------- @Test void maskPhoneKeepsHead3Tail4AndMasksMiddle() { assertEquals("098****4321", DeviceTrustService.maskPhone("0987654321")); } @Test void maskPhoneReturnsShortValueAsIs() { assertNull(DeviceTrustService.maskPhone(null)); assertEquals("1234567", DeviceTrustService.maskPhone("1234567")); } // ---------- 辅助:确认测试桩行为不受 anyString 干扰 ---------- @Test void recordLoginSameDeviceTwiceOnlyQueriesByDeviceId() { when(mapper.selectById(anyString())).thenReturn(null); service.recordLogin("dev-1", "0987654321", 100L); service.recordLogin("dev-1", "0987654321", 100L); verify(mapper, org.mockito.Mockito.times(2)).selectById("dev-1"); assertSame("0987654321", "0987654321"); } }