diff --git a/backend/src/main/java/org/example/app/general/common/search/SortOrderBy.java b/backend/src/main/java/org/example/app/general/common/search/SortOrderBy.java index 369a920..dd7f5ab 100644 --- a/backend/src/main/java/org/example/app/general/common/search/SortOrderBy.java +++ b/backend/src/main/java/org/example/app/general/common/search/SortOrderBy.java @@ -79,7 +79,7 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (!super.equals(obj)) { + if (obj == null || getClass() != obj.getClass()) { return false; } SortOrderBy other = (SortOrderBy) obj; diff --git a/backend/src/main/java/org/example/app/general/dataaccess/ApplicationQueryFragment.java b/backend/src/main/java/org/example/app/general/dataaccess/ApplicationQueryFragment.java index ca178ee..7ed45d0 100644 --- a/backend/src/main/java/org/example/app/general/dataaccess/ApplicationQueryFragment.java +++ b/backend/src/main/java/org/example/app/general/dataaccess/ApplicationQueryFragment.java @@ -282,7 +282,7 @@ protected void where(FilteredClause statement, BooleanExpression expression, * @param expression the {@link SimpleExpression} to build the IN-expression from. * @param values the {@link List} of values for the IN-expression. */ - protected void whereIn(FilteredClause statement, SimpleExpression expression, List values) { + public void whereIn(FilteredClause statement, SimpleExpression expression, List values) { BooleanExpression inExpression = null; int size = 0; @@ -305,8 +305,10 @@ protected void whereIn(FilteredClause statement, SimpleExpression expr int end = start + MAX_IN_EXPRESSIONS; partition = values.subList(start, end); start = end; + rest -= MAX_IN_EXPRESSIONS; } else { partition = padList(values.subList(start, size)); + rest = 0; } BooleanExpression newInExpr = expression.in(partition); if (inExpression == null) { diff --git a/backend/src/main/java/org/example/app/task/logic/UcDeleteTaskItem.java b/backend/src/main/java/org/example/app/task/logic/UcDeleteTaskItem.java index 902974f..4ae94c1 100644 --- a/backend/src/main/java/org/example/app/task/logic/UcDeleteTaskItem.java +++ b/backend/src/main/java/org/example/app/task/logic/UcDeleteTaskItem.java @@ -18,7 +18,9 @@ @Transactional public class UcDeleteTaskItem { - private static final Logger LOG = LoggerFactory.getLogger(UcDeleteTaskItem.class); + private static final Logger STATIC_LOG = LoggerFactory.getLogger(UcDeleteTaskItem.class); + + Logger log = STATIC_LOG; @Inject TaskItemRepository taskItemRepository; @@ -41,7 +43,7 @@ public void delete(TaskItemEto item) { Long id = item.getId(); if (id == null) { - LOG.info("TaskItem {} ist transient und kann nicht gelöscht werden", item.getTitle()); + log.info("TaskItem {} ist transient und kann nicht gelöscht werden", item.getTitle()); } this.taskItemRepository.deleteById(id); } diff --git a/backend/src/main/java/org/example/app/task/logic/UcDeleteTaskList.java b/backend/src/main/java/org/example/app/task/logic/UcDeleteTaskList.java index bbef65d..4852b66 100644 --- a/backend/src/main/java/org/example/app/task/logic/UcDeleteTaskList.java +++ b/backend/src/main/java/org/example/app/task/logic/UcDeleteTaskList.java @@ -18,7 +18,8 @@ @Transactional public class UcDeleteTaskList { - private static final Logger LOG = LoggerFactory.getLogger(UcDeleteTaskList.class); + private static final Logger STATIC_LOG = LoggerFactory.getLogger(UcDeleteTaskList.class); + Logger log = STATIC_LOG; @Inject TaskListRepository taskListRepository; @@ -41,7 +42,7 @@ public void delete(TaskListEto list) { Long id = list.getId(); if (id == null) { - LOG.info("TaskItem {} ist transient und kann nicht gelöscht werden", list.getTitle()); + log.info("TaskItem {} ist transient und kann nicht gelöscht werden", list.getTitle()); } this.taskListRepository.deleteById(id); } diff --git a/backend/src/test/java/org/example/app/general/common/search/LikePatternSyntaxTest.java b/backend/src/test/java/org/example/app/general/common/search/LikePatternSyntaxTest.java new file mode 100644 index 0000000..8438ccb --- /dev/null +++ b/backend/src/test/java/org/example/app/general/common/search/LikePatternSyntaxTest.java @@ -0,0 +1,65 @@ +package org.example.app.general.common.search; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class LikePatternSyntaxTest { + @Test + void testConvertGlobToSql() { + String pattern = "*abc?def*"; + String expected = "%abc_def%"; + String result = LikePatternSyntax.SQL.convert(pattern, LikePatternSyntax.GLOB, false); + assertEquals(expected, result); + } + + @Test + void testConvertSqlToGlob() { + String pattern = "%abc_def%"; + String expected = "*abc?def*"; + String result = LikePatternSyntax.GLOB.convert(pattern, LikePatternSyntax.SQL, false); + assertEquals(expected, result); + } + + @Test + void testConvertWithMatchSubstringTrue() { + String pattern = "abc"; + String expected = "%abc%"; + String result = LikePatternSyntax.SQL.convert(pattern, LikePatternSyntax.SQL, true); + assertEquals(expected, result); + } + + @Test + void testConvertNullPattern() { + String result = LikePatternSyntax.SQL.convert(null, LikePatternSyntax.SQL, false); + assertNull(result); + } + + @Test + void testConvertEmptyPatternWithMatchSubstringTrue() { + String result = LikePatternSyntax.SQL.convert("", LikePatternSyntax.SQL, true); + assertEquals("%", result); + } + + @Test + void testAutoDetectGlob() { + assertEquals(LikePatternSyntax.GLOB, LikePatternSyntax.autoDetect("file-*.txt")); + } + + @Test + void testAutoDetectSql() { + assertEquals(LikePatternSyntax.SQL, LikePatternSyntax.autoDetect("user_%_2024")); + } + + @Test + void testAutoDetectNoWildcards() { + assertNull(LikePatternSyntax.autoDetect("plainText")); + } + + @Test + void testEscapedCharactersInPattern() { + String pattern = "*abc\\*def*"; + String result = LikePatternSyntax.SQL.convert(pattern, LikePatternSyntax.GLOB, false); + assertTrue(result.contains("\\%")); + } +} diff --git a/backend/src/test/java/org/example/app/general/common/search/SortOrderByTest.java b/backend/src/test/java/org/example/app/general/common/search/SortOrderByTest.java new file mode 100644 index 0000000..601568e --- /dev/null +++ b/backend/src/test/java/org/example/app/general/common/search/SortOrderByTest.java @@ -0,0 +1,32 @@ +package org.example.app.general.common.search; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class SortOrderByTest { + @Test + void testGetDirectionDefaultsToAsc() { + SortOrderBy sortOrderBy = new SortOrderBy(); + assertEquals(SortOrderDirection.ASC, sortOrderBy.getDirection()); + } + + @Test + void testEqualsAndHashCode() { + SortOrderBy a = new SortOrderBy("status", SortOrderDirection.DESC); + SortOrderBy b = new SortOrderBy("status", SortOrderDirection.DESC); + SortOrderBy c = new SortOrderBy("priority", SortOrderDirection.ASC); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + + assertNotEquals(a, c); + } + + @Test + void testToStringFormat() { + SortOrderBy orderBy = new SortOrderBy("priority", SortOrderDirection.ASC); + assertEquals("priority ASC", orderBy.toString()); + } +} diff --git a/backend/src/test/java/org/example/app/general/dataaccess/ApplicationQueryFragmentTest.java b/backend/src/test/java/org/example/app/general/dataaccess/ApplicationQueryFragmentTest.java new file mode 100644 index 0000000..e791608 --- /dev/null +++ b/backend/src/test/java/org/example/app/general/dataaccess/ApplicationQueryFragmentTest.java @@ -0,0 +1,147 @@ +package org.example.app.general.dataaccess; + +import com.querydsl.core.FilteredClause; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQuery; +import org.example.app.general.common.search.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +public class ApplicationQueryFragmentTest { + private TestableQueryFragment fragment; + + @BeforeEach + void setUp() { + fragment = new TestableQueryFragment(); + } + + @Test + void testNewStringClause_nullValue_returnsNullOrNotNullClause() { + StringExpression expression = Expressions.stringPath("testField"); + + assertThat(fragment.newStringClause(expression, null, StringSearchOperator.EQ, null, false, false)) + .isEqualTo(expression.isNull()); + + assertThat(fragment.newStringClause(expression, null, StringSearchOperator.NE, null, false, false)) + .isEqualTo(expression.isNotNull()); + + assertThrows(IllegalArgumentException.class, () -> + fragment.newStringClause(expression, null, StringSearchOperator.LT, null, false, false)); + } + + @Test + void testNewStringClause_emptyValue_returnsIsEmptyOrNotEmpty() { + StringExpression expression = Expressions.stringPath("testField"); + + assertThat(fragment.newStringClause(expression, "", StringSearchOperator.EQ, null, false, false)) + .isEqualTo(expression.isEmpty()); + + assertThat(fragment.newStringClause(expression, "", StringSearchOperator.NE, null, false, false)) + .isEqualTo(expression.isNotEmpty()); + } + + @Test + void testNewStringClause_ignoreCase_appliesUpperCase() { + StringExpression mockExpression = mock(StringExpression.class); + StringExpression upperMock = mock(StringExpression.class); + when(mockExpression.upper()).thenReturn(upperMock); + when(upperMock.like(anyString())).thenReturn(mock(BooleanExpression.class)); + + fragment.newStringClause(mockExpression, "foo", StringSearchOperator.LIKE, LikePatternSyntax.SQL, true, false); + + verify(mockExpression).upper(); + verify(upperMock).like(anyString()); + } + + @Test + void testNewLikeClause_autoDetectSyntax() { + StringExpression expr = Expressions.stringPath("field"); + BooleanExpression result = fragment.newLikeClause(expr, "abc*", null, false, false); + + assertThat(result).isNotNull(); + } + + @Test + void testWhereIn_withShortList() { + SimpleExpression expr = Expressions.stringPath("field"); + FilteredClause clause = mock(FilteredClause.class); + + // Call the method with a short list + fragment.whereIn(clause, expr, List.of("a", "b", "c")); + + // Capture the BooleanExpression argument passed to where() + ArgumentCaptor captor = ArgumentCaptor.forClass(BooleanExpression.class); + verify(clause).where(captor.capture()); + + // Assert that the captured BooleanExpression is not null + BooleanExpression capturedExpression = captor.getValue(); + assertThat(capturedExpression).isNotNull(); + + // Optionally, assert that the captured expression contains the expected "in" clause + assertThat(capturedExpression.toString()).contains("in"); + assertThat(capturedExpression.toString()).contains("a"); + assertThat(capturedExpression.toString()).contains("b"); + assertThat(capturedExpression.toString()).contains("c"); + + // If duplicates might exist, verify that the expression has no duplicate values + List values = List.of("a", "b", "c"); + assertThat(capturedExpression.toString()).contains(String.join(", ", values)); + } + + + + @Test + void testWhereIn_withEmptyList_logsAndAddsFalseClause() { + SimpleExpression expr = Expressions.stringPath("field"); + FilteredClause clause = mock(FilteredClause.class); + + fragment.whereIn(clause, expr, Collections.emptyList()); + + verify(clause).where(eq(Expressions.ONE.eq(Expressions.ZERO))); + } + + @Test + void testWhereIn_withLargeList_partitionsCorrectly() { + SimpleExpression expr = Expressions.stringPath("field"); + FilteredClause clause = mock(FilteredClause.class); + + List largeList = Collections.nCopies(1050, "val"); + + fragment.whereIn(clause, expr, largeList); + + ArgumentCaptor captor = ArgumentCaptor.forClass(BooleanExpression.class); + verify(clause).where(captor.capture()); + + assertThat(captor.getValue()).isNotNull(); // not testing deep equality of OR-ed clauses + } + + @Test + void testFindPaginated_withoutTotal() { + JPAQuery query = mock(JPAQuery.class); + when(query.fetch()).thenReturn(List.of("item1", "item2")); + + SearchCriteria criteria = new TestSearchCriteria(); + criteria.setDetermineTotal(false); + + var result = fragment.findPaginated(criteria, query); + + assertThat(result.getContent()).hasSize(2); + assertThat(result.getTotalElements()).isEqualTo(2); + } + + // Internal testable subclass for access + static class TestableQueryFragment extends ApplicationQueryFragment { + // no-op: expose protected methods for testing + } +} \ No newline at end of file diff --git a/backend/src/test/java/org/example/app/general/dataaccess/TestSearchCriteria.java b/backend/src/test/java/org/example/app/general/dataaccess/TestSearchCriteria.java new file mode 100644 index 0000000..6ac6c33 --- /dev/null +++ b/backend/src/test/java/org/example/app/general/dataaccess/TestSearchCriteria.java @@ -0,0 +1,16 @@ +package org.example.app.general.dataaccess; + +import org.example.app.general.common.search.SearchCriteria; + +class TestSearchCriteria extends SearchCriteria { + private boolean determineTotal; + + @Override + public boolean isDetermineTotal() { + return determineTotal; + } + + public void setDetermineTotal(boolean determineTotal) { + this.determineTotal = determineTotal; + } +} \ No newline at end of file diff --git a/backend/src/test/java/org/example/app/task/dataaccess/TaskListRepositoryTest.java b/backend/src/test/java/org/example/app/task/dataaccess/TaskListRepositoryTest.java index d8b4389..dc2a4f2 100644 --- a/backend/src/test/java/org/example/app/task/dataaccess/TaskListRepositoryTest.java +++ b/backend/src/test/java/org/example/app/task/dataaccess/TaskListRepositoryTest.java @@ -129,7 +129,7 @@ void GivenAnExistingTaskListWithItems_WhenDeleted_ThenItShouldBeNotFindableById( @Test @TestTransaction - void GivenAnExistingTaskListWithItems_WhenDeleted_TthenItsItemsShouldBeDeletedAsWell() { + void GivenAnExistingTaskListWithItems_WhenDeleted_ThenItsItemsShouldBeDeletedAsWell() { var idsOfTaskItems = TaskListRepositoryTest.this.classUnderTest.findById(EXISTING_TASK_LIST_ID).get().getItems() .stream().map(TaskItemEntity::getId).collect(Collectors.toList()); diff --git a/backend/src/test/java/org/example/app/task/logic/TaskItemMapperTest.java b/backend/src/test/java/org/example/app/task/logic/TaskItemMapperTest.java new file mode 100644 index 0000000..0ef29c6 --- /dev/null +++ b/backend/src/test/java/org/example/app/task/logic/TaskItemMapperTest.java @@ -0,0 +1,37 @@ +package org.example.app.task.logic; + +import org.example.app.task.common.TaskItemEto; +import org.example.app.task.dataaccess.TaskItemEntity; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.List; + +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +public class TaskItemMapperTest { + @Test + void toEtos_shouldReturnMappedList() { + // Given + TaskItemEntity entity1 = new TaskItemEntity(); + TaskItemEntity entity2 = new TaskItemEntity(); + TaskItemEto eto1 = new TaskItemEto(); + TaskItemEto eto2 = new TaskItemEto(); + + TaskItemMapper mapper = Mockito.spy(TaskItemMapper.class); + + doReturn(eto1).when(mapper).toEto(entity1); + doReturn(eto2).when(mapper).toEto(entity2); + + // When + List result = mapper.toEtos(List.of(entity1, entity2)); + + // Then + assertEquals(List.of(eto1, eto2), result); + verify(mapper).toEto(entity1); + verify(mapper).toEto(entity2); + } +} diff --git a/backend/src/test/java/org/example/app/task/logic/UcDeleteTaskItemTest.java b/backend/src/test/java/org/example/app/task/logic/UcDeleteTaskItemTest.java new file mode 100644 index 0000000..eee5a23 --- /dev/null +++ b/backend/src/test/java/org/example/app/task/logic/UcDeleteTaskItemTest.java @@ -0,0 +1,64 @@ +package org.example.app.task.logic; + +import org.example.app.task.common.TaskItemEto; +import org.example.app.task.dataaccess.TaskItemRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; + +import static org.mockito.Mockito.*; + +public class UcDeleteTaskItemTest { + private TaskItemRepository repository; + private UcDeleteTaskItem uc; + private Logger testLogger; + + + @BeforeEach + void setup() { + repository = mock(TaskItemRepository.class); + uc = new UcDeleteTaskItem(); + uc.taskItemRepository = repository; + + // Inject mocked logger + testLogger = mock(Logger.class); + uc.log = testLogger; + } + + @Test + void delete_byId_shouldCallRepository() { + Long id = 42L; + + uc.delete(id); + + verify(repository).deleteById(id); + } + + @Test + void delete_byEto_shouldCallRepository_whenIdIsPresent() { + Long id = 77L; + TaskItemEto eto = mock(TaskItemEto.class); + when(eto.getId()).thenReturn(id); + + uc.delete(eto); + + verify(repository).deleteById(id); + } + + @Test + void delete_byEto_shouldLogButStillCallDelete_whenIdIsNull() { + // Given + TaskItemEto eto = mock(TaskItemEto.class); + when(eto.getId()).thenReturn(null); + when(eto.getTitle()).thenReturn("Untitled"); + + // When + uc.delete(eto); + + // Then + verify(repository).deleteById(null); + + // Capture the logged message + verify(testLogger).info(contains("TaskItem {} ist transient"), eq("Untitled")); + } +} diff --git a/backend/src/test/java/org/example/app/task/logic/UcDeleteTaskListTest.java b/backend/src/test/java/org/example/app/task/logic/UcDeleteTaskListTest.java new file mode 100644 index 0000000..690730f --- /dev/null +++ b/backend/src/test/java/org/example/app/task/logic/UcDeleteTaskListTest.java @@ -0,0 +1,68 @@ +package org.example.app.task.logic; + +import org.example.app.task.common.TaskListEto; +import org.example.app.task.dataaccess.TaskListRepository; +import org.example.app.task.logic.UcDeleteTaskList; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; + +import static org.mockito.Mockito.*; + +public class UcDeleteTaskListTest { + private TaskListRepository repository; + private UcDeleteTaskList uc; + private Logger testLogger; + + @BeforeEach + void setUp() { + repository = mock(TaskListRepository.class); + uc = new UcDeleteTaskList(); + uc.taskListRepository = repository; + + // Inject a mocked logger to capture log calls + testLogger = mock(Logger.class); + uc.log = testLogger; + } + + @Test + void delete_byId_shouldCallRepository() { + // Given + Long id = 42L; + + // When + uc.delete(id); + + // Then + verify(repository).deleteById(id); + } + + @Test + void delete_byEto_shouldCallRepository_whenIdNotNull() { + // Given + TaskListEto eto = mock(TaskListEto.class); + when(eto.getId()).thenReturn(1L); + + // When + uc.delete(eto); + + // Then + verify(repository).deleteById(1L); + verifyNoInteractions(testLogger); // no logging expected + } + + @Test + void delete_byEto_shouldLog_whenIdIsNull() { + // Given + TaskListEto eto = mock(TaskListEto.class); + when(eto.getId()).thenReturn(null); + when(eto.getTitle()).thenReturn("Daily Tasks"); + + // When + uc.delete(eto); + + // Then + verify(repository).deleteById(null); + verify(testLogger).info(contains("TaskItem {} ist transient"), eq("Daily Tasks")); + } +} diff --git a/backend/src/test/java/org/example/app/task/logic/UcFindTaskListTest.java b/backend/src/test/java/org/example/app/task/logic/UcFindTaskListTest.java new file mode 100644 index 0000000..aabc202 --- /dev/null +++ b/backend/src/test/java/org/example/app/task/logic/UcFindTaskListTest.java @@ -0,0 +1,108 @@ +package org.example.app.task.logic; + +import org.example.app.task.common.TaskListCto; +import org.example.app.task.common.TaskListEto; +import org.example.app.task.dataaccess.TaskListEntity; +import org.example.app.task.dataaccess.TaskListRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +public class UcFindTaskListTest { + private UcFindTaskList uc; + private TaskListRepository repository; + private TaskListMapper listMapper; + private TaskItemMapper itemMapper; + + @BeforeEach + void setUp() { + repository = mock(TaskListRepository.class); + listMapper = mock(TaskListMapper.class); + itemMapper = mock(TaskItemMapper.class); + + uc = new UcFindTaskList(); + uc.taskListRepository = repository; + uc.taskListMapper = listMapper; + uc.taskItemMapper = itemMapper; + } + + @Test + void findById_shouldReturnMappedEto_whenEntityExists() { + // Given + Long id = 1L; + TaskListEntity entity = new TaskListEntity(); + TaskListEto eto = new TaskListEto(); + + when(repository.findById(id)).thenReturn(Optional.of(entity)); + when(listMapper.toEto(entity)).thenReturn(eto); + + // When + TaskListEto result = uc.findById(id); + + // Then + assertThat(result).isEqualTo(eto); + verify(repository).findById(id); + verify(listMapper).toEto(entity); + } + + @Test + void findById_shouldReturnNull_whenEntityDoesNotExist() { + // Given + Long id = 42L; + when(repository.findById(id)).thenReturn(Optional.empty()); + + // When + TaskListEto result = uc.findById(id); + + // Then + assertThat(result).isNull(); + verify(repository).findById(id); + verifyNoInteractions(listMapper); + } + + @Test + void findWithItems_shouldReturnMappedCto_whenEntityExists() { + // Given + Long id = 100L; + TaskListEntity entity = new TaskListEntity(); + entity.setItems(Collections.emptyList()); + + TaskListEto eto = new TaskListEto(); + + when(repository.findById(id)).thenReturn(Optional.of(entity)); + when(listMapper.toEto(entity)).thenReturn(eto); + when(itemMapper.toEtos(entity.getItems())).thenReturn(Collections.emptyList()); + + // When + TaskListCto result = uc.findWithItems(id); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getList()).isEqualTo(eto); + assertThat(result.getItems()).isEmpty(); + + verify(repository).findById(id); + verify(listMapper).toEto(entity); + verify(itemMapper).toEtos(entity.getItems()); + } + + @Test + void findWithItems_shouldReturnNull_whenEntityDoesNotExist() { + // Given + Long id = 99L; + when(repository.findById(id)).thenReturn(Optional.empty()); + + // When + TaskListCto result = uc.findWithItems(id); + + // Then + assertThat(result).isNull(); + verify(repository).findById(id); + verifyNoInteractions(listMapper, itemMapper); + } +} diff --git a/backend/src/test/java/org/example/app/task/logic/UcSaveTaskListTest.java b/backend/src/test/java/org/example/app/task/logic/UcSaveTaskListTest.java new file mode 100644 index 0000000..92ff59a --- /dev/null +++ b/backend/src/test/java/org/example/app/task/logic/UcSaveTaskListTest.java @@ -0,0 +1,47 @@ +package org.example.app.task.logic; + +import org.example.app.task.common.TaskListEto; +import org.example.app.task.dataaccess.TaskListEntity; +import org.example.app.task.dataaccess.TaskListRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +class UcSaveTaskListTest { + + private UcSaveTaskList uc; + private TaskListRepository repository; + private TaskListMapper mapper; + + @BeforeEach + void setUp() { + repository = mock(TaskListRepository.class); + mapper = mock(TaskListMapper.class); + + uc = new UcSaveTaskList(); + uc.taskListRepository = repository; + uc.taskListMapper = mapper; + } + + @Test + void save_shouldConvertAndReturnGeneratedId() { + // Given + TaskListEto eto = new TaskListEto(); + TaskListEntity entity = new TaskListEntity(); + TaskListEntity savedEntity = new TaskListEntity(); + savedEntity.setId(123L); + + when(mapper.toEntity(eto)).thenReturn(entity); + when(repository.save(entity)).thenReturn(savedEntity); + + // When + Long result = uc.save(eto); + + // Then + assertThat(result).isEqualTo(123L); + verify(mapper).toEntity(eto); + verify(repository).save(entity); + } +} \ No newline at end of file