完成自动化测试套件实施(W1-W11)
W1-W3: 基线修复与测试基础设施搭建 - 修复 Jenkins JDK 21 兼容性,统一 E2E 目录,修复 storageState 冲突 - 搭建后端测试基类 BaseContractTest + Testcontainers PostgreSQL - 创建 TestDataFactory 链式构造,完善 Vitest 基座与 Playwright fixtures - 建立 docker-compose.test.yml 与测试数据隔离方案 W4-W5: 单元测试补齐(阶段 2) - 补齐 gym-member/gym-groupCourse/gym-checkIn/gym-payment 核心模块单元测试 - 补齐 gym-coach/manage-sys 模块单元测试 - 前端 utils/composables/stores 单元测试,37 文件 502 项测试 - JaCoCo 覆盖率门禁从 30% 调整至 55%,21 模块全部通过 W6-W7: 集成与契约测试(阶段 3) - Repository 集成测试:会员/团课/签到/支付关键表,Testcontainers 100% 通过 - Handler 集成测试:WebTestClient 覆盖正向/异常/权限路径 - 网关集成测试:JWT/RBAC/签名/限流/重试 - Flyway 迁移测试:验证迁移脚本可重复执行 - OpenAPI 契约测试:覆盖 ≥80% P0 接口,202 项契约测试 0 失败 - 跨模块契约测试:会员-支付-团课数据一致性 W8-W9: E2E 与用户旅程测试(阶段 4) - 管理员 Web 核心流程 E2E:用户/角色/菜单/字典/配置 - 小程序会员端核心页面 E2E:购卡/预约/签到 - 5 条 P0 用户旅程全链路自动化,60 条 journey 测试 0 失败 W10: 变异测试与质量门禁(阶段 5) - 后端 PIT 配置:pitest-maven 1.19.1 + JUnit 5,覆盖率阈值 55%/变异阈值 45% - P0 模块基线:manage-sys 48%,gym-member 30%,gym-payment 36% - 前端 StrykerJS 配置:utils/stores 变异测试,dateFormat.ts 70.83% - Jenkins 质量门禁:JaCoCo/PIT/E2E 统一检查,不达标阻断构建 W11: 持续运行与改进(阶段 6) - 测试指标收集脚本 scripts/collect-test-metrics.py + HTML 看板生成器 - Flaky Test 治理 SOP:检测→隔离→根因分析→修复→验证闭环 - 测试资产定期评审流程:月度/季度/事件驱动三级机制 - 快速参考指南 docs/testing/quick-reference.md - 累计 10 份测试文档,7 个里程碑全部达成
This commit was merged in pull request #54.
This commit is contained in:
@@ -61,6 +61,11 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
|
||||
+1
@@ -156,6 +156,7 @@ public class GroupCourseHandler {
|
||||
response.put("data", course);
|
||||
return ServerResponse.ok().bodyValue(response);
|
||||
})
|
||||
.switchIfEmpty(ServerResponse.notFound().build())
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", false);
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
package cn.novalon.gym.manage.groupcourse.handler;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.enums.MemberCardRecordStatus;
|
||||
import cn.novalon.gym.manage.member.enums.MemberCardType;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
||||
import cn.novalon.gym.manage.member.service.IMemberCardRecordService;
|
||||
import cn.novalon.gym.manage.member.service.IMemberStoredCardService;
|
||||
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 reactor.core.publisher.Mono;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BookingSagaHandlerTest {
|
||||
|
||||
@Mock
|
||||
private IGroupCourseBookingRepository bookingRepository;
|
||||
|
||||
@Mock
|
||||
private IMemberCardRecordService memberCardRecordService;
|
||||
|
||||
@Mock
|
||||
private IMemberStoredCardService memberStoredCardService;
|
||||
|
||||
@Mock
|
||||
private MemberCardRepository memberCardRepository;
|
||||
|
||||
private BookingSagaHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
handler = new BookingSagaHandler(bookingRepository, memberCardRecordService,
|
||||
memberStoredCardService, memberCardRepository);
|
||||
}
|
||||
|
||||
// ==================== executeBooking ====================
|
||||
|
||||
@Test
|
||||
void executeBooking_shouldSucceedForStoredValueCard() {
|
||||
GroupCourseBooking booking = createTestBooking(1L, 100L, 10L);
|
||||
MemberCardRecord record = createTestMemberCardRecord(50L, 100L, 1L);
|
||||
MemberCard card = createTestMemberCard(1L, MemberCardType.STORED_VALUE_CARD.name());
|
||||
|
||||
when(bookingRepository.save(booking)).thenReturn(Mono.just(booking));
|
||||
when(memberCardRecordService.findById(50L)).thenReturn(Mono.just(record));
|
||||
when(memberCardRepository.findById(1L)).thenReturn(Mono.just(card));
|
||||
when(memberStoredCardService.consume(100L, BigDecimal.valueOf(50.0))).thenReturn(Mono.just(1));
|
||||
|
||||
Mono<GroupCourseBooking> result = handler.executeBooking(booking, 50L, BigDecimal.valueOf(50.0));
|
||||
|
||||
GroupCourseBooking savedBooking = result.block();
|
||||
assertThat(savedBooking).isNotNull();
|
||||
assertThat(savedBooking.getId()).isEqualTo(1L);
|
||||
|
||||
verify(bookingRepository).save(booking);
|
||||
verify(memberCardRecordService).findById(50L);
|
||||
verify(memberCardRepository).findById(1L);
|
||||
verify(memberStoredCardService).consume(100L, BigDecimal.valueOf(50.0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeBooking_shouldFailWhenRecordNotFound() {
|
||||
GroupCourseBooking booking = createTestBooking(1L, 100L, 10L);
|
||||
|
||||
when(bookingRepository.save(booking)).thenReturn(Mono.just(booking));
|
||||
when(memberCardRecordService.findById(99L)).thenReturn(Mono.empty());
|
||||
|
||||
Mono<GroupCourseBooking> result = handler.executeBooking(booking, 99L, BigDecimal.valueOf(50.0));
|
||||
|
||||
try {
|
||||
result.block();
|
||||
} catch (Exception e) {
|
||||
assertThat(e.getMessage()).contains("会员卡记录不存在");
|
||||
}
|
||||
|
||||
verify(bookingRepository).save(booking);
|
||||
// Saga rollback 由于 error handler 传播链会调用多次 deleteBooking
|
||||
verify(bookingRepository, atLeast(1)).deleteById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeBooking_shouldFailWhenCardNotBelongToMember() {
|
||||
GroupCourseBooking booking = createTestBooking(1L, 100L, 10L);
|
||||
MemberCardRecord record = createTestMemberCardRecord(50L, 999L, 1L); // memberId mismatch
|
||||
|
||||
when(bookingRepository.save(booking)).thenReturn(Mono.just(booking));
|
||||
when(memberCardRecordService.findById(50L)).thenReturn(Mono.just(record));
|
||||
|
||||
Mono<GroupCourseBooking> result = handler.executeBooking(booking, 50L, BigDecimal.valueOf(50.0));
|
||||
|
||||
try {
|
||||
result.block();
|
||||
} catch (Exception e) {
|
||||
assertThat(e.getMessage()).contains("会员卡不归属当前用户");
|
||||
}
|
||||
|
||||
verify(bookingRepository).save(booking);
|
||||
verify(bookingRepository, atLeast(1)).deleteById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeBooking_shouldFailForCountCard() {
|
||||
GroupCourseBooking booking = createTestBooking(1L, 100L, 10L);
|
||||
MemberCardRecord record = createTestMemberCardRecord(50L, 100L, 1L);
|
||||
MemberCard card = createTestMemberCard(1L, MemberCardType.COUNT_CARD.name());
|
||||
|
||||
when(bookingRepository.save(booking)).thenReturn(Mono.just(booking));
|
||||
when(memberCardRecordService.findById(50L)).thenReturn(Mono.just(record));
|
||||
when(memberCardRepository.findById(1L)).thenReturn(Mono.just(card));
|
||||
|
||||
Mono<GroupCourseBooking> result = handler.executeBooking(booking, 50L, BigDecimal.valueOf(50.0));
|
||||
|
||||
try {
|
||||
result.block();
|
||||
} catch (Exception e) {
|
||||
assertThat(e.getMessage()).contains("团课预约仅支持储值卡和时长卡支付");
|
||||
}
|
||||
|
||||
verify(bookingRepository).save(booking);
|
||||
verify(bookingRepository, atLeast(1)).deleteById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeBooking_shouldSucceedForTimeCard() {
|
||||
GroupCourseBooking booking = createTestBooking(1L, 100L, 10L);
|
||||
MemberCardRecord record = createTestMemberCardRecord(50L, 100L, 1L);
|
||||
record.setExpireTime(LocalDateTime.now().plusDays(30));
|
||||
record.setStatus(MemberCardRecordStatus.ACTIVE);
|
||||
MemberCard card = createTestMemberCard(1L, MemberCardType.TIME_CARD.name());
|
||||
|
||||
when(bookingRepository.save(booking)).thenReturn(Mono.just(booking));
|
||||
when(memberCardRecordService.findById(50L)).thenReturn(Mono.just(record));
|
||||
when(memberCardRepository.findById(1L)).thenReturn(Mono.just(card));
|
||||
when(memberStoredCardService.consume(100L, BigDecimal.valueOf(50.0))).thenReturn(Mono.just(1));
|
||||
|
||||
Mono<GroupCourseBooking> result = handler.executeBooking(booking, 50L, BigDecimal.valueOf(50.0));
|
||||
|
||||
GroupCourseBooking savedBooking = result.block();
|
||||
assertThat(savedBooking).isNotNull();
|
||||
|
||||
verify(bookingRepository).save(booking);
|
||||
verify(memberCardRecordService).findById(50L);
|
||||
verify(memberCardRepository).findById(1L);
|
||||
verify(memberStoredCardService).consume(100L, BigDecimal.valueOf(50.0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeBooking_shouldFailWhenTimeCardExpired() {
|
||||
GroupCourseBooking booking = createTestBooking(1L, 100L, 10L);
|
||||
MemberCardRecord record = createTestMemberCardRecord(50L, 100L, 1L);
|
||||
record.setExpireTime(LocalDateTime.now().minusDays(1));
|
||||
record.setStatus(MemberCardRecordStatus.ACTIVE);
|
||||
MemberCard card = createTestMemberCard(1L, MemberCardType.TIME_CARD.name());
|
||||
|
||||
when(bookingRepository.save(booking)).thenReturn(Mono.just(booking));
|
||||
when(memberCardRecordService.findById(50L)).thenReturn(Mono.just(record));
|
||||
when(memberCardRepository.findById(1L)).thenReturn(Mono.just(card));
|
||||
// 需要 mock consume 避免 Saga 构造时 NPE
|
||||
when(memberStoredCardService.consume(anyLong(), any())).thenReturn(Mono.just(1));
|
||||
|
||||
Mono<GroupCourseBooking> result = handler.executeBooking(booking, 50L, BigDecimal.valueOf(50.0));
|
||||
|
||||
try {
|
||||
result.block();
|
||||
} catch (Exception e) {
|
||||
assertThat(e.getMessage()).contains("已过期");
|
||||
}
|
||||
|
||||
verify(bookingRepository).save(booking);
|
||||
verify(bookingRepository, atLeast(1)).deleteById(1L);
|
||||
}
|
||||
|
||||
// ==================== executeCancelBooking ====================
|
||||
|
||||
@Test
|
||||
void executeCancelBooking_shouldSucceed() {
|
||||
GroupCourseBooking booking = createTestBooking(1L, 100L, 10L);
|
||||
booking.setStatus("1");
|
||||
|
||||
when(bookingRepository.updateStatus(1L, "1")).thenReturn(Mono.just(1));
|
||||
when(memberStoredCardService.refundBalanceWithFee(100L, BigDecimal.valueOf(50.0), 1L))
|
||||
.thenReturn(Mono.just(BigDecimal.valueOf(50.0)));
|
||||
when(bookingRepository.findById(1L)).thenReturn(Mono.just(booking));
|
||||
|
||||
Mono<GroupCourseBooking> result = handler.executeCancelBooking(1L, 10L, 50L, 100L, BigDecimal.valueOf(50.0), 1L);
|
||||
|
||||
GroupCourseBooking cancelledBooking = result.block();
|
||||
assertThat(cancelledBooking).isNotNull();
|
||||
assertThat(cancelledBooking.getStatus()).isEqualTo("1");
|
||||
|
||||
verify(bookingRepository).updateStatus(1L, "1");
|
||||
verify(memberStoredCardService).refundBalanceWithFee(100L, BigDecimal.valueOf(50.0), 1L);
|
||||
verify(bookingRepository).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeCancelBooking_shouldFailWhenUpdateStatusFails() {
|
||||
when(bookingRepository.updateStatus(1L, "1")).thenReturn(Mono.just(0));
|
||||
when(bookingRepository.findById(1L)).thenReturn(Mono.just(createTestBooking(1L, 100L, 10L)));
|
||||
// 需要 mock refundBalanceWithFee 避免 SagaStep 构造时 NPE
|
||||
when(memberStoredCardService.refundBalanceWithFee(anyLong(), any(), anyLong()))
|
||||
.thenReturn(Mono.just(BigDecimal.ZERO));
|
||||
|
||||
Mono<GroupCourseBooking> result = handler.executeCancelBooking(1L, 10L, 50L, 100L, BigDecimal.valueOf(50.0), 1L);
|
||||
|
||||
try {
|
||||
result.block();
|
||||
} catch (Exception e) {
|
||||
assertThat(e.getMessage()).contains("更新预约状态失败");
|
||||
}
|
||||
|
||||
verify(bookingRepository).updateStatus(1L, "1");
|
||||
}
|
||||
|
||||
// ==================== helper ====================
|
||||
|
||||
private GroupCourseBooking createTestBooking(Long id, Long memberId, Long courseId) {
|
||||
GroupCourseBooking booking = new GroupCourseBooking();
|
||||
booking.setId(id);
|
||||
booking.setMemberId(memberId);
|
||||
booking.setCourseId(courseId);
|
||||
booking.setStatus("0");
|
||||
return booking;
|
||||
}
|
||||
|
||||
private MemberCardRecord createTestMemberCardRecord(Long id, Long memberId, Long memberCardId) {
|
||||
MemberCardRecord record = new MemberCardRecord();
|
||||
record.setId(id);
|
||||
record.setMemberId(memberId);
|
||||
record.setMemberCardId(memberCardId);
|
||||
record.setStatus(MemberCardRecordStatus.ACTIVE);
|
||||
record.setExpireTime(LocalDateTime.now().plusDays(30));
|
||||
return record;
|
||||
}
|
||||
|
||||
private MemberCard createTestMemberCard(Long memberCardId, String cardType) {
|
||||
return MemberCard.builder()
|
||||
.memberCardId(memberCardId)
|
||||
.memberCardType(cardType)
|
||||
.memberCardName("测试卡")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+471
@@ -0,0 +1,471 @@
|
||||
package cn.novalon.gym.manage.groupcourse.handler;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
|
||||
import cn.novalon.gym.manage.groupcourse.service.ICourseLabelService;
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CourseLabelHandlerTest {
|
||||
|
||||
@Mock
|
||||
private ICourseLabelService courseLabelService;
|
||||
|
||||
private CourseLabelHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
handler = new CourseLabelHandler(courseLabelService);
|
||||
}
|
||||
|
||||
// ==================== getAllLabels ====================
|
||||
|
||||
@Test
|
||||
void getAllLabels_shouldReturnOk() {
|
||||
CourseLabel label1 = new CourseLabel();
|
||||
label1.setId(1L);
|
||||
label1.setLabelName("适合新手");
|
||||
CourseLabel label2 = new CourseLabel();
|
||||
label2.setId(2L);
|
||||
label2.setLabelName("高强度");
|
||||
when(courseLabelService.findAll()).thenReturn(Flux.just(label1, label2));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getAllLabels(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllLabels_shouldReturnOkWhenEmpty() {
|
||||
when(courseLabelService.findAll()).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getAllLabels(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== getLabelsByPage ====================
|
||||
|
||||
@Test
|
||||
void getLabelsByPage_shouldReturnOk() {
|
||||
PageRequest pageRequest = new PageRequest();
|
||||
PageResponse<CourseLabel> pageResponse = new PageResponse<>();
|
||||
when(courseLabelService.findByPage(any(PageRequest.class))).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(pageRequest));
|
||||
Mono<ServerResponse> result = handler.getLabelsByPage(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).findByPage(any(PageRequest.class));
|
||||
}
|
||||
|
||||
// ==================== getLabelById ====================
|
||||
|
||||
@Test
|
||||
void getLabelById_shouldReturnOkWhenFound() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setId(1L);
|
||||
label.setLabelName("适合新手");
|
||||
when(courseLabelService.findById(1L)).thenReturn(Mono.just(label));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getLabelById(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLabelById_shouldReturnNotFound() {
|
||||
when(courseLabelService.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getLabelById(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// ==================== searchLabels ====================
|
||||
|
||||
@Test
|
||||
void searchLabels_shouldReturnOk() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setId(1L);
|
||||
label.setLabelName("新手");
|
||||
when(courseLabelService.findByKeyword("新手")).thenReturn(Flux.just(label));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("keyword", "新手")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.searchLabels(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).findByKeyword("新手");
|
||||
}
|
||||
|
||||
@Test
|
||||
void searchLabels_shouldReturnOkWhenEmpty() {
|
||||
when(courseLabelService.findByKeyword("")).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.searchLabels(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== createLabel ====================
|
||||
|
||||
@Test
|
||||
void createLabel_shouldReturnOk() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setLabelName("适合新手");
|
||||
label.setColor("#52c41a");
|
||||
CourseLabel created = new CourseLabel();
|
||||
created.setId(1L);
|
||||
created.setLabelName("适合新手");
|
||||
created.setColor("#52c41a");
|
||||
when(courseLabelService.create(any(CourseLabel.class))).thenReturn(Mono.just(created));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.createLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).create(any(CourseLabel.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createLabel_shouldReturn400WhenNameEmpty() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setLabelName("");
|
||||
label.setColor("#52c41a");
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.createLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(courseLabelService, never()).create(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createLabel_shouldReturn400WhenNameNull() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setColor("#52c41a");
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.createLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(courseLabelService, never()).create(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createLabel_shouldReturn400WhenNameTooLong() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setLabelName("a".repeat(51));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.createLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(courseLabelService, never()).create(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createLabel_shouldSetDefaultColorWhenColorEmpty() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setLabelName("适合新手");
|
||||
CourseLabel created = new CourseLabel();
|
||||
created.setId(1L);
|
||||
created.setLabelName("适合新手");
|
||||
created.setColor("#1890ff");
|
||||
when(courseLabelService.create(any(CourseLabel.class))).thenReturn(Mono.just(created));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.createLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createLabel_shouldReturn400WhenServiceError() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setLabelName("适合新手");
|
||||
when(courseLabelService.create(any(CourseLabel.class)))
|
||||
.thenReturn(Mono.error(new RuntimeException("标签已存在")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.createLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== updateLabel ====================
|
||||
|
||||
@Test
|
||||
void updateLabel_shouldReturnOk() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setLabelName("适合进阶");
|
||||
CourseLabel updated = new CourseLabel();
|
||||
updated.setId(1L);
|
||||
updated.setLabelName("适合进阶");
|
||||
when(courseLabelService.update(eq(1L), any(CourseLabel.class))).thenReturn(Mono.just(updated));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.updateLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).update(eq(1L), any(CourseLabel.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLabel_shouldReturn400WhenNameTooLong() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setLabelName("a".repeat(51));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.updateLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(courseLabelService, never()).update(anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLabel_shouldReturn400WhenServiceError() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setLabelName("适合进阶");
|
||||
when(courseLabelService.update(eq(1L), any(CourseLabel.class)))
|
||||
.thenReturn(Mono.error(new RuntimeException("标签不存在")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.body(Mono.just(label));
|
||||
Mono<ServerResponse> result = handler.updateLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== deleteLabel ====================
|
||||
|
||||
@Test
|
||||
void deleteLabel_shouldReturnOk() {
|
||||
when(courseLabelService.delete(1L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.deleteLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).delete(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteLabel_shouldReturn400WhenServiceError() {
|
||||
when(courseLabelService.delete(1L)).thenReturn(Mono.error(new RuntimeException("删除失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.deleteLabel(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== getLabelsByTypeId ====================
|
||||
|
||||
@Test
|
||||
void getLabelsByTypeId_shouldReturnOk() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setId(1L);
|
||||
label.setLabelName("适合新手");
|
||||
when(courseLabelService.findByTypeId(1L)).thenReturn(Flux.just(label));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getLabelsByTypeId(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).findByTypeId(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLabelsByTypeId_shouldReturnOkWhenEmpty() {
|
||||
when(courseLabelService.findByTypeId(1L)).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getLabelsByTypeId(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== addLabelsToType ====================
|
||||
|
||||
@Test
|
||||
void addLabelsToType_shouldReturnOk() {
|
||||
when(courseLabelService.addLabelsToType(1L, List.of(1L, 2L))).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.body(Mono.just(Map.of("labelIds", List.of(1L, 2L))));
|
||||
Mono<ServerResponse> result = handler.addLabelsToType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).addLabelsToType(1L, List.of(1L, 2L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addLabelsToType_shouldReturn400WhenLabelIdsEmpty() {
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.body(Mono.just(Map.of("labelIds", List.of())));
|
||||
Mono<ServerResponse> result = handler.addLabelsToType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(courseLabelService, never()).addLabelsToType(anyLong(), anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addLabelsToType_shouldReturn400WhenLabelIdsNull() {
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.body(Mono.just(Map.of()));
|
||||
Mono<ServerResponse> result = handler.addLabelsToType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(courseLabelService, never()).addLabelsToType(anyLong(), anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addLabelsToType_shouldReturn400WhenServiceError() {
|
||||
when(courseLabelService.addLabelsToType(1L, List.of(1L))).thenReturn(Mono.error(new RuntimeException("添加失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.body(Mono.just(Map.of("labelIds", List.of(1L))));
|
||||
Mono<ServerResponse> result = handler.addLabelsToType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== removeLabelFromType ====================
|
||||
|
||||
@Test
|
||||
void removeLabelFromType_shouldReturnOk() {
|
||||
when(courseLabelService.removeLabelFromType(1L, 1L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.pathVariable("labelId", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.removeLabelFromType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).removeLabelFromType(1L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeLabelFromType_shouldReturn400WhenServiceError() {
|
||||
when(courseLabelService.removeLabelFromType(1L, 1L)).thenReturn(Mono.error(new RuntimeException("移除失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.pathVariable("labelId", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.removeLabelFromType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== clearLabelsFromType ====================
|
||||
|
||||
@Test
|
||||
void clearLabelsFromType_shouldReturnOk() {
|
||||
when(courseLabelService.clearLabelsFromType(1L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.clearLabelsFromType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(courseLabelService).clearLabelsFromType(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearLabelsFromType_shouldReturn400WhenServiceError() {
|
||||
when(courseLabelService.clearLabelsFromType(1L)).thenReturn(Mono.error(new RuntimeException("清空失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("typeId", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.clearLabelsFromType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
package cn.novalon.gym.manage.groupcourse.handler;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseBookingHandlerTest {
|
||||
|
||||
@Mock
|
||||
private IGroupCourseBookingService bookingService;
|
||||
|
||||
private GroupCourseBookingHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
handler = new GroupCourseBookingHandler(bookingService);
|
||||
}
|
||||
|
||||
// ==================== bookCourse ====================
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldReturnOk() {
|
||||
GroupCourseBooking booking = new GroupCourseBooking();
|
||||
booking.setId(1L);
|
||||
booking.setCourseId(10L);
|
||||
booking.setMemberId(100L);
|
||||
when(bookingService.bookCourse(10L, 100L)).thenReturn(Mono.just(booking));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(Map.of("courseId", 10L, "memberId", 100L)));
|
||||
Mono<ServerResponse> result = handler.bookCourse(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(bookingService).bookCourse(10L, 100L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldReturn400WhenCourseIdMissing() {
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(Map.of("memberId", 100L)));
|
||||
Mono<ServerResponse> result = handler.bookCourse(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(bookingService, never()).bookCourse(anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldReturn400WhenMemberIdMissing() {
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(Map.of("courseId", 10L)));
|
||||
Mono<ServerResponse> result = handler.bookCourse(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(bookingService, never()).bookCourse(anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldReturn400WhenServiceError() {
|
||||
when(bookingService.bookCourse(10L, 100L)).thenReturn(Mono.error(new RuntimeException("预约失败,课程已满")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(Map.of("courseId", 10L, "memberId", 100L)));
|
||||
Mono<ServerResponse> result = handler.bookCourse(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== cancelBooking ====================
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldReturnOk() {
|
||||
GroupCourseBooking booking = new GroupCourseBooking();
|
||||
booking.setId(1L);
|
||||
booking.setStatus("1");
|
||||
when(bookingService.cancelBooking(1L, 100L)).thenReturn(Mono.just(booking));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("bookingId", "1")
|
||||
.body(Mono.just(Map.of("memberId", 100L)));
|
||||
Mono<ServerResponse> result = handler.cancelBooking(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(bookingService).cancelBooking(1L, 100L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldReturn400WhenServiceError() {
|
||||
when(bookingService.cancelBooking(1L, 100L)).thenReturn(Mono.error(new RuntimeException("取消失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("bookingId", "1")
|
||||
.body(Mono.just(Map.of("memberId", 100L)));
|
||||
Mono<ServerResponse> result = handler.cancelBooking(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== getBookingsByMemberId ====================
|
||||
|
||||
@Test
|
||||
void getBookingsByMemberId_shouldReturnOk() {
|
||||
GroupCourseBooking b1 = new GroupCourseBooking();
|
||||
b1.setId(1L);
|
||||
GroupCourseBooking b2 = new GroupCourseBooking();
|
||||
b2.setId(2L);
|
||||
when(bookingService.getBookingsByMemberId(100L)).thenReturn(Flux.just(b1, b2));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("memberId", "100")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getBookingsByMemberId(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(bookingService).getBookingsByMemberId(100L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBookingsByMemberId_shouldReturnOkWhenEmpty() {
|
||||
when(bookingService.getBookingsByMemberId(999L)).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("memberId", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getBookingsByMemberId(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== getBookingById ====================
|
||||
|
||||
@Test
|
||||
void getBookingById_shouldReturnOkWhenFound() {
|
||||
GroupCourseBooking booking = new GroupCourseBooking();
|
||||
booking.setId(1L);
|
||||
when(bookingService.getBookingById(1L)).thenReturn(Mono.just(booking));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("bookingId", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getBookingById(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(bookingService).getBookingById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBookingById_shouldReturnNotFound() {
|
||||
when(bookingService.getBookingById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("bookingId", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getBookingById(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// ==================== getBookingsByCourseId ====================
|
||||
|
||||
@Test
|
||||
void getBookingsByCourseId_shouldReturnOk() {
|
||||
GroupCourseBooking b1 = new GroupCourseBooking();
|
||||
b1.setId(1L);
|
||||
when(bookingService.getBookingsByCourseId(10L)).thenReturn(Flux.just(b1));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("courseId", "10")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getBookingsByCourseId(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(bookingService).getBookingsByCourseId(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBookingsByCourseId_shouldReturnOkWhenEmpty() {
|
||||
when(bookingService.getBookingsByCourseId(999L)).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("courseId", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getBookingsByCourseId(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -195,10 +195,10 @@ class GroupCourseHandlerTest {
|
||||
@Test
|
||||
void signIn_shouldReturnOk() {
|
||||
GroupCourse course = createTestCourse(1L, "瑜伽课");
|
||||
when(validator.validate(any())).thenReturn(java.util.Collections.emptySet());
|
||||
when(groupCourseService.signIn(eq(1L), eq(10001L))).thenReturn(Mono.just(course));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("memberId", "10001")
|
||||
.pathVariable("courseId", "1")
|
||||
.body(Mono.just(java.util.Map.of("memberId", 10001L, "courseId", 1L)));
|
||||
|
||||
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
package cn.novalon.gym.manage.groupcourse.handler;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseRecommend;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseRecommendService;
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseRecommendHandlerTest {
|
||||
|
||||
@Mock
|
||||
private IGroupCourseRecommendService recommendService;
|
||||
|
||||
private GroupCourseRecommendHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
handler = new GroupCourseRecommendHandler(recommendService);
|
||||
}
|
||||
|
||||
// ==================== getAllRecommendations ====================
|
||||
|
||||
@Test
|
||||
void getAllRecommendations_shouldReturnOk() {
|
||||
GroupCourseRecommend r1 = new GroupCourseRecommend();
|
||||
r1.setId(1L);
|
||||
r1.setRecommendTitle("本周热门");
|
||||
GroupCourseRecommend r2 = new GroupCourseRecommend();
|
||||
r2.setId(2L);
|
||||
r2.setRecommendTitle("新课程推荐");
|
||||
when(recommendService.findAll("priority", "desc")).thenReturn(Flux.just(r1, r2));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getAllRecommendations(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).findAll("priority", "desc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllRecommendations_shouldRespectSortParams() {
|
||||
GroupCourseRecommend r = new GroupCourseRecommend();
|
||||
r.setId(1L);
|
||||
when(recommendService.findAll("id", "asc")).thenReturn(Flux.just(r));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("sortBy", "id")
|
||||
.queryParam("sortOrder", "asc")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getAllRecommendations(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).findAll("id", "asc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllRecommendations_shouldReturnOkWhenEmpty() {
|
||||
when(recommendService.findAll("priority", "desc")).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getAllRecommendations(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== getAllActiveRecommendations ====================
|
||||
|
||||
@Test
|
||||
void getAllActiveRecommendations_shouldReturnOk() {
|
||||
GroupCourseRecommend r = new GroupCourseRecommend();
|
||||
r.setId(1L);
|
||||
r.setIsActive(true);
|
||||
when(recommendService.findAllActive()).thenReturn(Flux.just(r));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getAllActiveRecommendations(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).findAllActive();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllActiveRecommendations_shouldReturnOkWhenEmpty() {
|
||||
when(recommendService.findAllActive()).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getAllActiveRecommendations(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== getRecommendationById ====================
|
||||
|
||||
@Test
|
||||
void getRecommendationById_shouldReturnOkWhenFound() {
|
||||
GroupCourseRecommend r = new GroupCourseRecommend();
|
||||
r.setId(1L);
|
||||
r.setRecommendTitle("本周热门");
|
||||
when(recommendService.findById(1L)).thenReturn(Mono.just(r));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getRecommendationById(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRecommendationById_shouldReturnNotFound() {
|
||||
when(recommendService.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getRecommendationById(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// ==================== getRecommendationsByCourseId ====================
|
||||
|
||||
@Test
|
||||
void getRecommendationsByCourseId_shouldReturnOk() {
|
||||
GroupCourseRecommend r = new GroupCourseRecommend();
|
||||
r.setId(1L);
|
||||
r.setCourseId(10L);
|
||||
when(recommendService.findByCourseId(10L)).thenReturn(Flux.just(r));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("courseId", "10")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getRecommendationsByCourseId(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).findByCourseId(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRecommendationsByCourseId_shouldReturnOkWhenEmpty() {
|
||||
when(recommendService.findByCourseId(999L)).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("courseId", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getRecommendationsByCourseId(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== createRecommendation ====================
|
||||
|
||||
@Test
|
||||
void createRecommendation_shouldReturnOk() {
|
||||
GroupCourseRecommend recommend = new GroupCourseRecommend();
|
||||
recommend.setCourseId(10L);
|
||||
recommend.setRecommendTitle("本周热门");
|
||||
recommend.setPriority(10);
|
||||
GroupCourseRecommend created = new GroupCourseRecommend();
|
||||
created.setId(1L);
|
||||
created.setCourseId(10L);
|
||||
created.setRecommendTitle("本周热门");
|
||||
created.setPriority(10);
|
||||
when(recommendService.create(any(GroupCourseRecommend.class))).thenReturn(Mono.just(created));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(recommend));
|
||||
Mono<ServerResponse> result = handler.createRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).create(any(GroupCourseRecommend.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createRecommendation_shouldReturn400WhenCourseIdNull() {
|
||||
GroupCourseRecommend recommend = new GroupCourseRecommend();
|
||||
recommend.setRecommendTitle("本周热门");
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(recommend));
|
||||
Mono<ServerResponse> result = handler.createRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(recommendService, never()).create(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createRecommendation_shouldReturn400WhenServiceError() {
|
||||
GroupCourseRecommend recommend = new GroupCourseRecommend();
|
||||
recommend.setCourseId(10L);
|
||||
when(recommendService.create(any(GroupCourseRecommend.class)))
|
||||
.thenReturn(Mono.error(new RuntimeException("推荐已存在")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(recommend));
|
||||
Mono<ServerResponse> result = handler.createRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== updateRecommendation ====================
|
||||
|
||||
@Test
|
||||
void updateRecommendation_shouldReturnOk() {
|
||||
GroupCourseRecommend recommend = new GroupCourseRecommend();
|
||||
recommend.setRecommendTitle("更新推荐");
|
||||
GroupCourseRecommend updated = new GroupCourseRecommend();
|
||||
updated.setId(1L);
|
||||
updated.setRecommendTitle("更新推荐");
|
||||
when(recommendService.update(eq(1L), any(GroupCourseRecommend.class))).thenReturn(Mono.just(updated));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.body(Mono.just(recommend));
|
||||
Mono<ServerResponse> result = handler.updateRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).update(eq(1L), any(GroupCourseRecommend.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateRecommendation_shouldReturn400WhenServiceError() {
|
||||
GroupCourseRecommend recommend = new GroupCourseRecommend();
|
||||
recommend.setRecommendTitle("更新推荐");
|
||||
when(recommendService.update(eq(1L), any(GroupCourseRecommend.class)))
|
||||
.thenReturn(Mono.error(new RuntimeException("推荐不存在")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.body(Mono.just(recommend));
|
||||
Mono<ServerResponse> result = handler.updateRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== deleteRecommendation ====================
|
||||
|
||||
@Test
|
||||
void deleteRecommendation_shouldReturnOk() {
|
||||
when(recommendService.delete(1L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.deleteRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).delete(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteRecommendation_shouldReturn400WhenServiceError() {
|
||||
when(recommendService.delete(1L)).thenReturn(Mono.error(new RuntimeException("删除失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.deleteRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== enableRecommendation ====================
|
||||
|
||||
@Test
|
||||
void enableRecommendation_shouldReturnOk() {
|
||||
GroupCourseRecommend r = new GroupCourseRecommend();
|
||||
r.setId(1L);
|
||||
r.setIsActive(true);
|
||||
when(recommendService.enable(1L)).thenReturn(Mono.just(r));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.enableRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).enable(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableRecommendation_shouldReturn400WhenServiceError() {
|
||||
when(recommendService.enable(1L)).thenReturn(Mono.error(new RuntimeException("启用失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.enableRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== disableRecommendation ====================
|
||||
|
||||
@Test
|
||||
void disableRecommendation_shouldReturnOk() {
|
||||
GroupCourseRecommend r = new GroupCourseRecommend();
|
||||
r.setId(1L);
|
||||
r.setIsActive(false);
|
||||
when(recommendService.disable(1L)).thenReturn(Mono.just(r));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.disableRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(recommendService).disable(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableRecommendation_shouldReturn400WhenServiceError() {
|
||||
when(recommendService.disable(1L)).thenReturn(Mono.error(new RuntimeException("禁用失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.disableRecommendation(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
package cn.novalon.gym.manage.groupcourse.handler;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseTypeService;
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseTypeHandlerTest {
|
||||
|
||||
@Mock
|
||||
private IGroupCourseTypeService groupCourseTypeService;
|
||||
|
||||
private GroupCourseTypeHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
handler = new GroupCourseTypeHandler(groupCourseTypeService);
|
||||
}
|
||||
|
||||
// ==================== getAllGroupCourseTypes ====================
|
||||
|
||||
@Test
|
||||
void getAllGroupCourseTypes_shouldReturnOk() {
|
||||
GroupCourseType type1 = new GroupCourseType();
|
||||
type1.setId(1L);
|
||||
type1.setTypeName("瑜伽入门");
|
||||
GroupCourseType type2 = new GroupCourseType();
|
||||
type2.setId(2L);
|
||||
type2.setTypeName("力量训练");
|
||||
when(groupCourseTypeService.findAll(false)).thenReturn(Flux.just(type1, type2));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getAllGroupCourseTypes(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).findAll(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllGroupCourseTypes_shouldIncludeDeletedWhenParamSet() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setId(1L);
|
||||
type.setTypeName("测试");
|
||||
when(groupCourseTypeService.findAll(true)).thenReturn(Flux.just(type));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("includeDeleted", "true")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getAllGroupCourseTypes(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).findAll(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllGroupCourseTypes_shouldReturnOkWhenEmpty() {
|
||||
when(groupCourseTypeService.findAll(false)).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getAllGroupCourseTypes(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== getGroupCourseTypeById ====================
|
||||
|
||||
@Test
|
||||
void getGroupCourseTypeById_shouldReturnOkWhenFound() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setId(1L);
|
||||
type.setTypeName("瑜伽入门");
|
||||
when(groupCourseTypeService.findById(1L)).thenReturn(Mono.just(type));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getGroupCourseTypeById(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGroupCourseTypeById_shouldReturnNotFound() {
|
||||
when(groupCourseTypeService.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "999")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getGroupCourseTypeById(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// ==================== searchGroupCourseTypes ====================
|
||||
|
||||
@Test
|
||||
void searchGroupCourseTypes_shouldReturnOk() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setId(1L);
|
||||
type.setTypeName("瑜伽");
|
||||
when(groupCourseTypeService.findByKeyword("瑜伽")).thenReturn(Flux.just(type));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.queryParam("keyword", "瑜伽")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.searchGroupCourseTypes(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).findByKeyword("瑜伽");
|
||||
}
|
||||
|
||||
@Test
|
||||
void searchGroupCourseTypes_shouldReturnOkWhenEmpty() {
|
||||
when(groupCourseTypeService.findByKeyword("")).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.searchGroupCourseTypes(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== getGroupCourseTypesByCategory ====================
|
||||
|
||||
@Test
|
||||
void getGroupCourseTypesByCategory_shouldReturnOk() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setId(1L);
|
||||
type.setTypeName("瑜伽入门");
|
||||
when(groupCourseTypeService.findByCategoryAndKeyword("柔韧与平衡类", "")).thenReturn(Flux.just(type));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("category", "柔韧与平衡类")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getGroupCourseTypesByCategory(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).findByCategoryAndKeyword("柔韧与平衡类", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGroupCourseTypesByCategory_shouldReturnOkWithKeyword() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setId(1L);
|
||||
type.setTypeName("瑜伽入门");
|
||||
when(groupCourseTypeService.findByCategoryAndKeyword("柔韧与平衡类", "瑜伽")).thenReturn(Flux.just(type));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("category", "柔韧与平衡类")
|
||||
.queryParam("keyword", "瑜伽")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.getGroupCourseTypesByCategory(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).findByCategoryAndKeyword("柔韧与平衡类", "瑜伽");
|
||||
}
|
||||
|
||||
// ==================== getCategories ====================
|
||||
|
||||
@Test
|
||||
void getCategories_shouldReturnOk() {
|
||||
when(groupCourseTypeService.findCategories()).thenReturn(Flux.just("有氧类", "力量类", "柔韧与平衡类"));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getCategories(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).findCategories();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCategories_shouldReturnOkWhenEmpty() {
|
||||
when(groupCourseTypeService.findCategories()).thenReturn(Flux.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder().build();
|
||||
Mono<ServerResponse> result = handler.getCategories(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// ==================== createGroupCourseType ====================
|
||||
|
||||
@Test
|
||||
void createGroupCourseType_shouldReturnOk() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setTypeName("瑜伽入门");
|
||||
type.setBaseDifficulty(2);
|
||||
GroupCourseType created = new GroupCourseType();
|
||||
created.setId(1L);
|
||||
created.setTypeName("瑜伽入门");
|
||||
created.setBaseDifficulty(2);
|
||||
when(groupCourseTypeService.create(any(GroupCourseType.class))).thenReturn(Mono.just(created));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(type));
|
||||
Mono<ServerResponse> result = handler.createGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).create(any(GroupCourseType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createGroupCourseType_shouldReturn400WhenNameEmpty() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setTypeName("");
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(type));
|
||||
Mono<ServerResponse> result = handler.createGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(groupCourseTypeService, never()).create(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createGroupCourseType_shouldReturn400WhenNameNull() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(type));
|
||||
Mono<ServerResponse> result = handler.createGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
verify(groupCourseTypeService, never()).create(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createGroupCourseType_shouldSetDefaultDifficulty() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setTypeName("瑜伽入门");
|
||||
GroupCourseType created = new GroupCourseType();
|
||||
created.setId(1L);
|
||||
created.setTypeName("瑜伽入门");
|
||||
created.setBaseDifficulty(1);
|
||||
when(groupCourseTypeService.create(any(GroupCourseType.class))).thenReturn(Mono.just(created));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(type));
|
||||
Mono<ServerResponse> result = handler.createGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createGroupCourseType_shouldReturn400WhenServiceError() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setTypeName("瑜伽入门");
|
||||
when(groupCourseTypeService.create(any(GroupCourseType.class)))
|
||||
.thenReturn(Mono.error(new RuntimeException("类型已存在")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(type));
|
||||
Mono<ServerResponse> result = handler.createGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== updateGroupCourseType ====================
|
||||
|
||||
@Test
|
||||
void updateGroupCourseType_shouldReturnOk() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setTypeName("瑜伽进阶");
|
||||
type.setBaseDifficulty(4);
|
||||
GroupCourseType updated = new GroupCourseType();
|
||||
updated.setId(1L);
|
||||
updated.setTypeName("瑜伽进阶");
|
||||
updated.setBaseDifficulty(4);
|
||||
when(groupCourseTypeService.update(eq(1L), any(GroupCourseType.class))).thenReturn(Mono.just(updated));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.body(Mono.just(type));
|
||||
Mono<ServerResponse> result = handler.updateGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).update(eq(1L), any(GroupCourseType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateGroupCourseType_shouldReturn400WhenServiceError() {
|
||||
GroupCourseType type = new GroupCourseType();
|
||||
type.setTypeName("瑜伽进阶");
|
||||
when(groupCourseTypeService.update(eq(1L), any(GroupCourseType.class)))
|
||||
.thenReturn(Mono.error(new RuntimeException("类型不存在")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.body(Mono.just(type));
|
||||
Mono<ServerResponse> result = handler.updateGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== deleteGroupCourseType ====================
|
||||
|
||||
@Test
|
||||
void deleteGroupCourseType_shouldReturnOk() {
|
||||
when(groupCourseTypeService.delete(1L)).thenReturn(Mono.empty());
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.deleteGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).delete(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteGroupCourseType_shouldReturn400WhenServiceError() {
|
||||
when(groupCourseTypeService.delete(1L)).thenReturn(Mono.error(new RuntimeException("删除失败")));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.pathVariable("id", "1")
|
||||
.build();
|
||||
Mono<ServerResponse> result = handler.deleteGroupCourseType(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// ==================== getGroupCourseTypesByPage ====================
|
||||
|
||||
@Test
|
||||
void getGroupCourseTypesByPage_shouldReturnOk() {
|
||||
PageRequest pageRequest = new PageRequest();
|
||||
PageResponse<GroupCourseType> pageResponse = new PageResponse<>();
|
||||
when(groupCourseTypeService.findByPage(any(PageRequest.class))).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
MockServerRequest request = MockServerRequest.builder()
|
||||
.body(Mono.just(pageRequest));
|
||||
Mono<ServerResponse> result = handler.getGroupCourseTypesByPage(request);
|
||||
|
||||
ServerResponse response = result.block();
|
||||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(groupCourseTypeService).findByPage(any(PageRequest.class));
|
||||
}
|
||||
}
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.ICourseLabelRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
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 reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CourseLabelServiceTest {
|
||||
|
||||
@Mock
|
||||
private ICourseLabelRepository courseLabelRepository;
|
||||
|
||||
@Mock
|
||||
private IGroupCourseRepository groupCourseRepository;
|
||||
|
||||
@Mock
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
private CourseLabelService courseLabelService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
courseLabelService = new CourseLabelService(courseLabelRepository, groupCourseRepository, redisUtil);
|
||||
}
|
||||
|
||||
// ==================== findById ====================
|
||||
|
||||
@Test
|
||||
void findById_shouldReturnLabel() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setId(1L);
|
||||
label.setLabelName("适合新手");
|
||||
when(courseLabelRepository.findById(1L)).thenReturn(Mono.just(label));
|
||||
|
||||
StepVerifier.create(courseLabelService.findById(1L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getLabelName()).isEqualTo("适合新手");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findById_shouldReturnEmptyWhenNotFound() {
|
||||
when(courseLabelRepository.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.findById(999L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findById(999L);
|
||||
}
|
||||
|
||||
// ==================== findAll ====================
|
||||
|
||||
@Test
|
||||
void findAll_shouldReturnAllLabels() {
|
||||
CourseLabel label1 = new CourseLabel();
|
||||
label1.setId(1L);
|
||||
label1.setLabelName("适合新手");
|
||||
CourseLabel label2 = new CourseLabel();
|
||||
label2.setId(2L);
|
||||
label2.setLabelName("高强度");
|
||||
when(courseLabelRepository.findAll()).thenReturn(Flux.just(label1, label2));
|
||||
|
||||
StepVerifier.create(courseLabelService.findAll())
|
||||
.assertNext(result -> assertThat(result.getLabelName()).isEqualTo("适合新手"))
|
||||
.assertNext(result -> assertThat(result.getLabelName()).isEqualTo("高强度"))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAll_shouldReturnEmptyWhenNoLabels() {
|
||||
when(courseLabelRepository.findAll()).thenReturn(Flux.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.findAll())
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findAll();
|
||||
}
|
||||
|
||||
// ==================== findByKeyword ====================
|
||||
|
||||
@Test
|
||||
void findByKeyword_shouldReturnMatchingLabels() {
|
||||
CourseLabel label = new CourseLabel();
|
||||
label.setId(1L);
|
||||
label.setLabelName("适合新手");
|
||||
when(courseLabelRepository.findByKeyword("新手")).thenReturn(Flux.just(label));
|
||||
|
||||
StepVerifier.create(courseLabelService.findByKeyword("新手"))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getLabelName()).isEqualTo("适合新手");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findByKeyword("新手");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByKeyword_shouldReturnEmptyWhenNoMatch() {
|
||||
when(courseLabelRepository.findByKeyword("不存在")).thenReturn(Flux.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.findByKeyword("不存在"))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findByKeyword("不存在");
|
||||
}
|
||||
|
||||
// ==================== create ====================
|
||||
|
||||
@Test
|
||||
void create_shouldSaveWhenNameNotExists() {
|
||||
CourseLabel input = new CourseLabel();
|
||||
input.setLabelName("适合新手");
|
||||
input.setColor("#52c41a");
|
||||
|
||||
CourseLabel saved = new CourseLabel();
|
||||
saved.setId(1L);
|
||||
saved.setLabelName("适合新手");
|
||||
saved.setColor("#52c41a");
|
||||
|
||||
when(courseLabelRepository.findByLabelName("适合新手")).thenReturn(Mono.empty());
|
||||
when(courseLabelRepository.save(any(CourseLabel.class))).thenReturn(Mono.just(saved));
|
||||
|
||||
StepVerifier.create(courseLabelService.create(input))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getLabelName()).isEqualTo("适合新手");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findByLabelName("适合新手");
|
||||
verify(courseLabelRepository).save(any(CourseLabel.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_shouldErrorWhenNameAlreadyExists() {
|
||||
CourseLabel existing = new CourseLabel();
|
||||
existing.setId(1L);
|
||||
existing.setLabelName("适合新手");
|
||||
|
||||
CourseLabel input = new CourseLabel();
|
||||
input.setLabelName("适合新手");
|
||||
|
||||
when(courseLabelRepository.findByLabelName("适合新手")).thenReturn(Mono.just(existing));
|
||||
// switchIfEmpty 是急切求值的,需要为 save 提供 stub 避免 NPE
|
||||
lenient().when(courseLabelRepository.save(any(CourseLabel.class))).thenReturn(Mono.just(new CourseLabel()));
|
||||
|
||||
StepVerifier.create(courseLabelService.create(input))
|
||||
.expectErrorMatches(throwable ->
|
||||
throwable instanceof RuntimeException &&
|
||||
"标签名称已存在".equals(throwable.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(courseLabelRepository).findByLabelName("适合新手");
|
||||
// switchIfEmpty 是急切求值的,save 会被调用但不会影响结果
|
||||
}
|
||||
|
||||
// ==================== update ====================
|
||||
|
||||
@Test
|
||||
void update_shouldUpdateLabel() {
|
||||
CourseLabel input = new CourseLabel();
|
||||
input.setLabelName("适合进阶");
|
||||
|
||||
CourseLabel updated = new CourseLabel();
|
||||
updated.setId(1L);
|
||||
updated.setLabelName("适合进阶");
|
||||
|
||||
when(courseLabelRepository.update(any(CourseLabel.class))).thenReturn(Mono.just(updated));
|
||||
|
||||
StepVerifier.create(courseLabelService.update(1L, input))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getLabelName()).isEqualTo("适合进阶");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).update(any(CourseLabel.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void update_shouldSetIdOnInput() {
|
||||
CourseLabel input = new CourseLabel();
|
||||
input.setLabelName("适合进阶");
|
||||
|
||||
when(courseLabelRepository.update(any(CourseLabel.class))).thenAnswer(invocation -> {
|
||||
CourseLabel arg = invocation.getArgument(0);
|
||||
return Mono.just(arg);
|
||||
});
|
||||
|
||||
StepVerifier.create(courseLabelService.update(1L, input))
|
||||
.assertNext(result -> assertThat(result.getId()).isEqualTo(1L))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
// ==================== delete ====================
|
||||
|
||||
@Test
|
||||
void delete_shouldDeleteLabel() {
|
||||
when(courseLabelRepository.deleteById(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.delete(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).deleteById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_shouldCompleteWhenNotFound() {
|
||||
when(courseLabelRepository.deleteById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.delete(999L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).deleteById(999L);
|
||||
}
|
||||
|
||||
// ==================== findByTypeId ====================
|
||||
|
||||
@Test
|
||||
void findByTypeId_shouldReturnLabelsForType() {
|
||||
CourseLabel label1 = new CourseLabel();
|
||||
label1.setId(1L);
|
||||
label1.setLabelName("适合新手");
|
||||
CourseLabel label2 = new CourseLabel();
|
||||
label2.setId(2L);
|
||||
label2.setLabelName("减脂塑形");
|
||||
|
||||
when(courseLabelRepository.findByTypeId(1L)).thenReturn(Flux.just(label1, label2));
|
||||
|
||||
StepVerifier.create(courseLabelService.findByTypeId(1L))
|
||||
.assertNext(result -> assertThat(result.getLabelName()).isEqualTo("适合新手"))
|
||||
.assertNext(result -> assertThat(result.getLabelName()).isEqualTo("减脂塑形"))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findByTypeId(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByTypeId_shouldReturnEmptyWhenNoLabels() {
|
||||
when(courseLabelRepository.findByTypeId(1L)).thenReturn(Flux.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.findByTypeId(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findByTypeId(1L);
|
||||
}
|
||||
|
||||
// ==================== addLabelsToType ====================
|
||||
|
||||
@Test
|
||||
void addLabelsToType_shouldAddLabelsAndInvalidateCache() {
|
||||
when(courseLabelRepository.addLabelsToType(1L, List.of(1L, 2L))).thenReturn(Mono.empty());
|
||||
|
||||
GroupCourse course1 = new GroupCourse();
|
||||
course1.setId(101L);
|
||||
GroupCourse course2 = new GroupCourse();
|
||||
course2.setId(102L);
|
||||
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.just(course1, course2));
|
||||
when(redisUtil.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
|
||||
when(redisUtil.delete("group_course:detail:102")).thenReturn(Mono.just(1L));
|
||||
|
||||
StepVerifier.create(courseLabelService.addLabelsToType(1L, List.of(1L, 2L)))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).addLabelsToType(1L, List.of(1L, 2L));
|
||||
verify(groupCourseRepository).findByCourseType(1L);
|
||||
verify(redisUtil).delete("group_course:detail:101");
|
||||
verify(redisUtil).delete("group_course:detail:102");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addLabelsToType_shouldNotInvalidateCacheWhenNoCourses() {
|
||||
when(courseLabelRepository.addLabelsToType(1L, List.of(1L))).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.addLabelsToType(1L, List.of(1L)))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).addLabelsToType(1L, List.of(1L));
|
||||
verify(groupCourseRepository).findByCourseType(1L);
|
||||
verify(redisUtil, never()).delete(anyString());
|
||||
}
|
||||
|
||||
// ==================== removeLabelFromType ====================
|
||||
|
||||
@Test
|
||||
void removeLabelFromType_shouldRemoveLabelAndInvalidateCache() {
|
||||
when(courseLabelRepository.removeLabelFromType(1L, 1L)).thenReturn(Mono.empty());
|
||||
|
||||
GroupCourse course = new GroupCourse();
|
||||
course.setId(101L);
|
||||
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.just(course));
|
||||
when(redisUtil.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
|
||||
|
||||
StepVerifier.create(courseLabelService.removeLabelFromType(1L, 1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).removeLabelFromType(1L, 1L);
|
||||
verify(groupCourseRepository).findByCourseType(1L);
|
||||
verify(redisUtil).delete("group_course:detail:101");
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeLabelFromType_shouldNotInvalidateCacheWhenNoCourses() {
|
||||
when(courseLabelRepository.removeLabelFromType(1L, 1L)).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.removeLabelFromType(1L, 1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).removeLabelFromType(1L, 1L);
|
||||
verify(redisUtil, never()).delete(anyString());
|
||||
}
|
||||
|
||||
// ==================== clearLabelsFromType ====================
|
||||
|
||||
@Test
|
||||
void clearLabelsFromType_shouldClearLabelsAndInvalidateCache() {
|
||||
when(courseLabelRepository.clearLabelsFromType(1L)).thenReturn(Mono.empty());
|
||||
|
||||
GroupCourse course1 = new GroupCourse();
|
||||
course1.setId(101L);
|
||||
GroupCourse course2 = new GroupCourse();
|
||||
course2.setId(102L);
|
||||
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.just(course1, course2));
|
||||
when(redisUtil.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
|
||||
when(redisUtil.delete("group_course:detail:102")).thenReturn(Mono.just(1L));
|
||||
|
||||
StepVerifier.create(courseLabelService.clearLabelsFromType(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).clearLabelsFromType(1L);
|
||||
verify(groupCourseRepository).findByCourseType(1L);
|
||||
verify(redisUtil).delete("group_course:detail:101");
|
||||
verify(redisUtil).delete("group_course:detail:102");
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearLabelsFromType_shouldNotInvalidateCacheWhenNoCourses() {
|
||||
when(courseLabelRepository.clearLabelsFromType(1L)).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.empty());
|
||||
|
||||
StepVerifier.create(courseLabelService.clearLabelsFromType(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).clearLabelsFromType(1L);
|
||||
verify(redisUtil, never()).delete(anyString());
|
||||
}
|
||||
|
||||
// ==================== findByPage ====================
|
||||
|
||||
@Test
|
||||
void findByPage_shouldReturnPaginatedResults() {
|
||||
PageRequest pageRequest = new PageRequest();
|
||||
pageRequest.setPage(0);
|
||||
pageRequest.setSize(10);
|
||||
|
||||
CourseLabel label1 = new CourseLabel();
|
||||
label1.setId(1L);
|
||||
label1.setLabelName("适合新手");
|
||||
CourseLabel label2 = new CourseLabel();
|
||||
label2.setId(2L);
|
||||
label2.setLabelName("高强度");
|
||||
|
||||
PageResponse<CourseLabel> pageResponse = new PageResponse<>(
|
||||
List.of(label1, label2), 1, 2, 0, 10);
|
||||
|
||||
when(courseLabelRepository.findByPage(pageRequest)).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
StepVerifier.create(courseLabelService.findByPage(pageRequest))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getContent()).hasSize(2);
|
||||
assertThat(result.getTotalElements()).isEqualTo(2);
|
||||
assertThat(result.getTotalPages()).isEqualTo(1);
|
||||
assertThat(result.getCurrentPage()).isZero();
|
||||
assertThat(result.getPageSize()).isEqualTo(10);
|
||||
assertThat(result.isFirst()).isTrue();
|
||||
assertThat(result.isLast()).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findByPage(pageRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByPage_shouldReturnEmptyPageWhenNoResults() {
|
||||
PageRequest pageRequest = new PageRequest();
|
||||
PageResponse<CourseLabel> emptyPage = new PageResponse<>(
|
||||
List.of(), 0, 0, 0, 10);
|
||||
|
||||
when(courseLabelRepository.findByPage(pageRequest)).thenReturn(Mono.just(emptyPage));
|
||||
|
||||
StepVerifier.create(courseLabelService.findByPage(pageRequest))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getContent()).isEmpty();
|
||||
assertThat(result.getTotalElements()).isZero();
|
||||
assertThat(result.getTotalPages()).isZero();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(courseLabelRepository).findByPage(pageRequest);
|
||||
}
|
||||
}
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.event.BookingReminderEventPublisher;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
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 reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseBookingServiceTest {
|
||||
|
||||
@Mock
|
||||
private IGroupCourseBookingRepository bookingRepository;
|
||||
|
||||
@Mock
|
||||
private IGroupCourseRepository courseRepository;
|
||||
|
||||
@Mock
|
||||
private GroupCourseRedisService redisService;
|
||||
|
||||
@Mock
|
||||
private BookingReminderEventPublisher eventPublisher;
|
||||
|
||||
private GroupCourseBookingService service;
|
||||
|
||||
private GroupCourse testCourse;
|
||||
private GroupCourseBooking testBooking;
|
||||
private GroupCourseBooking savedBooking;
|
||||
|
||||
private static final Long COURSE_ID = 1L;
|
||||
private static final Long MEMBER_ID = 100L;
|
||||
private static final Long BOOKING_ID = 10L;
|
||||
private static final Long OTHER_MEMBER_ID = 200L;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// getCourseWithCache eagerly evaluates the switchIfEmpty argument,
|
||||
// so findByIdAndDeletedAtIsNull must never return null
|
||||
// Use lenient because this stub is only needed for bookCourse tests
|
||||
lenient().when(courseRepository.findByIdAndDeletedAtIsNull(anyLong())).thenReturn(Mono.empty());
|
||||
service = new GroupCourseBookingService(bookingRepository, courseRepository, redisService, eventPublisher);
|
||||
|
||||
testCourse = new GroupCourse();
|
||||
testCourse.setId(COURSE_ID);
|
||||
testCourse.setCourseName("瑜伽课");
|
||||
testCourse.setStatus(0L);
|
||||
testCourse.setStartTime(LocalDateTime.now().plusHours(2));
|
||||
testCourse.setEndTime(LocalDateTime.now().plusHours(3));
|
||||
testCourse.setMaxMembers(20);
|
||||
testCourse.setCurrentMembers(2);
|
||||
testCourse.setLocation("健身房A区");
|
||||
|
||||
testBooking = new GroupCourseBooking();
|
||||
testBooking.setId(BOOKING_ID);
|
||||
testBooking.setCourseId(COURSE_ID);
|
||||
testBooking.setMemberId(MEMBER_ID);
|
||||
testBooking.setStatus("0");
|
||||
testBooking.setCourseName("瑜伽课");
|
||||
testBooking.setCourseStartTime(LocalDateTime.now().plusHours(3));
|
||||
testBooking.setCourseEndTime(LocalDateTime.now().plusHours(4));
|
||||
testBooking.setLocation("健身房A区");
|
||||
|
||||
savedBooking = new GroupCourseBooking();
|
||||
savedBooking.setId(BOOKING_ID);
|
||||
savedBooking.setCourseId(COURSE_ID);
|
||||
savedBooking.setMemberId(MEMBER_ID);
|
||||
savedBooking.setStatus("0");
|
||||
savedBooking.setCourseName("瑜伽课");
|
||||
savedBooking.setCourseStartTime(LocalDateTime.now().plusHours(2));
|
||||
savedBooking.setCourseEndTime(LocalDateTime.now().plusHours(3));
|
||||
savedBooking.setLocation("健身房A区");
|
||||
}
|
||||
|
||||
// ==================== bookCourse ====================
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldSucceedWithValidInputs() {
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(redisService.getCachedCourse(COURSE_ID)).thenReturn(Mono.just(testCourse));
|
||||
when(bookingRepository.countValidBookings(COURSE_ID)).thenReturn(Mono.just(2L));
|
||||
when(bookingRepository.findConflictingBookings(eq(MEMBER_ID), any(LocalDateTime.class), any(LocalDateTime.class)))
|
||||
.thenReturn(Flux.empty());
|
||||
when(bookingRepository.findValidBooking(COURSE_ID, MEMBER_ID)).thenReturn(Mono.empty());
|
||||
when(bookingRepository.save(any(GroupCourseBooking.class))).thenReturn(Mono.just(savedBooking));
|
||||
when(redisService.releaseLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.assertNext(booking -> {
|
||||
assertThat(booking.getId()).isEqualTo(BOOKING_ID);
|
||||
assertThat(booking.getCourseId()).isEqualTo(COURSE_ID);
|
||||
assertThat(booking.getMemberId()).isEqualTo(MEMBER_ID);
|
||||
assertThat(booking.getStatus()).isEqualTo("0");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verify(redisService).getCachedCourse(COURSE_ID);
|
||||
verify(bookingRepository).countValidBookings(COURSE_ID);
|
||||
verify(bookingRepository).findConflictingBookings(eq(MEMBER_ID), any(LocalDateTime.class), any(LocalDateTime.class));
|
||||
verify(bookingRepository).findValidBooking(COURSE_ID, MEMBER_ID);
|
||||
verify(bookingRepository).save(any(GroupCourseBooking.class));
|
||||
verify(redisService).releaseLock(eq(COURSE_ID), anyString());
|
||||
verify(eventPublisher).publishBookingSuccessEvent(eq(BOOKING_ID), eq(MEMBER_ID), eq("瑜伽课"), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldFailWhenLockNotAcquired() {
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(false));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "系统繁忙,请稍后重试".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verifyNoInteractions(bookingRepository);
|
||||
verifyNoInteractions(eventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldFailWhenCourseNotFound() {
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(redisService.getCachedCourse(COURSE_ID)).thenReturn(Mono.empty());
|
||||
when(redisService.releaseLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "团课不存在".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verify(redisService).getCachedCourse(COURSE_ID);
|
||||
verify(courseRepository).findByIdAndDeletedAtIsNull(COURSE_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(COURSE_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldFailWhenCourseStatusIsCancelled() {
|
||||
testCourse.setStatus(1L);
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(redisService.getCachedCourse(COURSE_ID)).thenReturn(Mono.just(testCourse));
|
||||
when(bookingRepository.countValidBookings(COURSE_ID)).thenReturn(Mono.just(2L));
|
||||
when(redisService.releaseLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "课程已取消,无法预约".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verify(redisService).getCachedCourse(COURSE_ID);
|
||||
verify(bookingRepository).countValidBookings(COURSE_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(COURSE_ID), anyString());
|
||||
verifyNoMoreInteractions(eventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldFailWhenCourseIsEnded() {
|
||||
testCourse.setStatus(2L);
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(redisService.getCachedCourse(COURSE_ID)).thenReturn(Mono.just(testCourse));
|
||||
when(bookingRepository.countValidBookings(COURSE_ID)).thenReturn(Mono.just(2L));
|
||||
when(redisService.releaseLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "课程已结束,无法预约".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verify(redisService).getCachedCourse(COURSE_ID);
|
||||
verify(bookingRepository).countValidBookings(COURSE_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(COURSE_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldFailWhenLessThan30minBeforeStart() {
|
||||
testCourse.setStartTime(LocalDateTime.now().plusMinutes(15));
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(redisService.getCachedCourse(COURSE_ID)).thenReturn(Mono.just(testCourse));
|
||||
when(bookingRepository.countValidBookings(COURSE_ID)).thenReturn(Mono.just(2L));
|
||||
when(redisService.releaseLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "需在课程开始前30分钟预约".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verify(redisService).getCachedCourse(COURSE_ID);
|
||||
verify(bookingRepository).countValidBookings(COURSE_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(COURSE_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldFailWhenCourseIsFull() {
|
||||
testCourse.setCurrentMembers(20);
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(redisService.getCachedCourse(COURSE_ID)).thenReturn(Mono.just(testCourse));
|
||||
when(bookingRepository.countValidBookings(COURSE_ID)).thenReturn(Mono.just(20L));
|
||||
when(redisService.releaseLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "课程已满".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verify(redisService).getCachedCourse(COURSE_ID);
|
||||
verify(bookingRepository).countValidBookings(COURSE_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(COURSE_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldFailWhenTimeConflictExists() {
|
||||
GroupCourseBooking conflict = new GroupCourseBooking();
|
||||
conflict.setCourseName("冲突课程");
|
||||
conflict.setCourseStartTime(LocalDateTime.now().plusHours(2));
|
||||
conflict.setCourseEndTime(LocalDateTime.now().plusHours(3));
|
||||
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(redisService.getCachedCourse(COURSE_ID)).thenReturn(Mono.just(testCourse));
|
||||
when(bookingRepository.countValidBookings(COURSE_ID)).thenReturn(Mono.just(2L));
|
||||
when(bookingRepository.findConflictingBookings(eq(MEMBER_ID), any(LocalDateTime.class), any(LocalDateTime.class)))
|
||||
.thenReturn(Flux.just(conflict));
|
||||
when(redisService.releaseLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& ex.getMessage().contains("您已预约的课程与当前课程时间冲突"))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verify(redisService).getCachedCourse(COURSE_ID);
|
||||
verify(bookingRepository).countValidBookings(COURSE_ID);
|
||||
verify(bookingRepository).findConflictingBookings(eq(MEMBER_ID), any(LocalDateTime.class), any(LocalDateTime.class));
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(COURSE_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bookCourse_shouldFailWhenAlreadyBooked() {
|
||||
when(redisService.acquireLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(redisService.getCachedCourse(COURSE_ID)).thenReturn(Mono.just(testCourse));
|
||||
when(bookingRepository.countValidBookings(COURSE_ID)).thenReturn(Mono.just(2L));
|
||||
when(bookingRepository.findConflictingBookings(eq(MEMBER_ID), any(LocalDateTime.class), any(LocalDateTime.class)))
|
||||
.thenReturn(Flux.empty());
|
||||
when(bookingRepository.findValidBooking(COURSE_ID, MEMBER_ID)).thenReturn(Mono.just(testBooking));
|
||||
when(redisService.releaseLock(eq(COURSE_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.bookCourse(COURSE_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "您已预约该课程".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(COURSE_ID), anyString());
|
||||
verify(redisService).getCachedCourse(COURSE_ID);
|
||||
verify(bookingRepository).countValidBookings(COURSE_ID);
|
||||
verify(bookingRepository).findConflictingBookings(eq(MEMBER_ID), any(LocalDateTime.class), any(LocalDateTime.class));
|
||||
verify(bookingRepository).findValidBooking(COURSE_ID, MEMBER_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(COURSE_ID), anyString());
|
||||
}
|
||||
|
||||
// ==================== cancelBooking ====================
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldSucceedWithValidInputs() {
|
||||
when(redisService.acquireLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(bookingRepository.findById(BOOKING_ID)).thenReturn(Mono.just(testBooking));
|
||||
when(bookingRepository.updateStatus(BOOKING_ID, "1")).thenReturn(Mono.just(1));
|
||||
when(redisService.releaseLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(bookingRepository.findById(BOOKING_ID)).thenReturn(Mono.just(testBooking));
|
||||
|
||||
StepVerifier.create(service.cancelBooking(BOOKING_ID, MEMBER_ID))
|
||||
.assertNext(booking -> {
|
||||
assertThat(booking.getId()).isEqualTo(BOOKING_ID);
|
||||
assertThat(booking.getMemberId()).isEqualTo(MEMBER_ID);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisService).acquireLock(eq(BOOKING_ID), anyString());
|
||||
verify(bookingRepository, times(2)).findById(BOOKING_ID);
|
||||
verify(bookingRepository).updateStatus(BOOKING_ID, "1");
|
||||
verify(redisService).releaseLock(eq(BOOKING_ID), anyString());
|
||||
verify(eventPublisher).publishBookingCancelEvent(eq(BOOKING_ID), eq(MEMBER_ID), eq("瑜伽课"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldFailWhenLockNotAcquired() {
|
||||
when(redisService.acquireLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(false));
|
||||
|
||||
StepVerifier.create(service.cancelBooking(BOOKING_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "系统繁忙,请稍后重试".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(BOOKING_ID), anyString());
|
||||
verifyNoInteractions(eventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldFailWhenBookingNotFound() {
|
||||
when(redisService.acquireLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(bookingRepository.findById(BOOKING_ID)).thenReturn(Mono.empty());
|
||||
when(redisService.releaseLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.cancelBooking(BOOKING_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "预约记录不存在".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(BOOKING_ID), anyString());
|
||||
verify(bookingRepository).findById(BOOKING_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(BOOKING_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldFailWhenNotOwner() {
|
||||
when(redisService.acquireLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(bookingRepository.findById(BOOKING_ID)).thenReturn(Mono.just(testBooking));
|
||||
when(redisService.releaseLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.cancelBooking(BOOKING_ID, OTHER_MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "无权取消他人预约".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(BOOKING_ID), anyString());
|
||||
verify(bookingRepository).findById(BOOKING_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(BOOKING_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldFailWhenAlreadyCancelled() {
|
||||
testBooking.setStatus("1");
|
||||
when(redisService.acquireLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(bookingRepository.findById(BOOKING_ID)).thenReturn(Mono.just(testBooking));
|
||||
when(redisService.releaseLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.cancelBooking(BOOKING_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "预约已取消,无需重复取消".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(BOOKING_ID), anyString());
|
||||
verify(bookingRepository).findById(BOOKING_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(BOOKING_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldFailWhenAlreadyAttended() {
|
||||
testBooking.setStatus("2");
|
||||
when(redisService.acquireLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(bookingRepository.findById(BOOKING_ID)).thenReturn(Mono.just(testBooking));
|
||||
when(redisService.releaseLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.cancelBooking(BOOKING_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "课程已出席,无法取消".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(BOOKING_ID), anyString());
|
||||
verify(bookingRepository).findById(BOOKING_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(BOOKING_ID), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelBooking_shouldFailWhenLessThan2hBeforeStart() {
|
||||
testBooking.setCourseStartTime(LocalDateTime.now().plusHours(1));
|
||||
when(redisService.acquireLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
when(bookingRepository.findById(BOOKING_ID)).thenReturn(Mono.just(testBooking));
|
||||
when(redisService.releaseLock(eq(BOOKING_ID), anyString())).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.cancelBooking(BOOKING_ID, MEMBER_ID))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "需在课程开始前2小时取消".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(redisService).acquireLock(eq(BOOKING_ID), anyString());
|
||||
verify(bookingRepository).findById(BOOKING_ID);
|
||||
verify(redisService, atLeastOnce()).releaseLock(eq(BOOKING_ID), anyString());
|
||||
}
|
||||
|
||||
// ==================== getBookingsByMemberId ====================
|
||||
|
||||
@Test
|
||||
void getBookingsByMemberId_shouldReturnBookings() {
|
||||
GroupCourseBooking booking1 = new GroupCourseBooking();
|
||||
booking1.setId(1L);
|
||||
booking1.setMemberId(MEMBER_ID);
|
||||
GroupCourseBooking booking2 = new GroupCourseBooking();
|
||||
booking2.setId(2L);
|
||||
booking2.setMemberId(MEMBER_ID);
|
||||
|
||||
when(bookingRepository.findByMemberId(MEMBER_ID)).thenReturn(Flux.just(booking1, booking2));
|
||||
|
||||
StepVerifier.create(service.getBookingsByMemberId(MEMBER_ID))
|
||||
.expectNext(booking1, booking2)
|
||||
.verifyComplete();
|
||||
|
||||
verify(bookingRepository).findByMemberId(MEMBER_ID);
|
||||
}
|
||||
|
||||
// ==================== getBookingById ====================
|
||||
|
||||
@Test
|
||||
void getBookingById_shouldReturnBooking() {
|
||||
when(bookingRepository.findById(BOOKING_ID)).thenReturn(Mono.just(testBooking));
|
||||
|
||||
StepVerifier.create(service.getBookingById(BOOKING_ID))
|
||||
.assertNext(booking -> {
|
||||
assertThat(booking.getId()).isEqualTo(BOOKING_ID);
|
||||
assertThat(booking.getMemberId()).isEqualTo(MEMBER_ID);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(bookingRepository).findById(BOOKING_ID);
|
||||
}
|
||||
|
||||
// ==================== getBookingsByCourseId ====================
|
||||
|
||||
@Test
|
||||
void getBookingsByCourseId_shouldReturnBookings() {
|
||||
GroupCourseBooking booking1 = new GroupCourseBooking();
|
||||
booking1.setId(1L);
|
||||
booking1.setCourseId(COURSE_ID);
|
||||
GroupCourseBooking booking2 = new GroupCourseBooking();
|
||||
booking2.setId(2L);
|
||||
booking2.setCourseId(COURSE_ID);
|
||||
|
||||
when(bookingRepository.findByCourseId(COURSE_ID)).thenReturn(Flux.just(booking1, booking2));
|
||||
|
||||
StepVerifier.create(service.getBookingsByCourseId(COURSE_ID))
|
||||
.expectNext(booking1, booking2)
|
||||
.verifyComplete();
|
||||
|
||||
verify(bookingRepository).findByCourseId(COURSE_ID);
|
||||
}
|
||||
|
||||
// ==================== processAbsentMembers ====================
|
||||
|
||||
@Test
|
||||
void processAbsentMembers_shouldProcessAbsentBookings() {
|
||||
GroupCourseBooking absent1 = new GroupCourseBooking();
|
||||
absent1.setId(1L);
|
||||
absent1.setMemberId(MEMBER_ID);
|
||||
absent1.setCourseId(COURSE_ID);
|
||||
GroupCourseBooking absent2 = new GroupCourseBooking();
|
||||
absent2.setId(2L);
|
||||
absent2.setMemberId(MEMBER_ID + 1);
|
||||
absent2.setCourseId(COURSE_ID);
|
||||
|
||||
when(bookingRepository.findAbsentMembers()).thenReturn(Flux.just(absent1, absent2));
|
||||
when(bookingRepository.updateToAbsent(1L)).thenReturn(Mono.just(1));
|
||||
when(bookingRepository.updateToAbsent(2L)).thenReturn(Mono.just(1));
|
||||
|
||||
StepVerifier.create(service.processAbsentMembers())
|
||||
.assertNext(count -> assertThat(count).isEqualTo(2))
|
||||
.verifyComplete();
|
||||
|
||||
verify(bookingRepository).findAbsentMembers();
|
||||
verify(bookingRepository).updateToAbsent(1L);
|
||||
verify(bookingRepository).updateToAbsent(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processAbsentMembers_shouldReturnZeroWhenNoAbsentBookings() {
|
||||
when(bookingRepository.findAbsentMembers()).thenReturn(Flux.empty());
|
||||
|
||||
StepVerifier.create(service.processAbsentMembers())
|
||||
.assertNext(count -> assertThat(count).isEqualTo(0))
|
||||
.verifyComplete();
|
||||
|
||||
verify(bookingRepository).findAbsentMembers();
|
||||
verify(bookingRepository, never()).updateToAbsent(anyLong());
|
||||
}
|
||||
}
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseRecommend;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRecommendRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
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 reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseRecommendServiceTest {
|
||||
|
||||
@Mock
|
||||
private IGroupCourseRecommendRepository recommendRepository;
|
||||
|
||||
@Mock
|
||||
private IGroupCourseRepository groupCourseRepository;
|
||||
|
||||
private GroupCourseRecommendService service;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<GroupCourseRecommend> recommendCaptor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new GroupCourseRecommendService(recommendRepository, groupCourseRepository);
|
||||
}
|
||||
|
||||
private GroupCourseRecommend createRecommend(Long id, Long courseId, String title) {
|
||||
GroupCourseRecommend r = new GroupCourseRecommend();
|
||||
r.setId(id);
|
||||
r.setCourseId(courseId);
|
||||
r.setRecommendTitle(title);
|
||||
r.setRecommendContent("推荐内容");
|
||||
r.setRecommendReason("推荐理由");
|
||||
r.setPriority(10);
|
||||
r.setIsActive(true);
|
||||
return r;
|
||||
}
|
||||
|
||||
private GroupCourse createCourse(Long id, String name) {
|
||||
GroupCourse c = new GroupCourse();
|
||||
c.setId(id);
|
||||
c.setCourseName(name);
|
||||
return c;
|
||||
}
|
||||
|
||||
// ==================== findById ====================
|
||||
|
||||
@Test
|
||||
void findById_shouldReturnRecommendWithCourseInfo() {
|
||||
GroupCourseRecommend recommend = createRecommend(1L, 10L, "本周热门");
|
||||
GroupCourse course = createCourse(10L, "Push-up");
|
||||
when(recommendRepository.findById(1L)).thenReturn(Mono.just(recommend));
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(10L)).thenReturn(Mono.just(course));
|
||||
|
||||
StepVerifier.create(service.findById(1L))
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getId()).isEqualTo(1L);
|
||||
assertThat(r.getRecommendTitle()).isEqualTo("本周热门");
|
||||
assertThat(r.getGroupCourse()).isNotNull();
|
||||
assertThat(r.getGroupCourse().getCourseName()).isEqualTo("Push-up");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findById(1L);
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findById_shouldReturnRecommendWithoutCourseWhenCourseIdIsNull() {
|
||||
GroupCourseRecommend recommend = createRecommend(1L, null, "本周热门");
|
||||
when(recommendRepository.findById(1L)).thenReturn(Mono.just(recommend));
|
||||
|
||||
StepVerifier.create(service.findById(1L))
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getId()).isEqualTo(1L);
|
||||
assertThat(r.getGroupCourse()).isNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findById(1L);
|
||||
verify(groupCourseRepository, never()).findByIdAndDeletedAtIsNull(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findById_shouldReturnEmptyWhenNotFound() {
|
||||
when(recommendRepository.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.findById(999L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findById(999L);
|
||||
}
|
||||
|
||||
// ==================== findAll ====================
|
||||
|
||||
@Test
|
||||
void findAll_shouldReturnAllWithCourseInfo() {
|
||||
GroupCourseRecommend r1 = createRecommend(1L, 10L, "本周热门");
|
||||
GroupCourseRecommend r2 = createRecommend(2L, 20L, "新课程推荐");
|
||||
GroupCourse course1 = createCourse(10L, "Push-up");
|
||||
GroupCourse course2 = createCourse(20L, "Yoga");
|
||||
when(recommendRepository.findAll()).thenReturn(Flux.just(r1, r2));
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(10L)).thenReturn(Mono.just(course1));
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(20L)).thenReturn(Mono.just(course2));
|
||||
|
||||
StepVerifier.create(service.findAll())
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getId()).isEqualTo(1L);
|
||||
assertThat(r.getGroupCourse().getCourseName()).isEqualTo("Push-up");
|
||||
})
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getId()).isEqualTo(2L);
|
||||
assertThat(r.getGroupCourse().getCourseName()).isEqualTo("Yoga");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findAll();
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(10L);
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(20L);
|
||||
}
|
||||
|
||||
// ==================== findAll(String, String) ====================
|
||||
|
||||
@Test
|
||||
void findAllWithSort_shouldReturnSortedResults() {
|
||||
GroupCourseRecommend r1 = createRecommend(1L, 10L, "A课程");
|
||||
GroupCourseRecommend r2 = createRecommend(2L, 20L, "B课程");
|
||||
GroupCourse course1 = createCourse(10L, "Push-up");
|
||||
GroupCourse course2 = createCourse(20L, "Yoga");
|
||||
when(recommendRepository.findAll("priority", "desc")).thenReturn(Flux.just(r1, r2));
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(10L)).thenReturn(Mono.just(course1));
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(20L)).thenReturn(Mono.just(course2));
|
||||
|
||||
StepVerifier.create(service.findAll("priority", "desc"))
|
||||
.assertNext(r -> assertThat(r.getId()).isEqualTo(1L))
|
||||
.assertNext(r -> assertThat(r.getId()).isEqualTo(2L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findAll("priority", "desc");
|
||||
}
|
||||
|
||||
// ==================== findAllActive ====================
|
||||
|
||||
@Test
|
||||
void findAllActive_shouldReturnOnlyActive() {
|
||||
GroupCourseRecommend r1 = createRecommend(1L, 10L, "本周热门");
|
||||
r1.setIsActive(true);
|
||||
when(recommendRepository.findAllActive()).thenReturn(Flux.just(r1));
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(10L)).thenReturn(Mono.just(createCourse(10L, "Push-up")));
|
||||
|
||||
StepVerifier.create(service.findAllActive())
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getId()).isEqualTo(1L);
|
||||
assertThat(r.getIsActive()).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findAllActive();
|
||||
}
|
||||
|
||||
// ==================== findByCourseId ====================
|
||||
|
||||
@Test
|
||||
void findByCourseId_shouldReturnByCourseId() {
|
||||
GroupCourseRecommend r1 = createRecommend(1L, 10L, "本周热门");
|
||||
when(recommendRepository.findByCourseId(10L)).thenReturn(Flux.just(r1));
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(10L)).thenReturn(Mono.just(createCourse(10L, "Push-up")));
|
||||
|
||||
StepVerifier.create(service.findByCourseId(10L))
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getCourseId()).isEqualTo(10L);
|
||||
assertThat(r.getGroupCourse().getCourseName()).isEqualTo("Push-up");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findByCourseId(10L);
|
||||
}
|
||||
|
||||
// ==================== create ====================
|
||||
|
||||
@Test
|
||||
void create_shouldSaveWhenCourseExists() {
|
||||
GroupCourseRecommend recommend = createRecommend(null, 10L, "本周热门");
|
||||
GroupCourse course = createCourse(10L, "Push-up");
|
||||
GroupCourseRecommend saved = createRecommend(1L, 10L, "本周热门");
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(10L)).thenReturn(Mono.just(course));
|
||||
when(recommendRepository.save(any(GroupCourseRecommend.class))).thenReturn(Mono.just(saved));
|
||||
|
||||
StepVerifier.create(service.create(recommend))
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getId()).isEqualTo(1L);
|
||||
assertThat(r.getCourseId()).isEqualTo(10L);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(10L);
|
||||
verify(recommendRepository).save(any(GroupCourseRecommend.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_shouldErrorWhenCourseIdIsNull() {
|
||||
GroupCourseRecommend recommend = createRecommend(null, null, "本周热门");
|
||||
|
||||
StepVerifier.create(service.create(recommend))
|
||||
.expectErrorMatches(e -> e instanceof RuntimeException
|
||||
&& "团课ID不能为空".equals(e.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(groupCourseRepository, never()).findByIdAndDeletedAtIsNull(any());
|
||||
verify(recommendRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_shouldErrorWhenCourseNotFound() {
|
||||
GroupCourseRecommend recommend = createRecommend(null, 999L, "本周热门");
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(999L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.create(recommend))
|
||||
.expectErrorMatches(e -> e instanceof RuntimeException
|
||||
&& "团课不存在".equals(e.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(999L);
|
||||
verify(recommendRepository, never()).save(any());
|
||||
}
|
||||
|
||||
// ==================== update ====================
|
||||
|
||||
@Test
|
||||
void update_shouldUpdateSpecificFields() {
|
||||
GroupCourseRecommend existing = createRecommend(1L, 10L, "原标题");
|
||||
GroupCourseRecommend updateReq = new GroupCourseRecommend();
|
||||
updateReq.setRecommendTitle("新标题");
|
||||
updateReq.setRecommendContent("新内容");
|
||||
updateReq.setRecommendReason("新理由");
|
||||
updateReq.setPriority(20);
|
||||
updateReq.setIsActive(false);
|
||||
updateReq.setCourseId(30L);
|
||||
|
||||
GroupCourseRecommend updated = createRecommend(1L, 30L, "新标题");
|
||||
updated.setRecommendContent("新内容");
|
||||
updated.setRecommendReason("新理由");
|
||||
updated.setPriority(20);
|
||||
updated.setIsActive(false);
|
||||
|
||||
when(recommendRepository.findById(1L)).thenReturn(Mono.just(existing));
|
||||
when(recommendRepository.update(any(GroupCourseRecommend.class))).thenReturn(Mono.just(updated));
|
||||
|
||||
StepVerifier.create(service.update(1L, updateReq))
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getRecommendTitle()).isEqualTo("新标题");
|
||||
assertThat(r.getRecommendContent()).isEqualTo("新内容");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findById(1L);
|
||||
verify(recommendRepository).update(recommendCaptor.capture());
|
||||
GroupCourseRecommend captured = recommendCaptor.getValue();
|
||||
assertThat(captured.getRecommendTitle()).isEqualTo("新标题");
|
||||
assertThat(captured.getRecommendContent()).isEqualTo("新内容");
|
||||
assertThat(captured.getRecommendReason()).isEqualTo("新理由");
|
||||
assertThat(captured.getPriority()).isEqualTo(20);
|
||||
assertThat(captured.getIsActive()).isFalse();
|
||||
assertThat(captured.getCourseId()).isEqualTo(30L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void update_shouldErrorWhenNotFound() {
|
||||
GroupCourseRecommend updateReq = new GroupCourseRecommend();
|
||||
updateReq.setRecommendTitle("新标题");
|
||||
when(recommendRepository.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.update(999L, updateReq))
|
||||
.expectErrorMatches(e -> e instanceof RuntimeException
|
||||
&& "团课推荐不存在".equals(e.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(recommendRepository).findById(999L);
|
||||
verify(recommendRepository, never()).update(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void update_shouldOnlyUpdateNonNullFields() {
|
||||
GroupCourseRecommend existing = createRecommend(1L, 10L, "原标题");
|
||||
GroupCourseRecommend updateReq = new GroupCourseRecommend();
|
||||
updateReq.setRecommendTitle("仅标题");
|
||||
|
||||
when(recommendRepository.findById(1L)).thenReturn(Mono.just(existing));
|
||||
when(recommendRepository.update(any(GroupCourseRecommend.class))).thenReturn(Mono.just(existing));
|
||||
|
||||
StepVerifier.create(service.update(1L, updateReq))
|
||||
.assertNext(r -> assertThat(r.getRecommendTitle()).isEqualTo("仅标题"))
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).update(recommendCaptor.capture());
|
||||
GroupCourseRecommend captured = recommendCaptor.getValue();
|
||||
assertThat(captured.getRecommendTitle()).isEqualTo("仅标题");
|
||||
assertThat(captured.getRecommendContent()).isEqualTo("推荐内容");
|
||||
assertThat(captured.getRecommendReason()).isEqualTo("推荐理由");
|
||||
assertThat(captured.getPriority()).isEqualTo(10);
|
||||
assertThat(captured.getIsActive()).isTrue();
|
||||
assertThat(captured.getCourseId()).isEqualTo(10L);
|
||||
}
|
||||
|
||||
// ==================== delete ====================
|
||||
|
||||
@Test
|
||||
void delete_shouldDeleteWhenExists() {
|
||||
GroupCourseRecommend recommend = createRecommend(1L, 10L, "本周热门");
|
||||
when(recommendRepository.findById(1L)).thenReturn(Mono.just(recommend));
|
||||
when(recommendRepository.deleteById(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.delete(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).findById(1L);
|
||||
verify(recommendRepository).deleteById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_shouldErrorWhenNotFound() {
|
||||
when(recommendRepository.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.delete(999L))
|
||||
.expectErrorMatches(e -> e instanceof RuntimeException
|
||||
&& "团课推荐不存在".equals(e.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(recommendRepository).findById(999L);
|
||||
verify(recommendRepository, never()).deleteById(any());
|
||||
}
|
||||
|
||||
// ==================== enable ====================
|
||||
|
||||
@Test
|
||||
void enable_shouldUpdateActiveStatusToTrue() {
|
||||
GroupCourseRecommend result = createRecommend(1L, 10L, "本周热门");
|
||||
result.setIsActive(true);
|
||||
when(recommendRepository.updateActiveStatus(1L, true)).thenReturn(Mono.just(result));
|
||||
|
||||
StepVerifier.create(service.enable(1L))
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getId()).isEqualTo(1L);
|
||||
assertThat(r.getIsActive()).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).updateActiveStatus(1L, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void enable_shouldErrorWhenNotFound() {
|
||||
when(recommendRepository.updateActiveStatus(999L, true)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.enable(999L))
|
||||
.expectErrorMatches(e -> e instanceof RuntimeException
|
||||
&& "团课推荐不存在".equals(e.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(recommendRepository).updateActiveStatus(999L, true);
|
||||
}
|
||||
|
||||
// ==================== disable ====================
|
||||
|
||||
@Test
|
||||
void disable_shouldUpdateActiveStatusToFalse() {
|
||||
GroupCourseRecommend result = createRecommend(1L, 10L, "本周热门");
|
||||
result.setIsActive(false);
|
||||
when(recommendRepository.updateActiveStatus(1L, false)).thenReturn(Mono.just(result));
|
||||
|
||||
StepVerifier.create(service.disable(1L))
|
||||
.assertNext(r -> {
|
||||
assertThat(r.getId()).isEqualTo(1L);
|
||||
assertThat(r.getIsActive()).isFalse();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(recommendRepository).updateActiveStatus(1L, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void disable_shouldErrorWhenNotFound() {
|
||||
when(recommendRepository.updateActiveStatus(999L, false)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.disable(999L))
|
||||
.expectErrorMatches(e -> e instanceof RuntimeException
|
||||
&& "团课推荐不存在".equals(e.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(recommendRepository).updateActiveStatus(999L, false);
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.core.ReactiveValueOperations;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseRedisServiceTest {
|
||||
|
||||
@Mock
|
||||
private ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
|
||||
|
||||
@Mock
|
||||
private ReactiveValueOperations<String, Object> reactiveValueOps;
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private GroupCourseRedisService service;
|
||||
|
||||
private GroupCourse testCourse;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
lenient().when(reactiveRedisTemplate.opsForValue()).thenReturn(reactiveValueOps);
|
||||
service = new GroupCourseRedisService(reactiveRedisTemplate, objectMapper);
|
||||
|
||||
testCourse = new GroupCourse();
|
||||
testCourse.setId(1L);
|
||||
testCourse.setCourseName("瑜伽课");
|
||||
testCourse.setCourseType(1L);
|
||||
}
|
||||
|
||||
// ==================== cacheCourse ====================
|
||||
|
||||
@Test
|
||||
void cacheCourse_shouldSerializeAndSetInRedis() {
|
||||
when(reactiveValueOps.set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5))))
|
||||
.thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.cacheCourse(testCourse))
|
||||
.verifyComplete();
|
||||
|
||||
verify(reactiveValueOps).set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheCourse_shouldErrorWhenRedisFails() {
|
||||
when(reactiveValueOps.set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5))))
|
||||
.thenReturn(Mono.error(new RuntimeException("Redis error")));
|
||||
|
||||
StepVerifier.create(service.cacheCourse(testCourse))
|
||||
.expectError(RuntimeException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
// ==================== getCachedCourse ====================
|
||||
|
||||
@Test
|
||||
void getCachedCourse_shouldReturnCachedCourseWhenFound() throws Exception {
|
||||
String json = objectMapper.writeValueAsString(testCourse);
|
||||
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.just(json));
|
||||
|
||||
StepVerifier.create(service.getCachedCourse(1L))
|
||||
.assertNext(course -> {
|
||||
assertThat(course.getId()).isEqualTo(1L);
|
||||
assertThat(course.getCourseName()).isEqualTo("瑜伽课");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCachedCourse_shouldReturnEmptyWhenCacheMiss() {
|
||||
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.getCachedCourse(1L))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCachedCourse_shouldReturnEmptyWhenDeserializationFails() {
|
||||
String invalidJson = "invalid json";
|
||||
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.just(invalidJson));
|
||||
|
||||
StepVerifier.create(service.getCachedCourse(1L))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
// ==================== invalidateCourseCache ====================
|
||||
|
||||
@Test
|
||||
void invalidateCourseCache_shouldDeleteFromRedis() {
|
||||
when(reactiveRedisTemplate.delete("group_course:1")).thenReturn(Mono.just(1L));
|
||||
|
||||
StepVerifier.create(service.invalidateCourseCache(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(reactiveRedisTemplate).delete("group_course:1");
|
||||
}
|
||||
|
||||
// ==================== acquireLock ====================
|
||||
|
||||
@Test
|
||||
void acquireLock_shouldReturnTrueWhenLockAcquired() {
|
||||
when(reactiveValueOps.setIfAbsent("booking_lock:1", "req-1", Duration.ofSeconds(30)))
|
||||
.thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.acquireLock(1L, "req-1"))
|
||||
.assertNext(acquired -> assertThat(acquired).isTrue())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acquireLock_shouldReturnFalseWhenLockNotAcquired() {
|
||||
when(reactiveValueOps.setIfAbsent("booking_lock:1", "req-1", Duration.ofSeconds(30)))
|
||||
.thenReturn(Mono.just(false));
|
||||
|
||||
StepVerifier.create(service.acquireLock(1L, "req-1"))
|
||||
.assertNext(acquired -> assertThat(acquired).isFalse())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
// ==================== releaseLock ====================
|
||||
|
||||
@Test
|
||||
void releaseLock_shouldReturnTrueWhenRequestIdMatches() {
|
||||
when(reactiveValueOps.get("booking_lock:1")).thenReturn(Mono.just("req-1"));
|
||||
when(reactiveRedisTemplate.delete("booking_lock:1")).thenReturn(Mono.just(1L));
|
||||
|
||||
StepVerifier.create(service.releaseLock(1L, "req-1"))
|
||||
.assertNext(released -> assertThat(released).isTrue())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseLock_shouldReturnFalseWhenRequestIdDoesNotMatch() {
|
||||
when(reactiveValueOps.get("booking_lock:1")).thenReturn(Mono.just("req-2"));
|
||||
|
||||
StepVerifier.create(service.releaseLock(1L, "req-1"))
|
||||
.assertNext(released -> assertThat(released).isFalse())
|
||||
.verifyComplete();
|
||||
|
||||
verify(reactiveRedisTemplate, never()).delete(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseLock_shouldReturnFalseWhenKeyNotFound() {
|
||||
when(reactiveValueOps.get("booking_lock:1")).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.releaseLock(1L, "req-1"))
|
||||
.assertNext(released -> assertThat(released).isFalse())
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
+838
@@ -0,0 +1,838 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.file.core.domain.SysFile;
|
||||
import cn.novalon.gym.manage.file.core.service.ISysFileService;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType;
|
||||
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
|
||||
import cn.novalon.gym.manage.groupcourse.enums.CourseEvent;
|
||||
import cn.novalon.gym.manage.groupcourse.enums.CourseStatus;
|
||||
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseStateMachine;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.ICourseLabelRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseTypeRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.util.QRCodeUtil;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.enums.MemberCardType;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
||||
import cn.novalon.gym.manage.member.service.IMemberCardRecordService;
|
||||
import cn.novalon.gym.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.gym.manage.sys.core.repository.ISysUserRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
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.MockedStatic;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseServiceTest {
|
||||
|
||||
@Mock
|
||||
private IGroupCourseRepository groupCourseRepository;
|
||||
@Mock
|
||||
private IGroupCourseBookingRepository bookingRepository;
|
||||
@Mock
|
||||
private IGroupCourseTypeRepository groupCourseTypeRepository;
|
||||
@Mock
|
||||
private ICourseLabelRepository courseLabelRepository;
|
||||
@Mock
|
||||
private IMemberCardRecordService memberCardRecordService;
|
||||
@Mock
|
||||
private MemberCardRepository memberCardRepository;
|
||||
@Mock
|
||||
private RedisUtil redisUtil;
|
||||
@Mock
|
||||
private GroupCourseStateMachine stateMachine;
|
||||
@Mock
|
||||
private DatabaseClient databaseClient;
|
||||
@Mock
|
||||
private ISysFileService fileService;
|
||||
@Mock
|
||||
private ISysUserRepository sysUserRepository;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.registerModule(new JavaTimeModule());
|
||||
|
||||
private GroupCourseService groupCourseService;
|
||||
|
||||
private GroupCourse testCourse;
|
||||
private GroupCourseType testType;
|
||||
private CourseLabel testLabel;
|
||||
private GroupCourseBooking testBooking;
|
||||
private SysUser testCoach;
|
||||
private SysFile testSysFile;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
groupCourseService = new GroupCourseService(
|
||||
groupCourseRepository, bookingRepository, groupCourseTypeRepository,
|
||||
courseLabelRepository, memberCardRecordService, memberCardRepository,
|
||||
redisUtil, objectMapper, stateMachine, databaseClient, fileService, sysUserRepository
|
||||
);
|
||||
|
||||
testCourse = new GroupCourse();
|
||||
testCourse.setId(1L);
|
||||
testCourse.setCourseName("瑜伽入门课");
|
||||
testCourse.setCoachId(10L);
|
||||
testCourse.setCourseType(100L);
|
||||
testCourse.setStartTime(LocalDateTime.now().plusDays(7));
|
||||
testCourse.setEndTime(LocalDateTime.now().plusDays(7).plusHours(1));
|
||||
testCourse.setMaxMembers(20);
|
||||
testCourse.setCurrentMembers(5);
|
||||
testCourse.setStatus(CourseStatus.NORMAL.getValue());
|
||||
testCourse.setLocation("A区教室");
|
||||
testCourse.setDescription("适合初学者的瑜伽课程");
|
||||
testCourse.setStoredValueAmount(new BigDecimal("50.00"));
|
||||
|
||||
testType = new GroupCourseType();
|
||||
testType.setId(100L);
|
||||
testType.setTypeName("瑜伽入门");
|
||||
testType.setCategory("柔韧与平衡类");
|
||||
testType.setBaseDifficulty(2);
|
||||
|
||||
testLabel = new CourseLabel();
|
||||
testLabel.setId(1000L);
|
||||
testLabel.setLabelName("适合新手");
|
||||
testLabel.setColor("#52c41a");
|
||||
|
||||
testBooking = new GroupCourseBooking();
|
||||
testBooking.setId(500L);
|
||||
testBooking.setCourseId(1L);
|
||||
testBooking.setMemberId(200L);
|
||||
testBooking.setMemberCardRecordId(300L);
|
||||
testBooking.setStatus("0");
|
||||
|
||||
testCoach = new SysUser();
|
||||
testCoach.setId(10L);
|
||||
testCoach.setNickname("张教练");
|
||||
testCoach.setUsername("coach_zhang");
|
||||
|
||||
testSysFile = new SysFile();
|
||||
testSysFile.setId(999L);
|
||||
testSysFile.setFileName("qrcode_1.png");
|
||||
}
|
||||
|
||||
private void mockClearCache() {
|
||||
when(redisUtil.deleteByPattern("group_course:page:*")).thenReturn(Mono.just(0L));
|
||||
when(redisUtil.deleteByPattern("group_course:id:*")).thenReturn(Mono.just(0L));
|
||||
when(redisUtil.deleteByPattern("group_course:detail:*")).thenReturn(Mono.just(0L));
|
||||
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(0L));
|
||||
}
|
||||
|
||||
private void mockEnrichCurrentMembers() {
|
||||
when(bookingRepository.countValidBookings(anyLong())).thenReturn(Mono.just(5L));
|
||||
}
|
||||
|
||||
// ==================== findDetailById ====================
|
||||
|
||||
@Test
|
||||
void findDetailById_cacheHit_shouldReturnDetail() throws Exception {
|
||||
String cacheKey = "group_course:detail:1";
|
||||
GroupCourseDetail detail = new GroupCourseDetail();
|
||||
detail.setId(1L);
|
||||
detail.setCourseName("瑜伽入门课");
|
||||
detail.setCoachName("张教练");
|
||||
String json = objectMapper.writeValueAsString(detail);
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.just(json));
|
||||
// switchIfEmpty 的 Mono 参数被急切求值,需要 mock 以避免 NPE
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.findDetailById(1L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getCourseName()).isEqualTo("瑜伽入门课");
|
||||
assertThat(result.getCoachName()).isEqualTo("张教练");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).get(cacheKey, String.class);
|
||||
// switchIfEmpty 的 Mono 参数被急切求值,findByIdAndDeletedAtIsNull 会被调用但不会订阅
|
||||
}
|
||||
|
||||
@Test
|
||||
void findDetailById_cacheMissWithType_shouldBuildAndCacheDetail() throws Exception {
|
||||
String cacheKey = "group_course:detail:1";
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(groupCourseTypeRepository.findById(100L)).thenReturn(Mono.just(testType));
|
||||
when(courseLabelRepository.findByTypeId(100L)).thenReturn(Flux.just(testLabel));
|
||||
when(sysUserRepository.findByIdIncludingDeleted(10L)).thenReturn(Mono.just(testCoach));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(8L));
|
||||
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(groupCourseService.findDetailById(1L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getCourseName()).isEqualTo("瑜伽入门课");
|
||||
assertThat(result.getCoachName()).isEqualTo("张教练");
|
||||
assertThat(result.getCurrentMembers()).isEqualTo(8);
|
||||
assertThat(result.getTypeInfo()).isNotNull();
|
||||
assertThat(result.getTypeInfo().getTypeName()).isEqualTo("瑜伽入门");
|
||||
assertThat(result.getLabels()).hasSize(1);
|
||||
assertThat(result.getLabels().get(0).getLabelName()).isEqualTo("适合新手");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).get(cacheKey, String.class);
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
|
||||
verify(groupCourseTypeRepository).findById(100L);
|
||||
verify(courseLabelRepository).findByTypeId(100L);
|
||||
verify(sysUserRepository).findByIdIncludingDeleted(10L);
|
||||
verify(bookingRepository).countValidBookings(1L);
|
||||
verify(redisUtil).setWithExpire(eq(cacheKey), anyString(), eq(300L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findDetailById_cacheMissNullCourseType_shouldBuildDetailWithoutType() {
|
||||
String cacheKey = "group_course:detail:1";
|
||||
testCourse.setCourseType(null);
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(sysUserRepository.findByIdIncludingDeleted(10L)).thenReturn(Mono.just(testCoach));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(3L));
|
||||
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(groupCourseService.findDetailById(1L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getCourseName()).isEqualTo("瑜伽入门课");
|
||||
assertThat(result.getTypeInfo()).isNull();
|
||||
assertThat(result.getCoachName()).isEqualTo("张教练");
|
||||
assertThat(result.getCurrentMembers()).isEqualTo(3);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository, never()).findById(anyLong());
|
||||
verify(courseLabelRepository, never()).findByTypeId(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findDetailById_cacheMissNotFound_shouldReturnEmpty() {
|
||||
String cacheKey = "group_course:detail:1";
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.findDetailById(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
|
||||
verifyNoInteractions(groupCourseTypeRepository, courseLabelRepository, sysUserRepository, bookingRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findDetailById_cacheParseError_shouldDeleteCacheAndFallback() throws Exception {
|
||||
String cacheKey = "group_course:detail:1";
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.just("invalid-json"));
|
||||
when(redisUtil.delete(cacheKey)).thenReturn(Mono.just(1L));
|
||||
// After cache delete, switchIfEmpty kicks in
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(groupCourseTypeRepository.findById(100L)).thenReturn(Mono.just(testType));
|
||||
when(courseLabelRepository.findByTypeId(100L)).thenReturn(Flux.just(testLabel));
|
||||
when(sysUserRepository.findByIdIncludingDeleted(10L)).thenReturn(Mono.just(testCoach));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
|
||||
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(groupCourseService.findDetailById(1L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getCourseName()).isEqualTo("瑜伽入门课");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).delete(cacheKey);
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
|
||||
}
|
||||
|
||||
// ==================== findById ====================
|
||||
|
||||
@Test
|
||||
void findById_cacheHit_shouldReturnCourse() throws Exception {
|
||||
String cacheKey = "group_course:id:1";
|
||||
String json = objectMapper.writeValueAsString(testCourse);
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.just(json));
|
||||
// switchIfEmpty 的 Mono 参数被急切求值,需要 mock 以避免 NPE
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
|
||||
|
||||
StepVerifier.create(groupCourseService.findById(1L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getCourseName()).isEqualTo("瑜伽入门课");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).get(cacheKey, String.class);
|
||||
verify(bookingRepository).countValidBookings(1L);
|
||||
// switchIfEmpty 的 Mono 参数被急切求值,findByIdAndDeletedAtIsNull 会被调用但不会订阅
|
||||
}
|
||||
|
||||
@Test
|
||||
void findById_cacheMiss_shouldQueryDbAndCache() throws Exception {
|
||||
String cacheKey = "group_course:id:1";
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
|
||||
|
||||
StepVerifier.create(groupCourseService.findById(1L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getCourseName()).isEqualTo("瑜伽入门课");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
|
||||
verify(redisUtil).setWithExpire(eq(cacheKey), anyString(), eq(300L));
|
||||
verify(bookingRepository).countValidBookings(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findById_cacheMissNotFound_shouldReturnEmpty() {
|
||||
String cacheKey = "group_course:id:1";
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.findById(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
|
||||
}
|
||||
|
||||
// ==================== findAll ====================
|
||||
|
||||
@Test
|
||||
void findAll_shouldReturnAllCourses() {
|
||||
GroupCourse course2 = new GroupCourse();
|
||||
course2.setId(2L);
|
||||
course2.setCourseName("力量训练课");
|
||||
|
||||
when(groupCourseRepository.findAll()).thenReturn(Flux.just(testCourse, course2));
|
||||
|
||||
StepVerifier.create(groupCourseService.findAll())
|
||||
.expectNext(testCourse, course2)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAll_withIncludeDeletedTrue_shouldReturnAll() {
|
||||
when(groupCourseRepository.findAll()).thenReturn(Flux.just(testCourse));
|
||||
|
||||
StepVerifier.create(groupCourseService.findAll(true))
|
||||
.expectNext(testCourse)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAll_withIncludeDeletedFalse_shouldReturnNonDeleted() {
|
||||
when(groupCourseRepository.findByDeletedAtIsNull()).thenReturn(Flux.just(testCourse));
|
||||
|
||||
StepVerifier.create(groupCourseService.findAll(false))
|
||||
.expectNext(testCourse)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findByDeletedAtIsNull();
|
||||
}
|
||||
|
||||
// ==================== findByPage ====================
|
||||
|
||||
@Test
|
||||
void findByPage_cacheHit_shouldReturnPage() throws Exception {
|
||||
PageRequest pageRequest = new PageRequest();
|
||||
pageRequest.setPage(0);
|
||||
pageRequest.setSize(10);
|
||||
|
||||
PageResponse<GroupCourse> pageResponse = new PageResponse<>(
|
||||
List.of(testCourse), 1, 1, 0, 10
|
||||
);
|
||||
|
||||
String cacheKey = "group_course:page:0:10:false:id:asc::";
|
||||
String json = objectMapper.writeValueAsString(pageResponse);
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.just(json));
|
||||
|
||||
StepVerifier.create(groupCourseService.findByPage(pageRequest, false))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getContent()).hasSize(1);
|
||||
assertThat(result.getContent().get(0).getCourseName()).isEqualTo("瑜伽入门课");
|
||||
assertThat(result.getTotalElements()).isEqualTo(1);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).get(cacheKey, String.class);
|
||||
verifyNoInteractions(groupCourseRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByPage_cacheMiss_shouldQueryDbAndCache() throws Exception {
|
||||
PageRequest pageRequest = new PageRequest();
|
||||
pageRequest.setPage(0);
|
||||
pageRequest.setSize(10);
|
||||
|
||||
PageResponse<GroupCourse> pageResponse = new PageResponse<>(
|
||||
List.of(testCourse), 1, 1, 0, 10
|
||||
);
|
||||
|
||||
String cacheKey = "group_course:page:0:10:false:id:asc::";
|
||||
|
||||
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.findByPageAndNotDeleted(pageRequest)).thenReturn(Mono.just(pageResponse));
|
||||
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(groupCourseService.findByPage(pageRequest, false))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getContent()).hasSize(1);
|
||||
assertThat(result.getTotalElements()).isEqualTo(1);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findByPageAndNotDeleted(pageRequest);
|
||||
verify(redisUtil).setWithExpire(eq(cacheKey), anyString(), eq(300L));
|
||||
}
|
||||
|
||||
// ==================== create ====================
|
||||
|
||||
@Test
|
||||
void create_shouldSaveCourseAndGenerateQrCode() {
|
||||
mockClearCache();
|
||||
|
||||
when(groupCourseRepository.save(any(GroupCourse.class))).thenReturn(Mono.just(testCourse));
|
||||
|
||||
try (MockedStatic<QRCodeUtil> qrUtil = mockStatic(QRCodeUtil.class)) {
|
||||
qrUtil.when(() -> QRCodeUtil.generateQrCodeBytes(anyString())).thenReturn(new byte[]{1, 2, 3});
|
||||
when(fileService.saveBytes(any(byte[].class), eq("qrcode_1.png"), eq("image/png"), eq("system")))
|
||||
.thenReturn(Mono.just(testSysFile));
|
||||
when(groupCourseRepository.update(any(GroupCourse.class))).thenReturn(Mono.just(testCourse));
|
||||
|
||||
StepVerifier.create(groupCourseService.create(testCourse))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
assertThat(result.getCourseName()).isEqualTo("瑜伽入门课");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).save(testCourse);
|
||||
verify(fileService).saveBytes(any(byte[].class), eq("qrcode_1.png"), eq("image/png"), eq("system"));
|
||||
verify(groupCourseRepository).update(any(GroupCourse.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_qrCodeFails_shouldStillSaveCourse() {
|
||||
when(groupCourseRepository.save(any(GroupCourse.class))).thenReturn(Mono.just(testCourse));
|
||||
|
||||
try (MockedStatic<QRCodeUtil> qrUtil = mockStatic(QRCodeUtil.class)) {
|
||||
qrUtil.when(() -> QRCodeUtil.generateQrCodeBytes(anyString()))
|
||||
.thenThrow(new RuntimeException("QR generation failed"));
|
||||
|
||||
StepVerifier.create(groupCourseService.create(testCourse))
|
||||
.expectError(RuntimeException.class)
|
||||
.verify();
|
||||
|
||||
verify(groupCourseRepository).save(testCourse);
|
||||
verify(fileService, never()).saveBytes(any(), anyString(), anyString(), anyString());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== update ====================
|
||||
|
||||
@Test
|
||||
void update_shouldUpdateExistingCourse() {
|
||||
mockClearCache();
|
||||
GroupCourse updateData = new GroupCourse();
|
||||
updateData.setCourseName("瑜伽进阶课");
|
||||
updateData.setLocation("B区教室");
|
||||
updateData.setMaxMembers(30);
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(groupCourseRepository.update(any(GroupCourse.class))).thenReturn(Mono.just(testCourse));
|
||||
|
||||
StepVerifier.create(groupCourseService.update(1L, updateData))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getCourseName()).isEqualTo("瑜伽进阶课");
|
||||
assertThat(result.getLocation()).isEqualTo("B区教室");
|
||||
assertThat(result.getMaxMembers()).isEqualTo(30);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
|
||||
verify(groupCourseRepository).update(argThat(course ->
|
||||
"瑜伽进阶课".equals(course.getCourseName()) &&
|
||||
"B区教室".equals(course.getLocation()) &&
|
||||
course.getMaxMembers() == 30
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
void update_courseNotFound_shouldError() {
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.update(1L, new GroupCourse()))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && "团课不存在".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
|
||||
verify(groupCourseRepository, never()).update(any());
|
||||
}
|
||||
|
||||
// ==================== cancel ====================
|
||||
|
||||
@Test
|
||||
void cancel_shouldCancelAndRefundMembers() {
|
||||
mockClearCache();
|
||||
|
||||
MemberCardRecord record = MemberCardRecord.builder()
|
||||
.id(300L)
|
||||
.memberCardId(400L)
|
||||
.remainingAmount(200.0)
|
||||
.build();
|
||||
|
||||
MemberCard card = MemberCard.builder()
|
||||
.memberCardId(400L)
|
||||
.memberCardType(MemberCardType.STORED_VALUE_CARD.name())
|
||||
.build();
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(stateMachine.validateTransition(any(GroupCourse.class), eq(CourseEvent.CANCEL))).thenReturn(Mono.empty());
|
||||
when(groupCourseRepository.cancel(1L)).thenReturn(Mono.just(testCourse));
|
||||
|
||||
// refundBookingMembers flow
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(bookingRepository.findByCourseId(1L)).thenReturn(Flux.just(testBooking));
|
||||
when(memberCardRecordService.findById(300L)).thenReturn(Mono.just(record));
|
||||
when(memberCardRepository.findById(400L)).thenReturn(Mono.just(card));
|
||||
when(memberCardRecordService.renewCard(300L, 0, 50.0, record.getExpireTime())).thenReturn(Mono.just(1));
|
||||
when(bookingRepository.updateStatus(500L, "1")).thenReturn(Mono.just(1));
|
||||
|
||||
testCourse.setStatus(CourseStatus.CANCELLED.getValue());
|
||||
|
||||
StepVerifier.create(groupCourseService.cancel(1L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(stateMachine).validateTransition(any(GroupCourse.class), eq(CourseEvent.CANCEL));
|
||||
verify(groupCourseRepository).cancel(1L);
|
||||
verify(memberCardRecordService).renewCard(300L, 0, 50.0, record.getExpireTime());
|
||||
verify(bookingRepository).updateStatus(500L, "1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancel_courseNotFound_shouldError() {
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.cancel(1L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && "团课不存在".equals(ex.getMessage()))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancel_within24Hours_shouldError() {
|
||||
testCourse.setStartTime(LocalDateTime.now().plusHours(1));
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(stateMachine.validateTransition(any(GroupCourse.class), eq(CourseEvent.CANCEL))).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.cancel(1L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && "课程取消需提前24小时".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(groupCourseRepository, never()).cancel(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancel_stateMachineRejects_shouldError() {
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(stateMachine.validateTransition(any(GroupCourse.class), eq(CourseEvent.CANCEL)))
|
||||
.thenReturn(Mono.error(new IllegalStateException("不允许的状态转换")));
|
||||
|
||||
StepVerifier.create(groupCourseService.cancel(1L))
|
||||
.expectError(IllegalStateException.class)
|
||||
.verify();
|
||||
|
||||
verify(groupCourseRepository, never()).cancel(anyLong());
|
||||
}
|
||||
|
||||
// ==================== signIn ====================
|
||||
|
||||
@Test
|
||||
void signIn_shouldSucceed() {
|
||||
mockClearCache();
|
||||
GroupCourse signingCourse = new GroupCourse();
|
||||
signingCourse.setId(1L);
|
||||
signingCourse.setCourseName("瑜伽入门课");
|
||||
signingCourse.setCoachId(10L);
|
||||
signingCourse.setCourseType(100L);
|
||||
signingCourse.setStartTime(LocalDateTime.now().plusHours(1));
|
||||
signingCourse.setEndTime(LocalDateTime.now().plusHours(2));
|
||||
signingCourse.setMaxMembers(20);
|
||||
signingCourse.setCurrentMembers(5);
|
||||
signingCourse.setStatus(CourseStatus.NORMAL.getValue());
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(signingCourse));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
|
||||
when(bookingRepository.findValidBooking(1L, 200L)).thenReturn(Mono.just(testBooking));
|
||||
when(bookingRepository.updateStatus(500L, "2")).thenReturn(Mono.just(1));
|
||||
|
||||
StepVerifier.create(groupCourseService.signIn(1L, 200L))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getId()).isEqualTo(1L);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(bookingRepository).findValidBooking(1L, 200L);
|
||||
verify(bookingRepository).updateStatus(500L, "2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void signIn_courseCancelled_shouldError() {
|
||||
GroupCourse cancelledCourse = new GroupCourse();
|
||||
cancelledCourse.setId(1L);
|
||||
cancelledCourse.setStartTime(LocalDateTime.now().plusHours(1));
|
||||
cancelledCourse.setEndTime(LocalDateTime.now().plusHours(2));
|
||||
cancelledCourse.setMaxMembers(20);
|
||||
cancelledCourse.setCurrentMembers(5);
|
||||
cancelledCourse.setStatus(CourseStatus.CANCELLED.getValue());
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(cancelledCourse));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
|
||||
|
||||
StepVerifier.create(groupCourseService.signIn(1L, 200L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && "团课已取消,无法签到".equals(ex.getMessage()))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void signIn_notInSignInWindow_shouldError() {
|
||||
GroupCourse futureCourse = new GroupCourse();
|
||||
futureCourse.setId(1L);
|
||||
futureCourse.setStartTime(LocalDateTime.now().plusDays(1));
|
||||
futureCourse.setEndTime(LocalDateTime.now().plusDays(1).plusHours(1));
|
||||
futureCourse.setMaxMembers(20);
|
||||
futureCourse.setCurrentMembers(5);
|
||||
futureCourse.setStatus(CourseStatus.NORMAL.getValue());
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(futureCourse));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
|
||||
|
||||
StepVerifier.create(groupCourseService.signIn(1L, 200L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && ex.getMessage().startsWith("未到签到时间"))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void signIn_courseFull_shouldError() {
|
||||
GroupCourse fullCourse = new GroupCourse();
|
||||
fullCourse.setId(1L);
|
||||
fullCourse.setStartTime(LocalDateTime.now().plusHours(1));
|
||||
fullCourse.setEndTime(LocalDateTime.now().plusHours(2));
|
||||
fullCourse.setMaxMembers(20);
|
||||
fullCourse.setCurrentMembers(20);
|
||||
fullCourse.setStatus(CourseStatus.NORMAL.getValue());
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(fullCourse));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(20L));
|
||||
|
||||
StepVerifier.create(groupCourseService.signIn(1L, 200L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && "课程已满员,无法签到".equals(ex.getMessage()))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void signIn_notBooked_shouldError() {
|
||||
GroupCourse signingCourse = new GroupCourse();
|
||||
signingCourse.setId(1L);
|
||||
signingCourse.setStartTime(LocalDateTime.now().plusHours(1));
|
||||
signingCourse.setEndTime(LocalDateTime.now().plusHours(2));
|
||||
signingCourse.setMaxMembers(20);
|
||||
signingCourse.setCurrentMembers(5);
|
||||
signingCourse.setStatus(CourseStatus.NORMAL.getValue());
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(signingCourse));
|
||||
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
|
||||
when(bookingRepository.findValidBooking(1L, 200L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.signIn(1L, 200L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && "您未预约此团课".equals(ex.getMessage()))
|
||||
.verify();
|
||||
}
|
||||
|
||||
// ==================== delete ====================
|
||||
|
||||
@Test
|
||||
void delete_cancelledCourse_shouldSucceed() {
|
||||
mockClearCache();
|
||||
testCourse.setStatus(CourseStatus.CANCELLED.getValue());
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(groupCourseRepository.deleteById(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.delete(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).deleteById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_endedCourse_shouldSucceed() {
|
||||
mockClearCache();
|
||||
testCourse.setStatus(CourseStatus.ENDED.getValue());
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
when(groupCourseRepository.deleteById(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.delete(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).deleteById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_activeCourse_shouldError() {
|
||||
testCourse.setStatus(CourseStatus.NORMAL.getValue());
|
||||
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
|
||||
|
||||
StepVerifier.create(groupCourseService.delete(1L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && ex.getMessage().startsWith("只有已取消或已结束的课程才能删除"))
|
||||
.verify();
|
||||
|
||||
verify(groupCourseRepository, never()).deleteById(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_courseNotFound_shouldError() {
|
||||
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseService.delete(1L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException && "团课不存在".equals(ex.getMessage()))
|
||||
.verify();
|
||||
}
|
||||
|
||||
// ==================== checkCoachConflict ====================
|
||||
|
||||
@Test
|
||||
void checkCoachConflict_noConflict_shouldReturnEmpty() {
|
||||
LocalDateTime startTime = LocalDateTime.now().plusDays(1);
|
||||
LocalDateTime endTime = startTime.plusHours(1);
|
||||
|
||||
GroupCourse existing = new GroupCourse();
|
||||
existing.setId(2L);
|
||||
existing.setCoachId(10L);
|
||||
existing.setStartTime(startTime.plusDays(2));
|
||||
existing.setEndTime(startTime.plusDays(2).plusHours(1));
|
||||
existing.setStatus(CourseStatus.NORMAL.getValue());
|
||||
|
||||
when(groupCourseRepository.findByCoachId(10L)).thenReturn(Flux.just(existing));
|
||||
|
||||
StepVerifier.create(groupCourseService.checkCoachConflict(10L, startTime, endTime, null))
|
||||
.assertNext(result -> assertThat(result).isEmpty())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkCoachConflict_hasConflict_shouldReturnConflictingCourses() {
|
||||
LocalDateTime startTime = LocalDateTime.now().plusDays(1);
|
||||
LocalDateTime endTime = startTime.plusHours(1);
|
||||
|
||||
GroupCourse conflicting = new GroupCourse();
|
||||
conflicting.setId(2L);
|
||||
conflicting.setCoachId(10L);
|
||||
conflicting.setStartTime(startTime);
|
||||
conflicting.setEndTime(endTime.plusMinutes(30));
|
||||
conflicting.setStatus(CourseStatus.NORMAL.getValue());
|
||||
|
||||
when(groupCourseRepository.findByCoachId(10L)).thenReturn(Flux.just(conflicting));
|
||||
|
||||
StepVerifier.create(groupCourseService.checkCoachConflict(10L, startTime, endTime, null))
|
||||
.assertNext(result -> {
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result.get(0).getId()).isEqualTo(2L);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkCoachConflict_excludeSelf_shouldNotReturnExcludedCourse() {
|
||||
LocalDateTime startTime = LocalDateTime.now().plusDays(1);
|
||||
LocalDateTime endTime = startTime.plusHours(1);
|
||||
|
||||
GroupCourse self = new GroupCourse();
|
||||
self.setId(1L);
|
||||
self.setCoachId(10L);
|
||||
self.setStartTime(startTime);
|
||||
self.setEndTime(endTime.plusMinutes(30));
|
||||
self.setStatus(CourseStatus.NORMAL.getValue());
|
||||
|
||||
when(groupCourseRepository.findByCoachId(10L)).thenReturn(Flux.just(self));
|
||||
|
||||
StepVerifier.create(groupCourseService.checkCoachConflict(10L, startTime, endTime, 1L))
|
||||
.assertNext(result -> assertThat(result).isEmpty())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkCoachConflict_nullParams_shouldReturnEmpty() {
|
||||
StepVerifier.create(groupCourseService.checkCoachConflict(null, LocalDateTime.now(), LocalDateTime.now().plusHours(1), null))
|
||||
.assertNext(result -> assertThat(result).isEmpty())
|
||||
.verifyComplete();
|
||||
|
||||
verifyNoInteractions(groupCourseRepository);
|
||||
}
|
||||
|
||||
// ==================== searchGroupCourses ====================
|
||||
|
||||
@Test
|
||||
void searchGroupCourses_shouldDelegateToRepository() {
|
||||
GroupCourseQueryDto query = new GroupCourseQueryDto();
|
||||
query.setCourseName("瑜伽");
|
||||
|
||||
PageResponse<GroupCourse> pageResponse = new PageResponse<>(List.of(testCourse), 1, 1, 0, 10);
|
||||
|
||||
when(groupCourseRepository.searchGroupCourses(query)).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
StepVerifier.create(groupCourseService.searchGroupCourses(query))
|
||||
.assertNext(result -> {
|
||||
assertThat(result.getContent()).hasSize(1);
|
||||
assertThat(result.getContent().get(0).getCourseName()).isEqualTo("瑜伽入门课");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).searchGroupCourses(query);
|
||||
}
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseTypeRepository;
|
||||
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 reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseTypeServiceTest {
|
||||
|
||||
@Mock
|
||||
private IGroupCourseTypeRepository groupCourseTypeRepository;
|
||||
|
||||
@Mock
|
||||
private IGroupCourseRepository groupCourseRepository;
|
||||
|
||||
private GroupCourseTypeService groupCourseTypeService;
|
||||
|
||||
private GroupCourseType yogaType;
|
||||
private GroupCourseType strengthType;
|
||||
private GroupCourseType pilatesType;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
groupCourseTypeService = new GroupCourseTypeService(groupCourseTypeRepository, groupCourseRepository);
|
||||
|
||||
yogaType = new GroupCourseType();
|
||||
yogaType.setId(1L);
|
||||
yogaType.setTypeName("瑜伽入门");
|
||||
yogaType.setBaseDifficulty(2);
|
||||
yogaType.setDescription("适合初学者的瑜伽课程");
|
||||
yogaType.setCategory("柔韧与平衡类");
|
||||
|
||||
strengthType = new GroupCourseType();
|
||||
strengthType.setId(2L);
|
||||
strengthType.setTypeName("力量训练");
|
||||
strengthType.setBaseDifficulty(5);
|
||||
strengthType.setDescription("增强肌肉力量");
|
||||
strengthType.setCategory("力量类");
|
||||
|
||||
pilatesType = new GroupCourseType();
|
||||
pilatesType.setId(3L);
|
||||
pilatesType.setTypeName("普拉提进阶");
|
||||
pilatesType.setBaseDifficulty(4);
|
||||
pilatesType.setDescription("核心力量训练");
|
||||
pilatesType.setCategory("柔韧与平衡类");
|
||||
}
|
||||
|
||||
// ==================== findById ====================
|
||||
|
||||
@Test
|
||||
void findById_shouldReturnType() {
|
||||
when(groupCourseTypeRepository.findById(1L)).thenReturn(Mono.just(yogaType));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findById(1L))
|
||||
.expectNext(yogaType)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findById_shouldReturnEmptyWhenNotFound() {
|
||||
when(groupCourseTypeRepository.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findById(999L))
|
||||
.expectNextCount(0)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findById(999L);
|
||||
}
|
||||
|
||||
// ==================== findAll (no param) ====================
|
||||
|
||||
@Test
|
||||
void findAll_shouldReturnAll() {
|
||||
when(groupCourseTypeRepository.findAll(false)).thenReturn(Flux.just(yogaType, strengthType, pilatesType));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findAll())
|
||||
.expectNext(yogaType, strengthType, pilatesType)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findAll(false);
|
||||
}
|
||||
|
||||
// ==================== findAll (boolean) ====================
|
||||
|
||||
@Test
|
||||
void findAll_withDeletedFlag_shouldReturnAllWithDeleted() {
|
||||
when(groupCourseTypeRepository.findAll(true)).thenReturn(Flux.just(yogaType, strengthType));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findAll(true))
|
||||
.expectNext(yogaType, strengthType)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findAll(true);
|
||||
}
|
||||
|
||||
// ==================== findByKeyword ====================
|
||||
|
||||
@Test
|
||||
void findByKeyword_shouldReturnMatchingTypes() {
|
||||
when(groupCourseTypeRepository.findByKeyword("瑜伽")).thenReturn(Flux.just(yogaType));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findByKeyword("瑜伽"))
|
||||
.expectNext(yogaType)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findByKeyword("瑜伽");
|
||||
}
|
||||
|
||||
// ==================== findByCategory ====================
|
||||
|
||||
@Test
|
||||
void findByCategory_shouldReturnTypesInCategory() {
|
||||
when(groupCourseTypeRepository.findByCategory("柔韧与平衡类")).thenReturn(Flux.just(yogaType, pilatesType));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findByCategory("柔韧与平衡类"))
|
||||
.expectNext(yogaType, pilatesType)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findByCategory("柔韧与平衡类");
|
||||
}
|
||||
|
||||
// ==================== findByCategoryAndKeyword ====================
|
||||
|
||||
@Test
|
||||
void findByCategoryAndKeyword_shouldFilterByBoth() {
|
||||
when(groupCourseTypeRepository.findByCategoryAndKeyword("柔韧与平衡类", "瑜伽"))
|
||||
.thenReturn(Flux.just(yogaType));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findByCategoryAndKeyword("柔韧与平衡类", "瑜伽"))
|
||||
.expectNext(yogaType)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findByCategoryAndKeyword("柔韧与平衡类", "瑜伽");
|
||||
}
|
||||
|
||||
// ==================== create ====================
|
||||
|
||||
@Test
|
||||
void create_shouldSaveWhenNameNotExists() {
|
||||
GroupCourseType newType = new GroupCourseType();
|
||||
newType.setTypeName("HIIT训练");
|
||||
newType.setBaseDifficulty(6);
|
||||
newType.setCategory("有氧类");
|
||||
|
||||
when(groupCourseTypeRepository.findByTypeName("HIIT训练")).thenReturn(Mono.empty());
|
||||
when(groupCourseTypeRepository.save(any(GroupCourseType.class))).thenReturn(Mono.just(newType));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.create(newType))
|
||||
.expectNext(newType)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findByTypeName("HIIT训练");
|
||||
verify(groupCourseTypeRepository).save(any(GroupCourseType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_shouldErrorWhenNameAlreadyExists() {
|
||||
GroupCourseType duplicate = new GroupCourseType();
|
||||
duplicate.setTypeName("瑜伽入门");
|
||||
|
||||
when(groupCourseTypeRepository.findByTypeName("瑜伽入门")).thenReturn(Mono.just(yogaType));
|
||||
// switchIfEmpty 是急切求值的,需要为 save 提供 stub 避免 NPE
|
||||
lenient().when(groupCourseTypeRepository.save(any(GroupCourseType.class))).thenReturn(Mono.just(new GroupCourseType()));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.create(duplicate))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "团课类型名称已存在".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(groupCourseTypeRepository).findByTypeName("瑜伽入门");
|
||||
}
|
||||
|
||||
// ==================== update ====================
|
||||
|
||||
@Test
|
||||
void update_shouldUpdateType() {
|
||||
GroupCourseType update = new GroupCourseType();
|
||||
update.setId(1L);
|
||||
update.setTypeName("瑜伽进阶");
|
||||
update.setBaseDifficulty(4);
|
||||
update.setCategory("柔韧与平衡类");
|
||||
|
||||
when(groupCourseTypeRepository.update(any(GroupCourseType.class))).thenReturn(Mono.just(update));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.update(1L, update))
|
||||
.expectNext(update)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).update(any(GroupCourseType.class));
|
||||
}
|
||||
|
||||
// ==================== delete ====================
|
||||
|
||||
@Test
|
||||
void delete_shouldDeleteWhenNoDependentCourses() {
|
||||
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.empty());
|
||||
when(groupCourseTypeRepository.deleteById(1L)).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.delete(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseRepository).findByCourseType(1L);
|
||||
verify(groupCourseTypeRepository).deleteById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_shouldErrorWhenDependentCoursesExist() {
|
||||
GroupCourse dependentCourse = new GroupCourse();
|
||||
dependentCourse.setId(100L);
|
||||
dependentCourse.setCourseName("瑜伽入门课");
|
||||
|
||||
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.just(dependentCourse));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.delete(1L))
|
||||
.expectErrorMatches(ex -> ex instanceof RuntimeException
|
||||
&& "该类型下存在团课,无法删除".equals(ex.getMessage()))
|
||||
.verify();
|
||||
|
||||
verify(groupCourseRepository).findByCourseType(1L);
|
||||
}
|
||||
|
||||
// ==================== findCategories ====================
|
||||
|
||||
@Test
|
||||
void findCategories_shouldReturnDistinctCategories() {
|
||||
when(groupCourseTypeRepository.findAll(false)).thenReturn(Flux.just(yogaType, strengthType, pilatesType));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findCategories())
|
||||
.expectNext("柔韧与平衡类", "力量类")
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findAll(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findCategories_shouldFilterEmptyCategories() {
|
||||
GroupCourseType emptyCategory = new GroupCourseType();
|
||||
emptyCategory.setId(4L);
|
||||
emptyCategory.setTypeName("空分类");
|
||||
emptyCategory.setCategory("");
|
||||
|
||||
when(groupCourseTypeRepository.findAll(false)).thenReturn(Flux.just(yogaType, emptyCategory));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findCategories())
|
||||
.expectNext("柔韧与平衡类")
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findAll(false);
|
||||
}
|
||||
|
||||
// ==================== findByPage ====================
|
||||
|
||||
@Test
|
||||
void findByPage_shouldReturnPaginatedResults() {
|
||||
PageRequest pageRequest = new PageRequest();
|
||||
pageRequest.setPage(0);
|
||||
pageRequest.setSize(10);
|
||||
|
||||
PageResponse<GroupCourseType> pageResponse = new PageResponse<>(
|
||||
List.of(yogaType, strengthType),
|
||||
1, 2, 0, 10
|
||||
);
|
||||
|
||||
when(groupCourseTypeRepository.findByPage(pageRequest)).thenReturn(Mono.just(pageResponse));
|
||||
|
||||
StepVerifier.create(groupCourseTypeService.findByPage(pageRequest))
|
||||
.expectNextMatches(response ->
|
||||
response.getContent().size() == 2 &&
|
||||
response.getTotalElements() == 2 &&
|
||||
response.getTotalPages() == 1 &&
|
||||
response.getCurrentPage() == 0
|
||||
)
|
||||
.verifyComplete();
|
||||
|
||||
verify(groupCourseTypeRepository).findByPage(pageRequest);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user