13404089107
2026-04-24 8b81df3a7638e768b640b7935d9da52b77cd7649
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package com.tcm.training.admin.app.auth.service;
 
import com.tcm.training.admin.app.auth.config.AppSmsAuthProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
 
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
 
/**
 * APP 短信验证码场景化调试配置测试。
 */
@ExtendWith(MockitoExtension.class)
class AppSmsCodeServiceTest {
 
    @Mock
    private StringRedisTemplate stringRedisTemplate;
 
    @Mock
    private ValueOperations<String, String> valueOperations;
 
    @Mock
    private AppSmsSender appSmsSender;
 
    private AppSmsAuthProperties appSmsAuthProperties;
 
    private AppSmsCodeService appSmsCodeService;
 
    /**
     * 每个用例前重新构造短信验证码服务。
     */
    @BeforeEach
    void setUp() {
        appSmsAuthProperties = new AppSmsAuthProperties();
        appSmsAuthProperties.getLogin().setEnabled(true);
        appSmsAuthProperties.getLogin().setCode("666666");
        appSmsAuthProperties.getRegister().setEnabled(false);
        appSmsAuthProperties.getRegister().setCode("");
        appSmsAuthProperties.getPasswordReset().setEnabled(false);
        appSmsAuthProperties.getPasswordReset().setCode("");
        appSmsAuthProperties.getPhoneChange().setEnabled(false);
        appSmsAuthProperties.getPhoneChange().setCode("");
 
        appSmsCodeService = new AppSmsCodeService(
                stringRedisTemplate,
                appSmsSender,
                appSmsAuthProperties
        );
    }
 
    /**
     * 仅开启登录调试验证码时,666666 应允许短信登录直接通过。
     */
    @Test
    void validateLoginCodeShouldAllowConfiguredDebugCode() {
        assertDoesNotThrow(() -> appSmsCodeService.validateLoginCode("13800138000", "666666"));
        verify(stringRedisTemplate, never()).opsForValue();
    }
 
    /**
     * 未开启注册调试验证码时,666666 不应绕过真实验证码校验。
     */
    @Test
    void validateRegisterCodeShouldRejectDebugCodeWhenSceneDisabled() {
        when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.get(anyString())).thenReturn(null);
 
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
                () -> appSmsCodeService.validateRegisterCode("13800138000", "666666"));
 
        assertEquals("验证码已失效,请重新获取", exception.getMessage());
    }
 
    /**
     * 验证码校验成功后不应删除缓存,保证 5 分钟有效期内可重复提交。
     */
    @Test
    void validateLoginCodeShouldKeepCachedCodeAfterSuccess() {
        when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.get("app:sms:login:13800138000")).thenReturn("123456");
 
        assertDoesNotThrow(() -> appSmsCodeService.validateLoginCode("13800138000", "123456"));
 
        verify(stringRedisTemplate, never()).delete(anyString());
    }
 
    /**
     * 同一验证码在缓存 TTL 内多次提交都应校验通过。
     */
    @Test
    void validateLoginCodeShouldAllowRepeatedSubmissionsWithinTtl() {
        when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.get("app:sms:login:13800138000")).thenReturn("123456");
 
        assertDoesNotThrow(() -> appSmsCodeService.validateLoginCode("13800138000", "123456"));
        assertDoesNotThrow(() -> appSmsCodeService.validateLoginCode("13800138000", "123456"));
 
        verify(stringRedisTemplate, never()).delete(anyString());
    }
 
    /**
     * 缓存中存在验证码但输入不一致时仍应提示验证码错误。
     */
    @Test
    void validateLoginCodeShouldRejectMismatchedCode() {
        when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.get("app:sms:login:13800138000")).thenReturn("123456");
 
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
                () -> appSmsCodeService.validateLoginCode("13800138000", "654321"));
 
        assertEquals("验证码错误", exception.getMessage());
        verify(stringRedisTemplate, never()).delete(anyString());
    }
 
    /**
     * 短信发送成功后才应写入验证码缓存。
     */
    @Test
    void createLoginCodeShouldCacheCodeAfterSendSucceeds() {
        when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations);
 
        appSmsCodeService.createLoginCode("13800138000");
 
        verify(appSmsSender).sendLoginCode(eq("13800138000"), anyString());
        verify(valueOperations).set(anyString(), anyString(), anyLong(), eq(java.util.concurrent.TimeUnit.SECONDS));
    }
 
    /**
     * 短信发送失败时不应写入验证码缓存。
     */
    @Test
    void createLoginCodeShouldNotCacheCodeWhenSendFails() {
        org.mockito.Mockito.doThrow(new IllegalStateException("短信发送失败,请稍后重试"))
                .when(appSmsSender).sendLoginCode(eq("13800138000"), anyString());
 
        IllegalStateException exception = assertThrows(IllegalStateException.class,
                () -> appSmsCodeService.createLoginCode("13800138000"));
 
        assertEquals("短信发送失败,请稍后重试", exception.getMessage());
        verify(valueOperations, never()).set(anyString(), anyString(), anyLong(), eq(java.util.concurrent.TimeUnit.SECONDS));
    }
}