Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/test/java/br/senac/xanxere/seguranca/AutenticacaoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package br.senac.xanxere.seguranca;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

/**
* AUTENTICAÇÃO: quem é você?
*
* Toda falha aqui deve resultar em 401 Unauthorized, nunca em 403.
*/
@SpringBootTest
@AutoConfigureMockMvc
class AutenticacaoTest {

@Autowired
private MockMvc mvc;

@Test
@DisplayName("rota pública responde 200 sem nenhuma credencial")
void rotaPublicaNaoExigeLogin() throws Exception {
mvc.perform(get("/api/publico"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.mensagem").exists());
}

@Test
@DisplayName("rota protegida sem credenciais responde 401")
void semCredenciaisRetorna401() throws Exception {
mvc.perform(get("/api/perfil"))
.andExpect(status().isUnauthorized())
// HttpStatusEntryPoint não manda o header que faz o navegador abrir popup de login
.andExpect(header().doesNotExist("WWW-Authenticate"));
}

@Test
@DisplayName("senha errada responde 401")
void senhaErradaRetorna401() throws Exception {
mvc.perform(get("/api/perfil").with(httpBasic("aluno", "senha-errada")))
.andExpect(status().isUnauthorized());
}

@Test
@DisplayName("usuário inexistente responde 401 (mesma resposta da senha errada, para não vazar quem existe)")
void usuarioInexistenteRetorna401() throws Exception {
mvc.perform(get("/api/perfil").with(httpBasic("ninguem", "qualquer")))
.andExpect(status().isUnauthorized());
}

@Test
@DisplayName("credenciais corretas autenticam e o perfil traz login e papéis com prefixo ROLE_")
void credenciaisCorretasAutenticam() throws Exception {
mvc.perform(get("/api/perfil").with(httpBasic("admin", "admin123")))
.andExpect(status().isOk())
.andExpect(jsonPath("$.login").value("admin"))
.andExpect(jsonPath("$.papeis").isArray())
.andExpect(jsonPath("$.papeis[?(@ == 'ROLE_ADMIN')]").exists())
.andExpect(jsonPath("$.papeis[?(@ == 'ROLE_PROFESSOR')]").exists());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package br.senac.xanxere.seguranca;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

/**
* AUTORIZAÇÃO por MÉTODO: a regra @PreAuthorize de NotasController.
*
* "hasAnyRole('PROFESSOR', 'ADMIN') or #aluno == authentication.name"
*/
@SpringBootTest
@AutoConfigureMockMvc
class AutorizacaoPorMetodoTest {

@Autowired
private MockMvc mvc;

@Test
@DisplayName("aluno vê as próprias notas")
void alunoVeAsPropriasNotas() throws Exception {
mvc.perform(get("/api/notas/aluno").with(httpBasic("aluno", "aluno123")))
.andExpect(status().isOk())
.andExpect(jsonPath("$.Programação").value(8.5));
}

@Test
@DisplayName("aluno não vê as notas de outro aluno (403)")
void alunoNaoVeNotasDeOutro() throws Exception {
mvc.perform(get("/api/notas/outro").with(httpBasic("aluno", "aluno123")))
.andExpect(status().isForbidden());
}

@Test
@DisplayName("professor vê as notas de qualquer aluno")
void professorVeNotasDeQualquerAluno() throws Exception {
mvc.perform(get("/api/notas/aluno").with(httpBasic("professor", "prof123")))
.andExpect(status().isOk());
}

@Test
@DisplayName("admin vê as notas de qualquer aluno")
void adminVeNotasDeQualquerAluno() throws Exception {
mvc.perform(get("/api/notas/aluno").with(httpBasic("admin", "admin123")))
.andExpect(status().isOk());
}

@Test
@DisplayName("sem login continua sendo 401, mesmo com regra de método")
void semLoginRetorna401() throws Exception {
mvc.perform(get("/api/notas/aluno"))
.andExpect(status().isUnauthorized());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package br.senac.xanxere.seguranca;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.stream.Stream;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

/**
* AUTORIZAÇÃO por URL: o que cada papel pode acessar?
*
* Aqui o usuário SEMPRE está autenticado; a única pergunta é 200 ou 403.
*/
@SpringBootTest
@AutoConfigureMockMvc
class AutorizacaoPorUrlTest {

@Autowired
private MockMvc mvc;

/**
* Matriz completa: (usuário, endpoint) -> status esperado. Espelha a tabela do README.
*/
static Stream<Arguments> matriz() {
return Stream.of(
Arguments.of("aluno", "aluno123", "/api/aluno", HttpStatus.OK),
Arguments.of("aluno", "aluno123", "/api/professor", HttpStatus.FORBIDDEN),
Arguments.of("aluno", "aluno123", "/api/admin", HttpStatus.FORBIDDEN),

Arguments.of("professor", "prof123", "/api/aluno", HttpStatus.OK),
Arguments.of("professor", "prof123", "/api/professor", HttpStatus.OK),
Arguments.of("professor", "prof123", "/api/admin", HttpStatus.FORBIDDEN),

Arguments.of("admin", "admin123", "/api/aluno", HttpStatus.OK),
Arguments.of("admin", "admin123", "/api/professor", HttpStatus.OK),
Arguments.of("admin", "admin123", "/api/admin", HttpStatus.OK)
);
}

@ParameterizedTest(name = "{0} em {2} -> {3}")
@MethodSource("matriz")
void papelContraEndpoint(String login, String senha, String url, HttpStatus esperado) throws Exception {
mvc.perform(get(url).with(httpBasic(login, senha)))
.andExpect(status().is(esperado.value()));
}

/**
* Alternativa ao httpBasic: @WithMockUser cria um usuário fictício já autenticado,
* sem passar pelo UserDetailsService. Útil para testar SÓ a autorização.
*/
@Nested
@DisplayName("com @WithMockUser")
class ComUsuarioSimulado {

@Test
@WithMockUser(roles = "ALUNO")
void alunoSimuladoNaoEntraNaAreaAdmin() throws Exception {
mvc.perform(get("/api/admin")).andExpect(status().isForbidden());
}

@Test
@WithMockUser(roles = "ADMIN")
void adminSimuladoEntraNaAreaAdmin() throws Exception {
mvc.perform(get("/api/admin")).andExpect(status().isOk());
}

@Test
@DisplayName("authorities sem o prefixo ROLE_ não valem como papel")
@WithMockUser(authorities = "ADMIN")
void authoritySemPrefixoNaoEhPapel() throws Exception {
mvc.perform(get("/api/admin")).andExpect(status().isForbidden());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package br.senac.xanxere.seguranca.usuario;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
* Teste unitário puro: não sobe o contexto do Spring, só instancia as classes.
* Verifica a ponte entre o nosso Usuario e o UserDetails que o Spring Security consome.
*/
class UsuarioDetailsServiceTest {

private PasswordEncoder encoder;
private UsuarioDetailsService service;

@BeforeEach
void setUp() {
encoder = new BCryptPasswordEncoder();
service = new UsuarioDetailsService(new UsuarioRepository(encoder));
}

@Test
@DisplayName("carrega o usuário com os papéis convertidos em authorities ROLE_*")
void carregaUsuarioComPapeis() {
UserDetails admin = service.loadUserByUsername("admin");

assertThat(admin.getUsername()).isEqualTo("admin");
assertThat(admin.getAuthorities())
.extracting(GrantedAuthority::getAuthority)
.containsExactlyInAnyOrder("ROLE_ADMIN", "ROLE_PROFESSOR");
}

@Test
@DisplayName("a senha guardada é um hash BCrypt que confere com a senha original")
void senhaGuardadaEhHashBcrypt() {
UserDetails aluno = service.loadUserByUsername("aluno");

assertThat(aluno.getPassword()).startsWith("$2").isNotEqualTo("aluno123");
assertThat(encoder.matches("aluno123", aluno.getPassword())).isTrue();
assertThat(encoder.matches("outra", aluno.getPassword())).isFalse();
}

@Test
@DisplayName("usuário desconhecido lança UsernameNotFoundException")
void usuarioDesconhecidoLancaExcecao() {
assertThatThrownBy(() -> service.loadUserByUsername("ninguem"))
.isInstanceOf(UsernameNotFoundException.class)
.hasMessageContaining("ninguem");
}
}
Loading