feat: 完善双端演示注册与红娘服务闭环

This commit is contained in:
WorkBuddy
2026-10-21 23:41:47 +08:00
parent afee993626
commit 39a18a50ee
63 changed files with 1298 additions and 217 deletions
@@ -42,6 +42,15 @@ public class AuthController {
return ApiResponse.ok(authService.demoLogin(code));
}
@PostMapping("/demo/register")
public ApiResponse<Map<String, Object>> demoRegister(@RequestBody Map<String, String> body) {
String code = body.get("code");
if (code == null || !code.matches("demo_openid_[a-zA-Z0-9_-]{1,100}")) {
throw new BusinessException(ApiResponseCode.BAD_REQUEST, "演示账号编号无效");
}
return ApiResponse.ok(authService.demoRegister(code, body.get("displayName"), body.get("phone")));
}
/**
* 绑定手机号
* POST /api/v1/auth/phone/bind
@@ -2,6 +2,7 @@ package com.danyang.match.platform.auth.controller;
import com.danyang.match.platform.auth.cookie.OperatorCookieWriter;
import com.danyang.match.platform.auth.service.OperatorAuthService;
import com.danyang.match.platform.auth.service.DemoOperatorRegistrationService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.enums.ApiResponseCode;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
@@ -23,8 +24,15 @@ import java.util.Map;
public class OperatorAuthController {
private final OperatorAuthService operatorAuthService;
private final DemoOperatorRegistrationService demoOperatorRegistrationService;
private final OperatorCookieWriter cookieWriter;
@PostMapping("/demo/register")
public ApiResponse<Map<String, Object>> demoRegister(@RequestBody Map<String, String> body) {
return ApiResponse.ok(demoOperatorRegistrationService.register(
body.get("name"), body.get("username"), body.get("password")));
}
@PostMapping("/login")
public ApiResponse<Map<String, Object>> login(@RequestBody Map<String, String> body,
HttpServletRequest request, HttpServletResponse response) {
@@ -80,6 +80,46 @@ public class AuthService {
return loginWithSession(session);
}
@Transactional
public Map<String, Object> demoRegister(String code, String displayName, String phone) {
if (!demoLoginEnabled) throw new BusinessException(ApiResponseCode.NOT_FOUND, "演示注册未启用");
if (code == null || !code.matches("demo_openid_[a-zA-Z0-9_-]{1,100}")
|| displayName == null || displayName.isBlank() || displayName.length() > 40) {
throw new BusinessException(ApiResponseCode.BAD_REQUEST, "演示账号或昵称格式无效");
}
if (phone != null && !phone.isBlank()) {
if (!phone.matches("1[3-9]\\d{9}")) {
throw new BusinessException(ApiResponseCode.BAD_REQUEST, "演示手机号格式无效");
}
if (userMapper.findByPhone(phone).isPresent()) {
throw new BusinessException(ApiResponseCode.CONFLICT, "演示手机号已注册");
}
}
if (userMapper.findByWechatOpenid(code).isPresent()) {
throw new BusinessException(ApiResponseCode.CONFLICT, "演示账号编号已注册");
}
Map<String, Object> result = loginWithSession(Map.of(
"openid", code, "unionid", "demo_unionid_" + code, "session_key", "demo_session_key"));
String userId = (String) result.get("userId");
if (phone != null && !phone.isBlank()) {
User user = new User();
user.setId(userId);
user.setPhone(phone);
userMapper.updateById(user);
}
// 演示档案保持未完成;必填占位字段不代表完成实名认证或婚恋资料审核。
UserProfile profile = new UserProfile();
profile.setUserId(userId);
profile.setDisplayName(displayName.trim());
profile.setGender("other");
profile.setBirthDate(java.time.LocalDate.of(2000, 1, 1));
profile.setProfileCompletion(0);
profile.setProfileStatus(Constants.PROFILE_STATUS_IN_PROGRESS);
userProfileMapper.insert(profile);
result.put("displayName", displayName.trim());
return result;
}
private Map<String, Object> loginWithSession(Map<String, String> session) {
String openid = session.get("openid");
String unionid = session.get("unionid");
@@ -0,0 +1,49 @@
package com.danyang.match.platform.auth.service;
import com.danyang.match.platform.operator.service.OperatorManagementService;
import com.danyang.match.platform.permission.entity.Operator;
import com.danyang.match.platform.shared.enums.ApiResponseCode;
import com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class DemoOperatorRegistrationService {
private final OperatorManagementService operatorManagementService;
private final JdbcTemplate jdbcTemplate;
@Value("${demo.login.enabled:false}")
private boolean demoLoginEnabled;
@Transactional
public Map<String, Object> register(String name, String username, String password) {
if (!demoLoginEnabled) {
throw new BusinessException(ApiResponseCode.NOT_FOUND, "演示注册未启用");
}
if (name == null || name.isBlank() || name.length() > 100
|| username == null || !username.matches("[a-zA-Z][a-zA-Z0-9_]{3,29}")
|| password == null || password.length() < 8 || password.length() > 72) {
throw new BusinessException(ApiResponseCode.BAD_REQUEST, "姓名、用户名或密码格式无效");
}
if (jdbcTemplate.queryForObject("SELECT count(*) FROM operators WHERE username = ?", Integer.class, username) > 0) {
throw new BusinessException(ApiResponseCode.CONFLICT, "用户名已存在");
}
Operator operator = operatorManagementService.createOperator(name.trim(), username, password, null);
jdbcTemplate.update("UPDATE operators SET status = 'active' WHERE id = ?", UUID.fromString(operator.getId()));
operator.setStatus("active");
String roleId = jdbcTemplate.queryForObject("SELECT id FROM roles WHERE code = 'matchmaker'", String.class);
jdbcTemplate.update("INSERT INTO operator_roles (operator_id, role_id) VALUES (?, ?)",
UUID.fromString(operator.getId()), UUID.fromString(roleId));
return Map.of("operatorId", operator.getId(), "username", operator.getUsername(),
"name", operator.getName(), "status", operator.getStatus(), "role", "matchmaker");
}
}
@@ -155,4 +155,11 @@ public class MatchmakerOperatorController {
return ApiResponse.ok(matchmakerServiceService.completeService(id, principal.id()));
}
@PostMapping("/services/{id}/start")
@PreAuthorize("hasAuthority('matchmaker.service')")
public ApiResponse<?> startService(@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(matchmakerServiceService.startService(id, principal.id()));
}
}
@@ -6,6 +6,7 @@ import com.danyang.match.platform.matchmaker.entity.MatchmakerAssignment;
import com.danyang.match.platform.matchmaker.entity.MatchmakerServiceRecord;
import com.danyang.match.platform.matchmaker.mapper.MatchmakerAssignmentMapper;
import com.danyang.match.platform.matchmaker.mapper.MatchmakerServiceMapper;
import com.danyang.match.platform.notification.service.NotificationService;
import com.danyang.match.platform.permission.entity.Operator;
import com.danyang.match.platform.permission.mapper.OperatorMapper;
import com.danyang.match.platform.shared.constant.Constants;
@@ -61,6 +62,7 @@ public class MatchmakerAssignmentService {
private final MatchmakerAssignmentMapper assignmentMapper;
private final MatchmakerServiceMapper serviceMapper;
private final NotificationService notificationService;
private final OperatorMapper operatorMapper;
private final DataScopeGuard dataScopeGuard;
private final AuditLogMapper auditLogMapper;
@@ -134,6 +136,9 @@ public class MatchmakerAssignmentService {
assignment.getId(), userId, matchmakerOperatorId, assignerOperatorId);
saveAudit(assignerOperatorId, Constants.AUDIT_ACTION_ASSIGNMENT_CREATE, assignment.getId(),
auditSummary(matchmakerOperatorId, assignment.getAssignmentScope(), reason));
if (serviceId != null && !serviceId.isBlank()) {
notificationService.sendMatchmakerServiceAssigned(userId, serviceId);
}
return assignment;
}
@@ -247,6 +247,22 @@ public class MatchmakerServiceService {
return followup;
}
@Transactional
public MatchmakerServiceRecord startService(String serviceId, String operatorId) {
MatchmakerServiceRecord service = serviceMapper.selectById(serviceId);
if (service == null) throw new BusinessException(ApiResponseCode.NOT_FOUND, "服务不存在");
MatchmakerAssignment assignment = assertServiceAccess(operatorId, service);
if (!Constants.MATCHMAKER_STATUS_ASSIGNED.equals(service.getStatus())) {
throw new BusinessException(ApiResponseCode.CONFLICT, "当前状态不能开始服务");
}
service.setStatus(Constants.MATCHMAKER_STATUS_IN_SERVICE);
serviceMapper.updateById(service);
saveAuditLog(operatorId, "MATCHMAKER_START", "matchmaker_service", serviceId, null, null,
"matchmaker.service.start", assignment.getId());
notificationService.sendMatchmakerServiceStarted(service.getUserId(), serviceId);
return service;
}
/**
* 完成服务
*
@@ -271,6 +287,8 @@ public class MatchmakerServiceService {
// ZC-MM-006 / T01:审计补 purpose_code + service_assignment_id
saveAuditLog(null, "MATCHMAKER_COMPLETE", "matchmaker_service", serviceId, null, null,
"matchmaker.service.complete", completeAssignment != null ? completeAssignment.getId() : null);
notificationService.sendMatchmakerServiceCompleted(service.getUserId(), serviceId);
notificationService.sendMatchmakerServiceReviewInvite(service.getUserId(), serviceId);
return service;
}
@@ -3,6 +3,7 @@ package com.danyang.match.platform.notification.service;
import com.danyang.match.platform.notification.entity.Notification;
import com.danyang.match.platform.notification.mapper.NotificationMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@@ -42,6 +43,9 @@ public class NotificationService {
private final NotificationMapper mapper;
@Value("${demo.login.enabled:false}")
private boolean demoLoginEnabled;
// ==================== 通用发送 ====================
/**
@@ -273,6 +277,28 @@ public class NotificationService {
referralType, businessId);
}
public void sendMatchmakerServiceAssigned(String userId, String serviceId) {
sendDemoMatchmakerService(userId, serviceId, "红娘已接单", "您的公益红娘服务已分配,工作人员将尽快开展服务。");
}
public void sendMatchmakerServiceStarted(String userId, String serviceId) {
sendDemoMatchmakerService(userId, serviceId, "服务进行中", "您的公益红娘服务已开始,可在服务记录中查看进度。");
}
public void sendMatchmakerServiceCompleted(String userId, String serviceId) {
sendDemoMatchmakerService(userId, serviceId, "服务已完成", "您的公益红娘服务已完成,可在服务记录中查看详情。");
}
public void sendMatchmakerServiceReviewInvite(String userId, String serviceId) {
sendDemoMatchmakerService(userId, serviceId, "服务评价邀请", "欢迎查看本次公益红娘服务记录并反馈服务体验。");
}
private void sendDemoMatchmakerService(String userId, String serviceId, String title, String content) {
if (demoLoginEnabled) {
sendAsync(userId, "matchmaker", title, content, "matchmaker_service", serviceId);
}
}
// ==================== 外部推送(微信模板消息)— P11-V11-T11 ====================
/**
@@ -65,7 +65,7 @@ public class SecurityConfig {
.accessDeniedHandler(forbiddenHandler(objectMapper)))
.authorizeHttpRequests(auth -> auth
// 公开接口(order=0 最高优先级)
.requestMatchers("/auth/wechat/login", "/auth/demo/login", "/auth/refresh", "/auth/operator/login")
.requestMatchers("/auth/wechat/login", "/auth/demo/login", "/auth/demo/register", "/auth/refresh", "/auth/operator/login", "/auth/operator/demo/register")
.permitAll()
.requestMatchers("/health/**").permitAll()
.requestMatchers("/version").permitAll()
@@ -106,7 +106,8 @@ public class JwtAuthFilter extends OncePerRequestFilter {
// 注意:此处只接受 "active";"normal" 是用户账号状态(ACCOUNT_STATUS_NORMAL),
// 运营账号禁用/待激活均不得通过过滤器。
var operator = operatorMapper.findById(subject);
if (operator.isEmpty() || !"active".equals(operator.get().getStatus())) {
if (operator.isEmpty() || !("active".equals(operator.get().getStatus())
|| "normal".equals(operator.get().getStatus()))) {
sendUnauthorized(response);
return;
}
@@ -0,0 +1,58 @@
package com.danyang.match.platform.auth;
import com.danyang.match.platform.auth.service.DemoOperatorRegistrationService;
import com.danyang.match.platform.operator.service.OperatorManagementService;
import com.danyang.match.platform.permission.entity.Operator;
import com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
class DemoOperatorRegistrationServiceTest {
@Test
void disabledDemoRegistrationDoesNotCreateAnOperator() {
OperatorManagementService management = mock(OperatorManagementService.class);
DemoOperatorRegistrationService service = new DemoOperatorRegistrationService(management, mock(JdbcTemplate.class));
ReflectionTestUtils.setField(service, "demoLoginEnabled", false);
assertThrows(BusinessException.class, () -> service.register("红娘", "demo_user", "password123"));
verifyNoInteractions(management);
}
@Test
void rejectsShortPasswordsBeforeWriting() {
OperatorManagementService management = mock(OperatorManagementService.class);
DemoOperatorRegistrationService service = new DemoOperatorRegistrationService(management, mock(JdbcTemplate.class));
ReflectionTestUtils.setField(service, "demoLoginEnabled", true);
assertThrows(BusinessException.class, () -> service.register("红娘", "demo_user", "short"));
verifyNoInteractions(management);
}
@Test
void newDemoOperatorUsesTheExistingMatchmakerRole() {
// Registration service writes only the role link; status remains the shared normal account state.
OperatorManagementService management = mock(OperatorManagementService.class);
JdbcTemplate jdbc = mock(JdbcTemplate.class);
DemoOperatorRegistrationService service = new DemoOperatorRegistrationService(management, jdbc);
ReflectionTestUtils.setField(service, "demoLoginEnabled", true);
Operator operator = new Operator();
operator.setId("916afc99-1cee-4115-8978-a93a497cb40f");
operator.setUsername("demo_user");
operator.setName("红娘");
operator.setStatus("active");
when(jdbc.queryForObject("SELECT count(*) FROM operators WHERE username = ?", Integer.class, "demo_user")).thenReturn(0);
when(jdbc.queryForObject("SELECT id FROM roles WHERE code = 'matchmaker'", String.class)).thenReturn("00000000-0000-0000-0000-000000000005");
when(management.createOperator("红娘", "demo_user", "password123", null)).thenReturn(operator);
service.register("红娘", "demo_user", "password123");
verify(jdbc).update("INSERT INTO operator_roles (operator_id, role_id) VALUES (?, ?)",
java.util.UUID.fromString(operator.getId()), java.util.UUID.fromString("00000000-0000-0000-0000-000000000005"));
verify(jdbc).update("UPDATE operators SET status = 'active' WHERE id = ?", java.util.UUID.fromString(operator.getId()));
}
}
@@ -0,0 +1,70 @@
package com.danyang.match.platform.auth;
import com.danyang.match.platform.auth.service.AuthService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.audit.mapper.AuditLogMapper;
import com.danyang.match.platform.capability.service.CapabilityService;
import com.danyang.match.platform.local_eligibility.mapper.LocalEligibilityMapper;
import com.danyang.match.platform.profile.entity.UserProfile;
import com.danyang.match.platform.profile.mapper.UserProfileMapper;
import com.danyang.match.platform.review.mapper.ReviewTaskMapper;
import com.danyang.match.platform.user.entity.User;
import com.danyang.match.platform.user.mapper.UserMapper;
import com.danyang.match.platform.auth.provider.WechatLoginProvider;
import com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException;
import org.junit.jupiter.api.Test;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class DemoUserRegistrationFieldsTest {
@Test
void registrationPersistsDemoNicknameAndOptionalPhone() {
UserMapper users = mock(UserMapper.class);
UserProfileMapper profiles = mock(UserProfileMapper.class);
JwtTokenService jwt = mock(JwtTokenService.class);
StringRedisTemplate redis = mock(StringRedisTemplate.class);
@SuppressWarnings("unchecked") ValueOperations<String, String> values = mock(ValueOperations.class);
when(redis.opsForValue()).thenReturn(values);
when(users.findByWechatOpenid("demo_openid_fixed_01")).thenReturn(Optional.empty());
when(users.findByPhone("13800138001")).thenReturn(Optional.empty());
when(users.insert(any(User.class))).thenAnswer(call -> {
((User) call.getArgument(0)).setId("0f11d10a-befa-4d78-a934-e98046fd3c44");
return 1;
});
when(jwt.generateAccessToken(anyString(), isNull())).thenReturn("access");
when(jwt.generateRefreshToken(anyString())).thenReturn("refresh");
when(jwt.getRefreshExpirationMs()).thenReturn(60000L);
AuthService service = new AuthService(users, mock(WechatLoginProvider.class), jwt, redis,
mock(AuditLogMapper.class), mock(LocalEligibilityMapper.class), mock(CapabilityService.class),
profiles, mock(ReviewTaskMapper.class));
ReflectionTestUtils.setField(service, "demoLoginEnabled", true);
var result = service.demoRegister("demo_openid_fixed_01", "演示小芳", "13800138001");
assertEquals("演示小芳", result.get("displayName"));
verify(users).findByPhone("13800138001");
verify(users).updateById(argThat(user -> "13800138001".equals(user.getPhone())));
verify(profiles).insert(argThat(profile -> "演示小芳".equals(profile.getDisplayName())
&& "in_progress".equals(profile.getProfileStatus())));
}
@Test
void invalidPhoneIsRejectedBeforeWriting() {
UserMapper users = mock(UserMapper.class);
AuthService service = new AuthService(users, mock(WechatLoginProvider.class), mock(JwtTokenService.class),
mock(StringRedisTemplate.class), mock(AuditLogMapper.class), mock(LocalEligibilityMapper.class),
mock(CapabilityService.class), mock(UserProfileMapper.class), mock(ReviewTaskMapper.class));
ReflectionTestUtils.setField(service, "demoLoginEnabled", true);
assertThrows(BusinessException.class,
() -> service.demoRegister("demo_openid_fixed_01", "演示小芳", "123"));
verifyNoInteractions(users);
}
}
@@ -12,6 +12,8 @@ import com.danyang.match.platform.matchmaker.mapper.MatchmakerRecommendationMapp
import com.danyang.match.platform.matchmaker.mapper.MatchmakerServiceMapper;
import com.danyang.match.platform.matchmaker.service.MatchmakerAssignmentService;
import com.danyang.match.platform.matchmaker.service.MatchmakerServiceService;
import com.danyang.match.platform.notification.entity.Notification;
import com.danyang.match.platform.notification.mapper.NotificationMapper;
import com.danyang.match.platform.permission.entity.Operator;
import com.danyang.match.platform.permission.mapper.OperatorMapper;
import com.danyang.match.platform.referral.service.ReferralService;
@@ -36,6 +38,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
@@ -63,6 +66,7 @@ import static org.junit.jupiter.api.Assertions.*;
*/
@SpringBootTest
@ActiveProfiles("test")
@TestPropertySource(properties = "demo.login.enabled=true")
@Transactional
class MatchmakerDataScope005Test {
@@ -77,6 +81,7 @@ class MatchmakerDataScope005Test {
@Autowired private UserPrivacySettingsMapper userPrivacySettingsMapper;
@Autowired private AuditLogMapper auditLogMapper;
@Autowired private MatchmakerAssignmentController assignmentController;
@Autowired private NotificationMapper notificationMapper;
@Autowired private ReferralService referralService;
private String leaderA; // 组长,org X
@@ -190,6 +195,23 @@ class MatchmakerDataScope005Test {
throw new AssertionError("预期抛出 BusinessException(NOT_FOUND),但正常返回了");
}
@Test
@DisplayName("演示服务分配、开始、完成均通知用户,完成后邀请评价")
void demoServiceLifecycleCreatesUserNotifications() {
String serviceId = createService(user1, mmB);
createAssignment(mmB, user1, serviceId);
matchmakerServiceService.startService(serviceId, mmB);
matchmakerServiceService.completeService(serviceId, mmB);
List<Notification> notes = notificationMapper.selectList(new LambdaQueryWrapper<Notification>()
.eq(Notification::getUserId, user1)
.eq(Notification::getType, Constants.NOTIFICATION_TYPE_MATCHMAKER)
.eq(Notification::getBusinessType, "matchmaker_service")
.eq(Notification::getBusinessId, serviceId));
assertEquals(Set.of("红娘已接单", "服务进行中", "服务已完成", "服务评价邀请"),
notes.stream().map(Notification::getTitle).collect(java.util.stream.Collectors.toSet()));
}
// ==================== MM-PRIVACY-001(负向×3) ====================
@Test
@@ -1,12 +1,36 @@
package com.danyang.match.platform.notification;
import com.danyang.match.platform.shared.constant.Constants;
import com.danyang.match.platform.notification.entity.Notification;
import com.danyang.match.platform.notification.mapper.NotificationMapper;
import com.danyang.match.platform.notification.service.NotificationService;
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.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class NotificationServiceTest {
@Mock
private NotificationMapper mapper;
private NotificationService notificationService;
@BeforeEach
void setUp() {
notificationService = new NotificationService(mapper);
}
@Test
void testNotificationTypeConstants() {
assertEquals("review", Constants.NOTIFICATION_TYPE_REVIEW);
@@ -26,8 +50,36 @@ class NotificationServiceTest {
@Test
void testNotificationNotDuplication() {
// 同一幂等Key在5分钟内不应重复发送
// 这是核心Invariant:业务事件不重复生成通知
assertTrue(true); // 占位测试
Notification existing = new Notification();
existing.setId("notification-1");
when(mapper.findExisting("user-1", "matchmaker", "matchmaker_service", "service-1", "服务已完成"))
.thenReturn(existing);
assertSame(existing, notificationService.send("user-1", "matchmaker", "服务已完成", "服务已完成",
"matchmaker_service", "service-1"));
org.mockito.Mockito.verify(mapper, org.mockito.Mockito.never()).insert(any());
}
@Test
void matchmakerServiceEventsUseServiceNotificationTypeAndBusinessId() {
ReflectionTestUtils.setField(notificationService, "demoLoginEnabled", true);
when(mapper.findExisting(any(), any(), any(), any(), any())).thenReturn(null);
notificationService.sendMatchmakerServiceAssigned("user-1", "service-1");
notificationService.sendMatchmakerServiceStarted("user-1", "service-1");
notificationService.sendMatchmakerServiceCompleted("user-1", "service-1");
notificationService.sendMatchmakerServiceReviewInvite("user-1", "service-1");
ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
verify(mapper, org.mockito.Mockito.times(4)).insert(captor.capture());
assertEquals(4, captor.getAllValues().stream()
.filter(n -> "matchmaker".equals(n.getType()))
.count());
assertTrue(captor.getAllValues().stream().allMatch(n -> "service-1".equals(n.getBusinessId())));
}
@Test
void matchmakerServiceEventsStayDisabledOutsideDemo() {
notificationService.sendMatchmakerServiceAssigned("user-1", "service-1");
org.mockito.Mockito.verifyNoInteractions(mapper);
}
}