hejianhao
2026-08-28 16a57a8fc6868cea9171a1119698572407a2fc03
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
package com.tcm.training.admin.app.course.service;
 
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.tcm.training.admin.app.auth.service.AppAuthSessionService;
import com.tcm.training.admin.app.course.dto.AppHealthCoursePurchaseRequest;
import com.tcm.training.admin.app.course.vo.AppHealthCoursePurchaseResultVO;
import com.tcm.training.admin.live.entity.CoursePurchaseOrder;
import com.tcm.training.admin.live.entity.Expert;
import com.tcm.training.admin.live.entity.HealthCourse;
import com.tcm.training.admin.live.mapper.HealthCourseDownloadRecordMapper;
import com.tcm.training.admin.live.entity.HealthCourseFavoriteRecord;
import com.tcm.training.admin.live.entity.HealthCourseMemberJoinRecord;
import com.tcm.training.admin.live.entity.HealthCourseVideo;
import com.tcm.training.admin.live.mapper.CoursePurchaseOrderMapper;
import com.tcm.training.admin.live.mapper.ExpertMapper;
import com.tcm.training.admin.live.mapper.HealthCourseFavoriteRecordMapper;
import com.tcm.training.admin.live.mapper.HealthCourseMapper;
import com.tcm.training.admin.live.mapper.HealthCourseMemberJoinRecordMapper;
import com.tcm.training.admin.live.mapper.HealthCoursePlayRecordMapper;
import com.tcm.training.admin.live.mapper.HealthCourseVideoMapper;
import com.tcm.training.admin.live.mapper.LiveCourseCategoryMapper;
import com.tcm.training.admin.live.service.HealthCourseAppService;
import com.tcm.training.admin.live.vo.HealthCourseDetailVO;
import com.tcm.training.admin.member.entity.AppUser;
import com.tcm.training.admin.member.mapper.UserMapper;
import com.tcm.training.admin.system.service.SystemSettingsAppService;
import com.tcm.training.admin.system.vo.SystemSettingsVO;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
 
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
 
/**
 * APP 养生课程详情与购买服务测试。
 */
@ExtendWith(MockitoExtension.class)
class AppHealthCourseServiceTest {
 
    @BeforeAll
    static void initLambdaCache() {
        MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
        TableInfoHelper.initTableInfo(assistant, HealthCourse.class);
        TableInfoHelper.initTableInfo(assistant, HealthCourseVideo.class);
        TableInfoHelper.initTableInfo(assistant, Expert.class);
        TableInfoHelper.initTableInfo(assistant, HealthCourseFavoriteRecord.class);
        TableInfoHelper.initTableInfo(assistant, HealthCourseMemberJoinRecord.class);
        TableInfoHelper.initTableInfo(assistant, CoursePurchaseOrder.class);
        TableInfoHelper.initTableInfo(assistant, AppUser.class);
    }
 
    @Mock
    private HealthCourseAppService healthCourseAppService;
 
    @Mock
    private HealthCourseMapper healthCourseMapper;
 
    @Mock
    private HealthCourseVideoMapper healthCourseVideoMapper;
 
    @Mock
    private LiveCourseCategoryMapper liveCourseCategoryMapper;
 
    @Mock
    private ExpertMapper expertMapper;
 
    @Mock
    private HealthCourseDownloadRecordMapper healthCourseDownloadRecordMapper;
 
    @Mock
    private HealthCourseFavoriteRecordMapper healthCourseFavoriteRecordMapper;
 
    @Mock
    private HealthCourseMemberJoinRecordMapper healthCourseMemberJoinRecordMapper;
 
    @Mock
    private HealthCoursePlayRecordMapper healthCoursePlayRecordMapper;
 
    @Mock
    private AppAuthSessionService appAuthSessionService;
 
    @Mock
    private CoursePurchaseOrderMapper coursePurchaseOrderMapper;
 
    @Mock
    private UserMapper userMapper;
 
    @Mock
    private SystemSettingsAppService systemSettingsAppService;
 
