UserService.java

1
package com.edtech.service;
2
3
import com.edtech.dto.RegisterRequestDto;
4
import com.edtech.model.User;
5
import com.edtech.model.UserRole;
6
import com.edtech.repository.UserRepository;
7
import java.util.Arrays;
8
import java.util.Locale;
9
import java.util.Optional;
10
import org.springframework.beans.factory.annotation.Value;
11
import org.springframework.security.crypto.password.PasswordEncoder;
12
import org.springframework.stereotype.Service;
13
import org.springframework.transaction.annotation.Transactional;
14
15
/** Documentação para UserService. */
16
@Service
17
public class UserService {
18
19
  @Value("${institutional-email.allowed-domains:unb.br}")
20
  private String allowedInstitutionalDomains = "unb.br";
21
22
  private final UserRepository userRepository;
23
  private final PasswordEncoder passwordEncoder;
24
  private final com.edtech.repository.VerificationTokenRepository verificationTokenRepository;
25
  private final EmailService emailService;
26
  private static final java.security.SecureRandom SECURE_RANDOM = new java.security.SecureRandom();
27
28
  /** Documentação para o método UserService. */
29
  public UserService(
30
      UserRepository userRepository,
31
      PasswordEncoder passwordEncoder,
32
      com.edtech.repository.VerificationTokenRepository verificationTokenRepository,
33
      EmailService emailService) {
34
    this.userRepository = userRepository;
35
    this.passwordEncoder = passwordEncoder;
36
    this.verificationTokenRepository = verificationTokenRepository;
37
    this.emailService = emailService;
38
  }
39
40
  /** Documentação. */
41
  @Transactional(readOnly = true)
42
  public User authenticate(String email, String password) {
43
    String normalizedEmail = email.trim().toLowerCase(Locale.ROOT);
44
    User user =
45
        userRepository
46
            .findByEmailIgnoreCase(normalizedEmail)
47 1 1. lambda$authenticate$0 : replaced return value with null for com/edtech/service/UserService::lambda$authenticate$0 → KILLED
            .orElseThrow(() -> new InvalidCredentialsException("Credenciais inválidas."));
48
49 1 1. authenticate : negated conditional → KILLED
    if (!user.isActive()) {
50
      throw new AccountNotVerifiedException();
51
    }
52
53 1 1. authenticate : negated conditional → KILLED
    if (!passwordEncoder.matches(password, user.getPasswordHash())) {
54
      throw new InvalidCredentialsException("Credenciais inválidas.");
55
    }
56
57 1 1. authenticate : replaced return value with null for com/edtech/service/UserService::authenticate → KILLED
    return user;
58
  }
59
60
  /** Documentação. */
61
  @Transactional
62
  public User register(RegisterRequestDto request) {
63
    String normalizedEmail = request.email().trim().toLowerCase(Locale.ROOT);
64
65 1 1. register : negated conditional → KILLED
    if (!hasAllowedInstitutionalDomain(normalizedEmail)) {
66
      throw new InvalidInstitutionalEmailException(
67
          "O e-mail deve pertencer a um domínio institucional permitido.");
68
    }
69
70 1 1. register : negated conditional → KILLED
    if (userRepository.existsByEmailIgnoreCase(normalizedEmail)) {
71
      throw new DuplicateEmailException("E-mail ja cadastrado.");
72
    }
73
74
    UserRole initialRole = request.role();
75
76
    User user =
77
        new User(
78
            request.name().trim(),
79
            normalizedEmail,
80
            passwordEncoder.encode(request.password()),
81
            initialRole,
82
            java.util.UUID.fromString("00000000-0000-0000-0000-000000000001"));
83
84 1 1. register : removed call to com/edtech/model/User::setActive → KILLED
    user.setActive(false);
85
    userRepository.save(user);
86
87 1 1. register : removed call to com/edtech/repository/VerificationTokenRepository::deleteByEmail → KILLED
    verificationTokenRepository.deleteByEmail(normalizedEmail);
88
    String code = String.format("%06d", SECURE_RANDOM.nextInt(999999));
89
    com.edtech.model.VerificationToken token =
90
        new com.edtech.model.VerificationToken(
91
            code, normalizedEmail, java.time.LocalDateTime.now().plusMinutes(15));
92
    verificationTokenRepository.save(token);
93
94 1 1. register : removed call to com/edtech/service/EmailService::sendVerificationEmail → KILLED
    emailService.sendVerificationEmail(normalizedEmail, code);
95
96 1 1. register : replaced return value with null for com/edtech/service/UserService::register → KILLED
    return user;
97
  }
98
99
  /** Checks whether an email belongs to one of the configured institutional domains. */
100
  private boolean hasAllowedInstitutionalDomain(String email) {
101 2 1. hasAllowedInstitutionalDomain : replaced boolean return with false for com/edtech/service/UserService::hasAllowedInstitutionalDomain → KILLED
2. hasAllowedInstitutionalDomain : replaced boolean return with true for com/edtech/service/UserService::hasAllowedInstitutionalDomain → KILLED
    return Arrays.stream(allowedInstitutionalDomains.split(","))
102 1 1. lambda$hasAllowedInstitutionalDomain$1 : replaced return value with "" for com/edtech/service/UserService::lambda$hasAllowedInstitutionalDomain$1 → KILLED
        .map(domain -> domain.trim().toLowerCase(Locale.ROOT))
103 2 1. lambda$hasAllowedInstitutionalDomain$2 : replaced boolean return with true for com/edtech/service/UserService::lambda$hasAllowedInstitutionalDomain$2 → KILLED
2. lambda$hasAllowedInstitutionalDomain$2 : negated conditional → KILLED
        .filter(domain -> !domain.isBlank())
104 3 1. lambda$hasAllowedInstitutionalDomain$3 : replaced boolean return with true for com/edtech/service/UserService::lambda$hasAllowedInstitutionalDomain$3 → KILLED
2. lambda$hasAllowedInstitutionalDomain$3 : negated conditional → KILLED
3. lambda$hasAllowedInstitutionalDomain$3 : negated conditional → KILLED
        .anyMatch(domain -> email.endsWith("@" + domain) || email.endsWith("." + domain));
105
  }
106
107
  /** Documentação. */
108
  @Transactional
109
  public User verifyRegistration(String email, String code) {
110
    String normalizedEmail = email.trim().toLowerCase(Locale.ROOT);
111
    Optional<com.edtech.model.VerificationToken> tokenOpt =
112
        verificationTokenRepository.findByEmailAndToken(normalizedEmail, code);
113
114 2 1. verifyRegistration : negated conditional → KILLED
2. verifyRegistration : negated conditional → KILLED
    if (tokenOpt.isPresent() && !tokenOpt.get().isExpired()) {
115
      Optional<User> userOpt = userRepository.findByEmailIgnoreCase(normalizedEmail);
116 1 1. verifyRegistration : negated conditional → KILLED
      if (userOpt.isPresent()) {
117
        User user = userOpt.get();
118 1 1. verifyRegistration : removed call to com/edtech/model/User::setActive → SURVIVED
        user.setActive(true);
119
        userRepository.save(user);
120 1 1. verifyRegistration : removed call to com/edtech/repository/VerificationTokenRepository::deleteByEmail → SURVIVED
        verificationTokenRepository.deleteByEmail(normalizedEmail);
121 1 1. verifyRegistration : replaced return value with null for com/edtech/service/UserService::verifyRegistration → KILLED
        return user;
122
      }
123
    }
124
    throw new InvalidCredentialsException("Código inválido ou expirado.");
125
  }
126
127
  /** Generates and sends a fresh verification code for an inactive account. */
128
  @Transactional
129
  public void resendVerificationCode(String email) {
130
    String normalizedEmail = email.trim().toLowerCase(Locale.ROOT);
131
    User user =
132
        userRepository
133
            .findByEmailIgnoreCase(normalizedEmail)
134 1 1. lambda$resendVerificationCode$4 : replaced return value with null for com/edtech/service/UserService::lambda$resendVerificationCode$4 → NO_COVERAGE
            .orElseThrow(() -> new InvalidCredentialsException("Credenciais inválidas."));
135 1 1. resendVerificationCode : negated conditional → NO_COVERAGE
    if (user.isActive()) {
136
      return;
137
    }
138 1 1. resendVerificationCode : removed call to com/edtech/repository/VerificationTokenRepository::deleteByEmail → NO_COVERAGE
    verificationTokenRepository.deleteByEmail(normalizedEmail);
139
    String code = String.format("%06d", SECURE_RANDOM.nextInt(999999));
140
    verificationTokenRepository.save(
141
        new com.edtech.model.VerificationToken(
142
            code, normalizedEmail, java.time.LocalDateTime.now().plusMinutes(15)));
143 1 1. resendVerificationCode : removed call to com/edtech/service/EmailService::sendVerificationEmail → NO_COVERAGE
    emailService.sendVerificationEmail(normalizedEmail, code);
144
  }
145
146
  @Transactional
147
  public User saveUserWithoutHash(User user) {
148 1 1. saveUserWithoutHash : replaced return value with null for com/edtech/service/UserService::saveUserWithoutHash → SURVIVED
    return userRepository.save(user);
149
  }
150
}

