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:
2024-12-27 16:19:31 -03:00
committed by GitHub
parent 1cd2d248ac
commit a1098dad03
20 changed files with 1307 additions and 45 deletions
+18 -2
View File
@@ -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
+18 -2
View File
@@ -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'))
+1 -1
View File
@@ -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 минут',
+13
View File
@@ -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
+21
View File
@@ -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
View File
@@ -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();
}
}
@@ -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());
}
}