    @Captor
    private ArgumentCaptor<CoursePurchaseOrder> coursePurchaseOrderCaptor;
 
    @Captor
    private ArgumentCaptor<HealthCourseMemberJoinRecord> memberJoinRecordCaptor;
 
    private AppHealthCourseService appHealthCourseService;
 
    @BeforeEach
    void setUp() {
        appHealthCourseService = new AppHealthCourseService(
                healthCourseAppService,
                healthCourseMapper,
                healthCourseVideoMapper,
                liveCourseCategoryMapper,
                expertMapper,
                healthCourseDownloadRecordMapper,
                healthCourseFavoriteRecordMapper,
                healthCourseMemberJoinRecordMapper,
                healthCoursePlayRecordMapper,
                appAuthSessionService,
                coursePurchaseOrderMapper,
                userMapper,
                systemSettingsAppService
        );
    }
 
    /**
     * 已购整课时详情页应回显整课购买态并解锁全部小节。
     */
    @Test
    void detailShouldUnlockAllVideosWhenCourseAlreadyPurchased() {
        when(healthCourseAppService.detail(1L)).thenReturn(buildCourseDetail(1L));
        when(healthCourseVideoMapper.selectList(any())).thenReturn(Arrays.asList(
                buildVideo(11L, 1L, BigDecimal.ZERO, "免费小节"),
                buildVideo(12L, 1L, new BigDecimal("99.00"), "付费小节")
        ));
        when(appAuthSessionService.isLogin()).thenReturn(true);
        when(appAuthSessionService.getLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildMemberUser(200L));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(buildCourseOrder(1L, 200L));
        when(healthCourseMemberJoinRecordMapper.selectOne(any())).thenReturn(null);
        when(healthCourseFavoriteRecordMapper.selectOne(any())).thenReturn(null);
        when(systemSettingsAppService.current()).thenReturn(buildSystemSettings("400-800-9000"));
 
        com.tcm.training.admin.app.course.vo.AppHealthCourseDetailVO detailVO = appHealthCourseService.detail(1L);
 
        assertEquals("400-800-9000", detailVO.getServicePhone());
        assertTrue(Boolean.TRUE.equals(detailVO.getIsMember()));
        assertFalse(Boolean.TRUE.equals(detailVO.getMemberCourseJoined()));
        assertTrue(Boolean.TRUE.equals(detailVO.getCourseOwned()));
        assertTrue(Boolean.TRUE.equals(detailVO.getCoursePurchased()));
        assertTrue(Boolean.TRUE.equals(detailVO.getVideos().get(0).getPurchased()));
        assertTrue(Boolean.TRUE.equals(detailVO.getVideos().get(1).getPurchased()));
        assertFalse(Boolean.TRUE.equals(detailVO.getVideos().get(1).getLocked()));
    }
 
    /**
     * 仅购买单小节时详情页只应解锁对应小节。
     */
    @Test
    void detailShouldUnlockOnlyPurchasedChapterWhenOnlyVideoPurchased() {
        when(healthCourseAppService.detail(1L)).thenReturn(buildCourseDetail(1L));
        when(healthCourseVideoMapper.selectList(any())).thenReturn(Arrays.asList(
                buildVideo(11L, 1L, BigDecimal.ZERO, "免费小节"),
                buildVideo(12L, 1L, new BigDecimal("99.00"), "已购付费小节"),
                buildVideo(13L, 1L, new BigDecimal("199.00"), "未购付费小节")
        ));
        when(appAuthSessionService.isLogin()).thenReturn(true);
        when(appAuthSessionService.getLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildNormalUser(200L));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(null);
        when(coursePurchaseOrderMapper.selectList(any())).thenReturn(Collections.singletonList(buildVideoOrder(1L, 200L, 12L)));
        when(healthCourseMemberJoinRecordMapper.selectOne(any())).thenReturn(null);
        when(healthCourseFavoriteRecordMapper.selectOne(any())).thenReturn(null);
        when(systemSettingsAppService.current()).thenReturn(buildSystemSettings("400-800-9000"));
 
        com.tcm.training.admin.app.course.vo.AppHealthCourseDetailVO detailVO = appHealthCourseService.detail(1L);
 
        assertFalse(Boolean.TRUE.equals(detailVO.getCoursePurchased()));
        assertFalse(Boolean.TRUE.equals(detailVO.getCourseOwned()));
        assertTrue(Boolean.TRUE.equals(detailVO.getVideos().get(1).getPurchased()));
        assertFalse(Boolean.TRUE.equals(detailVO.getVideos().get(1).getLocked()));
        assertFalse(Boolean.TRUE.equals(detailVO.getVideos().get(2).getPurchased()));
        assertTrue(Boolean.TRUE.equals(detailVO.getVideos().get(2).getLocked()));
    }
 
