Saving reading position
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
package com.storycove.service;
|
||||
|
||||
import com.storycove.entity.Story;
|
||||
import com.storycove.repository.StoryRepository;
|
||||
import com.storycove.repository.TagRepository;
|
||||
import com.storycove.service.exception.ResourceNotFoundException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("Story Service Unit Tests - Reading Progress")
|
||||
class StoryServiceTest {
|
||||
|
||||
@Mock
|
||||
private StoryRepository storyRepository;
|
||||
|
||||
@Mock
|
||||
private TagRepository tagRepository;
|
||||
|
||||
private StoryService storyService;
|
||||
private Story testStory;
|
||||
private UUID testId;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testId = UUID.randomUUID();
|
||||
testStory = new Story("Test Story");
|
||||
testStory.setId(testId);
|
||||
testStory.setContentHtml("<p>Test content for reading progress tracking</p>");
|
||||
|
||||
// Create StoryService with only required repositories, all services can be null for these tests
|
||||
storyService = new StoryService(
|
||||
storyRepository,
|
||||
tagRepository,
|
||||
null, // authorService - not needed for reading progress tests
|
||||
null, // tagService - not needed for reading progress tests
|
||||
null, // seriesService - not needed for reading progress tests
|
||||
null, // sanitizationService - not needed for reading progress tests
|
||||
null // typesenseService - will test both with and without
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should update reading progress successfully")
|
||||
void shouldUpdateReadingProgress() {
|
||||
Integer position = 150;
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.of(testStory));
|
||||
when(storyRepository.save(any(Story.class))).thenReturn(testStory);
|
||||
|
||||
Story result = storyService.updateReadingProgress(testId, position);
|
||||
|
||||
assertEquals(position, result.getReadingPosition());
|
||||
assertNotNull(result.getLastReadAt());
|
||||
verify(storyRepository).findById(testId);
|
||||
verify(storyRepository).save(testStory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should update reading progress with zero position")
|
||||
void shouldUpdateReadingProgressWithZeroPosition() {
|
||||
Integer position = 0;
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.of(testStory));
|
||||
when(storyRepository.save(any(Story.class))).thenReturn(testStory);
|
||||
|
||||
Story result = storyService.updateReadingProgress(testId, position);
|
||||
|
||||
assertEquals(0, result.getReadingPosition());
|
||||
assertNotNull(result.getLastReadAt());
|
||||
verify(storyRepository).save(testStory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw exception for negative reading position")
|
||||
void shouldThrowExceptionForNegativeReadingPosition() {
|
||||
Integer position = -1;
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> storyService.updateReadingProgress(testId, position));
|
||||
|
||||
verify(storyRepository, never()).findById(any());
|
||||
verify(storyRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null reading position")
|
||||
void shouldHandleNullReadingPosition() {
|
||||
Integer position = null;
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.of(testStory));
|
||||
when(storyRepository.save(any(Story.class))).thenReturn(testStory);
|
||||
|
||||
Story result = storyService.updateReadingProgress(testId, position);
|
||||
|
||||
assertNull(result.getReadingPosition());
|
||||
assertNotNull(result.getLastReadAt());
|
||||
verify(storyRepository).save(testStory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw exception when story not found for reading progress update")
|
||||
void shouldThrowExceptionWhenStoryNotFoundForReadingProgress() {
|
||||
Integer position = 100;
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.empty());
|
||||
|
||||
assertThrows(ResourceNotFoundException.class,
|
||||
() -> storyService.updateReadingProgress(testId, position));
|
||||
|
||||
verify(storyRepository).findById(testId);
|
||||
verify(storyRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should mark story as read")
|
||||
void shouldMarkStoryAsRead() {
|
||||
Boolean isRead = true;
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.of(testStory));
|
||||
when(storyRepository.save(any(Story.class))).thenReturn(testStory);
|
||||
|
||||
Story result = storyService.updateReadingStatus(testId, isRead);
|
||||
|
||||
assertTrue(result.getIsRead());
|
||||
assertNotNull(result.getLastReadAt());
|
||||
// When marked as read, position should be set to content length
|
||||
assertTrue(result.getReadingPosition() > 0);
|
||||
verify(storyRepository).findById(testId);
|
||||
verify(storyRepository).save(testStory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should mark story as unread")
|
||||
void shouldMarkStoryAsUnread() {
|
||||
Boolean isRead = false;
|
||||
// First mark story as read to test transition
|
||||
testStory.markAsRead();
|
||||
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.of(testStory));
|
||||
when(storyRepository.save(any(Story.class))).thenReturn(testStory);
|
||||
|
||||
Story result = storyService.updateReadingStatus(testId, isRead);
|
||||
|
||||
assertFalse(result.getIsRead());
|
||||
assertNotNull(result.getLastReadAt());
|
||||
verify(storyRepository).save(testStory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null reading status")
|
||||
void shouldHandleNullReadingStatus() {
|
||||
Boolean isRead = null;
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.of(testStory));
|
||||
when(storyRepository.save(any(Story.class))).thenReturn(testStory);
|
||||
|
||||
Story result = storyService.updateReadingStatus(testId, isRead);
|
||||
|
||||
assertFalse(result.getIsRead());
|
||||
assertNotNull(result.getLastReadAt());
|
||||
verify(storyRepository).save(testStory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw exception when story not found for reading status update")
|
||||
void shouldThrowExceptionWhenStoryNotFoundForReadingStatus() {
|
||||
Boolean isRead = true;
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.empty());
|
||||
|
||||
assertThrows(ResourceNotFoundException.class,
|
||||
() -> storyService.updateReadingStatus(testId, isRead));
|
||||
|
||||
verify(storyRepository).findById(testId);
|
||||
verify(storyRepository, never()).save(any());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName("Should update lastReadAt timestamp when updating progress")
|
||||
void shouldUpdateLastReadAtWhenUpdatingProgress() {
|
||||
Integer position = 50;
|
||||
LocalDateTime beforeUpdate = LocalDateTime.now().minusMinutes(1);
|
||||
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.of(testStory));
|
||||
when(storyRepository.save(any(Story.class))).thenReturn(testStory);
|
||||
|
||||
Story result = storyService.updateReadingProgress(testId, position);
|
||||
|
||||
assertNotNull(result.getLastReadAt());
|
||||
assertTrue(result.getLastReadAt().isAfter(beforeUpdate));
|
||||
verify(storyRepository).save(testStory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should update lastReadAt timestamp when updating status")
|
||||
void shouldUpdateLastReadAtWhenUpdatingStatus() {
|
||||
Boolean isRead = true;
|
||||
LocalDateTime beforeUpdate = LocalDateTime.now().minusMinutes(1);
|
||||
|
||||
when(storyRepository.findById(testId)).thenReturn(Optional.of(testStory));
|
||||
when(storyRepository.save(any(Story.class))).thenReturn(testStory);
|
||||
|
||||
Story result = storyService.updateReadingStatus(testId, isRead);
|
||||
|
||||
assertNotNull(result.getLastReadAt());
|
||||
assertTrue(result.getLastReadAt().isAfter(beforeUpdate));
|
||||
verify(storyRepository).save(testStory);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user