Skip to content

Commit 5ffb041

Browse files
committed
增加单元测试
1 parent 3d8cadb commit 5ffb041

File tree

5 files changed

+820
-32
lines changed

5 files changed

+820
-32
lines changed

src/main/resources/application.yml

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ spring:
2424
database-platform: org.hibernate.dialect.MySQL8Dialect
2525
data:
2626
redis:
27-
host: localhost
27+
host: 47.97.110.128
2828
port: 6379
29-
password: CHANGE_ME_REDIS_PASSWORD
29+
password: 123456
3030
timeout: 2s
3131
lettuce:
3232
pool:
@@ -35,35 +35,35 @@ spring:
3535
max-wait: 1s
3636
min-idle: 50
3737

38-
# RabbitMQ 配置
39-
rabbitmq:
40-
host: localhost
41-
port: 5672
42-
username: guest
43-
password: guest
44-
virtual-host: /
45-
# 生产者确认机制,保证消息不丢失
46-
publisher-confirm-type: correlated
47-
publisher-returns: true
48-
template:
49-
mandatory: true
50-
# 消费者配置
51-
listener:
52-
simple:
53-
# 手动确认模式,保证消息处理成功
54-
acknowledge-mode: manual
55-
# 并发消费者数量
56-
concurrency: 4
57-
max-concurrency: 8
58-
# 预取数量(批量消费)
59-
prefetch: 100
60-
# 重试机制
61-
retry:
62-
enabled: true
63-
max-attempts: 3
64-
initial-interval: 1000
65-
multiplier: 2
66-
max-interval: 10000
38+
# RabbitMQ 配置(暂时禁用,未安装)
39+
# rabbitmq:
40+
# host: localhost
41+
# port: 5672
42+
# username: guest
43+
# password: guest
44+
# virtual-host: /
45+
# # 生产者确认机制,保证消息不丢失
46+
# publisher-confirm-type: correlated
47+
# publisher-returns: true
48+
# template:
49+
# mandatory: true
50+
# # 消费者配置
51+
# listener:
52+
# simple:
53+
# # 手动确认模式,保证消息处理成功
54+
# acknowledge-mode: manual
55+
# # 并发消费者数量
56+
# concurrency: 4
57+
# max-concurrency: 8
58+
# # 预取数量(批量消费)
59+
# prefetch: 100
60+
# # 重试机制
61+
# retry:
62+
# enabled: true
63+
# max-attempts: 3
64+
# initial-interval: 1000
65+
# multiplier: 2
66+
# max-interval: 10000
6767

6868

6969
server:
@@ -135,8 +135,9 @@ management:
135135
events:
136136
sampleRate: 0.0
137137
# 点击计数模式:mq(消息队列,推荐) | local(本地内存) | redis(Pipeline)
138+
# 暂时使用 redis 模式,等 RabbitMQ 安装后改为 mq
138139
clicks:
139-
mode: mq
140+
mode: redis
140141