    /**
     * 有效会员未拥有课程时详情页应返回会员态但保持未拥有。
     */
    @Test
    void detailShouldExposeMemberStatusWhenMemberHasNotOwnedCourse() {
        when(healthCourseAppService.detail(1L)).thenReturn(buildCourseDetail(1L));
        when(healthCourseVideoMapper.selectList(any())).thenReturn(Collections.singletonList(
                buildVideo(12L, 1L, new BigDecimal("99.00"), "付费小节")
        ));
        when(appAuthSessionService.isLogin()).thenReturn(true);
        when(appAuthSessionService.getLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildMemberUser(200L));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(null);
        when(coursePurchaseOrderMapper.selectList(any())).thenReturn(Collections.emptyList());
        when(healthCourseMemberJoinRecordMapper.selectOne(any())).thenReturn(null);
        when(healthCourseFavoriteRecordMapper.selectOne(any())).thenReturn(null);
        when(systemSettingsAppService.current()).thenReturn(buildSystemSettings("400-800-9000"));
 
        com.tcm.training.admin.app.course.vo.AppHealthCourseDetailVO detailVO = appHealthCourseService.detail(1L);
 
        assertTrue(Boolean.TRUE.equals(detailVO.getIsMember()));
        assertFalse(Boolean.TRUE.equals(detailVO.getMemberCourseJoined()));
        assertFalse(Boolean.TRUE.equals(detailVO.getCourseOwned()));
        assertFalse(Boolean.TRUE.equals(detailVO.getCoursePurchased()));
        assertTrue(Boolean.TRUE.equals(detailVO.getVideos().get(0).getLocked()));
    }
 
    /**
     * 会员已加课时详情页应将整课视为已拥有并解锁全部小节。
     */
    @Test
    void detailShouldTreatMemberJoinedCourseAsOwned() {
        when(healthCourseAppService.detail(1L)).thenReturn(buildCourseDetail(1L));
        when(healthCourseVideoMapper.selectList(any())).thenReturn(Arrays.asList(
                buildVideo(11L, 1L, BigDecimal.ZERO, "免费小节"),
                buildVideo(12L, 1L, new BigDecimal("99.00"), "付费小节")
        ));
        when(appAuthSessionService.isLogin()).thenReturn(true);
        when(appAuthSessionService.getLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildMemberUser(200L));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(null);
        when(healthCourseMemberJoinRecordMapper.selectOne(any())).thenReturn(buildMemberJoinRecord(1L, 200L));
        when(healthCourseFavoriteRecordMapper.selectOne(any())).thenReturn(null);
        when(systemSettingsAppService.current()).thenReturn(null);
 
        com.tcm.training.admin.app.course.vo.AppHealthCourseDetailVO detailVO = appHealthCourseService.detail(1L);
 
        assertEquals("", detailVO.getServicePhone());
        assertTrue(Boolean.TRUE.equals(detailVO.getIsMember()));
        assertTrue(Boolean.TRUE.equals(detailVO.getMemberCourseJoined()));
        assertTrue(Boolean.TRUE.equals(detailVO.getCourseOwned()));
        assertFalse(Boolean.TRUE.equals(detailVO.getCoursePurchased()));
        assertTrue(Boolean.TRUE.equals(detailVO.getVideos().get(1).getPurchased()));
        assertFalse(Boolean.TRUE.equals(detailVO.getVideos().get(1).getLocked()));
    }
 
