liujie
9 小时以前 fddf3d913d47f391f84f9e460cb76d427ef02c44
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
package com.ruoyi.user.service.impl;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
 
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.user.config.UniversalCodeProperties;
import com.ruoyi.user.domain.AccountUser;
import com.ruoyi.user.domain.vo.AccountUserInfoVo;
import com.ruoyi.user.domain.vo.EarningSummaryVo;
import com.ruoyi.user.enums.MemberLevelEnum;
import com.ruoyi.user.mapper.AccountEarningRecordMapper;
import com.ruoyi.user.mapper.AccountUserMapper;
 
/**
 * 会员服务单元测试
 *
 * 覆盖会员注册(通用推荐码放行、无效/停用推荐码拒绝、正常绑定推荐人)
 * 与个人中心会员信息查询(正常返回、入参/会员缺失校验、收益与等级兜底)。
 *
 * @author hugray
 */
@ExtendWith(MockitoExtension.class)
class AccountUserServiceImplTest
{
    private static final String PHONE = "13800000000";
 
    private static final String UNIVERSAL_CODE = "HUGRAY88";
 
    @Mock
    private AccountUserMapper accountUserMapper;
 
    @Mock
    private AccountEarningRecordMapper accountEarningRecordMapper;
 
    @Mock
    private UniversalCodeProperties universalCodeProperties;
 
    @InjectMocks
    private AccountUserServiceImpl accountUserService;
 
    @Test
    @DisplayName("命中通用推荐码应注册成功且不绑定推荐人")
    void registerMember_shouldNotBindReferrer_whenUniversalCodeHit()
    {
        when(universalCodeProperties.getUniversalReferralCode()).thenReturn(UNIVERSAL_CODE);
        when(accountUserMapper.selectAccountUserByPhone(PHONE)).thenReturn(null);
        when(accountUserMapper.selectAccountUserByReferralCode(anyString())).thenReturn(null);
        when(accountUserMapper.insertAccountUser(any(AccountUser.class))).thenReturn(1);
 
        accountUserService.registerMember(PHONE, UNIVERSAL_CODE);
 
        ArgumentCaptor<AccountUser> captor = ArgumentCaptor.forClass(AccountUser.class);
        verify(accountUserMapper).insertAccountUser(captor.capture());
        AccountUser saved = captor.getValue();
        assertNull(saved.getReferrerId());
        assertNotNull(saved.getMyReferralCode());
        // 姓名注册未采集,落库留空(不再以手机号填充)
        assertNull(saved.getNickname());
        // 通用推荐码注册直接发放 L4 会员等级
        assertEquals(MemberLevelEnum.L4.getCode(), saved.getSystemLevel());
        // 通用码不参与推荐人定位
        verify(accountUserMapper, never()).selectAccountUserByReferralCode(UNIVERSAL_CODE);
    }
 
    @Test
    @DisplayName("未配置通用推荐码且推荐码无效时应拒绝注册")
    void registerMember_shouldThrow_whenReferralCodeInvalid()
    {
        when(universalCodeProperties.getUniversalReferralCode()).thenReturn(null);
        when(accountUserMapper.selectAccountUserByPhone(PHONE)).thenReturn(null);
        when(accountUserMapper.selectAccountUserByReferralCode("BADCODE")).thenReturn(null);
 
        ServiceException ex = assertThrows(ServiceException.class,
            () -> accountUserService.registerMember(PHONE, "BADCODE"));
        assertEquals("推荐人码无效", ex.getMessage());
    }
 
    @Test
    @DisplayName("推荐人账号停用时应拒绝注册")
    void registerMember_shouldThrow_whenReferrerDisabled()
    {
        AccountUser referrer = new AccountUser();
        referrer.setUserId(100L);
        referrer.setStatus(UserConstants.USER_DISABLE);
        when(universalCodeProperties.getUniversalReferralCode()).thenReturn(null);
        when(accountUserMapper.selectAccountUserByPhone(PHONE)).thenReturn(null);
        when(accountUserMapper.selectAccountUserByReferralCode("VALID123")).thenReturn(referrer);
 
        ServiceException ex = assertThrows(ServiceException.class,
            () -> accountUserService.registerMember(PHONE, "VALID123"));
        assertEquals("推荐人账号异常", ex.getMessage());
    }
 