141142
cache:
142143
caffeine:
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package com.layor.tinyflow.Controller;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.layor.tinyflow.entity.*;
5+
import com.layor.tinyflow.service.ShortUrlService;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
11+
import org.springframework.boot.test.mock.mockito.MockBean;
12+
import org.springframework.data.domain.*;
13+
import org.springframework.http.MediaType;
14+
import org.springframework.security.test.context.support.WithMockUser;
15+
import org.springframework.test.web.servlet.MockMvc;
16+
17+
import java.time.LocalDateTime;
18+
import java.util.Arrays;
19+
import java.util.List;
20+
21+
import static org.mockito.ArgumentMatchers.*;
22+
import static org.mockito.Mockito.*;
23+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
24+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
25+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
26+
27+
/**
28+
* ShortUrlController 集成测试
29+
*/
30+
@WebMvcTest(ShortUrlController.class)
31+
@DisplayName("短链控制器测试")
32+
class ShortUrlControllerTest {
33+
34+
@Autowired
35+
private MockMvc mockMvc;
36+
37+
@Autowired
38+
private ObjectMapper objectMapper;
39+
40+
@MockBean
41+
private ShortUrlService shortUrlService;
42+
43+
private ShortenRequest shortenRequest;
44+
private ShortUrlDTO shortUrlDTO;
45+
private static final String TEST_LONG_URL = "https://www.example.com";
46+
private static final String TEST_SHORT_CODE = "abc123";
47+
private static final String BASE_URL = "http://localhost:8080";
48+
49+
@BeforeEach
50+
void setUp() {
51+
shortenRequest = new ShortenRequest();
52+
shortenRequest.setLongUrl(TEST_LONG_URL);
53+
54+
shortUrlDTO = ShortUrlDTO.builder()
55+
.shortCode(TEST_SHORT_CODE)
56+
.shortUrl(BASE_URL + "/" + TEST_SHORT_CODE)
57+
.longUrl(TEST_LONG_URL)
58+
.createdAt(LocalDateTime.now())
59+
.build();
60+
}
61+
62+
@Test
63+
@WithMockUser
64+
@DisplayName("POST /api/shorten - 创建短链成功")
65+
void testShorten_Success() throws Exception {
66+
// Given
67+
when(shortUrlService.createShortUrl(TEST_LONG_URL, null))
68+
.thenReturn(shortUrlDTO);
69+
70+
// When & Then
71+
mockMvc.perform(post("/api/shorten")
72+
.with(csrf())
73+
.contentType(MediaType.APPLICATION_JSON)
74+
.content(objectMapper.writeValueAsString(shortenRequest)))
75+
.andExpect(status().isOk())
76+
.andExpect(jsonPath("$.success").value(true))
77+
.andExpect(jsonPath("$.data.shortCode").value(TEST_SHORT_CODE))
78+
.andExpect(jsonPath("$.data.longUrl").value(TEST_LONG_URL))
79+
.andExpect(jsonPath("$.data.shortUrl").value(BASE_URL + "/" + TEST_SHORT_CODE));
80+
81+
verify(shortUrlService, times(1)).createShortUrl(TEST_LONG_URL, null);
82+
}
83+
84+
@Test
85+
@WithMockUser
86+
@DisplayName("POST /api/shorten - 使用自定义别名")
87+
void testShorten_WithCustomAlias() throws Exception {
88+
// Given
89+
String customAlias = "myalias";
90+
shortenRequest.setCustomAlias(customAlias);
91+
92+
ShortUrlDTO customDto = ShortUrlDTO.builder()
93+
.shortCode(customAlias)
94+
.shortUrl(BASE_URL + "/" + customAlias)
95+
.longUrl(TEST_LONG_URL)
96+
.createdAt(LocalDateTime.now())
97+
.build();
98+
99+
when(shortUrlService.createShortUrl(TEST_LONG_URL, customAlias))
100+
.thenReturn(customDto);
101+
102+
// When & Then
103+
mockMvc.perform(post("/api/shorten")
104+
.with(csrf())
105+
.contentType(MediaType.APPLICATION_JSON)
106+
.content(objectMapper.writeValueAsString(shortenRequest)))
107+
.andExpect(status().isOk())
108+
.andExpect(jsonPath("$.success").value(true))
109+
.andExpect(jsonPath("$.data.shortCode").value(customAlias));
110+
111+
verify(shortUrlService, times(1)).createShortUrl(TEST_LONG_URL, customAlias);
112+
}
113+
114+
@Test
115+
@WithMockUser
116+
@DisplayName("GET /api/urls - 获取短链列表")
117+
void testGetUrls_Success() throws Exception {
118+
// Given
119+
List<UrlListResponseDTO> urlList = Arrays.asList(
120+
UrlListResponseDTO.builder()
121+
.shortCode("code1")
122+
.longUrl("https://example1.com")
123+
.totalVisits(100L)
124+
.todayVisits(10)
125+
.createdAt(LocalDateTime.now())
126+
.build(),
127+
UrlListResponseDTO.builder()
128+
.shortCode("code2")
129+
.longUrl("https://example2.com")
130+
.totalVisits(200L)
131+
.todayVisits(20)
132+
.createdAt(LocalDateTime.now())
133+
.build()
134+
);
135+
136+
Pageable pageable = PageRequest.of(0, 10);
137+
Page<UrlListResponseDTO> page = new PageImpl<>(urlList, pageable, urlList.size());
138+
139+
when(shortUrlService.getAllUrls(0, 10)).thenReturn(page);
140+
141+
// When & Then
142+
mockMvc.perform(get("/api/urls")
143+
.param("page", "0")
144+
.param("size", "10"))
145+
.andExpect(status().isOk())
146+
.andExpect(jsonPath("$.success").value(true))
147+
.andExpect(jsonPath("$.data.content").isArray())
148+
.andExpect(jsonPath("$.data.content.length()").value(2))
149+
.andExpect(jsonPath("$.data.totalElements").value(2));
150+
151+
verify(shortUrlService, times(1)).getAllUrls(0, 10);
152+
}
153+
154+
@Test
155+
@WithMockUser
156+
@DisplayName("DELETE /api/{shortCode} - 删除短链成功")
157+
void testDeleteHistory_Success() throws Exception {
158+
// Given
159+
doNothing().when(shortUrlService).deleteByShortCode(TEST_SHORT_CODE);
160+
161+
// When & Then
162+
mockMvc.perform(delete("/api/" + TEST_SHORT_CODE)
163+
.with(csrf()))
164+
.andExpect(status().isOk());
165+
166+
verify(shortUrlService, times(1)).deleteByShortCode(TEST_SHORT_CODE);
167+
}
168+
169+
@Test
170+
@WithMockUser
171+
@DisplayName("PUT /api/{shortCode} - 更新短链别名")
172+
void testUpdateShortUrl_Success() throws Exception {
173+
// Given
174+
String newAlias = "newalias";
175+
shortenRequest.setShortCode(TEST_SHORT_CODE);
176+
shortenRequest.setCustomAlias(newAlias);
177+
178+
doNothing().when(shortUrlService).updateShortUrl(TEST_SHORT_CODE, newAlias);
179+
180+
// When & Then
181+
mockMvc.perform(put("/api/" + TEST_SHORT_CODE)
182+
.with(csrf())
183+
.contentType(MediaType.APPLICATION_JSON)
184+
.content(objectMapper.writeValueAsString(shortenRequest)))
185+
.andExpect(status().isOk());
186+
187+
verify(shortUrlService, times(1)).updateShortUrl(TEST_SHORT_CODE, newAlias);
188+
}
189+
190+
@Test
191+
@WithMockUser
192+
@DisplayName("GET /api/urls/click-stats - 获取点击统计")
193+
void testGetUrlClickStats_Success() throws Exception {
194+
// Given
195+
List<UrlClickStatsDTO> stats = Arrays.asList(
196+
new UrlClickStatsDTO("code1", 100L, 10),
197+
new UrlClickStatsDTO("code2", 200L, 20)
198+
);
199+
200+
when(shortUrlService.getUrlClickStats()).thenReturn(stats);
201+
202+
// When & Then
203+
mockMvc.perform(get("/api/urls/click-stats"))
204+
.andExpect(status().isOk())
205+
.andExpect(jsonPath("$.success").value(true))
206+
.andExpect(jsonPath("$.data").isArray())
207+
.andExpect(jsonPath("$.data.length()").value(2));
208+
209+
verify(shortUrlService, times(1)).getUrlClickStats();
210+
}
211+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.layor.tinyflow.Strategy;
2+
3+
import org.hashids.Hashids;
4+
import org.junit.jupiter.api.BeforeEach;
5+
import org.junit.jupiter.api.DisplayName;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.ExtendWith;
8+
import org.mockito.InjectMocks;
9+
import org.mockito.Mock;
10+
import org.mockito.junit.jupiter.MockitoExtension;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
import static org.mockito.Mockito.*;
14+
15+
/**
16+
* Hashids短码生成策略测试
17+
*/
18+
@ExtendWith(MockitoExtension.class)
19+
@DisplayName("Hashids策略测试")
20+
class HashidsStrategyTest {
21+
22+
@Mock
23+
private Hashids hashids;
24+
25+
@InjectMocks
26+
private HashidsStrategy hashidsStrategy;
27+
28+
@Test
29+
@DisplayName("编码ID - 成功")
30+
void testEncode_Success() {
31+
// Given
32+
long testId = 12345L;
33+
String expectedCode = "abc123";
34+
when(hashids.encode(testId)).thenReturn(expectedCode);
35+
36+
// When
37+
String result = hashidsStrategy.encode(testId);
38+
39+
// Then
40+
assertEquals(expectedCode, result);
41+
verify(hashids, times(1)).encode(testId);
42+
}
43+
44+
@Test
45+
@DisplayName("编码不同ID - 生成不同短码")
46+
void testEncode_DifferentIds() {
47+
// Given
48+
long id1 = 123L;
49+
long id2 = 456L;
50+
String code1 = "abc";
51+
String code2 = "def";
52+
53+
when(hashids.encode(id1)).thenReturn(code1);
54+
when(hashids.encode(id2)).thenReturn(code2);
55+
56+
// When
57+
String result1 = hashidsStrategy.encode(id1);
58+
String result2 = hashidsStrategy.encode(id2);
59+
60+
// Then
61+
assertNotEquals(result1, result2);
62+
assertEquals(code1, result1);
63+
assertEquals(code2, result2);
64+
}
65+
}

0 commit comments

Comments
 (0)