package com.mydreamjob.auth; import com.mydreamjob.audit.AuditService; import com.mydreamjob.auth.sms.SmsCodeSender; import com.mydreamjob.common.exception.BusinessException; import com.mydreamjob.common.exception.ErrorCode; import org.junit.jupiter.api.Test; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class SmsSendRateLimitAtomicityTest { @Test @SuppressWarnings("unchecked") void losingAtomicRedisReservationNeverPersistsOrSendsAnotherCode() { SmsVerificationCodeRepository codes = mock(SmsVerificationCodeRepository.class); RedisTemplate redis = mock(RedisTemplate.class); ValueOperations values = mock(ValueOperations.class); SmsCodeSender sender = mock(SmsCodeSender.class); SmsVerificationService service = new SmsVerificationService(codes, redis, mock(AuditService.class), sender, mock(SmsFailedAttemptRecorder.class)); when(redis.opsForValue()).thenReturn(values); when(values.setIfAbsent("sms:rate:19900009682", "1", 60L, TimeUnit.SECONDS)) .thenReturn(false); assertThatThrownBy(() -> service.sendCode("19900009682", "login", null, null, null)) .isInstanceOf(BusinessException.class) .satisfies(error -> assertThat(((BusinessException) error).getErrorCodeInt()) .isEqualTo(ErrorCode.SMS_RATE_LIMITED.getCode())); verify(codes, never()).save(any()); verify(sender, never()).sendVerificationCode(any(), any(), any()); } }