diff --git a/pom.xml b/pom.xml index 2029fbc..e98e19c 100644 --- a/pom.xml +++ b/pom.xml @@ -66,6 +66,12 @@ h2 test + + org.jacoco + jacoco-maven-plugin + 0.8.14 + compile + diff --git a/src/main/java/com/jonassavas/spring_task_api/controllers/TaskController.java b/src/main/java/com/jonassavas/spring_task_api/controllers/TaskController.java index 30f41ae..c99f260 100644 --- a/src/main/java/com/jonassavas/spring_task_api/controllers/TaskController.java +++ b/src/main/java/com/jonassavas/spring_task_api/controllers/TaskController.java @@ -1,16 +1,18 @@ package com.jonassavas.spring_task_api.controllers; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.jonassavas.spring_task_api.domain.dto.TaskRequestDto; import com.jonassavas.spring_task_api.domain.dto.TaskDto; import com.jonassavas.spring_task_api.domain.entities.TaskEntity; import com.jonassavas.spring_task_api.mappers.Mapper; +import com.jonassavas.spring_task_api.services.TaskGroupService; import com.jonassavas.spring_task_api.services.TaskService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -21,12 +23,19 @@ public class TaskController { private Mapper taskMapper; + private Mapper taskRequestMapper; private TaskService taskService; + private TaskGroupService taskGroupService; - public TaskController(Mapper taskMapper, TaskService taskService){ + public TaskController(Mapper taskMapper, + TaskService taskService, + TaskGroupService taskGroupService, + Mapper taskRequestMapper){ this.taskMapper = taskMapper; this.taskService = taskService; + this.taskGroupService = taskGroupService; + this.taskRequestMapper = taskRequestMapper; } @@ -34,6 +43,11 @@ public TaskController(Mapper taskMapper, TaskService taskSe public ResponseEntity createTask( @PathVariable Long groupId, @RequestBody TaskDto dto) { + + // Can't create a task without a valid task group + if(!taskGroupService.isExist(groupId)){ + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } TaskEntity taskEntity = taskMapper.mapFrom(dto); TaskEntity savedTask = taskService.createTask(groupId, taskEntity); @@ -43,8 +57,14 @@ public ResponseEntity createTask( @DeleteMapping(path = "/tasks/{id}") public ResponseEntity deleteTask(@PathVariable("id") Long id){ - taskService.deleteTask(id); + taskService.delete(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } + @PatchMapping(path = "/tasks/{id}") + public ResponseEntity update(@PathVariable Long id, @RequestBody TaskRequestDto dto){ + TaskEntity updated = taskService.update(id, dto); + + return new ResponseEntity<>(taskRequestMapper.mapTo(updated), HttpStatus.OK); + } } diff --git a/src/main/java/com/jonassavas/spring_task_api/controllers/TaskGroupController.java b/src/main/java/com/jonassavas/spring_task_api/controllers/TaskGroupController.java index 4a8b8b9..9c1a9d6 100644 --- a/src/main/java/com/jonassavas/spring_task_api/controllers/TaskGroupController.java +++ b/src/main/java/com/jonassavas/spring_task_api/controllers/TaskGroupController.java @@ -2,9 +2,10 @@ import org.springframework.web.bind.annotation.RestController; -import com.jonassavas.spring_task_api.domain.dto.CreateTaskGroupDto; +import com.jonassavas.spring_task_api.domain.dto.TaskGroupRequestDto; import com.jonassavas.spring_task_api.domain.dto.TaskDto; import com.jonassavas.spring_task_api.domain.dto.TaskGroupDto; +import com.jonassavas.spring_task_api.domain.dto.TaskGroupWithTasksDto; import com.jonassavas.spring_task_api.domain.entities.TaskEntity; import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; import com.jonassavas.spring_task_api.mappers.Mapper; @@ -16,38 +17,88 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; @RestController public class TaskGroupController { private TaskGroupService taskGroupService; - private Mapper createTaskGroupMapper; + private Mapper taskGroupRequestMapper; + private Mapper taskGroupWithTasksMapper; private Mapper taskGroupMapper; public TaskGroupController(TaskGroupService taskGroupService, - Mapper createTaskGroupMapper, - Mapper taskGroupMapper){ + Mapper taskGroupRequestMapper, + Mapper taskGroupMapper, + Mapper taskGroupWithTasksMapper){ this.taskGroupService = taskGroupService; - this.createTaskGroupMapper = createTaskGroupMapper; + this.taskGroupRequestMapper = taskGroupRequestMapper; this.taskGroupMapper = taskGroupMapper; + this.taskGroupWithTasksMapper = taskGroupWithTasksMapper; } @PostMapping(path = "/taskgroups") - public ResponseEntity createTaskGroup(@RequestBody CreateTaskGroupDto taskGroup) { - TaskGroupEntity taskGroupEntity = createTaskGroupMapper.mapFrom(taskGroup); + public ResponseEntity createTaskGroup(@RequestBody TaskGroupRequestDto taskGroup) { + TaskGroupEntity taskGroupEntity = taskGroupRequestMapper.mapFrom(taskGroup); TaskGroupEntity savedTaskGroupEntity = taskGroupService.save(taskGroupEntity); - return new ResponseEntity<>(createTaskGroupMapper.mapTo(savedTaskGroupEntity), HttpStatus.CREATED); + return new ResponseEntity<>(taskGroupRequestMapper.mapTo(savedTaskGroupEntity), HttpStatus.CREATED); } - @GetMapping(path = "/taskgroups") + @GetMapping("/taskgroups") public List listTaskGroups() { - List taskGroups = taskGroupService.findAll(); - return taskGroups.stream().map(taskGroupMapper::mapTo).collect(Collectors.toList()); + return taskGroupService.findAll() + .stream() + .map(taskGroupMapper::mapTo) + .toList(); + } + + // @GetMapping("/taskgroups/{id}/tasks") + // public List listTasksForGroup(@PathVariable Long id) { + // return taskService.findByGroupId(id) + // .stream() + // .map(taskMapper::mapTo) + // .toList(); + // } + + @GetMapping("/taskgroups/with-tasks") + public List listTaskGroupsWithTasks() { + return taskGroupService.findAllWithTasks() + .stream() + .map(taskGroupWithTasksMapper::mapTo) + .toList(); + } + + + + // @GetMapping(path = "/taskgroups") + // public List listTaskGroups() { + // List taskGroups = taskGroupService.findAll(); + // return taskGroups.stream().map(taskGroupMapper::mapTo).collect(Collectors.toList()); + // } + + @DeleteMapping(path = "/taskgroups/{id}") + public ResponseEntity deleteTaskGroup(@PathVariable("id") Long id){ + taskGroupService.delete(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @DeleteMapping(path = "/taskgroups/{groupId}/tasks") + public ResponseEntity deleteAllTasks(@PathVariable("groupId") Long groupId){ + taskGroupService.deleteAllTasks(groupId); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @PatchMapping(path = "/taskgroups/{id}") + public ResponseEntity updateTaskGroup(@PathVariable Long id, @RequestBody TaskGroupRequestDto dto){ + TaskGroupEntity updated = taskGroupService.update(id, dto); + return new ResponseEntity<>(taskGroupRequestMapper.mapTo(updated), HttpStatus.OK); } } diff --git a/src/main/java/com/jonassavas/spring_task_api/domain/dto/CreateTaskDto.java b/src/main/java/com/jonassavas/spring_task_api/domain/dto/CreateTaskDto.java deleted file mode 100644 index 5afceb3..0000000 --- a/src/main/java/com/jonassavas/spring_task_api/domain/dto/CreateTaskDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.jonassavas.spring_task_api.domain.dto; - -public class CreateTaskDto { - -} diff --git a/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupDto.java b/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupDto.java index b38fc8f..abf3a31 100644 --- a/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupDto.java +++ b/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupDto.java @@ -1,9 +1,5 @@ package com.jonassavas.spring_task_api.domain.dto; -import java.util.List; - -import com.jonassavas.spring_task_api.domain.entities.TaskEntity; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -18,5 +14,4 @@ public class TaskGroupDto { private String taskGroupName; - private List tasks; } diff --git a/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupRequestDto.java b/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupRequestDto.java new file mode 100644 index 0000000..79fd946 --- /dev/null +++ b/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupRequestDto.java @@ -0,0 +1,25 @@ +package com.jonassavas.spring_task_api.domain.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + + +/* + USED FOR: + - CREATE + - UPDATE + + This class serves as the DTO for incoming and outgoing requests. + This means that when the user wants to create/change any parameter + of their TaskGroup they do it via this DTO. +*/ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class TaskGroupRequestDto { + private String taskGroupName; +} diff --git a/src/main/java/com/jonassavas/spring_task_api/domain/dto/CreateTaskGroupDto.java b/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupWithTasksDto.java similarity index 62% rename from src/main/java/com/jonassavas/spring_task_api/domain/dto/CreateTaskGroupDto.java rename to src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupWithTasksDto.java index 81962ce..6d0d3a0 100644 --- a/src/main/java/com/jonassavas/spring_task_api/domain/dto/CreateTaskGroupDto.java +++ b/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskGroupWithTasksDto.java @@ -1,5 +1,9 @@ package com.jonassavas.spring_task_api.domain.dto; +import java.util.List; + +import com.jonassavas.spring_task_api.domain.entities.TaskEntity; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -9,10 +13,10 @@ @AllArgsConstructor @NoArgsConstructor @Builder -public class CreateTaskGroupDto { +public class TaskGroupWithTasksDto { private Long id; private String taskGroupName; - //private List tasks; //We don't allow tasks on create -} + private List tasks; +} \ No newline at end of file diff --git a/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskRequestDto.java b/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskRequestDto.java new file mode 100644 index 0000000..9c9db7a --- /dev/null +++ b/src/main/java/com/jonassavas/spring_task_api/domain/dto/TaskRequestDto.java @@ -0,0 +1,15 @@ +package com.jonassavas.spring_task_api.domain.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class TaskRequestDto { + private Long taskGroupId; + private String taskName; +} \ No newline at end of file diff --git a/src/main/java/com/jonassavas/spring_task_api/mappers/impl/CreateTaskGroupMapper.java b/src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskGroupRequestMapper.java similarity index 52% rename from src/main/java/com/jonassavas/spring_task_api/mappers/impl/CreateTaskGroupMapper.java rename to src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskGroupRequestMapper.java index 8b50cb8..890ce01 100644 --- a/src/main/java/com/jonassavas/spring_task_api/mappers/impl/CreateTaskGroupMapper.java +++ b/src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskGroupRequestMapper.java @@ -4,25 +4,25 @@ import org.springframework.stereotype.Component; -import com.jonassavas.spring_task_api.domain.dto.CreateTaskGroupDto; +import com.jonassavas.spring_task_api.domain.dto.TaskGroupRequestDto; import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; import com.jonassavas.spring_task_api.mappers.Mapper; @Component -public class CreateTaskGroupMapper implements Mapper{ +public class TaskGroupRequestMapper implements Mapper{ private ModelMapper modelMapper; - public CreateTaskGroupMapper(ModelMapper modelMapper){ + public TaskGroupRequestMapper(ModelMapper modelMapper){ this.modelMapper = modelMapper; } @Override - public CreateTaskGroupDto mapTo(TaskGroupEntity taskGroupEntity){ - return modelMapper.map(taskGroupEntity, CreateTaskGroupDto.class); + public TaskGroupRequestDto mapTo(TaskGroupEntity taskGroupEntity){ + return modelMapper.map(taskGroupEntity, TaskGroupRequestDto.class); } @Override - public TaskGroupEntity mapFrom(CreateTaskGroupDto taskGroupDto){ + public TaskGroupEntity mapFrom(TaskGroupRequestDto taskGroupDto){ return modelMapper.map(taskGroupDto, TaskGroupEntity.class); } } diff --git a/src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskGroupWithTasksMapper.java b/src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskGroupWithTasksMapper.java new file mode 100644 index 0000000..a1d13c5 --- /dev/null +++ b/src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskGroupWithTasksMapper.java @@ -0,0 +1,28 @@ +package com.jonassavas.spring_task_api.mappers.impl; + +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Component; + + +import com.jonassavas.spring_task_api.domain.dto.TaskGroupWithTasksDto; +import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; +import com.jonassavas.spring_task_api.mappers.Mapper; + +@Component +public class TaskGroupWithTasksMapper implements Mapper{ + private ModelMapper modelMapper; + + public TaskGroupWithTasksMapper(ModelMapper modelMapper){ + this.modelMapper = modelMapper; + } + + @Override + public TaskGroupWithTasksDto mapTo(TaskGroupEntity taskGroupEntity){ + return modelMapper.map(taskGroupEntity, TaskGroupWithTasksDto.class); + } + + @Override + public TaskGroupEntity mapFrom(TaskGroupWithTasksDto taskGroupDto){ + return modelMapper.map(taskGroupDto, TaskGroupEntity.class); + } +} diff --git a/src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskRequestMapper.java b/src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskRequestMapper.java new file mode 100644 index 0000000..3918c87 --- /dev/null +++ b/src/main/java/com/jonassavas/spring_task_api/mappers/impl/TaskRequestMapper.java @@ -0,0 +1,36 @@ +package com.jonassavas.spring_task_api.mappers.impl; + +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Component; + +import com.jonassavas.spring_task_api.domain.dto.TaskRequestDto; +import com.jonassavas.spring_task_api.domain.dto.TaskDto; +import com.jonassavas.spring_task_api.domain.entities.TaskEntity; +import com.jonassavas.spring_task_api.mappers.Mapper; + +@Component +public class TaskRequestMapper implements Mapper { + + private ModelMapper modelMapper; + + public TaskRequestMapper(ModelMapper modelMapper) { + this.modelMapper = modelMapper; + + // Skip taskGroup when mapping DTO -> Entity + this.modelMapper.typeMap(TaskRequestDto.class, TaskEntity.class) + .addMappings(mapper -> mapper.skip(TaskEntity::setTaskGroup)); + } + + @Override + public TaskRequestDto mapTo(TaskEntity taskEntity) { + TaskRequestDto dto = modelMapper.map(taskEntity, TaskRequestDto.class); + //dto.setTaskGroupId(taskEntity.getTaskGroup().getId()); + return dto; + } + + @Override + public TaskEntity mapFrom(TaskRequestDto createTaskDto) { + return modelMapper.map(createTaskDto, TaskEntity.class); + } +} + diff --git a/src/main/java/com/jonassavas/spring_task_api/repositories/TaskGroupRepository.java b/src/main/java/com/jonassavas/spring_task_api/repositories/TaskGroupRepository.java index 5f39eec..de1d7ba 100644 --- a/src/main/java/com/jonassavas/spring_task_api/repositories/TaskGroupRepository.java +++ b/src/main/java/com/jonassavas/spring_task_api/repositories/TaskGroupRepository.java @@ -1,5 +1,10 @@ package com.jonassavas.spring_task_api.repositories; +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @@ -10,6 +15,18 @@ which is used to indicate that the class provides the mechanism for storage, retrieval, update, delete, and search operation on objects.*/ @Repository -public interface TaskGroupRepository extends CrudRepository{ +public interface TaskGroupRepository extends JpaRepository{ + @Query(""" + SELECT tg FROM TaskGroupEntity tg + LEFT JOIN FETCH tg.tasks + """) + List findAllWithTasks(); + + @Query(""" + SELECT tg from TaskGroupEntity tg + LEFT JOIN FETCH tg.tasks + WHERE tg.id = :id + """) + Optional findByIdWithTasks(Long id); } diff --git a/src/main/java/com/jonassavas/spring_task_api/repositories/TaskRepository.java b/src/main/java/com/jonassavas/spring_task_api/repositories/TaskRepository.java index 60af341..e38e564 100644 --- a/src/main/java/com/jonassavas/spring_task_api/repositories/TaskRepository.java +++ b/src/main/java/com/jonassavas/spring_task_api/repositories/TaskRepository.java @@ -1,5 +1,6 @@ package com.jonassavas.spring_task_api.repositories; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @@ -10,6 +11,6 @@ which is used to indicate that the class provides the mechanism for storage, retrieval, update, delete, and search operation on objects.*/ @Repository -public interface TaskRepository extends CrudRepository { +public interface TaskRepository extends JpaRepository { } diff --git a/src/main/java/com/jonassavas/spring_task_api/services/TaskGroupService.java b/src/main/java/com/jonassavas/spring_task_api/services/TaskGroupService.java index 1acd3f0..ad3372e 100644 --- a/src/main/java/com/jonassavas/spring_task_api/services/TaskGroupService.java +++ b/src/main/java/com/jonassavas/spring_task_api/services/TaskGroupService.java @@ -2,10 +2,23 @@ import java.util.List; +import com.jonassavas.spring_task_api.domain.dto.TaskGroupRequestDto; import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; public interface TaskGroupService { TaskGroupEntity save(TaskGroupEntity taskGroupEntity); List findAll(); + + List findAllWithTasks(); + + TaskGroupEntity findByIdWithTasks(Long id); + + boolean isExist(Long id); + + void delete(Long id); + + void deleteAllTasks(Long id); + + TaskGroupEntity update(Long id, TaskGroupRequestDto dto); } diff --git a/src/main/java/com/jonassavas/spring_task_api/services/TaskService.java b/src/main/java/com/jonassavas/spring_task_api/services/TaskService.java index f02e280..f1e09e4 100644 --- a/src/main/java/com/jonassavas/spring_task_api/services/TaskService.java +++ b/src/main/java/com/jonassavas/spring_task_api/services/TaskService.java @@ -1,9 +1,18 @@ package com.jonassavas.spring_task_api.services; +import java.util.List; + +import com.jonassavas.spring_task_api.domain.dto.TaskRequestDto; import com.jonassavas.spring_task_api.domain.entities.TaskEntity; public interface TaskService { TaskEntity createTask(Long groupId, TaskEntity taskEntity); - void deleteTask(Long id); + void delete(Long id); + + boolean isExist(Long id); + + TaskEntity update(Long id, TaskRequestDto dto); + + List findAll(); } diff --git a/src/main/java/com/jonassavas/spring_task_api/services/impl/TaskGroupServiceImpl.java b/src/main/java/com/jonassavas/spring_task_api/services/impl/TaskGroupServiceImpl.java index 2b8a2cd..a340e76 100644 --- a/src/main/java/com/jonassavas/spring_task_api/services/impl/TaskGroupServiceImpl.java +++ b/src/main/java/com/jonassavas/spring_task_api/services/impl/TaskGroupServiceImpl.java @@ -6,11 +6,16 @@ import org.springframework.stereotype.Service; +import com.jonassavas.spring_task_api.domain.dto.TaskGroupRequestDto; import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; import com.jonassavas.spring_task_api.repositories.TaskGroupRepository; import com.jonassavas.spring_task_api.services.TaskGroupService; +import jakarta.persistence.EntityNotFoundException; +import jakarta.transaction.Transactional; + @Service +@Transactional public class TaskGroupServiceImpl implements TaskGroupService{ private TaskGroupRepository taskGroupRepository; @@ -31,4 +36,48 @@ public List findAll(){ .spliterator(), false) .collect(Collectors.toList()); } + + @Override + public List findAllWithTasks(){ + return taskGroupRepository.findAllWithTasks(); + } + + @Override + public TaskGroupEntity findByIdWithTasks(Long id){ + return taskGroupRepository.findByIdWithTasks(id) + .orElseThrow(() -> new EntityNotFoundException( + "TaskGroup not found with id " + id)); + } + + @Override + public boolean isExist(Long id){ + return taskGroupRepository.existsById(id); + } + + @Override + public void delete(Long id){ + taskGroupRepository.deleteById(id); + } + + @Override + public void deleteAllTasks(Long id){ + TaskGroupEntity taskGroup = taskGroupRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException( + "TaskGroup not found with id " + id)); + + taskGroup.getTasks().clear(); + } + + @Override + public TaskGroupEntity update(Long id, TaskGroupRequestDto dto){ + TaskGroupEntity taskGroup = taskGroupRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException( + "TaskGroup not found with it: " + id)); + + if(dto.getTaskGroupName() != null){ + taskGroup.setTaskGroupName(dto.getTaskGroupName()); + } + + return taskGroup; + } } diff --git a/src/main/java/com/jonassavas/spring_task_api/services/impl/TaskServiceImpl.java b/src/main/java/com/jonassavas/spring_task_api/services/impl/TaskServiceImpl.java index 9124068..4ca6c9d 100644 --- a/src/main/java/com/jonassavas/spring_task_api/services/impl/TaskServiceImpl.java +++ b/src/main/java/com/jonassavas/spring_task_api/services/impl/TaskServiceImpl.java @@ -1,7 +1,12 @@ package com.jonassavas.spring_task_api.services.impl; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + import org.springframework.stereotype.Service; +import com.jonassavas.spring_task_api.domain.dto.TaskRequestDto; import com.jonassavas.spring_task_api.domain.entities.TaskEntity; import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; import com.jonassavas.spring_task_api.repositories.TaskGroupRepository; @@ -9,8 +14,10 @@ import com.jonassavas.spring_task_api.services.TaskService; import jakarta.persistence.EntityNotFoundException; +import jakarta.transaction.Transactional; @Service +@Transactional public class TaskServiceImpl implements TaskService{ private final TaskRepository taskRepository; private final TaskGroupRepository taskGroupRepository; @@ -29,19 +36,61 @@ public TaskEntity createTask(Long groupId, TaskEntity taskEntity) { taskGroup.addTask(taskEntity); // Cascade saves task automatically - taskRepository.save(taskEntity); - - return taskEntity; + return taskRepository.save(taskEntity); } @Override - public void deleteTask(Long id){ + public void delete(Long id){ TaskEntity task = taskRepository.findById(id) .orElseThrow(() -> new EntityNotFoundException("Task not found with id " + id)); TaskGroupEntity group = task.getTaskGroup(); group.removeTask(task); // Triggers orphanRemoval + + taskRepository.delete(task); + } + + @Override + public boolean isExist(Long id){ + return taskRepository.existsById(id); + } + + @Override + public List findAll(){ + return StreamSupport.stream(taskRepository + .findAll() + .spliterator(), false) + .collect(Collectors.toList()); + } + + + @Override + public TaskEntity update(Long id, TaskRequestDto dto){ + TaskEntity task = taskRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException( + "Task not found with id " + id)); + + // Update task name + if(dto.getTaskName() != null){ + task.setTaskName(dto.getTaskName()); + } + + // Move task to another group (if requested) + if(dto.getTaskGroupId() != null && + !dto.getTaskGroupId().equals(task.getTaskGroup().getId())){ + TaskGroupEntity oldGroup = task.getTaskGroup(); + + TaskGroupEntity newGroup = taskGroupRepository + .findById(dto.getTaskGroupId()) + .orElseThrow(() -> new EntityNotFoundException( + "TaskGroup not found with id " + dto.getTaskGroupId())); + + oldGroup.removeTask(task); + newGroup.addTask(task); + } + + return task; } } diff --git a/src/test/java/com/jonassavas/spring_task_api/TestDataUtil.java b/src/test/java/com/jonassavas/spring_task_api/TestDataUtil.java index 23edb79..535895a 100644 --- a/src/test/java/com/jonassavas/spring_task_api/TestDataUtil.java +++ b/src/test/java/com/jonassavas/spring_task_api/TestDataUtil.java @@ -1,11 +1,19 @@ package com.jonassavas.spring_task_api; +import com.jonassavas.spring_task_api.domain.dto.TaskRequestDto; +import com.jonassavas.spring_task_api.domain.dto.TaskGroupRequestDto; import com.jonassavas.spring_task_api.domain.dto.TaskDto; import com.jonassavas.spring_task_api.domain.entities.TaskEntity; import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; public class TestDataUtil { + public static TaskRequestDto createTestCreateTaskDto(){ + return TaskRequestDto.builder() + .taskName("Create Task Dto") + .build(); + } + public static TaskDto createTestTaskDtoA(){ return TaskDto.builder() .taskGroupId(null) @@ -51,4 +59,22 @@ public static TaskGroupEntity createTaskGroupEntityC(){ .taskGroupName("Task Group C") .build(); } + + public static TaskGroupRequestDto createTaskGroupDtoA(){ + return TaskGroupRequestDto.builder() + .taskGroupName("Task Group A") + .build(); + } + + public static TaskGroupRequestDto createTaskGroupDtoB(){ + return TaskGroupRequestDto.builder() + .taskGroupName("Task Group B") + .build(); + } + + public static TaskGroupRequestDto createTaskGroupDtoC(){ + return TaskGroupRequestDto.builder() + .taskGroupName("Task Group C") + .build(); + } } diff --git a/src/test/java/com/jonassavas/spring_task_api/controllers/TaskControllerIntegrationTests.java b/src/test/java/com/jonassavas/spring_task_api/controllers/TaskControllerIntegrationTests.java index 95ee239..16aeabd 100644 --- a/src/test/java/com/jonassavas/spring_task_api/controllers/TaskControllerIntegrationTests.java +++ b/src/test/java/com/jonassavas/spring_task_api/controllers/TaskControllerIntegrationTests.java @@ -1,5 +1,9 @@ package com.jonassavas.spring_task_api.controllers; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -12,13 +16,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.jonassavas.spring_task_api.TestDataUtil; +import com.jonassavas.spring_task_api.domain.dto.TaskRequestDto; import com.jonassavas.spring_task_api.domain.dto.TaskDto; import com.jonassavas.spring_task_api.domain.entities.TaskEntity; import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; import com.jonassavas.spring_task_api.services.TaskGroupService; import com.jonassavas.spring_task_api.services.TaskService; -import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @@ -86,50 +90,164 @@ public void testThatCreateTaskReturnsSavedTask() throws Exception{ ); } - // TODO @Test public void testThatCreateTaskWithoutValidTaskGroupReturns404() throws Exception{ // TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); // taskGroupService.save(testTaskGroupEntityA); - // TaskDto testTaskDtoA = TestDataUtil.createTestTaskDtoA(); + TaskDto testTaskDtoA = TestDataUtil.createTestTaskDtoA(); - // testTaskDtoA.setTaskGroupId(testTaskGroupEntityA.getId()); + testTaskDtoA.setTaskGroupId(99L); - // String taskJson = objectMapper.writeValueAsString(testTaskDtoA); + String taskJson = objectMapper.writeValueAsString(testTaskDtoA); - // mockMvc.perform( - // MockMvcRequestBuilders.post("/taskgroups/" + testTaskGroupEntityA.getId() + "/tasks") - // .contentType(MediaType.APPLICATION_JSON) - // .content(taskJson) - // ).andExpect( - // MockMvcResultMatchers.jsonPath("$.taskGroupId").isNumber() - // ).andExpect( - // MockMvcResultMatchers.jsonPath("$.id").isNumber() - // ).andExpect( - // MockMvcResultMatchers.jsonPath("$.taskName").value("Task A") - // ); + mockMvc.perform( + MockMvcRequestBuilders.post("/taskgroups/" + 99 + "/tasks") + .contentType(MediaType.APPLICATION_JSON) + .content(taskJson) + ).andExpect( + MockMvcResultMatchers.status().isNotFound() + ); } - // TODO + @Test public void testThatDeleteTaskReturnsHttp204() throws Exception{ - // TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); - // taskGroupService.save(testTaskGroupEntityA); + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + + assertThat(taskGroupService + .findByIdWithTasks(testTaskGroupEntityA.getId()) + .getTasks().size()) + .isEqualTo(1); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/tasks/" + testTaskEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect( + MockMvcResultMatchers.status().isNoContent()); + + assertThat(taskGroupService + .findByIdWithTasks(testTaskGroupEntityA.getId()) + .getTasks().size()) + .isEqualTo(0); + } + + @Test + public void testThatDeleteTaskDeletesCorrectTask() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + + TaskEntity testTaskEntityB = TestDataUtil.createTestTaskEntityB(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityB); + + assertThat(taskGroupService + .findByIdWithTasks(testTaskGroupEntityA.getId()) + .getTasks().size()) + .isEqualTo(2); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/tasks/" + testTaskEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect( + MockMvcResultMatchers.status().isNoContent()); + + List result = taskGroupService + .findByIdWithTasks(testTaskGroupEntityA.getId()) + .getTasks(); + + assertThat(result.size()).isEqualTo(1); + assertThat(result) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityB.getId()); + + } + + + @Test + public void testUpdateTaskName() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); - // TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); - // taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); - // assertThat(testTaskGroupEntityA.getTasks()).hasSize(1); + TaskRequestDto testCreateTaskDto = TestDataUtil.createTestCreateTaskDto(); + testCreateTaskDto.setTaskName("UPDATED"); - // mockMvc.perform( - // MockMvcRequestBuilders.delete("/tasks/" + testTaskEntityA.getId()) - // .contentType(MediaType.APPLICATION_JSON) - // ).andExpect( - // MockMvcResultMatchers.status().isNoContent()); + String taskJson = objectMapper.writeValueAsString(testCreateTaskDto); - // assertThat(testTaskGroupEntityA.getTasks()).isEmpty(); + mockMvc.perform( + MockMvcRequestBuilders.patch("/tasks/" + testTaskEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(taskJson) + ).andExpect( + MockMvcResultMatchers.status().isOk() + ).andExpect( + MockMvcResultMatchers.jsonPath("$.taskName").value("UPDATED") + ); } + @Test + public void testUpdateTaskGroupId() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + + TaskGroupEntity testTaskGroupEntityB = TestDataUtil.createTaskGroupEntityB(); + taskGroupService.save(testTaskGroupEntityB); + + TaskRequestDto testCreateTaskDto = TestDataUtil.createTestCreateTaskDto(); + testCreateTaskDto.setTaskGroupId(testTaskGroupEntityB.getId()); + + String taskJson = objectMapper.writeValueAsString(testCreateTaskDto); + mockMvc.perform( + MockMvcRequestBuilders.patch("/tasks/" + testTaskEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(taskJson) + ).andExpect( + MockMvcResultMatchers.status().isOk() + ).andExpect( + MockMvcResultMatchers.jsonPath("$.taskGroupId").value(testTaskGroupEntityB.getId()) + ); + } + + @Test + public void testUpdateBothTaskGroupIdAndTaskName() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + + TaskGroupEntity testTaskGroupEntityB = TestDataUtil.createTaskGroupEntityB(); + taskGroupService.save(testTaskGroupEntityB); + + TaskRequestDto testCreateTaskDto = TestDataUtil.createTestCreateTaskDto(); + testCreateTaskDto.setTaskGroupId(testTaskGroupEntityB.getId()); + testCreateTaskDto.setTaskName("UPDATED"); + + String taskJson = objectMapper.writeValueAsString(testCreateTaskDto); + + mockMvc.perform( + MockMvcRequestBuilders.patch("/tasks/" + testTaskEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(taskJson) + ).andExpect( + MockMvcResultMatchers.status().isOk() + ).andExpect( + MockMvcResultMatchers.jsonPath("$.taskGroupId").value(testTaskGroupEntityB.getId()) + ).andExpect( + MockMvcResultMatchers.jsonPath("$.taskName").value("UPDATED") + ); + } } diff --git a/src/test/java/com/jonassavas/spring_task_api/controllers/TaskGroupControllerIntegrationTests.java b/src/test/java/com/jonassavas/spring_task_api/controllers/TaskGroupControllerIntegrationTests.java index fc6bdf2..293bec2 100644 --- a/src/test/java/com/jonassavas/spring_task_api/controllers/TaskGroupControllerIntegrationTests.java +++ b/src/test/java/com/jonassavas/spring_task_api/controllers/TaskGroupControllerIntegrationTests.java @@ -10,10 +10,18 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + import com.fasterxml.jackson.databind.ObjectMapper; import com.jonassavas.spring_task_api.TestDataUtil; +import com.jonassavas.spring_task_api.domain.dto.TaskGroupRequestDto; +import com.jonassavas.spring_task_api.domain.entities.TaskEntity; import com.jonassavas.spring_task_api.domain.entities.TaskGroupEntity; import com.jonassavas.spring_task_api.services.TaskGroupService; +import com.jonassavas.spring_task_api.services.TaskService; + @SpringBootTest @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @@ -21,14 +29,19 @@ public class TaskGroupControllerIntegrationTests { private TaskGroupService taskGroupService; + private TaskService taskService; private MockMvc mockMvc; private ObjectMapper objectMapper; @Autowired - public TaskGroupControllerIntegrationTests(MockMvc mockMvc, TaskGroupService taskGroupService, ObjectMapper objectMapper){ + public TaskGroupControllerIntegrationTests(MockMvc mockMvc, + TaskGroupService taskGroupService, + TaskService taskService, + ObjectMapper objectMapper){ this.mockMvc = mockMvc; this.taskGroupService = taskGroupService; + this.taskService = taskService; this.objectMapper = objectMapper; } @@ -57,8 +70,6 @@ public void testThatCreateTaskGroupReturnsSavedTaskGroup() throws Exception{ MockMvcRequestBuilders.post("/taskgroups") .contentType(MediaType.APPLICATION_JSON) .content(taskGroupJson) - ).andExpect( - MockMvcResultMatchers.jsonPath("$.id").isNumber() ).andExpect( MockMvcResultMatchers.jsonPath("$.taskGroupName").value("Task Group A") ); @@ -88,4 +99,213 @@ public void testThatListTaskGroupsReturnsListOfTaskGroups() throws Exception{ MockMvcResultMatchers.jsonPath("$[0].taskGroupName").value("Task Group A") ); } + + + @Test + public void testThatDeleteTaskGroupReturnsHttp204() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + assertThat(taskGroupService.findAll().size()).isEqualTo(1); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/taskgroups/" + testTaskGroupEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect( + MockMvcResultMatchers.status().isNoContent() + ); + + assertThat(taskGroupService.findAll().size()).isEqualTo(0); + } + + + @Test + public void testThatDeleteTaskGroupDeletesCorrectGroup() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + TaskGroupEntity testTaskGroupEntityB = TestDataUtil.createTaskGroupEntityB(); + taskGroupService.save(testTaskGroupEntityB); + + + assertThat(taskGroupService.findAll().size()).isEqualTo(2); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/taskgroups/" + testTaskGroupEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect( + MockMvcResultMatchers.status().isNoContent() + ); + + List result = taskGroupService.findAll(); + assertThat(result.size()).isEqualTo(1); + assertThat(result) + .extracting(TaskGroupEntity::getId) + .containsExactly(testTaskGroupEntityB.getId()); + } + + @Test + public void testThatDeleteTaskGroupDeletesTasks() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + TaskEntity testTaskEntityB = TestDataUtil.createTestTaskEntityB(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityB); + TaskEntity testTaskEntityC = TestDataUtil.createTestTaskEntityC(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityC); + + List result = taskGroupService.findAllWithTasks(); + assertThat(result.size()).isEqualTo(1); + assertThat(result.getFirst().getTasks()) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityA.getId(), + testTaskEntityB.getId(), + testTaskEntityC.getId()); + assertThat(taskService.findAll()) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityA.getId(), + testTaskEntityB.getId(), + testTaskEntityC.getId()); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/taskgroups/" + testTaskGroupEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect( + MockMvcResultMatchers.status().isNoContent() + ); + + assertThat(taskGroupService.findAllWithTasks().size()).isEqualTo(0); + assertThat(taskService.findAll().size()).isEqualTo(0); + } + + + @Test + public void testThatDeleteTaskGroupOnlyDeletesOwnTasks() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + + TaskGroupEntity testTaskGroupEntityB = TestDataUtil.createTaskGroupEntityB(); + taskGroupService.save(testTaskGroupEntityB); + + TaskEntity testTaskEntityB = TestDataUtil.createTestTaskEntityB(); + taskService.createTask(testTaskGroupEntityB.getId(), testTaskEntityB); + TaskEntity testTaskEntityC = TestDataUtil.createTestTaskEntityC(); + taskService.createTask(testTaskGroupEntityB.getId(), testTaskEntityC); + + List result = taskGroupService.findAllWithTasks(); + assertThat(result.size()).isEqualTo(2); + assertThat(result.get(0).getTasks()) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityA.getId()); + assertThat(result.get(1).getTasks()) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityB.getId(), + testTaskEntityC.getId()); + assertThat(taskService.findAll()) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityA.getId(), + testTaskEntityB.getId(), + testTaskEntityC.getId()); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/taskgroups/" + testTaskGroupEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect( + MockMvcResultMatchers.status().isNoContent() + ); + + result = taskGroupService.findAllWithTasks(); + assertThat(result.size()).isEqualTo(1); + assertThat(result) + .extracting(TaskGroupEntity::getId) + .containsExactly(testTaskGroupEntityB.getId()); + assertThat(result.get(0).getTasks()) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityB.getId(), + testTaskEntityC.getId()); + } + + + @Test + public void testThatUpdateTaskGroupReturnsHttp200() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + TaskGroupRequestDto testTaskGroupDtoA = TestDataUtil.createTaskGroupDtoA(); + testTaskGroupDtoA.setTaskGroupName("UPDATED"); + String taskGroupJson = objectMapper.writeValueAsString(testTaskGroupDtoA); + + mockMvc.perform( + MockMvcRequestBuilders.patch("/taskgroups/" + testTaskGroupEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(taskGroupJson) + ).andExpect( + MockMvcResultMatchers.status().isOk() + ).andExpect( + MockMvcResultMatchers.jsonPath("$.taskGroupName").value("UPDATED") + ); + } + + + @Test + public void testThatUpdateTaskGroupKeepsItsTasks() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + + assertThat(taskGroupService.findByIdWithTasks(testTaskGroupEntityA.getId()).getTasks()) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityA.getId()); + + TaskGroupRequestDto testTaskGroupDtoA = TestDataUtil.createTaskGroupDtoA(); + testTaskGroupDtoA.setTaskGroupName("UPDATED"); + String taskGroupJson = objectMapper.writeValueAsString(testTaskGroupDtoA); + + mockMvc.perform( + MockMvcRequestBuilders.patch("/taskgroups/" + testTaskGroupEntityA.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(taskGroupJson) + ).andExpect( + MockMvcResultMatchers.status().isOk() + ).andExpect( + MockMvcResultMatchers.jsonPath("$.taskGroupName").value("UPDATED") + ); + + assertThat(taskGroupService.findByIdWithTasks(testTaskGroupEntityA.getId()).getTasks()) + .extracting(TaskEntity::getId) + .containsExactly(testTaskEntityA.getId()); + } + + + @Test + public void testThatDeleteAllTasksDeletesCorrespondingTasks() throws Exception{ + TaskGroupEntity testTaskGroupEntityA = TestDataUtil.createTaskGroupEntityA(); + taskGroupService.save(testTaskGroupEntityA); + TaskEntity testTaskEntityA = TestDataUtil.createTestTaskEntityA(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityA); + TaskEntity testTaskEntityB = TestDataUtil.createTestTaskEntityB(); + taskService.createTask(testTaskGroupEntityA.getId(), testTaskEntityB); + + + TaskGroupEntity testTaskGroupEntityB = TestDataUtil.createTaskGroupEntityB(); + taskGroupService.save(testTaskGroupEntityB); + TaskEntity testTaskEntityC = TestDataUtil.createTestTaskEntityB(); + taskService.createTask(testTaskGroupEntityB.getId(), testTaskEntityC); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/taskgroups/" + testTaskGroupEntityA.getId() + "/tasks") + .contentType(MediaType.APPLICATION_JSON) + ).andExpect( + MockMvcResultMatchers.status().isNoContent() + ); + + List result = taskService.findAll(); + assertThat(result).extracting(TaskEntity::getId).containsExactly(testTaskEntityC.getId()); + } }