    @Test
    @DisplayName("推荐码有效时应绑定推荐人")
    void registerMember_shouldBindReferrer_whenReferralCodeValid()
    {
        AccountUser referrer = new AccountUser();
        referrer.setUserId(100L);
        referrer.setStatus(UserConstants.NORMAL);
        when(universalCodeProperties.getUniversalReferralCode()).thenReturn(null);
        when(accountUserMapper.selectAccountUserByPhone(PHONE)).thenReturn(null);
        // 仅 "VALID123" 命中推荐人;其余(本人推荐码查重)返回 null,避免影响唯一码生成
        when(accountUserMapper.selectAccountUserByReferralCode(anyString()))
            .thenAnswer(invocation -> "VALID123".equals(invocation.getArgument(0)) ? referrer : null);
        when(accountUserMapper.insertAccountUser(any(AccountUser.class))).thenReturn(1);
 
        accountUserService.registerMember(PHONE, "VALID123");
 
        ArgumentCaptor<AccountUser> captor = ArgumentCaptor.forClass(AccountUser.class);
        verify(accountUserMapper).insertAccountUser(captor.capture());
        assertEquals(100L, captor.getValue().getReferrerId());
        // 非通用码注册不设等级,走库默认值 C(普通用户)
        assertNull(captor.getValue().getSystemLevel());
    }
 
    @Test
    @DisplayName("会员存在时应返回昵称/头像/等级与收益汇总")
    void getAccountUserInfo_shouldReturnProfile_whenMemberExists()
    {
        AccountUser member = new AccountUser();
        member.setUserId(66L);
        member.setNickname("辉煌G891");
        member.setPhone("13355555555");
        member.setAvatar("https://cdn.example.com/a.png");
        member.setSystemLevel(MemberLevelEnum.L3.getCode());
        member.setAuthStatus("2");
        EarningSummaryVo summary = new EarningSummaryVo(1000L, 40000L, 12800000L);
        when(accountUserMapper.selectAccountUserById(66L)).thenReturn(member);
        when(accountEarningRecordMapper.selectEarningSummary(66L)).thenReturn(summary);
 
        AccountUserInfoVo vo = accountUserService.getAccountUserInfo(66L);
 
        assertEquals(66L, vo.getUserId());
        assertEquals("辉煌G891", vo.getNickname());
        assertEquals("133****5555", vo.getPhone());
        assertEquals("https://cdn.example.com/a.png", vo.getAvatar());
        assertEquals(MemberLevelEnum.L3.getCode(), vo.getMemberLevel());
        // 认证状态原样下发,供个人中心「实名认证」行徽标回显
        assertEquals("2", vo.getAuthStatus());
        assertEquals(1000L, vo.getEarnings().getToday());
        assertEquals(40000L, vo.getEarnings().getMonth());
        assertEquals(12800000L, vo.getEarnings().getTotal());
    }
 
    @Test
    @DisplayName("会员ID为空时应抛出获取会员信息失败")
    void getAccountUserInfo_shouldThrow_whenUserIdNull()
    {
        ServiceException ex = assertThrows(ServiceException.class,
            () -> accountUserService.getAccountUserInfo(null));
        assertEquals("获取会员信息失败", ex.getMessage());
    }
 
    @Test
    @DisplayName("会员不存在时应抛出获取会员信息失败")
    void getAccountUserInfo_shouldThrow_whenMemberNotFound()
    {
        when(accountUserMapper.selectAccountUserById(66L)).thenReturn(null);
 
        ServiceException ex = assertThrows(ServiceException.class,
            () -> accountUserService.getAccountUserInfo(66L));
        assertEquals("获取会员信息失败", ex.getMessage());
    }
 
    @Test
    @DisplayName("无收益流水时应兜底返回全零汇总")
    void getAccountUserInfo_shouldFallbackZeroEarnings_whenNoRecord()
    {
        AccountUser member = new AccountUser();
        member.setUserId(66L);
        member.setSystemLevel(MemberLevelEnum.L1.getCode());
        when(accountUserMapper.selectAccountUserById(66L)).thenReturn(member);
        when(accountEarningRecordMapper.selectEarningSummary(66L)).thenReturn(null);
 
        AccountUserInfoVo vo = accountUserService.getAccountUserInfo(66L);
 
        assertNotNull(vo.getEarnings());
        assertEquals(0L, vo.getEarnings().getToday());
        assertEquals(0L, vo.getEarnings().getMonth());
        assertEquals(0L, vo.getEarnings().getTotal());
    }
 
