test: add more unit and integration tests for the backend (#202)
* test: add server unit tests for controllers - partial issue #71 * test: add task controller unit tests issue #71 * docs: add cloud scripts to start services * docs: add sonar to backend issue #198 * docs: add sonar params * fix: russian text * test: add note controller test issue #71 * test: add auth service unit tests - wip issue #71 * test: add auth and home service unit tests issue #71
This commit is contained in:
@@ -63,6 +63,13 @@ jobs:
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Cache SonarQube Cloud packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -76,5 +83,14 @@ jobs:
|
||||
- name: Google Checkstyle
|
||||
run: cd server && ./mvnw --no-transfer-progress checkstyle:checkstyle -Dskip.checkstyle=false
|
||||
|
||||
#- name: All Tests
|
||||
# run: cd server && ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml
|
||||
- name: Sonar Analysis
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_SERVER }}
|
||||
run: cd server && ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=ricardo-campos-org_react-typescript-todolist_server
|
||||
-Dsonar.projectKey=ricardo-campos-org_react-typescript-todolist_server
|
||||
-Dsonar.coverage.jacoco.xmlReportPaths=target/coverage-reports/merged-test-report/jacoco.xml
|
||||
-Dsonar.exclusions=**/config/**,**/entity/**,**/exception/**,**/filter/**,**/**Builder*,**/RestExceptionEndpoint.*,**/JavaApiApiApplication.*
|
||||
|
||||
- name: All Tests
|
||||
run: cd server && ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml
|
||||
|
||||
@@ -114,6 +114,13 @@ jobs:
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Cache SonarQube Cloud packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -127,8 +134,17 @@ jobs:
|
||||
- name: Google Check-Style
|
||||
run: cd server && ./mvnw --no-transfer-progress checkstyle:checkstyle -Dskip.checkstyle=false
|
||||
|
||||
#- name: All Tests
|
||||
# run: cd server && ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml
|
||||
- name: Sonar Analysis
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_SERVER }}
|
||||
run: cd server && ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
|
||||
-Dsonar.projectKey=ricardo-campos-org_react-typescript-todolist_server
|
||||
-Dsonar.coverage.jacoco.xmlReportPaths=target/coverage-reports/merged-test-report/jacoco.xml
|
||||
-Dsonar.exclusions=**/config/**,**/entity/**,**/exception/**,**/filter/**,**/**Builder*,**/RestExceptionEndpoint.*,**/JavaApiApiApplication.*
|
||||
|
||||
- name: All Tests
|
||||
run: cd server && ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml
|
||||
|
||||
java-docker-build:
|
||||
name: Build Java Docker image
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('Portuguese Utils unit tests', () => {
|
||||
expect(translateServerResponse(keys[2], 'ru'))
|
||||
.toBe('Неправильный пароль: Пароль должен содержать хотя бы 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[3], 'ru'))
|
||||
.toBe('Электронная почти уже используется!');
|
||||
.toBe('Электронная почта уже используется!');
|
||||
expect(translateServerResponse(keys[4], 'ru'))
|
||||
.toBe('Доступ запрещен!');
|
||||
expect(translateServerResponse(keys[5], 'ru'))
|
||||
|
||||
@@ -97,7 +97,7 @@ export const serverResponsesTranslations: Record<string, string> = {
|
||||
BAD_PASSWORD_3_ru: 'Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 специальный символ.',
|
||||
BAD_PASSWORD_2_ru: 'Неправильный пароль: Пароль должен содержать как минимум 1 заглавную букву и 1 специальный символ.',
|
||||
BAD_PASSWORD_1_ru: 'Неправильный пароль: Пароль должен содержать хотя бы 1 специальный символ.',
|
||||
EMAIL_EXISTS_ru: 'Электронная почти уже используется!',
|
||||
EMAIL_EXISTS_ru: 'Электронная почта уже используется!',
|
||||
FORBIDDEN_ru: 'Доступ запрещен!',
|
||||
INTERNAL_ERROR_ru: 'Внутренняя ошибка сервера!',
|
||||
MAX_LOGIN_ATTEMPT_ru: 'Достигнут максимальный лимит попыток входа. Пожалуйста, подождите 30 минут',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
export $(cat .env | xargs)
|
||||
|
||||
docker run -d --rm \
|
||||
--name db \
|
||||
--network=host \
|
||||
-e POSTGRES_DB=$POSTGRES_DB \
|
||||
-e POSTGRES_USER=$POSTGRES_USER \
|
||||
-e POSTGRES_PASSWORD=$POSTGRES_PASSWORD \
|
||||
-e PGDATA=$PGDATA \
|
||||
-v ./data:/tmp \
|
||||
postgres:15.8-bookworm
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
|
||||
PR="$1"
|
||||
echo "PR: $PR"
|
||||
|
||||
echo "Getting env vars..."
|
||||
export $(cat .env | xargs)
|
||||
|
||||
docker run -d --rm \
|
||||
--name server \
|
||||
--network=host \
|
||||
-e POSTGRES_DB=$POSTGRES_DB \
|
||||
-e POSTGRES_USER=$POSTGRES_USER \
|
||||
-e POSTGRES_PASSWORD=$POSTGRES_PASSWORD \
|
||||
-e POSTGRES_PORT=$POSTGRES_PORT \
|
||||
-e POSTGRES_HOST=$POSTGRES_HOST \
|
||||
-e CORS_ALLOWED_ORIGINS=$CORS_ALLOWED_ORIGINS \
|
||||
-e SERVER_SERVLET_CONTEXT_PATH=$SERVER_SERVLET_CONTEXT_PATH \
|
||||
-e BUILD=ghcr.io/ricardo-campos-org/react-typescript-todolist/server:$PR \
|
||||
ghcr.io/ricardo-campos-org/react-typescript-todolist/server:$PR
|
||||
+4
-1
@@ -43,6 +43,8 @@
|
||||
<timestamp>${maven.build.timestamp}</timestamp>
|
||||
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
|
||||
<hibernate.version>6.6.2.Final</hibernate.version>
|
||||
<sonar.organization>ricardo-campos-org</sonar.organization>
|
||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
||||
</properties>
|
||||
|
||||
<!-- Profiles -->
|
||||
@@ -259,6 +261,7 @@
|
||||
<skip>${jacoco.skip}</skip>
|
||||
<excludes>
|
||||
<exclude>**/config/**</exclude>
|
||||
<exclude>**/entity/**</exclude>
|
||||
<exclude>**/exception/**</exclude>
|
||||
<exclude>**/filter/**</exclude>
|
||||
<exclude>**/*$*Builder*</exclude>
|
||||
@@ -350,7 +353,7 @@
|
||||
<limit>
|
||||
<counter>LINE</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>10%</minimum>
|
||||
<minimum>90%</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
|
||||
@@ -90,7 +90,7 @@ public class NoteController {
|
||||
description = "Note not found",
|
||||
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||
})
|
||||
public ResponseEntity<NoteResponse> putNote(
|
||||
public ResponseEntity<NoteResponse> patchNote(
|
||||
@Parameter(
|
||||
name = "id",
|
||||
in = ParameterIn.PATH,
|
||||
@@ -171,7 +171,7 @@ public class NoteController {
|
||||
description = "Note not found",
|
||||
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||
})
|
||||
public ResponseEntity<Void> deleteNotes(
|
||||
public ResponseEntity<Void> deleteNote(
|
||||
@Parameter(
|
||||
name = "id",
|
||||
in = ParameterIn.PATH,
|
||||
|
||||
@@ -48,7 +48,7 @@ public class TaskController {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "Tasks successfully retrieved",
|
||||
description = "Return an array containing found Tasks, or empty array otherwise.",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
@@ -91,7 +91,7 @@ public class TaskController {
|
||||
description = "Task not found",
|
||||
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||
})
|
||||
public ResponseEntity<TaskResponse> putTask(
|
||||
public ResponseEntity<TaskResponse> patchTask(
|
||||
@Parameter(
|
||||
name = "id",
|
||||
in = ParameterIn.PATH,
|
||||
@@ -172,7 +172,7 @@ public class TaskController {
|
||||
description = "Task not found",
|
||||
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||
})
|
||||
public ResponseEntity<Void> deleteTasks(
|
||||
public ResponseEntity<Void> deleteTask(
|
||||
@Parameter(
|
||||
name = "id",
|
||||
in = ParameterIn.PATH,
|
||||
|
||||
@@ -41,10 +41,6 @@ public class UserSessionController {
|
||||
@ApiResponse(
|
||||
responseCode = "403",
|
||||
description = "Forbidden. Access Denied",
|
||||
content = @Content(schema = @Schema(implementation = Void.class))),
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "User not found",
|
||||
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||
})
|
||||
public JwtAuthenticationResponse refresh() {
|
||||
@@ -65,10 +61,6 @@ public class UserSessionController {
|
||||
@ApiResponse(
|
||||
responseCode = "403",
|
||||
description = "Forbidden. Access Denied",
|
||||
content = @Content(schema = @Schema(implementation = Void.class))),
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "User not found",
|
||||
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||
})
|
||||
public UserResponse deteleAccount() {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package br.com.tasknoteapp.server.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/** This record represents a task request to be created. */
|
||||
@Schema(description = "Task request to be created.")
|
||||
public record TaskRequest(
|
||||
@Schema(description = "Task description.") @NotNull String description,
|
||||
@Schema(description = "Task description.") @NotNull @NotEmpty String description,
|
||||
@Schema(description = "Task urls. Optional.") List<String> urls,
|
||||
@Schema(description = "Due date. Optional.") String dueDate,
|
||||
@Schema(description = "Define high priority. Optional.") Boolean highPriority) {}
|
||||
|
||||
@@ -3,6 +3,8 @@ package br.com.tasknoteapp.server.response;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.UserEntity;
|
||||
|
||||
/** This record represents a User Response object. */
|
||||
@Schema(description = "This record represents a User Response object.")
|
||||
public record UserResponse(
|
||||
@@ -14,4 +16,21 @@ public record UserResponse(
|
||||
@Schema(
|
||||
description = "The inactivated date and time of the user",
|
||||
example = "2023-01-01T00:00:00")
|
||||
LocalDateTime inactivatedAt) {}
|
||||
LocalDateTime inactivatedAt) {
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link UserResponse} instance from a {@link UserEntity}.
|
||||
*
|
||||
* @param user The user entity instance with user info to be used as source.
|
||||
* @return UserResponse instance.
|
||||
*/
|
||||
public static UserResponse fromEntity(UserEntity user) {
|
||||
return new UserResponse(
|
||||
user.getId(),
|
||||
user.getEmail(),
|
||||
user.getAdmin(),
|
||||
user.getCreatedAt(),
|
||||
user.getInactivatedAt());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,37 +145,36 @@ public class AuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered users.
|
||||
* Get all registered users. Only allowed for admin users.
|
||||
*
|
||||
* @return List of UserEntity.
|
||||
* @throws UserForbiddenException when the user has no permission.
|
||||
* @throws UserForbiddenException when the user has no permissions.
|
||||
*/
|
||||
public List<UserResponse> getAllUsers() {
|
||||
Optional<String> currentUserEmail = authUtil.getCurrentUserEmail();
|
||||
String email = currentUserEmail.orElseThrow();
|
||||
UserEntity currentUser = findByEmail(email).orElseThrow();
|
||||
|
||||
log.info("Getting all users to user {}", currentUser.getId());
|
||||
|
||||
if (!currentUser.getAdmin() || !currentUser.getEmail().equals("ricardompcampos@gmail.com")) {
|
||||
log.info("User not allowed!");
|
||||
if (currentUserEmail.isEmpty()) {
|
||||
log.error("Unable to get current user from the request");
|
||||
throw new UserForbiddenException();
|
||||
}
|
||||
|
||||
List<UserEntity> users = userRepository.findAll();
|
||||
List<UserResponse> usersResponse = new ArrayList<>();
|
||||
for (UserEntity user : users) {
|
||||
UserResponse userResponse =
|
||||
new UserResponse(
|
||||
user.getId(),
|
||||
user.getEmail(),
|
||||
user.getAdmin(),
|
||||
user.getCreatedAt(),
|
||||
user.getInactivatedAt());
|
||||
usersResponse.add(userResponse);
|
||||
Optional<UserEntity> currentUserOpt = findByEmail(currentUserEmail.get());
|
||||
if (currentUserOpt.isEmpty()) {
|
||||
log.error("Unable to find user by email with value: {}", currentUserEmail.get());
|
||||
throw new UserForbiddenException();
|
||||
}
|
||||
|
||||
log.info("{} Users found!", usersResponse.size());
|
||||
UserEntity currentUser = currentUserOpt.get();
|
||||
if (!currentUser.getAdmin()) {
|
||||
log.warn("User {} not allowed to list users.", currentUser.getId());
|
||||
throw new UserForbiddenException();
|
||||
}
|
||||
|
||||
log.info("Getting all users to user {}", currentUser.getId());
|
||||
|
||||
List<UserEntity> users = userRepository.findAll();
|
||||
List<UserResponse> usersResponse = new ArrayList<>(users.size());
|
||||
users.forEach((u) -> usersResponse.add(UserResponse.fromEntity(u)));
|
||||
log.info("{} user(s) found!", usersResponse.size());
|
||||
|
||||
return usersResponse;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ public class TaskService {
|
||||
*/
|
||||
public List<TaskResponse> getAllTasks() {
|
||||
UserEntity user = getCurrentUser();
|
||||
|
||||
log.info("Get all tasks to user {}", user.getId());
|
||||
|
||||
List<TaskEntity> tasks = taskRepository.findAllByUser_id(user.getId());
|
||||
@@ -177,7 +176,7 @@ public class TaskService {
|
||||
* Search for tasks in the database given a search term.
|
||||
*
|
||||
* @param searchTerm The term to be used for the search.
|
||||
* @return {@link List} of {@link TaskResponse} with ound records or an empty list.
|
||||
* @return {@link List} of {@link TaskResponse} with found records or an empty list.
|
||||
*/
|
||||
public List<TaskResponse> searchTasks(String searchTerm) {
|
||||
UserEntity user = getCurrentUser();
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
package br.com.tasknoteapp.server.controller;
|
||||
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.NoteEntity;
|
||||
import br.com.tasknoteapp.server.exception.NoteNotFoundException;
|
||||
import br.com.tasknoteapp.server.request.NotePatchRequest;
|
||||
import br.com.tasknoteapp.server.request.NoteRequest;
|
||||
import br.com.tasknoteapp.server.response.NoteResponse;
|
||||
import br.com.tasknoteapp.server.response.NoteUrlResponse;
|
||||
import br.com.tasknoteapp.server.service.NoteService;
|
||||
import java.util.List;
|
||||
import org.hamcrest.Matchers;
|
||||
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.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class NoteControllerTest {
|
||||
|
||||
@Autowired private MockMvc mockMvc;
|
||||
|
||||
@MockBean private NoteService noteService;
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all notes with some notes found should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void getAllNotes_notesFound_shouldSucceed() throws Exception {
|
||||
NoteUrlResponse noteUrl = new NoteUrlResponse(111L, "https://test.com");
|
||||
NoteResponse note = new NoteResponse(111L, "title", "description", List.of(noteUrl));
|
||||
|
||||
when(noteService.getAllNotes()).thenReturn(List.of(note));
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/notes")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[0].id").value(note.id()))
|
||||
.andExpect(jsonPath("$[0].title").value(note.title()))
|
||||
.andExpect(jsonPath("$[0].description").value(note.description()))
|
||||
.andExpect(jsonPath("$[0].urls[0].id").value(noteUrl.id()))
|
||||
.andExpect(jsonPath("$[0].urls[0].url").value(noteUrl.url()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all notes without notes found should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void getAllNotes_noNotesFound_shouldSucceed() throws Exception {
|
||||
when(noteService.getAllNotes()).thenReturn(List.of());
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/notes")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", Matchers.empty()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all notes with 403 forbidden request should fail")
|
||||
void getAllNotes_forbidden_shouldFail() throws Exception {
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/notes")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Patch a note via patch request happy path should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void patchNote_happyPath_shouldSucceed() throws Exception {
|
||||
Long noteId = 123L;
|
||||
NotePatchRequest patchRequest = new NotePatchRequest("New title", "New description", List.of());
|
||||
|
||||
NoteResponse response =
|
||||
new NoteResponse(noteId, patchRequest.title(), patchRequest.description(), List.of());
|
||||
|
||||
when(noteService.patchNote(noteId, patchRequest)).thenReturn(response);
|
||||
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"title": "New title",
|
||||
"description": "New description",
|
||||
"urls": []
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
patch("/rest/notes/{id}", noteId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.id").value(response.id()))
|
||||
.andExpect(jsonPath("$.title").value(response.title()))
|
||||
.andExpect(jsonPath("$.description").value(response.description()))
|
||||
.andExpect(jsonPath("$.urls", Matchers.empty()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Patch a note via patch request with not found id should fail")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void patchNote_notFound_shouldFail() throws Exception {
|
||||
Long noteId = 123L;
|
||||
NotePatchRequest patchRequest = new NotePatchRequest("New title", "New description", List.of());
|
||||
|
||||
when(noteService.patchNote(noteId, patchRequest)).thenThrow(new NoteNotFoundException());
|
||||
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"title": "New title",
|
||||
"description": "New description",
|
||||
"urls": []
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
patch("/rest/notes/{id}", noteId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isNotFound())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Patch a note via patch request with 403 forbidden exception should fail")
|
||||
void patchNote_forbidden_shouldFail() throws Exception {
|
||||
Long noteId = 123L;
|
||||
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"title": "New title",
|
||||
"description": "New description",
|
||||
"urls": []
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
patch("/rest/notes/{id}", noteId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Post create note happy path should succeed and return 201")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void postNotes_happyPath_shoundSucceed() throws Exception {
|
||||
NoteRequest request = new NoteRequest("Title", "Description", List.of());
|
||||
|
||||
NoteEntity entity = new NoteEntity();
|
||||
entity.setId(1L);
|
||||
entity.setTitle(request.title());
|
||||
entity.setDescription(request.description());
|
||||
|
||||
when(noteService.createNote(request)).thenReturn(entity);
|
||||
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"title": "Title",
|
||||
"description": "Description",
|
||||
"urls": []
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/rest/notes")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.id").value(entity.getId()))
|
||||
.andExpect(jsonPath("$.title").value(entity.getTitle()))
|
||||
.andExpect(jsonPath("$.description").value(entity.getDescription()))
|
||||
.andExpect(jsonPath("$.urls", Matchers.empty()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Post create note with missing information should fail with 400 bad request")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void postNotes_missingInformation_shouldFail() throws Exception {
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"description": "Description"
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/rest/notes")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Post create note with 403 forbidden request should fail")
|
||||
void postNotes_forbidden_shouldFail() throws Exception {
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"description": "Forbidden"
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/rest/notes")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete note request happy path should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void deleteNote_happyPath_shouldSucceed() throws Exception {
|
||||
final Long noteId = 453L;
|
||||
|
||||
doNothing().when(noteService).deleteNote(noteId);
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
delete("/rest/notes/{id}", noteId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isNoContent())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete note with 403 request forbidden should fail")
|
||||
void deleteNote_forbidden_shouldFail() throws Exception {
|
||||
final Long noteId = 453L;
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
delete("/rest/notes/{id}", noteId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete note with 404 request not found should fail")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void deleteNote_notFound_shouldFail() throws Exception {
|
||||
final Long noteId = 453L;
|
||||
|
||||
doThrow(new NoteNotFoundException()).when(noteService).deleteNote(noteId);
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
delete("/rest/notes/{id}", noteId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isNotFound())
|
||||
.andReturn();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
package br.com.tasknoteapp.server.controller;
|
||||
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.TaskEntity;
|
||||
import br.com.tasknoteapp.server.exception.TaskNotFoundException;
|
||||
import br.com.tasknoteapp.server.request.TaskPatchRequest;
|
||||
import br.com.tasknoteapp.server.request.TaskRequest;
|
||||
import br.com.tasknoteapp.server.response.TaskResponse;
|
||||
import br.com.tasknoteapp.server.response.TaskUrlResponse;
|
||||
import br.com.tasknoteapp.server.service.TaskService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import org.hamcrest.Matchers;
|
||||
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.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class TaskControllerTest {
|
||||
|
||||
@Autowired private MockMvc mockMvc;
|
||||
|
||||
@MockBean private TaskService taskService;
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all tasks with some tasks found should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void getAllTasks_tasksFound_shouldSucceed() throws Exception {
|
||||
TaskUrlResponse taskUrl = new TaskUrlResponse(1L, "http://test.com");
|
||||
TaskResponse taskResponse =
|
||||
new TaskResponse(1L, "Desc", false, true, null, null, "Moments ago", List.of(taskUrl));
|
||||
when(taskService.getAllTasks()).thenReturn(List.of(taskResponse));
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/tasks")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[0].id").value(taskResponse.id()))
|
||||
.andExpect(jsonPath("$[0].description").value(taskResponse.description()))
|
||||
.andExpect(jsonPath("$[0].done", Matchers.is(false)))
|
||||
.andExpect(jsonPath("$[0].highPriority", Matchers.is(true)))
|
||||
.andExpect(jsonPath("$[0].dueDate", Matchers.nullValue()))
|
||||
.andExpect(jsonPath("$[0].dueDateFmt", Matchers.nullValue()))
|
||||
.andExpect(jsonPath("$[0].lastUpdate").value("Moments ago"))
|
||||
.andExpect(jsonPath("$[0].urls[0].id").value(taskUrl.id()))
|
||||
.andExpect(jsonPath("$[0].urls[0].url").value(taskUrl.url()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all tasks with no tasks found should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void getAllTasks_noTasksFound_shouldSucceed() throws Exception {
|
||||
when(taskService.getAllTasks()).thenReturn(List.of());
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/tasks")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", Matchers.empty()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all tasks with 403 forbidden request should fail")
|
||||
void getAllTasks_forbidden_shouldFail() throws Exception {
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/tasks")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Patch a task via patch request happy path should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void patchTask_happyPath_shouldSucceed() throws Exception {
|
||||
Long taskId = 111L;
|
||||
TaskPatchRequest patchRequest =
|
||||
new TaskPatchRequest("Description patched", false, List.of(), null, true);
|
||||
|
||||
TaskResponse taskResponse =
|
||||
new TaskResponse(
|
||||
taskId, "Description patched", false, true, null, null, "Moments ago", List.of());
|
||||
when(taskService.patchTask(taskId, patchRequest)).thenReturn(taskResponse);
|
||||
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"description": "Description patched",
|
||||
"done": false,
|
||||
"urls": [],
|
||||
"highPriority": true
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
patch("/rest/tasks/{id}", taskId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.id").value(taskId))
|
||||
.andExpect(jsonPath("$.description").value(taskResponse.description()))
|
||||
.andExpect(jsonPath("$.done").value(taskResponse.done()))
|
||||
.andExpect(jsonPath("$.highPriority").value(taskResponse.highPriority()))
|
||||
.andExpect(jsonPath("$.dueDate", Matchers.nullValue()))
|
||||
.andExpect(jsonPath("$.dueDateFmt", Matchers.nullValue()))
|
||||
.andExpect(jsonPath("$.lastUpdate").value("Moments ago"))
|
||||
.andExpect(jsonPath("$.urls", Matchers.empty()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Patch a task via patch request with not found id should fail")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void patchTask_notFound_shouldFail() throws Exception {
|
||||
Long taskId = 111L;
|
||||
TaskPatchRequest patchRequest =
|
||||
new TaskPatchRequest("Description patched", false, List.of(), null, true);
|
||||
|
||||
when(taskService.patchTask(taskId, patchRequest)).thenThrow(new TaskNotFoundException());
|
||||
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"description": "Description patched",
|
||||
"done": false,
|
||||
"urls": [],
|
||||
"highPriority": true
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
patch("/rest/tasks/{id}", taskId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isNotFound())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Patch a task via patch request with 403 forbidden exception")
|
||||
void patchTask_forbidden_shouldFail() throws Exception {
|
||||
Long taskId = 111L;
|
||||
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"description": "Description patched",
|
||||
"done": false,
|
||||
"urls": [],
|
||||
"highPriority": true
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
patch("/rest/tasks/{id}", taskId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Post create task happy path should succeed and return 201")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void postTasks_happyPath_shouldSucceed() throws Exception {
|
||||
TaskRequest request = new TaskRequest("Test task", List.of(), null, true);
|
||||
|
||||
TaskEntity entity = new TaskEntity();
|
||||
entity.setId(222L);
|
||||
entity.setDescription(request.description());
|
||||
entity.setDone(false);
|
||||
entity.setUrls(List.of());
|
||||
entity.setLastUpdate(LocalDateTime.now());
|
||||
entity.setDueDate(null);
|
||||
entity.setHighPriority(request.highPriority());
|
||||
when(taskService.createTask(request)).thenReturn(entity);
|
||||
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"description": "Test task",
|
||||
"done": false,
|
||||
"urls": [],
|
||||
"highPriority": true
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/rest/tasks")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.id").value(entity.getId()))
|
||||
.andExpect(jsonPath("$.description").value(request.description()))
|
||||
.andExpect(jsonPath("$.done", Matchers.is(false)))
|
||||
.andExpect(jsonPath("$.highPriority", Matchers.is(request.highPriority())))
|
||||
.andExpect(jsonPath("$.dueDate", Matchers.nullValue()))
|
||||
.andExpect(jsonPath("$.dueDateFmt", Matchers.nullValue()))
|
||||
.andExpect(jsonPath("$.lastUpdate").value("Moments ago"))
|
||||
.andExpect(jsonPath("$.urls", Matchers.empty()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Post create task with missing information should fail with 400 bad request")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void postTasks_missingInformation_shouldFail() throws Exception {
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"description": ""
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/rest/tasks")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Post create task with 403 forbidden request should fail")
|
||||
void postTasks_forbidden_shouldFail() throws Exception {
|
||||
final String payloadJson =
|
||||
"""
|
||||
{
|
||||
"description": "Forbidden"
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/rest/tasks")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete task request happy path should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void deleteTask_happyPath_shouldSucceed() throws Exception {
|
||||
final Long taskId = 333L;
|
||||
|
||||
doNothing().when(taskService).deleteTask(taskId);
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
delete("/rest/tasks/{id}", taskId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isNoContent())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete task with 403 request forbidden should fail")
|
||||
void deleteTask_forbidden_shouldFail() throws Exception {
|
||||
final Long taskId = 533L;
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
delete("/rest/tasks/{id}", taskId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete task with 404 request not found should fail")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void deleteTask_notFound_shouldFail() throws Exception {
|
||||
final Long taskId = 433L;
|
||||
|
||||
doThrow(new TaskNotFoundException()).when(taskService).deleteTask(taskId);
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
delete("/rest/tasks/{id}", taskId)
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isNotFound())
|
||||
.andReturn();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package br.com.tasknoteapp.server.controller;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
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 br.com.tasknoteapp.server.response.UserResponse;
|
||||
import br.com.tasknoteapp.server.service.AuthService;
|
||||
import java.util.List;
|
||||
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.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class UserControllerTest {
|
||||
|
||||
@Autowired private MockMvc mockMvc;
|
||||
|
||||
@MockBean private AuthService authService;
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all users happy path should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void getAllUsers_happyPath_shouldSucceed() throws Exception {
|
||||
UserResponse userResponse = new UserResponse(1L, "email@test.com", false, null, null);
|
||||
when(authService.getAllUsers()).thenReturn(List.of(userResponse));
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/users")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[0].userId").value(userResponse.userId()))
|
||||
.andExpect(jsonPath("$[0].email").value(userResponse.email()))
|
||||
.andExpect(jsonPath("$[0].admin").value(userResponse.admin()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all users with 403 forbidden request should fail")
|
||||
void getAllUsers_forbidden_shouldFail() throws Exception {
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/users")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
}
|
||||
+45
@@ -3,10 +3,12 @@ package br.com.tasknoteapp.server.controller;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import br.com.tasknoteapp.server.response.JwtAuthenticationResponse;
|
||||
import br.com.tasknoteapp.server.response.UserResponse;
|
||||
import br.com.tasknoteapp.server.service.UserSessionService;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -43,4 +45,47 @@ class UserSessionControllerTest {
|
||||
.andExpect(jsonPath("$.token").value(authResponse.token()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Refresh with 403 forbidden request should fail")
|
||||
void refresh_forbidden_shouldFail() throws Exception {
|
||||
mockMvc
|
||||
.perform(
|
||||
get("/rest/user-sessions/refresh")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete account happy path should succeed")
|
||||
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
|
||||
void deteleAccount_happyPath_shouldSucceed() throws Exception {
|
||||
UserResponse response = new UserResponse(1L, "email@test.com", false, null, null);
|
||||
when(userSessionService.deleteCurrentUserAccount()).thenReturn(response);
|
||||
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/rest/user-sessions/delete-account")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete account with 403 forbidden request should fail")
|
||||
void deteleAccount_forbidden_shouldFail() throws Exception {
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/rest/user-sessions/delete-account")
|
||||
.with(csrf().asHeader())
|
||||
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
package br.com.tasknoteapp.server.service;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.UserEntity;
|
||||
import br.com.tasknoteapp.server.entity.UserPwdLimitEntity;
|
||||
import br.com.tasknoteapp.server.exception.BadPasswordException;
|
||||
import br.com.tasknoteapp.server.exception.MaxLoginLimitAttemptException;
|
||||
import br.com.tasknoteapp.server.exception.UserAlreadyExistsException;
|
||||
import br.com.tasknoteapp.server.exception.UserForbiddenException;
|
||||
import br.com.tasknoteapp.server.exception.UserNotFoundException;
|
||||
import br.com.tasknoteapp.server.exception.WrongUserOrPasswordException;
|
||||
import br.com.tasknoteapp.server.repository.UserPwdLimitRepository;
|
||||
import br.com.tasknoteapp.server.repository.UserRepository;
|
||||
import br.com.tasknoteapp.server.request.LoginRequest;
|
||||
import br.com.tasknoteapp.server.response.UserResponse;
|
||||
import br.com.tasknoteapp.server.util.AuthUtil;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
class AuthServiceTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
|
||||
@Mock private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Mock private JwtService jwtService;
|
||||
|
||||
@Mock private AuthenticationManager authenticationManager;
|
||||
|
||||
@Mock private AuthUtil authUtil;
|
||||
|
||||
@Mock private UserPwdLimitRepository userPwdLimitRepository;
|
||||
|
||||
private AuthService authService;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
authService =
|
||||
new AuthService(
|
||||
userRepository,
|
||||
passwordEncoder,
|
||||
jwtService,
|
||||
authenticationManager,
|
||||
authUtil,
|
||||
userPwdLimitRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SignUp new user happy path should succeed")
|
||||
void signUpNewUser_happyPath_shouldSucceed() {
|
||||
LoginRequest request = new LoginRequest("email@domain.com", "123456@abcde!");
|
||||
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.empty());
|
||||
when(authUtil.validatePassword(request.password())).thenReturn(Optional.empty());
|
||||
|
||||
UserEntity entity = new UserEntity();
|
||||
entity.setId(3L);
|
||||
entity.setEmail(request.email());
|
||||
|
||||
when(userRepository.save(any())).thenReturn(entity);
|
||||
when(jwtService.generateToken(request.email())).thenReturn("a1b2c3");
|
||||
|
||||
String token = authService.signUpNewUser(request);
|
||||
|
||||
Assertions.assertNotNull(token);
|
||||
Assertions.assertFalse(token.isBlank());
|
||||
Assertions.assertEquals("a1b2c3", token);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SignUp new user with existing email should fail")
|
||||
void signUpNewUser_emailExists_shouldFail() {
|
||||
LoginRequest request = new LoginRequest("email@domain.com", "123456@abcde!");
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
|
||||
|
||||
Assertions.assertThrows(
|
||||
UserAlreadyExistsException.class,
|
||||
() -> {
|
||||
authService.signUpNewUser(request);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SignUp new user with bad password should fail")
|
||||
void signUpNewUser_badPassword_shouldFail() {
|
||||
LoginRequest request = new LoginRequest("email@domain.com", "123456");
|
||||
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.empty());
|
||||
when(authUtil.validatePassword(request.password())).thenReturn(Optional.of("Bad password"));
|
||||
|
||||
Assertions.assertThrows(
|
||||
BadPasswordException.class,
|
||||
() -> {
|
||||
authService.signUpNewUser(request);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Find user by email happy path should succeed")
|
||||
void findByEmail_happyPath_shouldSucceed() {
|
||||
String email = "user@email.com";
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setEmail(email);
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing));
|
||||
|
||||
Optional<UserEntity> userOp = authService.findByEmail(email);
|
||||
|
||||
Assertions.assertTrue(userOp.isPresent());
|
||||
Assertions.assertEquals(email, userOp.get().getEmail());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Find user by email not found should succeed")
|
||||
void findByEmail_notFound_shouldSucceed() {
|
||||
String email = "user@email.com";
|
||||
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.empty());
|
||||
|
||||
Optional<UserEntity> userOp = authService.findByEmail(email);
|
||||
|
||||
Assertions.assertTrue(userOp.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Load user by username happy path should succeed")
|
||||
void loadUserByUsername_happyPath_shouldSucceed() {
|
||||
String email = "user@domain.com";
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setEmail(email);
|
||||
existing.setPassword(email + "123");
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing));
|
||||
|
||||
User user = authService.loadUserByUsername(email);
|
||||
|
||||
Assertions.assertNotNull(user);
|
||||
Assertions.assertEquals(email, user.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Load user by username not found should fail")
|
||||
void loadUserByUsername_notFound_shouldFail() {
|
||||
String email = "user@domain.com";
|
||||
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.empty());
|
||||
|
||||
Assertions.assertThrows(
|
||||
UserNotFoundException.class,
|
||||
() -> {
|
||||
authService.loadUserByUsername(email);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SignIn user happy path should succeed")
|
||||
void signInUser_happyPath_shouldSucceed() {
|
||||
LoginRequest request = new LoginRequest("email@domain.com", "123456");
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setId(919L);
|
||||
existing.setEmail(request.email());
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
|
||||
|
||||
Sort sort = Sort.by(Direction.DESC, "whenHappened");
|
||||
when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort)).thenReturn(List.of());
|
||||
when(authenticationManager.authenticate(any())).thenReturn(null);
|
||||
when(jwtService.generateToken(request.email())).thenReturn("a1b2c3");
|
||||
|
||||
doNothing().when(userPwdLimitRepository).deleteAllForUser(existing.getId());
|
||||
|
||||
String token = authService.signInUser(request);
|
||||
|
||||
Assertions.assertNotNull(token);
|
||||
Assertions.assertEquals("a1b2c3", token);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SignIn wrong user or password should fail")
|
||||
void signInUser_wrongUserOrPassword_shouldFail() {
|
||||
LoginRequest request = new LoginRequest("email@domain.com", "123456");
|
||||
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.empty());
|
||||
|
||||
Assertions.assertThrows(
|
||||
WrongUserOrPasswordException.class,
|
||||
() -> {
|
||||
authService.signInUser(request);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SignIn max login attempt should fail")
|
||||
void signInUser_maxLoginAttempt_shouldFail() {
|
||||
LoginRequest request = new LoginRequest("email@domain.com", "123456");
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setId(919L);
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
|
||||
|
||||
UserPwdLimitEntity limit1 = new UserPwdLimitEntity();
|
||||
limit1.setWhenHappened(LocalDateTime.now().minusMinutes(1));
|
||||
UserPwdLimitEntity limit2 = new UserPwdLimitEntity();
|
||||
UserPwdLimitEntity limit3 = new UserPwdLimitEntity();
|
||||
Sort sort = Sort.by(Direction.DESC, "whenHappened");
|
||||
when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort))
|
||||
.thenReturn(List.of(limit1, limit2, limit3));
|
||||
|
||||
Assertions.assertThrows(
|
||||
MaxLoginLimitAttemptException.class,
|
||||
() -> {
|
||||
authService.signInUser(request);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SignIn bad credentials should fail")
|
||||
void signInUser_badCredentials_shouldFail() {
|
||||
LoginRequest request = new LoginRequest("email@domain.com", "123456");
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setId(919L);
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
|
||||
|
||||
Sort sort = Sort.by(Direction.DESC, "whenHappened");
|
||||
when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort)).thenReturn(List.of());
|
||||
when(authenticationManager.authenticate(any())).thenThrow(new BadCredentialsException("Wrong"));
|
||||
|
||||
String token = authService.signInUser(request);
|
||||
|
||||
Assertions.assertNull(token);
|
||||
verify(userPwdLimitRepository, times(1)).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all users happy path should succeed")
|
||||
void getAllUsers_happyPath_shouldSucceed() {
|
||||
String email = "user@domain.com";
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email));
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setId(919L);
|
||||
existing.setEmail(email);
|
||||
existing.setAdmin(true);
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing));
|
||||
|
||||
UserEntity user1 = new UserEntity();
|
||||
user1.setEmail("user1@domain.com");
|
||||
UserEntity user2 = new UserEntity();
|
||||
user2.setEmail("user2@domain.com");
|
||||
when(userRepository.findAll()).thenReturn(List.of(user1, user2));
|
||||
|
||||
List<UserResponse> users = authService.getAllUsers();
|
||||
|
||||
Assertions.assertNotNull(users);
|
||||
Assertions.assertFalse(users.isEmpty());
|
||||
Assertions.assertEquals(user1.getEmail(), users.get(0).email());
|
||||
Assertions.assertEquals(user2.getEmail(), users.get(1).email());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all users no current user should fail")
|
||||
void getAllUsers_noCurrentUser_shouldFail() {
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.empty());
|
||||
|
||||
Assertions.assertThrows(UserForbiddenException.class, () -> {
|
||||
authService.getAllUsers();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all users user not found should fail")
|
||||
void getAllUsers_userNotFound_shouldFail() {
|
||||
String email = "user@domain.com";
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email));
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.empty());
|
||||
|
||||
Assertions.assertThrows(UserForbiddenException.class, () -> {
|
||||
authService.getAllUsers();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Get all users user not admin should fail")
|
||||
void getAllUsers_userNotAdmin_shouldFail() {
|
||||
String email = "user@domain.com";
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email));
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setId(919L);
|
||||
existing.setEmail(email);
|
||||
existing.setAdmin(false);
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing));
|
||||
|
||||
Assertions.assertThrows(UserForbiddenException.class, () -> {
|
||||
authService.getAllUsers();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Refresh current user token happy path should succeed")
|
||||
void refreshCurrentUserToken_happyPath_shouldSucceed() {
|
||||
String email = "user@domain.com";
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email));
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setId(919L);
|
||||
existing.setEmail(email);
|
||||
existing.setAdmin(false);
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing));
|
||||
|
||||
when(jwtService.generateToken(email)).thenReturn("a1b2c3");
|
||||
|
||||
String token = authService.refreshCurrentUserToken();
|
||||
|
||||
Assertions.assertNotNull(token);
|
||||
Assertions.assertEquals("a1b2c3", token);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Delete user account happy path should succeed")
|
||||
void deleteUserAccount_happyPath_shouldSucceed() {
|
||||
String email = "user@domain.com";
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email));
|
||||
|
||||
UserEntity existing = new UserEntity();
|
||||
existing.setId(919L);
|
||||
existing.setEmail(email);
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing));
|
||||
doNothing().when(userPwdLimitRepository).deleteAllForUser(existing.getId());
|
||||
doNothing().when(userRepository).delete(existing);
|
||||
|
||||
UserResponse response = authService.deleteUserAccount();
|
||||
|
||||
Assertions.assertNotNull(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package br.com.tasknoteapp.server.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import br.com.tasknoteapp.server.response.NoteResponse;
|
||||
import br.com.tasknoteapp.server.response.SearchResponse;
|
||||
import br.com.tasknoteapp.server.response.SummaryResponse;
|
||||
import br.com.tasknoteapp.server.response.TaskResponse;
|
||||
import java.util.List;
|
||||
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;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HomeServiceTest {
|
||||
|
||||
@Mock private TaskService taskService;
|
||||
|
||||
@Mock private NoteService noteService;
|
||||
|
||||
private HomeService homeService;
|
||||
|
||||
private List<TaskResponse> tasks;
|
||||
private List<NoteResponse> notes;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
homeService = new HomeService(taskService, noteService);
|
||||
|
||||
TaskResponse task1 = new TaskResponse(2L, "Task 1", false, false, null, null, null, List.of());
|
||||
TaskResponse task2 = new TaskResponse(3L, "Task 2", false, false, null, null, null, List.of());
|
||||
tasks = List.of(task1, task2);
|
||||
|
||||
NoteResponse note1 = new NoteResponse(453L, "Note 1", "desc", List.of());
|
||||
NoteResponse note2 = new NoteResponse(455L, "Note 2", "desc", List.of());
|
||||
notes = List.of(note1, note2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSummary_shouldReturnSummaryResponse() {
|
||||
when(taskService.getAllTasks()).thenReturn(tasks);
|
||||
when(noteService.getAllNotes()).thenReturn(notes);
|
||||
|
||||
SummaryResponse summary = homeService.getSummary();
|
||||
|
||||
assertEquals(2, summary.pendingTaskCount());
|
||||
assertEquals(0, summary.doneTaskCount());
|
||||
assertEquals(2, summary.notesCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_shouldReturnSearchResponse() {
|
||||
String term = "note";
|
||||
when(taskService.searchTasks(term)).thenReturn(List.of());
|
||||
when(noteService.searchNotes(term)).thenReturn(notes);
|
||||
|
||||
SearchResponse searchResponse = homeService.search(term);
|
||||
|
||||
assertEquals(0, searchResponse.tasks().size());
|
||||
assertEquals(2, searchResponse.notes().size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user