    /**
     * 首次购买整课时应直接写入已支付订单。
     */
    @Test
    void purchaseCourseShouldInsertPaidOrderWhenFirstTime() {
        when(appAuthSessionService.requireLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildNormalUser(200L));
        when(healthCourseMapper.selectById(1L)).thenReturn(buildCourseEntity(1L, new BigDecimal("599.80")));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(null);
        when(healthCourseVideoMapper.selectList(any())).thenReturn(Arrays.asList(
                buildVideo(11L, 1L, BigDecimal.ZERO, "免费小节"),
                buildVideo(12L, 1L, new BigDecimal("99.00"), "付费小节")
        ));
        AppHealthCoursePurchaseRequest request = new AppHealthCoursePurchaseRequest();
        request.setPaymentMethod("WECHAT");
 
        AppHealthCoursePurchaseResultVO resultVO = appHealthCourseService.purchaseCourse(1L, request);
 
        verify(coursePurchaseOrderMapper).insert(coursePurchaseOrderCaptor.capture());
        assertEquals("COURSE", coursePurchaseOrderCaptor.getValue().getPurchaseTargetType());
        assertEquals("WECHAT", coursePurchaseOrderCaptor.getValue().getPaymentMethod());
        assertEquals("PAID", coursePurchaseOrderCaptor.getValue().getStatus());
        assertTrue(Boolean.TRUE.equals(resultVO.getCoursePurchased()));
        assertEquals(2, resultVO.getPurchasedVideoIds().size());
    }
 
    /**
     * 首次购买付费小节时应写入小节已支付订单。
     */
    @Test
    void purchaseVideoShouldInsertPaidOrderWhenFirstTime() {
        when(appAuthSessionService.requireLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildNormalUser(200L));
        when(healthCourseMapper.selectById(1L)).thenReturn(buildCourseEntity(1L, new BigDecimal("599.80")));
        when(healthCourseVideoMapper.selectById(12L)).thenReturn(buildVideo(12L, 1L, new BigDecimal("99.00"), "付费小节"));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(null);
        when(coursePurchaseOrderMapper.selectList(any())).thenReturn(Collections.singletonList(buildVideoOrder(1L, 200L, 12L)));
        AppHealthCoursePurchaseRequest request = new AppHealthCoursePurchaseRequest();
        request.setPaymentMethod("ALIPAY");
 
        AppHealthCoursePurchaseResultVO resultVO = appHealthCourseService.purchaseVideo(1L, 12L, request);
 
        verify(coursePurchaseOrderMapper).insert(coursePurchaseOrderCaptor.capture());
        assertEquals("COURSE_CHAPTER", coursePurchaseOrderCaptor.getValue().getPurchaseTargetType());
        assertEquals(Long.valueOf(12L), coursePurchaseOrderCaptor.getValue().getCourseVideoId());
        assertEquals("ALIPAY", coursePurchaseOrderCaptor.getValue().getPaymentMethod());
        assertFalse(Boolean.TRUE.equals(resultVO.getCoursePurchased()));
        assertEquals(Collections.singletonList(12L), resultVO.getPurchasedVideoIds());
    }
 
