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
1 change: 1 addition & 0 deletions api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**
!**/src/test/**
lombok.config

### IntelliJ IDEA ###
.idea
Expand Down
6 changes: 6 additions & 0 deletions api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {
group = 'com.code4ro'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
targetCompatibility = '11'

repositories {
mavenCentral()
Expand All @@ -29,6 +30,11 @@ dependencies {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.springframework.security:spring-security-test'

// Needed for TestRestTemplate to work with 40x responses
testImplementation'org.apache.httpcomponents:httpclient:4.5.12'

testImplementation 'org.apache.commons:commons-lang3:3.10'
}

test {
Expand Down
Empty file modified api/gradlew
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

@Service
public class CustomUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

@Autowired
Expand All @@ -24,7 +25,7 @@ public CustomUserDetailsService(final UserRepository userRepository) {
public UserDetails loadUserByUsername(final String email) {
final User applicationUser = userRepository.findByEmail(email)
.orElseThrow(() ->
new UsernameNotFoundException("User not found with email : " + email)
new UsernameNotFoundException("User not found with email: " + email)
);

return UserPrincipal.create(applicationUser);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.code4ro.nextdoor.core.exception.handler;

import com.code4ro.nextdoor.authentication.controller.AuthenticationController;
import com.code4ro.nextdoor.core.exception.ExceptionResponse;
import com.code4ro.nextdoor.core.exception.I18nError;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.Collections;

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice(assignableTypes = AuthenticationController.class)
public class AuthExceptionHandler extends GlobalExceptionHandler {

@ExceptionHandler(BadCredentialsException.class)
protected ResponseEntity<Object> handleBadCredentials(final BadCredentialsException ex) {
final ExceptionResponse exceptionResponse = new ExceptionResponse();
exceptionResponse.setI18nErrors(Collections.singletonList(
new I18nError("login.bad.credentials", null)));
return new ResponseEntity<>(exceptionResponse, HttpStatus.UNAUTHORIZED);
}
}
8 changes: 4 additions & 4 deletions api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ spring:
profiles:
active: dev
datasource:
url: jdbc:mysql://localhost:3306/nextdoor?serverTimezone=Europe/Bucharest
username: root
password: root
url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/nextdoor?serverTimezone=Europe/Bucharest
username: ${MYSQL_USER:root}
password: ${MYSQL_PASSWORD:root}
jpa:
hibernate:
ddl-auto: update
flyway:
enabled: true
locations: classpath:db/migrate,db/data
locations: classpath:db/migrate,classpath:db/data
baselineOnMigrate: true
app:
jwtSecret: test
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.code4ro.nextdoor.authentication.controller;

import com.code4ro.nextdoor.authentication.dto.LoginRequest;
import com.code4ro.nextdoor.authentication.dto.RegistrationRequest;
import com.code4ro.nextdoor.common.controller.AbstractControllerIntegrationTest;
import com.code4ro.nextdoor.core.exception.ExceptionResponse;
import com.code4ro.nextdoor.util.RandomObjectFiller;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql;

import java.net.MalformedURLException;
import java.net.URL;

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

@DisplayName("Integration test for /api/authentication endpoint")
class AuthenticationControllerIntegrationTest extends AbstractControllerIntegrationTest {

@DisplayName("tests registration scenarios")
@Test
void register() throws MalformedURLException {
RegistrationRequest registrationRequest = RandomObjectFiller.createAndFill(RegistrationRequest.class);
HttpEntity<RegistrationRequest> entity = new HttpEntity<>(registrationRequest, headers);

ResponseEntity<Void> response = restTemplate.postForEntity(
new URL(getBaseUrl() + "/register").toString(), entity, Void.class);

SoftAssertions.assertSoftly(softly -> {
assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getHeaders().getLocation()).isNotNull();
assertThat(response.getHeaders().getLocation().toString().contains("/api/users/")).isTrue();
});

// TODO: we could now call the users API, when that gets implemented, and check if we get the actual user

// Try second registration with the same data, should result in error

ResponseEntity<ExceptionResponse> response_duplicate = restTemplate.postForEntity(
new URL(getBaseUrl() + "/register").toString(), entity, ExceptionResponse.class);

SoftAssertions.assertSoftly(softly -> {
assertThat(response_duplicate).isNotNull();
assertThat(response_duplicate.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
assertThat(response_duplicate.getHeaders().getLocation()).isNull();
assertThat(response_duplicate.getBody()).isNotNull();
assertThat(response_duplicate.getBody().getI18nErrors().size()).isEqualTo(1);
assertThat(response_duplicate.getBody().getI18nErrors().get(0).getI18nErrorKey()).isEqualTo("user.duplicate.email");
});
}

@DisplayName("tests successful login")
@Sql(scripts = "AuthenticationControllerIntegrationTest.sql")
@Test
void login_success() throws MalformedURLException {
LoginRequest loginRequest = new LoginRequest();
loginRequest.setEmail("test@test.com");
loginRequest.setPassword("pass");
HttpEntity<LoginRequest> entity = new HttpEntity<>(loginRequest, headers);

ResponseEntity<Void> response = restTemplate.postForEntity(
new URL(getBaseUrl() + "/login").toString(), entity, Void.class);

SoftAssertions.assertSoftly(softly -> {
assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
});
}

@DisplayName("tests wrong password login")
@Sql(scripts = "AuthenticationControllerIntegrationTest.sql")
@Test
void login_bad_password() throws MalformedURLException {
LoginRequest loginRequestWrongPass = new LoginRequest();
loginRequestWrongPass.setEmail("test@test.com");
loginRequestWrongPass.setPassword("wrongpass");
HttpEntity<LoginRequest> entity = new HttpEntity<>(loginRequestWrongPass, headers);

ResponseEntity<ExceptionResponse> response_wrong_pass = restTemplate.postForEntity(
new URL(getBaseUrl() + "/login").toString(), entity, ExceptionResponse.class);

SoftAssertions.assertSoftly(softly -> {
assertThat(response_wrong_pass).isNotNull();
assertThat(response_wrong_pass.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
assertThat(response_wrong_pass.getBody()).isNotNull();
assertThat(response_wrong_pass.getBody().getI18nErrors().size()).isEqualTo(1);
assertThat(response_wrong_pass.getBody().getI18nErrors().get(0).getI18nErrorKey()).isEqualTo("login.bad.credentials");
});
}

@DisplayName("tests wrong username login")
@Sql(scripts = "AuthenticationControllerIntegrationTest.sql")
@Test
void login_bad_username() throws MalformedURLException {
LoginRequest loginRequestWrongUser = new LoginRequest();
loginRequestWrongUser.setEmail("testwrong@test.com");
loginRequestWrongUser.setPassword("pass");
HttpEntity<LoginRequest> entity = new HttpEntity<>(loginRequestWrongUser, headers);

ResponseEntity<ExceptionResponse> response_wrong_user = restTemplate.postForEntity(
new URL(getBaseUrl() + "/login").toString(), entity, ExceptionResponse.class);

SoftAssertions.assertSoftly(softly -> {
assertThat(response_wrong_user).isNotNull();
assertThat(response_wrong_user.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
assertThat(response_wrong_user.getBody()).isNotNull();
assertThat(response_wrong_user.getBody().getI18nErrors().size()).isEqualTo(1);
assertThat(response_wrong_user.getBody().getI18nErrors().get(0).getI18nErrorKey()).isEqualTo("login.bad.credentials");
});
}

@Override
protected String getBaseUrl() {
return "http://localhost:" + port + "/api/authentication";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.code4ro.nextdoor.authentication.controller;

import com.code4ro.nextdoor.authentication.dto.LoginRequest;
import com.code4ro.nextdoor.authentication.dto.RegistrationRequest;
import com.code4ro.nextdoor.authentication.entity.User;
import com.code4ro.nextdoor.authentication.service.AuthenticationService;
import com.code4ro.nextdoor.authentication.service.impl.CustomUserDetailsService;
import com.code4ro.nextdoor.common.controller.AbstractControllerUnitTest;
import com.code4ro.nextdoor.core.exception.NextDoorValidationException;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;

import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.core.StringEndsWith.endsWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
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;

@DisplayName("Unit test for AuthenticationController")
@WebMvcTest(AuthenticationController.class)
public class AuthenticationControllerUnitTest extends AbstractControllerUnitTest {

private static final String EMAIL = "test@test.com";
private static final String PASSWORD = "pass";
private static final UUID USER_ID = new UUID(10, 100);

@MockBean
private AuthenticationService authenticationService;

@MockBean
protected CustomUserDetailsService customUserDetailsService;

@MockBean
protected AuthenticationManager authenticationManager;

@Test
void register_success() throws Exception {
RegistrationRequest registrationRequest = new RegistrationRequest();
registrationRequest.setEmail(EMAIL);
registrationRequest.setPassword(PASSWORD);

User user = new User(EMAIL, PASSWORD);
user.setId(USER_ID);

when(authenticationService.register(any(RegistrationRequest.class))).thenReturn(user);

String json = objectMapper.writeValueAsString(registrationRequest);

mvc.perform(post("/api/authentication/register")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(header().string(HttpHeaders.LOCATION, endsWith("/api/users/" + USER_ID)));

ArgumentCaptor<RegistrationRequest> requestArgumentCaptor = ArgumentCaptor.forClass(RegistrationRequest.class);
verify(authenticationService, times(1)).register(requestArgumentCaptor.capture());

SoftAssertions.assertSoftly(softly -> {
assertThat(requestArgumentCaptor.getValue()).isNotNull();
assertThat(requestArgumentCaptor.getValue().getEmail()).isEqualTo(EMAIL);
assertThat(requestArgumentCaptor.getValue().getPassword()).isEqualTo(PASSWORD);
});
}

@Test
void register_duplicate_error() throws Exception {
RegistrationRequest registrationRequest = new RegistrationRequest();
registrationRequest.setEmail(EMAIL);
registrationRequest.setPassword(PASSWORD);

User user = new User(EMAIL, PASSWORD);
user.setId(USER_ID);

when(authenticationService.register(any(RegistrationRequest.class)))
.thenThrow(new NextDoorValidationException("user.duplicate.email", HttpStatus.CONFLICT));

String json = objectMapper.writeValueAsString(registrationRequest);

mvc.perform(post("/api/authentication/register")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isConflict())
.andExpect(jsonPath("$.i18nErrors[0].i18nErrorKey")
.value("user.duplicate.email"));
}

@Test
void login_success() throws Exception {
LoginRequest loginRequest = new LoginRequest();
loginRequest.setEmail(EMAIL);
loginRequest.setPassword(PASSWORD);

Authentication authentication = new UsernamePasswordAuthenticationToken(EMAIL, PASSWORD);
when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenReturn(authentication);

String json = objectMapper.writeValueAsString(loginRequest);

mvc.perform(post("/api/authentication/login")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

@Test
void login_badCredentials_error() throws Exception {
LoginRequest loginRequest = new LoginRequest();
loginRequest.setEmail(EMAIL);
loginRequest.setPassword(PASSWORD);

when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
.thenThrow(new BadCredentialsException("Invalid credentials"));

String json = objectMapper.writeValueAsString(loginRequest);

mvc.perform(post("/api/authentication/login")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.i18nErrors[0].i18nErrorKey")
.value("login.bad.credentials"));
}
}
Loading