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
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;
|
|
}
|
|
}
|
|
|