    /**
     * 已买整课后再买小节时应直接返回整课已购结果而不重复插单。
     */
    @Test
    void purchaseVideoShouldBeIdempotentWhenCourseAlreadyPurchased() {
        when(appAuthSessionService.requireLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildNormalUser(200L));
        when(healthCourseMapper.selectById(1L)).thenReturn(buildCourseEntity(1L, new BigDecimal("599.80")));
        when(healthCourseVideoMapper.selectById(12L)).thenReturn(buildVideo(12L, 1L, new BigDecimal("99.00"), "付费小节"));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(buildCourseOrder(1L, 200L));
        when(healthCourseVideoMapper.selectList(any())).thenReturn(Arrays.asList(
                buildVideo(11L, 1L, BigDecimal.ZERO, "免费小节"),
                buildVideo(12L, 1L, new BigDecimal("99.00"), "付费小节")
        ));
        AppHealthCoursePurchaseRequest request = new AppHealthCoursePurchaseRequest();
        request.setPaymentMethod("WECHAT");
 
        AppHealthCoursePurchaseResultVO resultVO = appHealthCourseService.purchaseVideo(1L, 12L, request);
 
        verify(coursePurchaseOrderMapper, never()).insert(any());
        assertTrue(Boolean.TRUE.equals(resultVO.getCoursePurchased()));
        assertEquals(2, resultVO.getPurchasedVideoIds().size());
    }
 
    /**
     * 非会员执行会员加课时应直接报错。
     */
    @Test
    void memberJoinCourseShouldFailWhenUserIsNotMember() {
        when(appAuthSessionService.requireLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildNormalUser(200L));
        when(healthCourseMapper.selectById(1L)).thenReturn(buildCourseEntity(1L, new BigDecimal("599.80")));
 
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
                () -> appHealthCourseService.memberJoinCourse(1L));
 