Mutations

47

1.1
Location : lambda$authenticate$0
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:authenticate_UserNotFound_ThrowsException()]
replaced return value with null for com/edtech/service/UserService::lambda$authenticate$0 → KILLED

49

1.1
Location : authenticate
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:authenticate_InactiveUser_ThrowsException()]
negated conditional → KILLED

53

1.1
Location : authenticate
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:authenticate_WrongPassword_ThrowsException()]
negated conditional → KILLED

57

1.1
Location : authenticate
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:authenticate_Success()]
replaced return value with null for com/edtech/service/UserService::authenticate → KILLED

65

1.1
Location : register
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithAuditorEmail_AssignsAuditorRole()]
negated conditional → KILLED

70

1.1
Location : register
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithAuditorEmail_AssignsAuditorRole()]
negated conditional → KILLED

84

1.1
Location : register
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:registerRejectsDuplicatedEmail()]
removed call to com/edtech/model/User::setActive → KILLED

87

1.1
Location : register
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:registerRejectsDuplicatedEmail()]
removed call to com/edtech/repository/VerificationTokenRepository::deleteByEmail → KILLED

94

1.1
Location : register
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:registerRejectsDuplicatedEmail()]
removed call to com/edtech/service/EmailService::sendVerificationEmail → KILLED

