Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package io.spring.api.exception;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import javax.validation.metadata.ConstraintDescriptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.context.request.WebRequest;

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.lang.annotation.Annotation;

@ExtendWith(MockitoExtension.class)
class CustomizeExceptionHandlerTest {

private CustomizeExceptionHandler handler;
private WebRequest webRequest;

@BeforeEach
void setUp() {
handler = new CustomizeExceptionHandler();
webRequest = mock(WebRequest.class);
}

@Test
void handleInvalidRequest_with_field_errors() {
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(new Object(), "article");
errors.addError(new FieldError("article", "title", "can't be empty"));
errors.addError(new FieldError("article", "body", "can't be empty"));

InvalidRequestException ex = new InvalidRequestException(errors);

ResponseEntity<Object> response = handler.handleInvalidRequest(ex, webRequest);

assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatusCode());
assertNotNull(response.getBody());
ErrorResource errorResource = (ErrorResource) response.getBody();
assertEquals(2, errorResource.getFieldErrors().size());
}

@Test
@SuppressWarnings("unchecked")
void handleInvalidAuthentication_returns_422_with_message() {
InvalidAuthenticationException ex = new InvalidAuthenticationException();

ResponseEntity<Object> response = handler.handleInvalidAuthentication(ex, webRequest);

assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatusCode());
Map<String, Object> body = (Map<String, Object>) response.getBody();
assertNotNull(body);
assertEquals("invalid email or password", body.get("message"));
}

@Test
void handleMethodArgumentNotValid_returns_422() throws Exception {
BeanPropertyBindingResult bindingResult =
new BeanPropertyBindingResult(new Object(), "user");
bindingResult.addError(new FieldError("user", "email", "must not be blank"));

MethodArgumentNotValidException ex =
new MethodArgumentNotValidException(null, bindingResult);

ResponseEntity<Object> response =
handler.handleMethodArgumentNotValid(ex, null, HttpStatus.BAD_REQUEST, webRequest);

assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatusCode());
assertNotNull(response.getBody());
ErrorResource errorResource = (ErrorResource) response.getBody();
assertEquals(1, errorResource.getFieldErrors().size());
assertEquals("email", errorResource.getFieldErrors().get(0).getField());
}

