fix: loggin issues (#319)

* fix: loggin issues

closes #302

feat: improve build number in docker image

* test: fix integration test for the taskController

* test: add unit test to taskService

* test: add more unit tests to taskService

* test: add delete unit tests for taskService

* test: add patch unit tests for taskService
This commit is contained in:
2025-03-02 18:45:36 -03:00
committed by GitHub
parent 3d94692086
commit 86b66b803d
9 changed files with 250 additions and 16 deletions
+12 -2
View File
@@ -86,6 +86,10 @@ jobs:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d-%H%M%S')" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v6
@@ -93,7 +97,8 @@ jobs:
push: true
context: ./client
tags: ghcr.io/ricardo-campos-org/react-typescript-todolist/client:${{ github.event.number }}
build-args: VITE_BUILD=${{ github.event.number }}
build-args: |
BUILD=v${{ github.event.number }}-${{ github.run_id }}-${{ steps.date.outputs.date }}
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -164,6 +169,10 @@ jobs:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d-%H%M%S')" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v6
@@ -171,6 +180,7 @@ jobs:
push: true
context: ./server
tags: ghcr.io/ricardo-campos-org/react-typescript-todolist/server:${{ github.event.number }}
build-args: BUILD=${{ github.event.number }}
build-args: |
BUILD=v${{ github.event.number }}-${{ github.run_id }}-${{ steps.date.outputs.date }}
cache-from: type=gha
cache-to: type=gha,mode=max
+8
View File
@@ -6,8 +6,16 @@
### TaskNote API
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=br.com.tasknoteapp%3Aserver&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=br.com.tasknoteapp%3Aserver)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ricardo-campos-org_react-typescript-todolist_server&metric=coverage)](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_server)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=br.com.tasknoteapp%3Aserver&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=br.com.tasknoteapp%3Aserver)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=br.com.tasknoteapp%3Aserver&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=br.com.tasknoteapp%3Aserver)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=br.com.tasknoteapp%3Aserver&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=br.com.tasknoteapp%3Aserver)
### TaskNote WebApp
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ricardo-campos-org_react-typescript-todolist_client&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ricardo-campos-org_react-typescript-todolist_client&metric=coverage)](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=ricardo-campos-org_react-typescript-todolist_client&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=ricardo-campos-org_react-typescript-todolist_client&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ricardo-campos-org_react-typescript-todolist_client&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
## 📋 Table of Contents
+1 -1
View File
@@ -53,7 +53,7 @@ function handleError(httpStatusCode: number) {
async function handleResponse(response: Response) {
// Successful responses
if (response.ok) {
const codesToIgnore: number[] = [204, 201];
const codesToIgnore: number[] = [204];
if (codesToIgnore.includes(response.status)) {
return;
}
+3
View File
@@ -22,6 +22,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
WORKDIR /app
COPY --from=build /app/target/server ./tasknote-api
ARG BUILD
ENV BUILD=${BUILD}
# User, port and health check
USER 1001
EXPOSE ${PORT}
@@ -162,7 +162,7 @@ public class TaskController {
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
schema = @Schema(implementation = TaskResponse.class))),
@ApiResponse(
responseCode = "400",
description = "Wrong or missing information",
@@ -179,8 +179,8 @@ public class TaskController {
@RequestBody
@Valid
TaskRequest taskRequest) {
taskService.createTask(taskRequest);
return ResponseEntity.status(HttpStatus.CREATED).build();
TaskResponse response = taskService.createTask(taskRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
/**
@@ -49,7 +49,7 @@ public class TaskService {
log.info("{} tasks found!", tasks.size());
return tasks.stream()
.map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllNotesUrls(tr.getId())))
.map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllTasksUrls(tr.getId())))
.toList();
}
@@ -69,7 +69,7 @@ public class TaskService {
}
log.info("Task found! Id {}", taskId);
return TaskResponse.fromEntity(task.get(), getAllNotesUrls(taskId));
return TaskResponse.fromEntity(task.get(), getAllTasksUrls(taskId));
}
/**
@@ -77,7 +77,7 @@ public class TaskService {
*
* @param taskRequest The {@link TaskRequest} containing all task data.
*/
public void createTask(TaskRequest taskRequest) {
public TaskResponse createTask(TaskRequest taskRequest) {
UserEntity user = getCurrentUser();
log.info("Creating task to user {}", user.getId());
@@ -99,6 +99,7 @@ public class TaskService {
}
log.info("Task created! Id {}", created.getId());
return TaskResponse.fromEntity(created, getAllTasksUrls(created.getId()));
}
/**
@@ -161,7 +162,7 @@ public class TaskService {
log.info("Task patched! Id {}", patchedTask.getId());
return TaskResponse.fromEntity(patchedTask, getAllNotesUrls(taskId));
return TaskResponse.fromEntity(patchedTask, getAllTasksUrls(taskId));
}
/**
@@ -209,7 +210,7 @@ public class TaskService {
log.info("{} tasks found!", tasks.size());
return tasks.stream()
.map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllNotesUrls(tr.getId())))
.map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllTasksUrls(tr.getId())))
.toList();
}
@@ -219,8 +220,8 @@ public class TaskService {
return authService.findByEmail(email).orElseThrow();
}
private List<String> getAllNotesUrls(Long noteId) {
List<TaskUrlEntity> urls = taskUrlRepository.findAllById_taskId(noteId);
private List<String> getAllTasksUrls(Long taskId) {
List<TaskUrlEntity> urls = taskUrlRepository.findAllById_taskId(taskId);
return urls.stream().map(TaskUrlEntity::getId).map(TaskUrlEntityPk::getUrl).toList();
}
@@ -267,7 +267,18 @@ class TaskControllerTest {
void postTasks_happyPath_shouldSucceed() throws Exception {
TaskRequest request = new TaskRequest("Test task", List.of("www.url.com"), null, true, "tag");
doNothing().when(taskService).createTask(request);
TaskResponse taskResponse =
new TaskResponse(
858L,
"Description patched",
false,
true,
null,
null,
"Moments ago",
"tag",
List.of());
when(taskService.createTask(request)).thenReturn(taskResponse);
final String payloadJson =
"""
@@ -1,16 +1,23 @@
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.TaskEntity;
import br.com.tasknoteapp.server.entity.TaskUrlEntity;
import br.com.tasknoteapp.server.entity.TaskUrlEntityPk;
import br.com.tasknoteapp.server.entity.UserEntity;
import br.com.tasknoteapp.server.exception.TaskNotFoundException;
import br.com.tasknoteapp.server.repository.TaskRepository;
import br.com.tasknoteapp.server.repository.TaskUrlRepository;
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.util.AuthUtil;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@@ -164,4 +171,198 @@ class TaskServiceTest {
Assertions.assertNotNull(entity);
Assertions.assertEquals("development", entity.getTag());
}
@Test
@DisplayName("Create task with null url it should succeed")
void createTask_nullUrl_shouldSucceed() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL));
UserEntity userEntity = new UserEntity();
userEntity.setId(USER_ID);
userEntity.setEmail(USER_EMAIL);
when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity));
TaskRequest request =
new TaskRequest("Write unit tests", null, "2025-12-12", false, "development");
TaskEntity entity = new TaskEntity();
entity.setId(123L);
entity.setDescription(request.description());
entity.setHighPriority(request.highPriority());
entity.setTag(request.tag());
when(taskRepository.save(any())).thenReturn(new TaskEntity());
TaskResponse response = taskService.createTask(request);
Assertions.assertNotNull(response);
Assertions.assertTrue(response.urls().isEmpty());
}
@Test
@DisplayName("Create task with empty url it should succeed")
void createTask_emptyUrl_shouldSucceed() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL));
UserEntity userEntity = new UserEntity();
userEntity.setId(USER_ID);
userEntity.setEmail(USER_EMAIL);
when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity));
TaskRequest request =
new TaskRequest("Write unit tests", List.of(), "2025-12-12", false, "development");
TaskEntity entity = new TaskEntity();
entity.setId(123L);
entity.setDescription(request.description());
entity.setHighPriority(request.highPriority());
entity.setTag(request.tag());
when(taskRepository.save(any())).thenReturn(new TaskEntity());
TaskResponse response = taskService.createTask(request);
Assertions.assertNotNull(response);
Assertions.assertTrue(response.urls().isEmpty());
}
@Test
@DisplayName("Create task with valid url it should succeed")
void createTask_fullUrl_shouldSucceed() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL));
UserEntity userEntity = new UserEntity();
userEntity.setId(USER_ID);
userEntity.setEmail(USER_EMAIL);
when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity));
TaskRequest request =
new TaskRequest(
"Write unit tests", List.of("debian.org"), "2025-12-12", false, "development");
TaskEntity entity = new TaskEntity();
entity.setId(123L);
entity.setDescription(request.description());
entity.setHighPriority(request.highPriority());
entity.setTag(request.tag());
when(taskRepository.save(any())).thenReturn(entity);
TaskUrlEntity urlEntity = new TaskUrlEntity();
urlEntity.setId(new TaskUrlEntityPk(entity.getId(), "debian.org"));
when(taskUrlRepository.findAllById_taskId(entity.getId())).thenReturn(List.of(urlEntity));
TaskResponse response = taskService.createTask(request);
Assertions.assertNotNull(response);
Assertions.assertFalse(response.urls().isEmpty());
Assertions.assertEquals("debian.org", response.urls().get(0));
}
@Test
@DisplayName("Get all tasks happy path should succeed")
void getAllTasks_happyPath_shouldSucceed() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL));
UserEntity userEntity = new UserEntity();
userEntity.setId(USER_ID);
userEntity.setEmail(USER_EMAIL);
when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity));
TaskEntity entity = new TaskEntity();
entity.setDescription("Writ unit tests");
entity.setHighPriority(true);
entity.setTag("dev");
when(taskRepository.findAllByUser_id(USER_ID)).thenReturn(List.of(entity));
List<TaskResponse> responses = taskService.getAllTasks();
Assertions.assertFalse(responses.isEmpty());
Assertions.assertEquals(1, responses.size());
Assertions.assertEquals(entity.getTag(), responses.get(0).tag());
}
@Test
@DisplayName("Delete a task following the happy path should succeed")
void deleteTask_happyPath_shouldSucceed() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL));
UserEntity userEntity = new UserEntity();
userEntity.setId(USER_ID);
userEntity.setEmail(USER_EMAIL);
when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity));
Long taskId = 2525L;
TaskEntity taskEntity = new TaskEntity();
taskEntity.setId(taskId);
taskEntity.setDescription("Test task");
taskEntity.setHighPriority(true);
taskEntity.setTag("test");
taskEntity.setUser(userEntity);
when(taskRepository.findById(taskId)).thenReturn(Optional.of(taskEntity));
when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of());
doNothing().when(taskRepository).delete(taskEntity);
taskService.deleteTask(taskId);
verify(taskRepository, times(1)).delete(any());
}
@Test
@DisplayName("Delete a not existing tasks it should fail")
void deleteTask_notFound_shouldFail() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL));
UserEntity userEntity = new UserEntity();
userEntity.setId(USER_ID);
userEntity.setEmail(USER_EMAIL);
when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity));
Long taskId = 2526L;
when(taskRepository.findById(taskId)).thenReturn(Optional.empty());
Assertions.assertThrows(
TaskNotFoundException.class,
() -> {
taskService.deleteTask(taskId);
});
}
@Test
@DisplayName("Patch a task following the happy path it should succeed")
void patchTask_happyPath_shouldSucceed() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL));
UserEntity userEntity = new UserEntity();
userEntity.setId(USER_ID);
userEntity.setEmail(USER_EMAIL);
when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity));
Long taskId = 2525L;
TaskEntity taskEntity = new TaskEntity();
taskEntity.setId(taskId);
taskEntity.setDescription("Test task");
taskEntity.setHighPriority(true);
taskEntity.setTag("test");
taskEntity.setUser(userEntity);
when(taskRepository.findById(taskId)).thenReturn(Optional.of(taskEntity));
when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of());
TaskEntity entity = new TaskEntity();
entity.setDescription("Updated description");
entity.setHighPriority(false);
entity.setTag(taskEntity.getTag());
when(taskRepository.save(any())).thenReturn(entity);
TaskPatchRequest patch =
new TaskPatchRequest("Updated description", null, null, null, false, null);
TaskResponse patched = taskService.patchTask(taskId, patch);
Assertions.assertNotNull(patched);
Assertions.assertEquals("Updated description", patched.description());
Assertions.assertFalse(patched.highPriority());
}
}
+2 -2
View File
@@ -27,7 +27,7 @@ npm ci --ignore-scripts --no-update-notifier --omit=dev \
If you want to build with Docker:
```sh
docker build -t client ./client
docker build --build-arg VITE_BUILD="Development-$(date "+%Y-%m-%d %H:%M:%S")" -t client ./client
```
That's it!
@@ -37,7 +37,7 @@ That's it!
For the backend there's a Dockerfile ready, just run (from the project root):
```bash
docker build -t server ./server
docker build --build-arg BUILD="Development-$(date "+%Y-%m-%d %H:%M:%S")" -t server ./server
```
That's it!