    @Test
    @DisplayName("等级为空时应兜底为 C")
    void getAccountUserInfo_shouldFallbackC_whenSystemLevelBlank()
    {
        assertFallbackLevel(null, MemberLevelEnum.C.getCode());
        assertFallbackLevel("", MemberLevelEnum.C.getCode());
    }
 
    @Test
    @DisplayName("等级为非法值时应兜底为 C")
    void getAccountUserInfo_shouldFallbackC_whenSystemLevelIllegal()
    {
        assertFallbackLevel("L9", MemberLevelEnum.C.getCode());
    }
 
    @Test
    @DisplayName("认证状态为空时应兜底为未认证(0)")
    void getAccountUserInfo_shouldFallbackNoneAuthStatus_whenBlank()
    {
        AccountUser member = new AccountUser();
        member.setUserId(66L);
        member.setSystemLevel(MemberLevelEnum.C.getCode());
        when(accountUserMapper.selectAccountUserById(66L)).thenReturn(member);
        when(accountEarningRecordMapper.selectEarningSummary(66L)).thenReturn(EarningSummaryVo.zero());
 
        AccountUserInfoVo vo = accountUserService.getAccountUserInfo(66L);
 
        assertEquals("0", vo.getAuthStatus());
    }
 
    /**
     * 校验不同等级取值下的等级兜底结果
     *
     * @param rawLevel 库中原始等级值
     * @param expectedLevel 期望下发的等级
     */
    private void assertFallbackLevel(String rawLevel, String expectedLevel)
    {
        AccountUser member = new AccountUser();
        member.setUserId(66L);
        member.setSystemLevel(rawLevel);
        when(accountUserMapper.selectAccountUserById(66L)).thenReturn(member);
        when(accountEarningRecordMapper.selectEarningSummary(66L)).thenReturn(EarningSummaryVo.zero());
 
        AccountUserInfoVo vo = accountUserService.getAccountUserInfo(66L);
 
        assertEquals(expectedLevel, vo.getMemberLevel());
    }
 
    @Test
    @DisplayName("更新头像时应只写入 avatar 列")
    void updateAvatar_shouldUpdateAvatar_whenValid()
    {
        when(accountUserMapper.updateUserAvatar(any(AccountUser.class))).thenReturn(1);
 
        accountUserService.updateAvatar(66L, "https://cdn.example.com/new.png");
 
        ArgumentCaptor<AccountUser> captor = ArgumentCaptor.forClass(AccountUser.class);
        verify(accountUserMapper).updateUserAvatar(captor.capture());
        assertEquals(66L, captor.getValue().getUserId());
        assertEquals("https://cdn.example.com/new.png", captor.getValue().getAvatar());
    }
 
    @Test
    @DisplayName("头像地址为空时应抛出提示且不落库")
    void updateAvatar_shouldThrow_whenAvatarBlank()
    {
        ServiceException ex = assertThrows(ServiceException.class,
            () -> accountUserService.updateAvatar(66L, ""));
 
        assertEquals("头像地址不能为空", ex.getMessage());
        verify(accountUserMapper, never()).updateUserAvatar(any(AccountUser.class));
    }
 
    @Test
    @DisplayName("头像地址超长时应抛出提示且不落库")
    void updateAvatar_shouldThrow_whenAvatarTooLong()
    {
        String longUrl = "a".repeat(256);
 
        ServiceException ex = assertThrows(ServiceException.class,
            () -> accountUserService.updateAvatar(66L, longUrl));
 
        assertEquals("头像地址过长", ex.getMessage());
        verify(accountUserMapper, never()).updateUserAvatar(any(AccountUser.class));
    }
 
    @Test
    @DisplayName("会员不存在(影响行数为 0)时应抛出更新失败")
    void updateAvatar_shouldThrow_whenMemberNotFound()
    {
        when(accountUserMapper.updateUserAvatar(any(AccountUser.class))).thenReturn(0);
 
        ServiceException ex = assertThrows(ServiceException.class,
            () -> accountUserService.updateAvatar(66L, "https://cdn.example.com/new.png"));
 
        assertEquals("更新头像失败", ex.getMessage());
    }
 
    @Test
    @DisplayName("会员ID为空时应抛出更新失败")
    void updateAvatar_shouldThrow_whenUserIdNull()
    {
        ServiceException ex = assertThrows(ServiceException.class,
            () -> accountUserService.updateAvatar(null, "https://cdn.example.com/new.png"));
 
        assertEquals("更新头像失败", ex.getMessage());
    }
}