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:
@@ -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
|
||||
|
||||
@@ -6,8 +6,16 @@
|
||||
### TaskNote API
|
||||
[](https://sonarcloud.io/summary/new_code?id=br.com.tasknoteapp%3Aserver)
|
||||
[](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_server)
|
||||
[](https://sonarcloud.io/summary/new_code?id=br.com.tasknoteapp%3Aserver)
|
||||
[](https://sonarcloud.io/summary/new_code?id=br.com.tasknoteapp%3Aserver)
|
||||
[](https://sonarcloud.io/summary/new_code?id=br.com.tasknoteapp%3Aserver)
|
||||
|
||||
### TaskNote WebApp
|
||||
[](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
|
||||
[](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
|
||||
[](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
|
||||
[](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
|
||||
[](https://sonarcloud.io/summary/new_code?id=ricardo-campos-org_react-typescript-todolist_client)
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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!
|
||||
|
||||
Reference in New Issue
Block a user