package com.mosquito.project.service; import com.mosquito.project.persistence.entity.ShortLinkEntity; import com.mosquito.project.persistence.repository.ShortLinkRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @DisplayName("短链服务测试") class ShortLinkServiceTest { @Mock private ShortLinkRepository repository; @InjectMocks private ShortLinkService shortLinkService; private final String TEST_URL = "https://example.com/test"; private final String TEST_CODE = "test123"; @BeforeEach void setUp() { lenient().when(repository.existsByCode(anyString())).thenReturn(false); lenient().when(repository.save(any(ShortLinkEntity.class))).thenAnswer(invocation -> { ShortLinkEntity entity = invocation.getArgument(0); entity.setId(1L); entity.setCreatedAt(OffsetDateTime.now(ZoneOffset.UTC)); return entity; }); } @Test @DisplayName("创建短链 - 基本功能") void shouldCreateShortLink_Basic() { // When ShortLinkEntity result = shortLinkService.create(TEST_URL); // Then assertNotNull(result); assertEquals(TEST_URL, result.getOriginalUrl()); assertNotNull(result.getCode()); assertEquals(8, result.getCode().length()); // DEFAULT_CODE_LEN assertNotNull(result.getCreatedAt()); verify(repository).save(any(ShortLinkEntity.class)); } @Test @DisplayName("创建短链 - 带activityId参数") void shouldCreateShortLink_WithActivityId() { String urlWithActivity = TEST_URL + "?activityId=123"; // When ShortLinkEntity result = shortLinkService.create(urlWithActivity); // Then assertNotNull(result); assertEquals(urlWithActivity, result.getOriginalUrl()); assertEquals(123L, result.getActivityId()); verify(repository).save(any(ShortLinkEntity.class)); } @Test @DisplayName("创建短链 - 带inviter参数") void shouldCreateShortLink_WithInviter() { String urlWithInviter = TEST_URL + "?inviter=456"; // When ShortLinkEntity result = shortLinkService.create(urlWithInviter); // Then assertNotNull(result); assertEquals(urlWithInviter, result.getOriginalUrl()); assertEquals(456L, result.getInviterUserId()); verify(repository).save(any(ShortLinkEntity.class)); } @Test @DisplayName("创建短链 - 带多个参数") void shouldCreateShortLink_WithMultipleParams() { String urlWithParams = TEST_URL + "?activityId=123&inviter=456&other=value"; // When ShortLinkEntity result = shortLinkService.create(urlWithParams); // Then assertNotNull(result); assertEquals(urlWithParams, result.getOriginalUrl()); assertEquals(123L, result.getActivityId()); assertEquals(456L, result.getInviterUserId()); verify(repository).save(any(ShortLinkEntity.class)); } @Test @DisplayName("创建短链 - 代码冲突时重新生成") void shouldCreateShortLink_WhenCodeConflict() { // Given - 第一次生成冲突,第二次成功 when(repository.existsByCode(anyString())) .thenReturn(true) // 第一次冲突 .thenReturn(false); // 第二次成功 // When ShortLinkEntity result = shortLinkService.create(TEST_URL); // Then assertNotNull(result); assertNotNull(result.getCode()); verify(repository, times(2)).existsByCode(anyString()); verify(repository).save(any(ShortLinkEntity.class)); } @Test @DisplayName("创建短链 - 多次冲突时增加长度") void shouldCreateShortLink_WithIncreasedLength() { // Given - 所有尝试都冲突 when(repository.existsByCode(anyString())).thenReturn(true); // When ShortLinkEntity result = shortLinkService.create(TEST_URL); // Then assertNotNull(result); assertNotNull(result.getCode()); assertEquals(10, result.getCode().length()); // len + 2 verify(repository, times(5)).existsByCode(anyString()); // 5次重试 verify(repository).save(any(ShortLinkEntity.class)); } @Test @DisplayName("创建短链 - 无效URL不影响核心功能") void shouldCreateShortLink_WithInvalidUrl() { String invalidUrl = "not-a-valid-url"; // When ShortLinkEntity result = shortLinkService.create(invalidUrl); // Then assertNotNull(result); assertEquals(invalidUrl, result.getOriginalUrl()); assertNotNull(result.getCode()); assertNull(result.getActivityId()); assertNull(result.getInviterUserId()); verify(repository).save(any(ShortLinkEntity.class)); } @Test @DisplayName("根据代码查找短链 - 找到") void shouldFindByCode_WhenExists() { // Given ShortLinkEntity entity = new ShortLinkEntity(); entity.setCode(TEST_CODE); entity.setOriginalUrl(TEST_URL); when(repository.findByCode(TEST_CODE)).thenReturn(Optional.of(entity)); // When Optional result = shortLinkService.findByCode(TEST_CODE); // Then assertTrue(result.isPresent()); assertEquals(TEST_CODE, result.get().getCode()); assertEquals(TEST_URL, result.get().getOriginalUrl()); verify(repository).findByCode(TEST_CODE); } @Test @DisplayName("根据代码查找短链 - 未找到") void shouldReturnEmpty_WhenNotExists() { // Given when(repository.findByCode(TEST_CODE)).thenReturn(Optional.empty()); // When Optional result = shortLinkService.findByCode(TEST_CODE); // Then assertFalse(result.isPresent()); verify(repository).findByCode(TEST_CODE); } @Test @DisplayName("URL参数解析 - 编码的参数") void shouldParseEncodedParams() { String urlWithEncoded = TEST_URL + "?activityId=" + java.net.URLEncoder.encode("123", java.nio.charset.StandardCharsets.UTF_8); // When ShortLinkEntity result = shortLinkService.create(urlWithEncoded); // Then assertEquals(123L, result.getActivityId()); } @Test @DisplayName("URL参数解析 - 格式错误的参数") void shouldHandleMalformedParams() { String urlWithMalformed = TEST_URL + "?activityId=abc&inviter=xyz"; // When ShortLinkEntity result = shortLinkService.create(urlWithMalformed); // Then assertNotNull(result); // 应该忽略格式错误的参数,但不影响创建 verify(repository).save(any(ShortLinkEntity.class)); } @Test @DisplayName("生成代码的唯一性验证") void shouldGenerateUniqueCodes() { // When ShortLinkEntity result1 = shortLinkService.create(TEST_URL + "1"); ShortLinkEntity result2 = shortLinkService.create(TEST_URL + "2"); // Then assertNotEquals(result1.getCode(), result2.getCode()); verify(repository, times(2)).save(any(ShortLinkEntity.class)); } }