feat(watch): 支持设备观看与登录合并
This commit is contained in:
1 parent
4b0fbd73d3
commit
848c746505
14 files changed
+613
-70
No files matched your search
+65
@@ -8,9 +8,13 @@ import com.fruitshorts.app.service.AppCatalogService;
|
||||
import com.fruitshorts.app.vo.AppEpisodeStatsVO;
|
||||
import com.fruitshorts.common.exception.ErrorCode;
|
||||
import com.fruitshorts.common.pojo.CursorResult;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.interaction.InteractionIdentity;
|
||||
import com.fruitshorts.interaction.service.LikeService;
|
||||
import com.fruitshorts.playback.service.FavoriteService;
|
||||
import com.fruitshorts.playback.service.PlaybackService;
|
||||
import com.fruitshorts.playback.service.WatchProgressService;
|
||||
import com.fruitshorts.playback.vo.ProgressRespVO;
|
||||
import com.fruitshorts.content.service.EpisodeService;
|
||||
import com.fruitshorts.framework.interaction.InteractionIdentityResolver;
|
||||
import com.fruitshorts.app.service.AnonymousInteractionRateLimiter;
|
||||
@@ -42,9 +46,11 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -63,6 +69,10 @@ class AppCommentCountAnonymousSecurityTest {
|
||||
private AppCatalogService catalogService;
|
||||
@Autowired
|
||||
private AppEpisodeStatsService episodeStatsService;
|
||||
@Autowired
|
||||
private FavoriteService favoriteService;
|
||||
@Autowired
|
||||
private WatchProgressService watchProgressService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -78,6 +88,16 @@ class AppCommentCountAnonymousSecurityTest {
|
||||
stats.setLiked(true);
|
||||
stats.setFavorited(false);
|
||||
given(episodeStatsService.get(11L, null, "device-a")).willReturn(stats);
|
||||
given(favoriteService.page(any(InteractionIdentity.class), any(Integer.class), any(Integer.class)))
|
||||
.willReturn(new PageResult<>(0L, List.of()));
|
||||
given(catalogService.toCards(List.of())).willReturn(List.of());
|
||||
given(watchProgressService.recentPage(
|
||||
any(InteractionIdentity.class), any(Integer.class), any(Integer.class)))
|
||||
.willReturn(new PageResult<>(0L, List.of()));
|
||||
given(watchProgressService.ofDrama(any(InteractionIdentity.class), any(Long.class)))
|
||||
.willReturn(null);
|
||||
given(watchProgressService.save(any(InteractionIdentity.class), any()))
|
||||
.willReturn(new ProgressRespVO());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,6 +148,35 @@ class AppCommentCountAnonymousSecurityTest {
|
||||
.andExpect(jsonPath("$.data.favoriteCount").value(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_allow_anonymous_library_and_watch_operations() throws Exception {
|
||||
mockMvc.perform(get("/app/favorites").header("X-Device-Id", "device-a"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
mockMvc.perform(get("/app/watch/recent").header("X-Device-Id", "device-a"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
mockMvc.perform(get("/app/watch/progress")
|
||||
.header("X-Device-Id", "device-a")
|
||||
.param("dramaId", "7"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
mockMvc.perform(post("/app/watch/progress")
|
||||
.header("X-Device-Id", "device-a")
|
||||
.contentType("application/json")
|
||||
.content("{\"episodeId\":11,\"positionMs\":1000}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
mockMvc.perform(delete("/app/watch/progress/7")
|
||||
.header("X-Device-Id", "device-a"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
mockMvc.perform(delete("/app/watch/progress")
|
||||
.header("X-Device-Id", "device-a"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_keep_paid_and_entitlement_operations_login_required() throws Exception {
|
||||
mockMvc.perform(post("/app/episodes/11/unlock"))
|
||||
@@ -183,6 +232,11 @@ class AppCommentCountAnonymousSecurityTest {
|
||||
return mock(FavoriteService.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
WatchProgressService watchProgressService() {
|
||||
return mock(WatchProgressService.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
AppInteractionController appInteractionController(
|
||||
LikeService likeService,
|
||||
@@ -207,6 +261,17 @@ class AppCommentCountAnonymousSecurityTest {
|
||||
rateLimiter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
AppWatchController appWatchController(
|
||||
WatchProgressService watchProgressService,
|
||||
InteractionIdentityResolver identityResolver,
|
||||
AnonymousInteractionRateLimiter rateLimiter) {
|
||||
return new AppWatchController(
|
||||
watchProgressService,
|
||||
identityResolver,
|
||||
rateLimiter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
AppEpisodeStatsService appEpisodeStatsService() {
|
||||
return mock(AppEpisodeStatsService.class);
|
||||
|
||||
+20
-14
@@ -6,6 +6,7 @@ import com.fruitshorts.app.service.AppCatalogService;
|
||||
import com.fruitshorts.app.support.ControllerContractSupport;
|
||||
import com.fruitshorts.app.vo.AppDramaCardVO;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.interaction.InteractionIdentity;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.framework.security.LoginUser;
|
||||
import com.fruitshorts.framework.interaction.InteractionIdentityResolver;
|
||||
@@ -24,6 +25,8 @@ import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
@@ -46,14 +49,17 @@ class AppLibraryPagingContractTest {
|
||||
favoriteService = mock(FavoriteService.class);
|
||||
catalogService = mock(AppCatalogService.class);
|
||||
progressService = mock(WatchProgressService.class);
|
||||
InteractionIdentityResolver identityResolver = new InteractionIdentityResolver();
|
||||
AnonymousInteractionRateLimiter rateLimiter =
|
||||
mock(AnonymousInteractionRateLimiter.class);
|
||||
favoriteMvc = ControllerContractSupport.mockMvcOf(
|
||||
new AppFavoriteController(
|
||||
favoriteService,
|
||||
catalogService,
|
||||
mock(InteractionIdentityResolver.class),
|
||||
mock(AnonymousInteractionRateLimiter.class)));
|
||||
identityResolver,
|
||||
rateLimiter));
|
||||
watchMvc = ControllerContractSupport.mockMvcOf(
|
||||
new AppWatchController(progressService));
|
||||
new AppWatchController(progressService, identityResolver, rateLimiter));
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
new LoginUser(USER_ID, "app-user", "APP_USER", List.of()),
|
||||
@@ -72,9 +78,9 @@ class AppLibraryPagingContractTest {
|
||||
Drama first = drama(101L, "收藏一");
|
||||
Drama second = drama(102L, "收藏二");
|
||||
Drama third = drama(103L, "收藏三");
|
||||
given(favoriteService.page(USER_ID, 1, 20))
|
||||
given(favoriteService.page(any(InteractionIdentity.class), eq(1), eq(20)))
|
||||
.willReturn(new PageResult<>(21L, List.of(first, second)));
|
||||
given(favoriteService.page(USER_ID, 2, 20))
|
||||
given(favoriteService.page(any(InteractionIdentity.class), eq(2), eq(20)))
|
||||
.willReturn(new PageResult<>(21L, List.of(third)));
|
||||
given(catalogService.toCards(List.of(first, second)))
|
||||
.willReturn(List.of(card(101L), card(102L)));
|
||||
@@ -95,8 +101,8 @@ class AppLibraryPagingContractTest {
|
||||
.andExpect(jsonPath("$.data.list.length()").value(1))
|
||||
.andExpect(jsonPath("$.data.list[0].id").value("103"));
|
||||
|
||||
verify(favoriteService).page(USER_ID, 1, 20);
|
||||
verify(favoriteService).page(USER_ID, 2, 20);
|
||||
verify(favoriteService).page(any(InteractionIdentity.class), eq(1), eq(20));
|
||||
verify(favoriteService).page(any(InteractionIdentity.class), eq(2), eq(20));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -104,9 +110,9 @@ class AppLibraryPagingContractTest {
|
||||
ProgressRespVO first = progress(12L, 88L, 6, "剧名一", 68);
|
||||
ProgressRespVO second = progress(13L, 99L, 2, "剧名二", 20);
|
||||
ProgressRespVO third = progress(14L, 100L, 1, "剧名三", 16);
|
||||
given(progressService.recentPage(USER_ID, 1, 20))
|
||||
given(progressService.recentPage(any(InteractionIdentity.class), eq(1), eq(20)))
|
||||
.willReturn(new PageResult<>(23L, List.of(first, second)));
|
||||
given(progressService.recentPage(USER_ID, 2, 20))
|
||||
given(progressService.recentPage(any(InteractionIdentity.class), eq(2), eq(20)))
|
||||
.willReturn(new PageResult<>(23L, List.of(third)));
|
||||
|
||||
watchMvc.perform(get("/app/watch/recent"))
|
||||
@@ -126,14 +132,14 @@ class AppLibraryPagingContractTest {
|
||||
.andExpect(jsonPath("$.data.list[0].dramaId").value("14"))
|
||||
.andExpect(jsonPath("$.data.list[0].dramaTitle").value("剧名三"));
|
||||
|
||||
verify(progressService).recentPage(USER_ID, 1, 20);
|
||||
verify(progressService).recentPage(USER_ID, 2, 20);
|
||||
verify(progressService).recentPage(any(InteractionIdentity.class), eq(1), eq(20));
|
||||
verify(progressService).recentPage(any(InteractionIdentity.class), eq(2), eq(20));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_keep_total_and_return_empty_list_when_watch_page_is_out_of_range()
|
||||
throws Exception {
|
||||
given(progressService.recentPage(USER_ID, 9, 20))
|
||||
given(progressService.recentPage(any(InteractionIdentity.class), eq(9), eq(20)))
|
||||
.willReturn(new PageResult<>(23L, List.of()));
|
||||
|
||||
watchMvc.perform(get("/app/watch/recent").param("pageIndex", "9"))
|
||||
@@ -146,7 +152,7 @@ class AppLibraryPagingContractTest {
|
||||
@Test
|
||||
void should_ignore_removed_limit_and_still_return_page_result() throws Exception {
|
||||
// limit 已从契约移除:即使客户端误传,也应走分页并返回 PageResult。
|
||||
given(progressService.recentPage(USER_ID, 1, 20))
|
||||
given(progressService.recentPage(any(InteractionIdentity.class), eq(1), eq(20)))
|
||||
.willReturn(new PageResult<>(3L, List.of(progress(12L, 88L, 6, "剧名一", 68))));
|
||||
|
||||
watchMvc.perform(get("/app/watch/recent").param("limit", "5"))
|
||||
@@ -156,7 +162,7 @@ class AppLibraryPagingContractTest {
|
||||
.andExpect(jsonPath("$.data.list.length()").value(1))
|
||||
.andExpect(jsonPath("$.data.list[0].dramaTitle").value("剧名一"));
|
||||
|
||||
verify(progressService).recentPage(USER_ID, 1, 20);
|
||||
verify(progressService).recentPage(any(InteractionIdentity.class), eq(1), eq(20));
|
||||
}
|
||||
|
||||
private Drama drama(Long id, String title) {
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.app.service;
|
||||
|
||||
import com.fruitshorts.interaction.service.CommentService;
|
||||
import com.fruitshorts.interaction.service.LikeService;
|
||||
import com.fruitshorts.playback.service.FavoriteService;
|
||||
import com.fruitshorts.playback.service.WatchProgressService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
class InteractionAccountMergeServiceTest {
|
||||
|
||||
@Test
|
||||
void should_merge_all_device_interactions_and_watch_data() {
|
||||
LikeService likeService = mock(LikeService.class);
|
||||
FavoriteService favoriteService = mock(FavoriteService.class);
|
||||
CommentService commentService = mock(CommentService.class);
|
||||
WatchProgressService watchService = mock(WatchProgressService.class);
|
||||
given(likeService.mergeDeviceIntoUser(9L, "device-a"))
|
||||
.willReturn(new LikeService.MergeResult(1, 0));
|
||||
given(favoriteService.mergeDeviceIntoUser(9L, "device-a"))
|
||||
.willReturn(new FavoriteService.MergeResult(1, 0));
|
||||
given(commentService.mergeDeviceIntoUser(9L, "device-a"))
|
||||
.willReturn(new CommentService.MergeResult(1, 0));
|
||||
given(watchService.mergeDeviceIntoUser(9L, "device-a"))
|
||||
.willReturn(new WatchProgressService.MergeResult(2, 1, 3, 2));
|
||||
InteractionAccountMergeService service = new InteractionAccountMergeService(
|
||||
likeService, favoriteService, commentService, watchService);
|
||||
|
||||
service.merge(9L, "device-a");
|
||||
|
||||
verify(likeService).mergeDeviceIntoUser(9L, "device-a");
|
||||
verify(favoriteService).mergeDeviceIntoUser(9L, "device-a");
|
||||
verify(commentService).mergeDeviceIntoUser(9L, "device-a");
|
||||
verify(watchService).mergeDeviceIntoUser(9L, "device-a");
|
||||
}
|
||||
}
|
||||
+18
@@ -157,6 +157,24 @@ class FavoriteServiceTest {
|
||||
verify(dramaService).listPublishedByIds(List.of(51L, 52L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_page_anonymous_favorites_by_device() {
|
||||
Page<UserFavorite> databasePage = new Page<>(1, 20, 0);
|
||||
databasePage.setRecords(List.of());
|
||||
given(favoriteMapper.selectPage(any(Page.class), any())).willReturn(databasePage);
|
||||
|
||||
favoriteService.page(
|
||||
new InteractionIdentity(null, "device-a"), 1, 20);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<Wrapper<UserFavorite>> wrapperCaptor =
|
||||
ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(favoriteMapper).selectPage(any(Page.class), wrapperCaptor.capture());
|
||||
assertThat(wrapperCaptor.getValue().getSqlSegment())
|
||||
.contains("user_id IS NULL")
|
||||
.contains("device_id");
|
||||
}
|
||||
|
||||
private UserFavorite favorite(Long dramaId) {
|
||||
UserFavorite favorite = new UserFavorite();
|
||||
favorite.setDramaId(dramaId);
|
||||
|
||||
+128
-2
@@ -7,11 +7,13 @@ import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.interaction.InteractionIdentity;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
import com.fruitshorts.content.service.EpisodeService;
|
||||
import com.fruitshorts.playback.dal.entity.WatchProgress;
|
||||
import com.fruitshorts.playback.dal.entity.WatchHistory;
|
||||
import com.fruitshorts.playback.dal.mapper.WatchHistoryMapper;
|
||||
import com.fruitshorts.playback.dal.mapper.WatchProgressMapper;
|
||||
import com.fruitshorts.playback.vo.ProgressRespVO;
|
||||
@@ -43,6 +45,7 @@ class WatchProgressServiceTest {
|
||||
}
|
||||
|
||||
private WatchProgressMapper progressMapper;
|
||||
private WatchHistoryMapper historyMapper;
|
||||
private DramaService dramaService;
|
||||
private EpisodeService episodeService;
|
||||
private WatchProgressService progressService;
|
||||
@@ -50,11 +53,12 @@ class WatchProgressServiceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
progressMapper = mock(WatchProgressMapper.class);
|
||||
historyMapper = mock(WatchHistoryMapper.class);
|
||||
dramaService = mock(DramaService.class);
|
||||
episodeService = mock(EpisodeService.class);
|
||||
progressService = new WatchProgressService(
|
||||
progressMapper,
|
||||
mock(WatchHistoryMapper.class),
|
||||
historyMapper,
|
||||
dramaService,
|
||||
episodeService);
|
||||
}
|
||||
@@ -120,7 +124,7 @@ class WatchProgressServiceTest {
|
||||
hidden.setId(9L);
|
||||
hidden.setUserId(7L);
|
||||
hidden.setDeleted(1);
|
||||
given(progressMapper.selectAny(7L, 5L)).willReturn(hidden);
|
||||
given(progressMapper.selectAnyForIdentity(7L, "", 5L)).willReturn(hidden);
|
||||
given(progressMapper.selectById(9L)).willReturn(hidden);
|
||||
ProgressSaveReqVO req = new ProgressSaveReqVO();
|
||||
req.setEpisodeId(88L);
|
||||
@@ -134,6 +138,117 @@ class WatchProgressServiceTest {
|
||||
assertThat(hidden.getDeleted()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_save_and_read_anonymous_progress_by_device() {
|
||||
DramaEpisode episode = episode(88L, 1);
|
||||
episode.setDramaId(5L);
|
||||
given(episodeService.getPublished(88L)).willReturn(episode);
|
||||
given(dramaService.getPublished(5L)).willReturn(drama(5L, "剧名", 10));
|
||||
given(progressMapper.selectAnyForIdentity(null, "device-a", 5L))
|
||||
.willReturn(null);
|
||||
given(progressMapper.insert(any(WatchProgress.class))).willAnswer(invocation -> {
|
||||
WatchProgress saved = invocation.getArgument(0);
|
||||
saved.setId(99L);
|
||||
return 1;
|
||||
});
|
||||
given(progressMapper.selectById(99L)).willAnswer(invocation -> {
|
||||
WatchProgress saved = new WatchProgress();
|
||||
saved.setId(99L);
|
||||
saved.setDramaId(5L);
|
||||
saved.setEpisodeId(88L);
|
||||
saved.setPositionMs(12_000L);
|
||||
saved.setDurationMs(60_000L);
|
||||
saved.setPlayCount(1);
|
||||
saved.setDeviceId("device-a");
|
||||
return saved;
|
||||
});
|
||||
ProgressSaveReqVO req = new ProgressSaveReqVO();
|
||||
req.setEpisodeId(88L);
|
||||
req.setPositionMs(12_000L);
|
||||
req.setDurationMs(60_000L);
|
||||
|
||||
progressService.save(new InteractionIdentity(null, "device-a"), req);
|
||||
|
||||
ArgumentCaptor<WatchProgress> progressCaptor =
|
||||
ArgumentCaptor.forClass(WatchProgress.class);
|
||||
verify(progressMapper).insert(progressCaptor.capture());
|
||||
assertThat(progressCaptor.getValue().getUserId()).isNull();
|
||||
assertThat(progressCaptor.getValue().getDeviceId()).isEqualTo("device-a");
|
||||
ArgumentCaptor<WatchHistory> historyCaptor =
|
||||
ArgumentCaptor.forClass(WatchHistory.class);
|
||||
verify(historyMapper).insert(historyCaptor.capture());
|
||||
assertThat(historyCaptor.getValue().getUserId()).isNull();
|
||||
assertThat(historyCaptor.getValue().getDeviceId()).isEqualTo("device-a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_merge_watch_data_and_keep_more_advanced_progress() {
|
||||
DramaEpisode episode = episode(88L, 2);
|
||||
episode.setDramaId(5L);
|
||||
given(episodeService.listByIds(List.of(88L))).willReturn(List.of(episode));
|
||||
|
||||
WatchProgress deviceProgress = progress(5L, 88L);
|
||||
deviceProgress.setId(1L);
|
||||
deviceProgress.setPositionMs(90_000L);
|
||||
deviceProgress.setPlayCount(3);
|
||||
given(progressMapper.selectList(any())).willReturn(List.of(deviceProgress));
|
||||
WatchProgress accountProgress = progress(5L, 88L);
|
||||
accountProgress.setId(2L);
|
||||
accountProgress.setUserId(9L);
|
||||
accountProgress.setPositionMs(60_000L);
|
||||
accountProgress.setPlayCount(2);
|
||||
given(progressMapper.selectAnyForIdentity(9L, "", 5L))
|
||||
.willReturn(accountProgress);
|
||||
|
||||
WatchHistory deviceHistory = history(5L, 88L, 90_000L);
|
||||
deviceHistory.setId(3L);
|
||||
given(historyMapper.selectList(any())).willReturn(List.of(deviceHistory));
|
||||
WatchHistory accountHistory = history(5L, 88L, 60_000L);
|
||||
accountHistory.setId(4L);
|
||||
accountHistory.setUserId(9L);
|
||||
given(historyMapper.selectOne(any())).willReturn(accountHistory);
|
||||
|
||||
WatchProgressService.MergeResult result =
|
||||
progressService.mergeDeviceIntoUser(9L, "device-a");
|
||||
|
||||
assertThat(result.progressMigrated()).isEqualTo(1);
|
||||
assertThat(result.progressIgnored()).isZero();
|
||||
assertThat(result.historyMigrated()).isEqualTo(1);
|
||||
assertThat(result.historyIgnored()).isZero();
|
||||
assertThat(accountProgress.getPositionMs()).isEqualTo(90_000L);
|
||||
assertThat(accountProgress.getPlayCount()).isEqualTo(3);
|
||||
verify(progressMapper).updateAny(accountProgress);
|
||||
verify(progressMapper).deleteById(1L);
|
||||
verify(historyMapper).updateById(accountHistory);
|
||||
verify(historyMapper).deleteById(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ignore_device_progress_when_account_progress_is_ahead() {
|
||||
DramaEpisode episode = episode(88L, 2);
|
||||
episode.setDramaId(5L);
|
||||
given(episodeService.listByIds(List.of(88L))).willReturn(List.of(episode));
|
||||
|
||||
WatchProgress deviceProgress = progress(5L, 88L);
|
||||
deviceProgress.setId(1L);
|
||||
deviceProgress.setPositionMs(50_000L);
|
||||
given(progressMapper.selectList(any())).willReturn(List.of(deviceProgress));
|
||||
WatchProgress accountProgress = progress(5L, 88L);
|
||||
accountProgress.setId(2L);
|
||||
accountProgress.setUserId(9L);
|
||||
accountProgress.setPositionMs(70_000L);
|
||||
given(progressMapper.selectAnyForIdentity(9L, "", 5L))
|
||||
.willReturn(accountProgress);
|
||||
|
||||
WatchProgressService.MergeResult result =
|
||||
progressService.mergeDeviceIntoUser(9L, "device-a");
|
||||
|
||||
assertThat(result.progressMigrated()).isZero();
|
||||
assertThat(result.progressIgnored()).isEqualTo(1);
|
||||
verify(progressMapper, never()).updateAny(any(WatchProgress.class));
|
||||
verify(progressMapper).deleteById(1L);
|
||||
}
|
||||
|
||||
private Drama drama(Long id, String title, int totalEpisodes) {
|
||||
Drama drama = new Drama();
|
||||
drama.setId(id);
|
||||
@@ -149,4 +264,15 @@ class WatchProgressServiceTest {
|
||||
episode.setEpNo(epNo);
|
||||
return episode;
|
||||
}
|
||||
|
||||
private WatchHistory history(Long dramaId, Long episodeId, long positionMs) {
|
||||
WatchHistory history = new WatchHistory();
|
||||
history.setDramaId(dramaId);
|
||||
history.setEpisodeId(episodeId);
|
||||
history.setPositionMs(positionMs);
|
||||
history.setDurationMs(120_000L);
|
||||
history.setDeviceId("device-a");
|
||||
history.setWatchedAt(LocalDateTime.of(2026, 10, 27, 9, 0));
|
||||
return history;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user