You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

144 lines
6.3 KiB

package com.mydreamjob.auth.company;
import com.mydreamjob.audit.AuditContext;
import com.mydreamjob.audit.AuditService;
import com.mydreamjob.auth.AuthSessionService;
import com.mydreamjob.auth.SmsVerificationService;
import com.mydreamjob.auth.User;
import com.mydreamjob.auth.UserRepository;
import com.mydreamjob.auth.password.PasswordPolicy;
import com.mydreamjob.company.Company;
import com.mydreamjob.company.CompanyRepository;
import com.mydreamjob.common.exception.BusinessException;
import com.mydreamjob.common.exception.ErrorCode;
import com.mydreamjob.deletion.AccountDeletionPhoneHasher;
import com.mydreamjob.deletion.AccountDeletionRepository;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.dao.DataAccessResourceFailureException;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Optional;
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.Mockito.*;
@ExtendWith(MockitoExtension.class)
class CompanyRegistrationServiceTest {
private static final String PHONE = "19900000111";
@Mock SmsVerificationService sms;
@Mock UserRepository users;
@Mock CompanyRepository companies;
@Mock AccountDeletionRepository deletions;
@Mock AccountDeletionPhoneHasher hasher;
@Mock AuthSessionService sessions;
@Mock AuditService audit;
private final PasswordEncoder encoder = new BCryptPasswordEncoder();
private final PasswordPolicy policy = new PasswordPolicy();
private CompanyRegistrationProperties properties;
private CompanyRegistrationService service;
@BeforeEach
void setUp() {
properties = new CompanyRegistrationProperties();
properties.setEnabled("true");
service = new CompanyRegistrationService(properties, sms, users, companies, policy, encoder,
deletions, hasher, sessions, audit,
Clock.fixed(Instant.parse("2026-09-22T00:00:00Z"), ZoneOffset.UTC));
}
@Test
void createsCompanyUserCompanyAndSession() {
when(hasher.hash(PHONE)).thenReturn("phone-hash");
when(deletions.findByDeletedPhoneHash("phone-hash")).thenReturn(Optional.empty());
when(users.findByPhoneForUpdate(PHONE)).thenReturn(Optional.empty());
when(users.saveAndFlush(any(User.class))).thenAnswer(inv -> {
User u = inv.getArgument(0);
u.setId(11L);
return u;
});
when(companies.saveAndFlush(any(Company.class))).thenAnswer(inv -> {
Company c = inv.getArgument(0);
c.setId(21L);
return c;
});
when(sessions.createSession(any(), any(), any()))
.thenReturn(new AuthSessionService.SessionTokens("access", "refresh", "sid"));
CompanyRegistrationResponse result = service.register(request(), AuditContext.builder().build());
assertThat(result.role()).isEqualTo("COMPANY");
assertThat(result.onboardingStatus()).isEqualTo("UNVERIFIED");
verify(sms).verifyCode(PHONE, "123456", "company-register");
verify(users).saveAndFlush(argThat(u -> u.getRole() == User.Role.COMPANY
&& u.getRoles().contains(User.Role.COMPANY)
&& u.getRoles().size() == 1
&& !u.getVerified()
&& u.getPasswordHash() != null));
verify(companies).saveAndFlush(argThat(c -> c.getOwnerUserId().equals(11L)
&& !c.getVerified()));
verify(audit).logRequired(eq(11L), eq("COMPANY_REGISTERED"), eq("COMPANY"), eq(21L),
anyString(), any(), any());
}
@Test
void disabledRegistrationFailsBeforeSmsConsumption() {
properties.setEnabled("false");
assertThatThrownBy(() -> service.register(request(), AuditContext.builder().build()))
.isInstanceOf(BusinessException.class)
.extracting("errorCodeInt").isEqualTo(ErrorCode.COMPANY_REGISTRATION_DISABLED.getCode());
verifyNoInteractions(sms, users, companies);
}
@Test
void dependencyFailureIsMappedToStableServiceUnavailableCode() {
when(sms.verifyCode(anyString(), anyString(), eq("company-register")))
.thenThrow(new DataAccessResourceFailureException("connection details must not escape"));
assertThatThrownBy(() -> service.register(request(), AuditContext.builder().build()))
.isInstanceOf(BusinessException.class)
.extracting("errorCodeInt")
.isEqualTo(ErrorCode.COMPANY_REGISTRATION_DEPENDENCY_UNAVAILABLE.getCode());
verifyNoInteractions(users, companies, sessions, audit);
}
@Test
void allExistingPrincipalRolesUseTheSameConflictResponse() {
when(hasher.hash(PHONE)).thenReturn("phone-hash");
when(deletions.findByDeletedPhoneHash("phone-hash")).thenReturn(Optional.empty());
for (User.Role role : User.Role.values()) {
reset(users);
User existing = User.builder().id(100L).phone(PHONE).role(role).roles(java.util.Set.of(role)).build();
when(users.findByPhoneForUpdate(PHONE)).thenReturn(Optional.of(existing));
assertThatThrownBy(() -> service.register(request(), AuditContext.builder().build()))
.isInstanceOf(BusinessException.class)
.extracting("errorCodeInt")
.isEqualTo(ErrorCode.COMPANY_REGISTRATION_CONFLICT.getCode());
}
}
private CompanyRegistrationRequest request() {
CompanyRegistrationRequest request = new CompanyRegistrationRequest();
request.setPhone(PHONE);
request.setCode("123456");
request.setPassword("StrongCompanyPassword#1");
CompanyRegistrationRequest.CompanyDetails details = new CompanyRegistrationRequest.CompanyDetails();
details.setName("示例企业");
details.setIndustry("互联网");
details.setDescription("企业简介");
details.setAddress("上海");
request.setCompany(details);
return request;
}
}