@Test
@SuppressWarnings("unchecked")
void handleConstraintViolation_with_multiple_violations() {
Set<ConstraintViolation<?>> violations = new HashSet<>();

ConstraintViolation<?> violation1 = mock(ConstraintViolation.class);
Path path1 = mock(Path.class);
when(path1.toString()).thenReturn("method.arg.title");
when(violation1.getPropertyPath()).thenReturn(path1);
when(violation1.getMessage()).thenReturn("must not be blank");
when(violation1.getRootBeanClass()).thenReturn((Class) Object.class);
ConstraintDescriptor<?> descriptor1 = mock(ConstraintDescriptor.class);
Annotation annotation1 = mock(Annotation.class);
when(annotation1.annotationType()).thenReturn((Class) Override.class);
when(descriptor1.getAnnotation()).thenReturn(annotation1);
doReturn(descriptor1).when(violation1).getConstraintDescriptor();

ConstraintViolation<?> violation2 = mock(ConstraintViolation.class);
Path path2 = mock(Path.class);
when(path2.toString()).thenReturn("method.arg.body");
when(violation2.getPropertyPath()).thenReturn(path2);
when(violation2.getMessage()).thenReturn("must not be empty");
when(violation2.getRootBeanClass()).thenReturn((Class) Object.class);
ConstraintDescriptor<?> descriptor2 = mock(ConstraintDescriptor.class);
Annotation annotation2 = mock(Annotation.class);
when(annotation2.annotationType()).thenReturn((Class) Override.class);
when(descriptor2.getAnnotation()).thenReturn(annotation2);
doReturn(descriptor2).when(violation2).getConstraintDescriptor();

violations.add(violation1);
violations.add(violation2);

ConstraintViolationException ex = new ConstraintViolationException(violations);

ErrorResource result = handler.handleConstraintViolation(ex, webRequest);

assertNotNull(result);
assertEquals(2, result.getFieldErrors().size());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.spring.api.exception;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class ErrorResourceSerializerTest {

private ErrorResourceSerializer serializer;
private ObjectMapper objectMapper;

@BeforeEach
void setUp() {
serializer = new ErrorResourceSerializer();
objectMapper = new ObjectMapper();
}

@Test
void serialize_single_field_error() throws Exception {
FieldErrorResource fieldError =
new FieldErrorResource("article", "title", "NotBlank", "can't be empty");
ErrorResource errorResource = new ErrorResource(Collections.singletonList(fieldError));

String json = serializeToJson(errorResource);

assertEquals("{\"errors\":{\"title\":[\"can't be empty\"]}}", json);
}

@Test
void serialize_multiple_errors_on_same_field_should_be_grouped() throws Exception {
List<FieldErrorResource> fieldErrors =
Arrays.asList(
new FieldErrorResource("article", "title", "NotBlank", "can't be empty"),
new FieldErrorResource("article", "title", "Size", "too short"));
ErrorResource errorResource = new ErrorResource(fieldErrors);

String json = serializeToJson(errorResource);

assertTrue(json.contains("\"errors\""));
assertTrue(json.contains("\"title\""));
assertTrue(json.contains("can't be empty"));
assertTrue(json.contains("too short"));
int titleCount = json.split("\"title\"").length - 1;
assertEquals(1, titleCount);
}

@Test
void serialize_errors_on_different_fields() throws Exception {
List<FieldErrorResource> fieldErrors =
Arrays.asList(
new FieldErrorResource("article", "title", "NotBlank", "can't be empty"),
new FieldErrorResource("article", "body", "NotBlank", "can't be empty"));
ErrorResource errorResource = new ErrorResource(fieldErrors);

String json = serializeToJson(errorResource);

assertTrue(json.contains("\"title\""));
assertTrue(json.contains("\"body\""));
assertTrue(json.contains("\"errors\""));
}

private String serializeToJson(ErrorResource errorResource) throws Exception {
StringWriter writer = new StringWriter();
JsonGenerator gen = new JsonFactory().createGenerator(writer);
SerializerProvider provider = objectMapper.getSerializerProvider();
serializer.serialize(errorResource, gen, provider);
gen.flush();
return writer.toString();
}
}
111 changes: 111 additions & 0 deletions src/test/java/io/spring/api/security/JwtTokenFilterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.spring.api.security;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import io.spring.core.service.JwtService;
import io.spring.core.user.User;
import io.spring.core.user.UserRepository;
import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;

@ExtendWith(MockitoExtension.class)
class JwtTokenFilterTest {

@Mock private JwtService jwtService;
@Mock private UserRepository userRepository;
@Mock private HttpServletRequest request;
@Mock private HttpServletResponse response;
@Mock private FilterChain filterChain;

@InjectMocks private JwtTokenFilter jwtTokenFilter;

@BeforeEach
void setUp() {
SecurityContextHolder.clearContext();
}

@Test
void should_continue_filter_chain_when_no_authorization_header() throws Exception {
when(request.getHeader("Authorization")).thenReturn(null);

jwtTokenFilter.doFilterInternal(request, response, filterChain);

verify(filterChain).doFilter(request, response);
assertNull(SecurityContextHolder.getContext().getAuthentication());
}

@Test
void should_continue_filter_chain_when_malformed_header_no_space() throws Exception {
when(request.getHeader("Authorization")).thenReturn("TokenWithNoSpace");

jwtTokenFilter.doFilterInternal(request, response, filterChain);

verify(filterChain).doFilter(request, response);
assertNull(SecurityContextHolder.getContext().getAuthentication());
}

@Test
void should_set_authentication_when_valid_token_and_user_exists() throws Exception {
String token = "valid-token";
User user = new User("test@test.com", "testuser", "pass", "", "");

when(request.getHeader("Authorization")).thenReturn("Token " + token);
when(jwtService.getSubFromToken(eq(token))).thenReturn(Optional.of(user.getId()));
when(userRepository.findById(eq(user.getId()))).thenReturn(Optional.of(user));

jwtTokenFilter.doFilterInternal(request, response, filterChain);

verify(filterChain).doFilter(request, response);
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
assertEquals(user, SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}

@Test
void should_not_set_authentication_when_valid_token_but_user_not_in_db() throws Exception {
String token = "valid-token";
String userId = "non-existent-user-id";

when(request.getHeader("Authorization")).thenReturn("Token " + token);
when(jwtService.getSubFromToken(eq(token))).thenReturn(Optional.of(userId));
when(userRepository.findById(eq(userId))).thenReturn(Optional.empty());

jwtTokenFilter.doFilterInternal(request, response, filterChain);

verify(filterChain).doFilter(request, response);
assertNull(SecurityContextHolder.getContext().getAuthentication());
}

@Test
void should_not_overwrite_existing_authentication() throws Exception {
String token = "valid-token";
User user = new User("test@test.com", "testuser", "pass", "", "");

UsernamePasswordAuthenticationToken existingAuth =
new UsernamePasswordAuthenticationToken("existing-principal", null);
SecurityContextHolder.getContext().setAuthentication(existingAuth);

when(request.getHeader("Authorization")).thenReturn("Token " + token);
when(jwtService.getSubFromToken(eq(token))).thenReturn(Optional.of(user.getId()));

jwtTokenFilter.doFilterInternal(request, response, filterChain);

verify(filterChain).doFilter(request, response);
assertSame(existingAuth, SecurityContextHolder.getContext().getAuthentication());
}
}
Loading