        assertEquals("当前用户不是有效会员", exception.getMessage());
        verify(healthCourseMemberJoinRecordMapper, never()).insert(any());
    }
 
    /**
     * 会员首次加课时应写入会员加课记录。
     */
    @Test
    void memberJoinCourseShouldInsertRecordWhenFirstJoin() {
        when(appAuthSessionService.requireLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildMemberUser(200L));
        when(healthCourseMapper.selectById(1L)).thenReturn(buildCourseEntity(1L, new BigDecimal("599.80")));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(null);
        when(healthCourseMemberJoinRecordMapper.selectOne(any())).thenReturn(null);
 
        boolean joined = appHealthCourseService.memberJoinCourse(1L);
 
        assertTrue(joined);
        verify(healthCourseMemberJoinRecordMapper).insert(memberJoinRecordCaptor.capture());
        assertEquals(Long.valueOf(1L), memberJoinRecordCaptor.getValue().getCourseId());
        assertEquals(Long.valueOf(200L), memberJoinRecordCaptor.getValue().getAppUserId());
    }
 
    /**
     * 会员重复加课时应幂等成功且不重复写记录。
     */
    @Test
    void memberJoinCourseShouldBeIdempotentWhenAlreadyJoined() {
        when(appAuthSessionService.requireLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildMemberUser(200L));
        when(healthCourseMapper.selectById(1L)).thenReturn(buildCourseEntity(1L, new BigDecimal("599.80")));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(null);
        when(healthCourseMemberJoinRecordMapper.selectOne(any())).thenReturn(buildMemberJoinRecord(1L, 200L));
 
        boolean joined = appHealthCourseService.memberJoinCourse(1L);
 
        assertTrue(joined);
        verify(healthCourseMemberJoinRecordMapper, never()).insert(any());
    }
 
    /**
     * 会员已付费购课后再次加课应幂等成功且不写会员加课记录。
     */
    @Test
    void memberJoinCourseShouldBeIdempotentWhenCourseAlreadyPurchased() {
        when(appAuthSessionService.requireLoginUserId()).thenReturn(200L);
        when(userMapper.selectById(200L)).thenReturn(buildMemberUser(200L));
        when(healthCourseMapper.selectById(1L)).thenReturn(buildCourseEntity(1L, new BigDecimal("599.80")));
        when(coursePurchaseOrderMapper.selectOne(any())).thenReturn(buildCourseOrder(1L, 200L));
 
        boolean joined = appHealthCourseService.memberJoinCourse(1L);
 
        assertTrue(joined);
        verify(healthCourseMemberJoinRecordMapper, never()).insert(any());
    }
 
    private HealthCourseDetailVO buildCourseDetail(Long courseId) {
        HealthCourseDetailVO detailVO = new HealthCourseDetailVO();
        detailVO.setId(courseId);
        detailVO.setTitle("中医针灸资格证");
        detailVO.setStatus(1);
        detailVO.setSalePrice(new BigDecimal("599.80"));
        detailVO.setLearnUserCount(88);
        detailVO.setSummary("课程简介");
        detailVO.setTargetUsers("适用人群");
        detailVO.setContent("<p>课程内容</p>");
        detailVO.setTotalDurationSeconds(1200);
        detailVO.setCoverImg("https://example.com/course.png");
        detailVO.setBannerImgs(Collections.singletonList("https://example.com/banner.png"));
        return detailVO;
    }
 
    private HealthCourse buildCourseEntity(Long courseId, BigDecimal salePrice) {
        HealthCourse course = new HealthCourse();
        course.setId(courseId);
        course.setTitle("中医针灸资格证");
        course.setCoverImg("https://example.com/course.png");
        course.setSalePrice(salePrice);
        course.setStatus(1);
        course.setDeleted(0);
        return course;
    }
 
    private HealthCourseVideo buildVideo(Long videoId, Long courseId, BigDecimal salePrice, String chapterName) {
        HealthCourseVideo video = new HealthCourseVideo();
        video.setId(videoId);
        video.setCourseId(courseId);
        video.setChapterName(chapterName);
        video.setSalePrice(salePrice);
        video.setDurationSeconds(600);
        video.setStatus(1);
        video.setDeleted(0);
        return video;
    }
 
    private AppUser buildNormalUser(Long userId) {
        AppUser user = new AppUser();
        user.setId(userId);
        user.setNickname("王美丽");
        user.setPhone("14712346905");
        user.setStatus(1);
        user.setDeleted(0);
        return user;
    }
 
    private AppUser buildMemberUser(Long userId) {
        AppUser user = buildNormalUser(userId);
        user.setMemberExpireAt(LocalDateTime.now().plusDays(7));
        return user;
    }
 
    private CoursePurchaseOrder buildCourseOrder(Long courseId, Long userId) {
        CoursePurchaseOrder order = new CoursePurchaseOrder();
        order.setId(1L);
        order.setOrderNo("CPO202604141000000200");
        order.setAppUserId(userId);
        order.setCourseId(courseId);
        order.setPurchaseTargetType("COURSE");
        order.setCourseType("HEALTH_COURSE");
        order.setPaymentMethod("WECHAT");
        order.setStatus("PAID");
        order.setPaidAt(LocalDateTime.now().minusMinutes(5));
        order.setDeleted(0);
        return order;
    }
 
    private CoursePurchaseOrder buildVideoOrder(Long courseId, Long userId, Long videoId) {
        CoursePurchaseOrder order = new CoursePurchaseOrder();
        order.setId(2L);
        order.setOrderNo("CPO202604141005000200");
        order.setAppUserId(userId);
        order.setCourseId(courseId);
        order.setCourseVideoId(videoId);
        order.setPurchaseTargetType("COURSE_CHAPTER");
        order.setCourseType("HEALTH_COURSE");
        order.setPaymentMethod("ALIPAY");
        order.setStatus("PAID");
        order.setPaidAt(LocalDateTime.now().minusMinutes(3));
        order.setDeleted(0);
        return order;
    }
 
    private HealthCourseMemberJoinRecord buildMemberJoinRecord(Long courseId, Long userId) {
        HealthCourseMemberJoinRecord record = new HealthCourseMemberJoinRecord();
        record.setId(3L);
        record.setCourseId(courseId);
        record.setAppUserId(userId);
        record.setJoinedAt(LocalDateTime.now().minusDays(1));
        record.setDeleted(0);
        return record;
    }
 
    private SystemSettingsVO buildSystemSettings(String servicePhone) {
        SystemSettingsVO settingsVO = new SystemSettingsVO();
        settingsVO.setId(1L);
        settingsVO.setServicePhone(servicePhone);
        settingsVO.setServiceOnlineTime("09:00-18:00");
        return settingsVO;
    }
}