96

1.1
Location : register
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithAuditorEmail_AssignsAuditorRole()]
replaced return value with null for com/edtech/service/UserService::register → KILLED

101

1.1
Location : hasAllowedInstitutionalDomain
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithAuditorEmail_AssignsAuditorRole()]
replaced boolean return with false for com/edtech/service/UserService::hasAllowedInstitutionalDomain → KILLED

2.2
Location : hasAllowedInstitutionalDomain
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithInvalidDomain_MustThrowException()]
replaced boolean return with true for com/edtech/service/UserService::hasAllowedInstitutionalDomain → KILLED

102

1.1
Location : lambda$hasAllowedInstitutionalDomain$1
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithAuditorEmail_AssignsAuditorRole()]
replaced return value with "" for com/edtech/service/UserService::lambda$hasAllowedInstitutionalDomain$1 → KILLED

103

1.1
Location : lambda$hasAllowedInstitutionalDomain$2
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:registerRejectsDuplicatedEmail()]
replaced boolean return with true for com/edtech/service/UserService::lambda$hasAllowedInstitutionalDomain$2 → KILLED

2.2
Location : lambda$hasAllowedInstitutionalDomain$2
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithAuditorEmail_AssignsAuditorRole()]
negated conditional → KILLED

104

1.1
Location : lambda$hasAllowedInstitutionalDomain$3
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithInvalidDomain_MustThrowException()]
replaced boolean return with true for com/edtech/service/UserService::lambda$hasAllowedInstitutionalDomain$3 → KILLED

2.2
Location : lambda$hasAllowedInstitutionalDomain$3
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithInvalidDomain_MustThrowException()]
negated conditional → KILLED

3.3
Location : lambda$hasAllowedInstitutionalDomain$3
Killed by : com.edtech.service.UserServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.UserServiceTest]/[method:register_WithAuditorEmail_AssignsAuditorRole()]
negated conditional → KILLED

114

1.1
Location : verifyRegistration
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:verifyRegistrationReturnsToken()]
negated conditional → KILLED

2.2
Location : verifyRegistration
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:verifyRegistrationReturnsToken()]
negated conditional → KILLED

116

1.1
Location : verifyRegistration
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:verifyRegistrationReturnsToken()]
negated conditional → KILLED

118

1.1
Location : verifyRegistration
Killed by : none
removed call to com/edtech/model/User::setActive → SURVIVED
Covering tests

120

1.1
Location : verifyRegistration
Killed by : none
removed call to com/edtech/repository/VerificationTokenRepository::deleteByEmail → SURVIVED
Covering tests

121

1.1
Location : verifyRegistration
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:verifyRegistrationReturnsToken()]
replaced return value with null for com/edtech/service/UserService::verifyRegistration → KILLED

134

1.1
Location : lambda$resendVerificationCode$4
Killed by : none
replaced return value with null for com/edtech/service/UserService::lambda$resendVerificationCode$4 → NO_COVERAGE

135

1.1
Location : resendVerificationCode
Killed by : none
negated conditional → NO_COVERAGE

138

1.1
Location : resendVerificationCode
Killed by : none
removed call to com/edtech/repository/VerificationTokenRepository::deleteByEmail → NO_COVERAGE

143

1.1
Location : resendVerificationCode
Killed by : none
removed call to com/edtech/service/EmailService::sendVerificationEmail → NO_COVERAGE

148

1.1
Location : saveUserWithoutHash
Killed by : none
replaced return value with null for com/edtech/service/UserService::saveUserWithoutHash → SURVIVED
Covering tests

Active mutators

Tests examined


Report generated by PIT 1.25.9 support