diff --git a/backend/src/main/java/shook/shook/auth/config/AuthConfig.java b/backend/src/main/java/shook/shook/auth/config/AuthConfig.java index 59f446a4..fdd674e6 100644 --- a/backend/src/main/java/shook/shook/auth/config/AuthConfig.java +++ b/backend/src/main/java/shook/shook/auth/config/AuthConfig.java @@ -2,6 +2,7 @@ import java.util.List; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -12,6 +13,7 @@ import shook.shook.auth.ui.interceptor.PathMethod; import shook.shook.auth.ui.interceptor.TokenInterceptor; +@Profile("!local") @Configuration public class AuthConfig implements WebMvcConfigurer { diff --git a/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java b/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java new file mode 100644 index 00000000..03d43379 --- /dev/null +++ b/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java @@ -0,0 +1,33 @@ +package shook.shook.auth.config; + +import java.util.List; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import shook.shook.auth.ui.argumentresolver.AuthArgumentResolver; +import shook.shook.auth.ui.interceptor.LocalInterceptor; + +@Profile("local") +@Configuration +public class LocalAuthConfig implements WebMvcConfigurer { + + private final AuthArgumentResolver authArgumentResolver; + private final LocalInterceptor localInterceptor; + + public LocalAuthConfig(final AuthArgumentResolver authArgumentResolver, final LocalInterceptor localInterceptor) { + this.authArgumentResolver = authArgumentResolver; + this.localInterceptor = localInterceptor; + } + + @Override + public void addInterceptors(final InterceptorRegistry registry) { + registry.addInterceptor(localInterceptor); + } + + @Override + public void addArgumentResolvers(final List resolvers) { + resolvers.add(authArgumentResolver); + } +} diff --git a/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java b/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java new file mode 100644 index 00000000..4f70aed5 --- /dev/null +++ b/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java @@ -0,0 +1,28 @@ +package shook.shook.auth.ui.interceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import shook.shook.auth.ui.AuthContext; + +@Profile("local") +@Component +public class LocalInterceptor implements HandlerInterceptor { + + private final AuthContext authContext; + + public LocalInterceptor(final AuthContext authContext) { + this.authContext = authContext; + } + + @Override + public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) + throws Exception { + final long memberId = Long.parseLong(request.getHeader("Authorization")); + authContext.setAuthenticatedMember(memberId); + + return true; + } +} diff --git a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java index f0f102ef..d0035607 100644 --- a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java +++ b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java @@ -1,12 +1,16 @@ package shook.shook.song.application; import jakarta.annotation.PostConstruct; +import jakarta.persistence.EntityManager; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import shook.shook.song.domain.InMemorySongs; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.repository.SongRepository; @RequiredArgsConstructor @@ -17,6 +21,7 @@ public class InMemorySongsScheduler { private final SongRepository songRepository; private final InMemorySongs inMemorySongs; + private final EntityManager entityManager; @PostConstruct public void initialize() { @@ -26,6 +31,25 @@ public void initialize() { @Scheduled(cron = "${schedules.in-memory-song.cron}") public void recreateCachedSong() { log.info("InMemorySongsScheduler worked"); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); + detachSongs(songs); + inMemorySongs.refreshSongs(songs); + } + + private void detachSongs(final List songs) { + songs.stream() + .peek(entityManager::detach) + .flatMap(song -> song.getKillingParts().stream()) + .forEach(entityManager::detach); + } + + @Transactional + @Scheduled(cron = "${schedules.in-memory-song.update-cron}") + public void updateCachedSong() { + log.info("InMemorySongsScheduler LikeCount update progressed"); + final List killingParts = inMemorySongs.getSongs().stream() + .flatMap(song -> song.getKillingParts().stream()) + .toList(); + killingParts.forEach(entityManager::merge); } } diff --git a/backend/src/main/java/shook/shook/song/application/SongService.java b/backend/src/main/java/shook/shook/song/application/SongService.java index 8f417a8a..ba86c3b1 100644 --- a/backend/src/main/java/shook/shook/song/application/SongService.java +++ b/backend/src/main/java/shook/shook/song/application/SongService.java @@ -14,7 +14,6 @@ import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; import shook.shook.member.exception.MemberException; -import shook.shook.song.application.dto.RecentSongCarouselResponse; import shook.shook.member_part.domain.MemberPart; import shook.shook.member_part.domain.repository.MemberPartRepository; import shook.shook.song.application.dto.RecentSongCarouselResponse; diff --git a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java index 73cac9d3..0146323d 100644 --- a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java +++ b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java @@ -8,6 +8,7 @@ import shook.shook.member.domain.repository.MemberRepository; import shook.shook.member.exception.MemberException; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; +import shook.shook.song.domain.InMemorySongs; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.KillingPartLike; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; @@ -22,6 +23,7 @@ public class KillingPartLikeService { private final KillingPartRepository killingPartRepository; private final MemberRepository memberRepository; private final KillingPartLikeRepository likeRepository; + private final InMemorySongs inMemorySongs; @Transactional public void updateLikeStatus( @@ -54,8 +56,8 @@ private void create(final KillingPart killingPart, final Member member) { final KillingPartLike likeOnKillingPart = likeRepository.findByKillingPartAndMember(killingPart, member) .orElseGet(() -> createNewLike(killingPart, member)); if (likeOnKillingPart.isDeleted()) { + inMemorySongs.like(killingPart, likeOnKillingPart); likeRepository.pressLike(likeOnKillingPart.getId()); - killingPartRepository.increaseLikeCount(killingPart.getId()); } } @@ -68,8 +70,8 @@ private KillingPartLike createNewLike(final KillingPart killingPart, final Membe private void delete(final KillingPart killingPart, final Member member) { killingPart.findLikeByMember(member) .ifPresent(likeOnKillingPart -> { + inMemorySongs.unlike(killingPart, likeOnKillingPart); likeRepository.cancelLike(likeOnKillingPart.getId()); - killingPartRepository.decreaseLikeCount(killingPart.getId()); }); } } diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index 6adecdff..83d69779 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -1,51 +1,55 @@ package shook.shook.song.domain; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import shook.shook.song.domain.killingpart.KillingPart; +import shook.shook.song.domain.killingpart.KillingPartLike; import shook.shook.song.exception.SongException; +import shook.shook.song.exception.killingpart.KillingPartException; +@RequiredArgsConstructor @Repository public class InMemorySongs { - private Map songsSortedInLikeCountById = new LinkedHashMap<>(); + private static final Comparator COMPARATOR = + Comparator.comparing(Song::getTotalLikeCount, Comparator.reverseOrder()) + .thenComparing(Song::getId, Comparator.reverseOrder()); - public void recreate(final List songs) { - songsSortedInLikeCountById = getSortedSong(songs); - } - - private static Map getSortedSong(final List songs) { - songs.sort(Comparator.comparing( - Song::getTotalLikeCount, - Comparator.reverseOrder() - ).thenComparing(Song::getId, Comparator.reverseOrder())); + private Map songs = new HashMap<>(); + private List sortedSongIds = new ArrayList<>(); - return songs.stream() - .collect(Collectors.toMap( - Song::getId, - song -> song, - (prev, update) -> update, - LinkedHashMap::new - )); + public synchronized void refreshSongs(final List songs) { + this.songs = songs.stream() + .collect(Collectors.toMap(Song::getId, song -> song, (prev, update) -> update, HashMap::new)); + this.sortedSongIds = new ArrayList<>(this.songs.keySet().stream() + .sorted(Comparator.comparing(this.songs::get, COMPARATOR)) + .toList()); } public List getSongs() { - return songsSortedInLikeCountById.values().stream() + return sortedSongIds.stream() + .map(songs::get) .toList(); } public List getSongs(final int limit) { - final List songs = getSongs(); + final List topSongIds = sortedSongIds.subList(0, Math.min(limit, sortedSongIds.size())); - return songs.subList(0, Math.min(limit, songs.size())); + return topSongIds.stream() + .map(songs::get) + .toList(); } - public List getSortedSongsByGenre(final Genre genre) { - return songsSortedInLikeCountById.values().stream() + private List getSortedSongsByGenre(final Genre genre) { + return sortedSongIds.stream() + .map(songs::get) .filter(song -> song.getGenre() == genre) .toList(); } @@ -57,12 +61,10 @@ public List getSortedSongsByGenre(final Genre genre, final int limit) { } public Song getSongById(final Long id) { - if (songsSortedInLikeCountById.containsKey(id)) { - return songsSortedInLikeCountById.get(id); + if (songs.containsKey(id)) { + return songs.get(id); } - throw new SongException.SongNotExistException( - Map.of("song id", String.valueOf(id)) - ); + throw new SongException.SongNotExistException(Map.of("song id", String.valueOf(id))); } public List getPrevLikedSongByGenre(final Song currentSong, final Genre genre, final int prevSongCount) { @@ -81,31 +83,118 @@ public List getNextLikedSongByGenre(final Song currentSong, final Genre ge } return songsWithGenre.subList(Math.min(currentSongIndex + 1, songsWithGenre.size() - 1), - Math.min(songsWithGenre.size(), currentSongIndex + nextSongCount + 1)); + Math.min(songsWithGenre.size(), currentSongIndex + nextSongCount + 1)); } public List getPrevLikedSongs(final Song currentSong, final int prevSongCount) { - final List songIds = songsSortedInLikeCountById.keySet().stream() - .toList(); - final int currentSongIndex = songIds.indexOf(currentSong.getId()); + final int currentSongIndex = sortedSongIds.indexOf(currentSong.getId()); - return songIds.subList(Math.max(0, currentSongIndex - prevSongCount), currentSongIndex).stream() - .map(songsSortedInLikeCountById::get) + return sortedSongIds.subList(Math.max(0, currentSongIndex - prevSongCount), currentSongIndex).stream() + .map(songs::get) .toList(); } public List getNextLikedSongs(final Song currentSong, final int nextSongCount) { - final List songIds = songsSortedInLikeCountById.keySet().stream() - .toList(); - final int currentSongIndex = songIds.indexOf(currentSong.getId()); + final int currentSongIndex = sortedSongIds.indexOf(currentSong.getId()); - if (currentSongIndex == songIds.size() - 1) { + if (currentSongIndex == sortedSongIds.size() - 1) { return Collections.emptyList(); } - return songIds.subList(Math.min(currentSongIndex + 1, songIds.size() - 1), - Math.min(songIds.size(), currentSongIndex + nextSongCount + 1)).stream() - .map(songsSortedInLikeCountById::get) + return sortedSongIds.subList(Math.min(currentSongIndex + 1, sortedSongIds.size() - 1), + Math.min(sortedSongIds.size(), currentSongIndex + nextSongCount + 1)).stream() + .map(songs::get) .toList(); } + + public void like(final KillingPart killingPart, final KillingPartLike likeOnKillingPart) { + final Song song = songs.get(killingPart.getSong().getId()); + final KillingPart killingPartById = findKillingPart(killingPart, song); + final boolean updated = killingPartById.like(likeOnKillingPart); + if (updated) { + reorder(song); + } + } + + public void unlike(final KillingPart killingPart, final KillingPartLike unlikeOnKillingPart) { + final Song song = songs.get(killingPart.getSong().getId()); + final KillingPart killingPartById = findKillingPart(killingPart, song); + final boolean updated = killingPartById.unlike(unlikeOnKillingPart); + if (updated) { + reorder(song); + } + } + + private KillingPart findKillingPart(final KillingPart killingPart, final Song song) { + return song.getKillingParts().stream() + .filter(kp -> kp.equals(killingPart)) + .findAny() + .orElseThrow( + () -> new KillingPartException.PartNotExistException( + Map.of("killing part id", String.valueOf(killingPart.getId())))); + } + + private void reorder(final Song updatedSong) { + synchronized (sortedSongIds) { + int currentSongIndex = sortedSongIds.indexOf(updatedSong.getId()); + + if (currentSongIndex == -1) { + return; + } + + moveForward(updatedSong, currentSongIndex); + moveBackward(updatedSong, currentSongIndex); + } + } + + private void moveForward(final Song changedSong, final int songIndex) { + int currentSongIndex = songIndex; + + while (canSwapWithPreviousSong(changedSong, currentSongIndex)) { + swap(currentSongIndex, currentSongIndex - 1); + currentSongIndex--; + } + } + + private boolean canSwapWithPreviousSong(final Song changedSong, final int currentSongIndex) { + return currentSongIndex > 0 && currentSongIndex < sortedSongIds.size() && + shouldSwapWithPrevious(changedSong, + songs.get(sortedSongIds.get(currentSongIndex - 1))); + } + + private boolean shouldSwapWithPrevious(final Song song, final Song prevSong) { + final boolean hasSameTotalLikeCountAndLargerIdThanPrevSong = + song.getTotalLikeCount() == prevSong.getTotalLikeCount() && song.getId() > prevSong.getId(); + final boolean hasLargerTotalLikeCountThanPrevSong = song.getTotalLikeCount() > prevSong.getTotalLikeCount(); + + return hasLargerTotalLikeCountThanPrevSong || hasSameTotalLikeCountAndLargerIdThanPrevSong; + } + + private void swap(final int currentIndex, final int otherIndex) { + final Long prevIndex = sortedSongIds.get(currentIndex); + sortedSongIds.set(currentIndex, sortedSongIds.get(otherIndex)); + sortedSongIds.set(otherIndex, prevIndex); + } + + private void moveBackward(final Song changedSong, final int songIndex) { + int currentSongIndex = songIndex; + + while (canSwapWithNextSong(changedSong, currentSongIndex)) { + swap(currentSongIndex, currentSongIndex + 1); + currentSongIndex++; + } + } + + private boolean canSwapWithNextSong(final Song changedSong, final int currentSongIndex) { + return currentSongIndex < sortedSongIds.size() - 1 && currentSongIndex > 0 + && shouldSwapWithNext(changedSong, songs.get(sortedSongIds.get(currentSongIndex - 1))); + } + + private boolean shouldSwapWithNext(final Song song, final Song nextSong) { + final boolean hasSameTotalLikeCountAndSmallerIdThanNextSong = + song.getTotalLikeCount() == nextSong.getTotalLikeCount() && song.getId() < nextSong.getId(); + final boolean hasSmallerTotalLikeCountThanNextSong = song.getTotalLikeCount() < nextSong.getTotalLikeCount(); + + return hasSmallerTotalLikeCountThanNextSong || hasSameTotalLikeCountAndSmallerIdThanNextSong; + } } diff --git a/backend/src/main/java/shook/shook/song/domain/KillingParts.java b/backend/src/main/java/shook/shook/song/domain/KillingParts.java index 5300cdbd..9957f7e1 100644 --- a/backend/src/main/java/shook/shook/song/domain/KillingParts.java +++ b/backend/src/main/java/shook/shook/song/domain/KillingParts.java @@ -58,12 +58,13 @@ public List getKillingParts() { public List getKillingPartsSortedByLikeCount() { return killingParts.stream() .sorted(Comparator.comparing(KillingPart::getLikeCount, Comparator.reverseOrder()) - .thenComparing(KillingPart::getStartSecond)) + .thenComparing(KillingPart::getStartSecond)) .toList(); } public int getKillingPartsTotalLikeCount() { return killingParts.stream() - .reduce(0, (sum, killingPart) -> sum + killingPart.getLikeCount(), Integer::sum); + .mapToInt(KillingPart::getLikeCount) + .reduce(0, Integer::sum); } } diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java index 0114edf8..2ff3c303 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java @@ -1,6 +1,7 @@ package shook.shook.song.domain.killingpart; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -19,6 +20,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -60,8 +62,9 @@ public class KillingPart { @Embedded private final KillingPartLikes killingPartLikes = new KillingPartLikes(); + @Convert(converter = LikeCountConverter.class) @Column(nullable = false) - private int likeCount = 0; + private AtomicInteger likeCount; @Column(nullable = false, updatable = false) private LocalDateTime createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); @@ -82,7 +85,7 @@ private KillingPart( this.startSecond = startSecond; this.length = new PartLength(length); this.song = song; - this.likeCount = likeCount; + this.likeCount = new AtomicInteger(likeCount); } private KillingPart(final int startSecond, final int length) { @@ -114,12 +117,14 @@ public void addComment(final KillingPartComment comment) { comments.addComment(comment); } - public void like(final KillingPartLike likeToAdd) { + public boolean like(final KillingPartLike likeToAdd) { validateLikeUpdate(likeToAdd); final boolean isLikeCreated = killingPartLikes.addLike(likeToAdd); if (isLikeCreated) { - this.likeCount++; + likeCount.incrementAndGet(); + return true; } + return false; } private void validateLikeUpdate(final KillingPartLike like) { @@ -136,12 +141,14 @@ private void validateLikeUpdate(final KillingPartLike like) { } } - public void unlike(final KillingPartLike likeToDelete) { + public boolean unlike(final KillingPartLike likeToDelete) { validateLikeUpdate(likeToDelete); final boolean isLikeDeleted = killingPartLikes.deleteLike(likeToDelete); if (isLikeDeleted) { - this.likeCount--; + likeCount.decrementAndGet(); + return true; } + return false; } public Optional findLikeByMember(final Member member) { @@ -180,7 +187,7 @@ public int getLength() { } public int getLikeCount() { - return likeCount; + return likeCount.get(); } public void setSong(final Song song) { diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPartLikes.java b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPartLikes.java index 91df7f94..3aef76b6 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPartLikes.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPartLikes.java @@ -2,9 +2,9 @@ import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,7 +18,7 @@ public class KillingPartLikes { @OneToMany(mappedBy = "killingPart") @Where(clause = "is_deleted = false") - private List likes = new ArrayList<>(); + private Set likes = new HashSet<>(); public boolean addLike(final KillingPartLike like) { if (like.isDeleted()) { diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java b/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java new file mode 100644 index 00000000..303494a1 --- /dev/null +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java @@ -0,0 +1,19 @@ +package shook.shook.song.domain.killingpart; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import java.util.concurrent.atomic.AtomicInteger; + +@Converter +public class LikeCountConverter implements AttributeConverter { + + @Override + public Integer convertToDatabaseColumn(final AtomicInteger attribute) { + return attribute.get(); + } + + @Override + public AtomicInteger convertToEntityAttribute(final Integer dbData) { + return new AtomicInteger(dbData); + } +} diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/repository/KillingPartRepository.java b/backend/src/main/java/shook/shook/song/domain/killingpart/repository/KillingPartRepository.java index 99679d46..17b8754d 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/repository/KillingPartRepository.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/repository/KillingPartRepository.java @@ -2,9 +2,6 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; @@ -14,6 +11,7 @@ public interface KillingPartRepository extends JpaRepository List findAllBySong(final Song song); + /* AtomicInteger 사용하면서 예외 발생하여 주석 처리 @Query("update KillingPart kp set kp.likeCount = kp.likeCount + 1 where kp.id = :id") @Modifying(clearAutomatically = true, flushAutomatically = true) void increaseLikeCount(@Param("id") final Long killingPartLikeId); @@ -21,4 +19,5 @@ public interface KillingPartRepository extends JpaRepository @Query("update KillingPart kp set kp.likeCount = kp.likeCount - 1 where kp.id = :id") @Modifying(clearAutomatically = true, flushAutomatically = true) void decreaseLikeCount(@Param("id") final Long killingPartLikeId); + */ } diff --git a/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java b/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java index d9321bd8..1fed695f 100644 --- a/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java +++ b/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java @@ -22,8 +22,9 @@ public interface SongRepository extends JpaRepository { @Query("SELECT s AS song " + "FROM Song s " + "LEFT JOIN FETCH s.killingParts.killingParts kp " - + "GROUP BY s.id, kp.id") - List findAllWithKillingParts(); + + "LEFT JOIN FETCH kp.killingPartLikes.likes kpl " + + "GROUP BY s.id, kp.id, kpl.id") + List findAllWithKillingPartsAndLikes(); @Query("SELECT s FROM Song s " + "LEFT JOIN s.killingParts.killingParts kp " diff --git a/backend/src/main/resources/application-test.yml b/backend/src/main/resources/application-test.yml index fc22e8b1..efda546b 100644 --- a/backend/src/main/resources/application-test.yml +++ b/backend/src/main/resources/application-test.yml @@ -50,4 +50,5 @@ schedules: in-memory-token: cron: "0/1 * * * * *" in-memory-song: - cron: "0 0/1 * * * *" # 1분 + cron: "0 0/5 * * * *" # 1분 + update-cron: "0 0/5 * * * *" # 1분 diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index c3334135..eb64e781 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -70,3 +70,4 @@ schedules: cron: "0 0 0/1 * * *" in-memory-song: cron: "0 0 0/1 * * *" #1시간 + update-cron: "0 0 0/1 * * *" #1시간 diff --git a/backend/src/main/resources/shook-security b/backend/src/main/resources/shook-security index be8515fd..48e7440b 160000 --- a/backend/src/main/resources/shook-security +++ b/backend/src/main/resources/shook-security @@ -1 +1 @@ -Subproject commit be8515fd110a4496fcce0c79c8fc1e312c49f6bc +Subproject commit 48e7440b247f24098ccad67de621e950c11df33c diff --git a/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java b/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java index cbe6ea25..56bad1a0 100644 --- a/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java +++ b/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java @@ -8,7 +8,14 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.test.context.jdbc.Sql; +import shook.shook.member.domain.Member; +import shook.shook.member.domain.repository.MemberRepository; import shook.shook.song.domain.InMemorySongs; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.killingpart.KillingPart; +import shook.shook.song.domain.killingpart.KillingPartLike; +import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; +import shook.shook.song.domain.killingpart.repository.KillingPartRepository; @Sql(value = "classpath:/killingpart/initialize_killing_part_song.sql") @EnableScheduling @@ -21,6 +28,15 @@ class InMemorySongsSchedulerTest { @Autowired private InMemorySongsScheduler scheduler; + @Autowired + private KillingPartLikeRepository likeRepository; + + @Autowired + private KillingPartRepository killingPartRepository; + + @Autowired + private MemberRepository memberRepository; + @DisplayName("InMemorySongs 를 재생성한다.") @Test void recreateCachedSong() { @@ -31,4 +47,24 @@ void recreateCachedSong() { // then assertThat(inMemorySongs.getSongs()).hasSize(4); } + + @DisplayName("InMemorySongs 의 상태로 데이터베이스를 업데이트한다.") + @Test + void updateCachedSong() { + // given + scheduler.recreateCachedSong(); + final Song song = inMemorySongs.getSongById(1L); + final KillingPart killingPart = song.getKillingParts().get(0); + final Member member = memberRepository.save(new Member("email@email.com", "nickname")); + inMemorySongs.like(killingPart, likeRepository.save( + new KillingPartLike(killingPart, member) + )); + + // when + scheduler.updateCachedSong(); + + // then + killingPartRepository.findById(killingPart.getId()) + .ifPresent(updatedKillingPart -> assertThat(updatedKillingPart.getLikeCount()).isEqualTo(1)); + } } diff --git a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java index 4cd3d876..534b5f54 100644 --- a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java @@ -18,8 +18,8 @@ import shook.shook.member_part.domain.MemberPart; import shook.shook.member_part.domain.repository.MemberPartRepository; import shook.shook.song.application.dto.KillingPartRegisterRequest; -import shook.shook.song.application.dto.RecentSongCarouselResponse; import shook.shook.song.application.dto.MemberPartResponse; +import shook.shook.song.application.dto.RecentSongCarouselResponse; import shook.shook.song.application.dto.SongResponse; import shook.shook.song.application.dto.SongSwipeResponse; import shook.shook.song.application.dto.SongWithKillingPartsRegisterRequest; @@ -56,12 +56,13 @@ class SongServiceTest extends UsingJpaTest { @Autowired private ArtistRepository artistRepository; - private final InMemorySongs inMemorySongs = new InMemorySongs(); + private InMemorySongs inMemorySongs; private SongService songService; @BeforeEach public void setUp() { + inMemorySongs = new InMemorySongs(); songService = new SongService( songRepository, killingPartRepository, @@ -117,15 +118,15 @@ void findById_exist_login_member() { //given final Member member = createAndSaveMember("email@naver.com", "email"); final Song song = registerNewSong("title"); + inMemorySongs.refreshSongs(List.of(song)); addLikeToEachKillingParts(song, member); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); addMemberPartToSong(10, 5, song, member); //when saveAndClearEntityManager(); final SongSwipeResponse response = songService.findSongByIdForFirstSwipe(song.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); //then assertAll( @@ -133,19 +134,19 @@ void findById_exist_login_member() { () -> assertThat(response.getNextSongs()).isEmpty(), () -> assertThat(response.getCurrentSong().getKillingParts().get(0)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(0).getId()) + song.getLikeCountSortedKillingParts().get(0).getId()) .hasFieldOrPropertyWithValue("rank", 1) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getCurrentSong().getKillingParts().get(1)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(1).getId()) + song.getLikeCountSortedKillingParts().get(1).getId()) .hasFieldOrPropertyWithValue("rank", 2) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getCurrentSong().getKillingParts().get(2)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(2).getId()) + song.getLikeCountSortedKillingParts().get(2).getId()) .hasFieldOrPropertyWithValue("rank", 3) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getCurrentSong().getMemberPart().getId()).isNotNull() @@ -162,13 +163,13 @@ private MemberPart addMemberPartToSong(final int startSecond, final int length, void findById_exist_not_login_member() { //given final Song song = registerNewSong("title"); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); + inMemorySongs.refreshSongs(List.of(song)); //when인 saveAndClearEntityManager(); final SongSwipeResponse response = songService.findSongByIdForFirstSwipe(song.getId(), - new MemberInfo(0L, Authority.ANONYMOUS)); + new MemberInfo(0L, Authority.ANONYMOUS)); //then assertAll( @@ -176,19 +177,19 @@ void findById_exist_not_login_member() { () -> assertThat(response.getNextSongs()).isEmpty(), () -> assertThat(response.getCurrentSong().getKillingParts().get(0)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(0).getId()) + song.getLikeCountSortedKillingParts().get(0).getId()) .hasFieldOrPropertyWithValue("rank", 1) .hasFieldOrPropertyWithValue("likeStatus", false), () -> assertThat(response.getCurrentSong().getKillingParts().get(1)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(1).getId()) + song.getLikeCountSortedKillingParts().get(1).getId()) .hasFieldOrPropertyWithValue("rank", 2) .hasFieldOrPropertyWithValue("likeStatus", false), () -> assertThat(response.getCurrentSong().getKillingParts().get(2)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(2).getId()) + song.getLikeCountSortedKillingParts().get(2).getId()) .hasFieldOrPropertyWithValue("rank", 3) .hasFieldOrPropertyWithValue("likeStatus", false), () -> assertThat(response.getCurrentSong().getMemberPart()).isNull() @@ -200,13 +201,14 @@ void findById_exist_not_login_member() { void findById_notExist() { //given final Member member = createAndSaveMember("email@naver.com", "email"); + inMemorySongs.refreshSongs(List.of()); //when //then assertThatThrownBy(() -> songService.findSongByIdForFirstSwipe( - 0L, - new MemberInfo(member.getId(), Authority.MEMBER) - ) + 0L, + new MemberInfo(member.getId(), Authority.MEMBER) + ) ).isInstanceOf(SongException.SongNotExistException.class); } @@ -227,8 +229,8 @@ void showHighLikedSongs() { addLikeToEachKillingParts(thirdSong, member2); addLikeToEachKillingParts(fourthSong, member1); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); saveAndClearEntityManager(); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); // when final List result = songService.showHighLikedSongs(); @@ -304,7 +306,6 @@ void firstFindByMember() { // 4, 3, 5, 2, 1 addLikeToEachKillingParts(thirdSong, member); addLikeToEachKillingParts(fourthSong, member); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); // 1, 2, 3 노래에 memberPart 추가 addMemberPartToSong(10, 5, firstSong, member); @@ -313,11 +314,12 @@ void firstFindByMember() { addMemberPartToSong(10, 5, fourthSong, member); saveAndClearEntityManager(); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final SongSwipeResponse result = songService.findSongByIdForFirstSwipe(fifthSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertAll( @@ -325,20 +327,20 @@ void firstFindByMember() { () -> assertThat(result.getPrevSongs()).hasSize(2), () -> assertThat(result.getNextSongs()).hasSize(2), () -> assertThat(result.getPrevSongs().stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(4L, 3L)), + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(4L, 3L)), () -> assertThat(result.getNextSongs().stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 1L)), + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 1L)), () -> assertThat(result.getCurrentSong().getMemberPart()).isNull(), () -> assertThat(result.getPrevSongs().stream() - .map(songResponse -> songResponse.getMemberPart().getId()) - .toList()) + .map(songResponse -> songResponse.getMemberPart().getId()) + .toList()) .usingRecursiveComparison() .isEqualTo(List.of(4L, 3L)), () -> assertThat(result.getNextSongs().stream() - .map(songResponse -> songResponse.getMemberPart().getId()) - .toList()) + .map(songResponse -> songResponse.getMemberPart().getId()) + .toList()) .usingRecursiveComparison() .isEqualTo(List.of(2L, 1L)) ); @@ -351,19 +353,21 @@ void firstFindByAnonymous() { final Member member = createAndSaveMember("first@naver.com", "first"); final Long notExistSongId = Long.MAX_VALUE; + saveAndClearEntityManager(); + // when // then assertThatThrownBy( () -> songService.findSongByIdForFirstSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); assertThatThrownBy( () -> songService.findSongByIdForBeforeSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); assertThatThrownBy( () -> songService.findSongByIdForAfterSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); } @@ -385,8 +389,6 @@ void findSongByIdForBeforeSwipe() { addLikeToEachKillingParts(fourthSong, member2); addLikeToEachKillingParts(firstSong, member2); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); - addMemberPartToSong(10, 5, firstSong, member); addMemberPartToSong(10, 5, secondSong, member); addMemberPartToSong(10, 5, standardSong, member); @@ -395,20 +397,21 @@ void findSongByIdForBeforeSwipe() { // 정렬 순서: 2L, 4L, 1L, 5L, 3L saveAndClearEntityManager(); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final List beforeResponses = songService.findSongByIdForBeforeSwipe(standardSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertThat(beforeResponses.stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); assertThat(beforeResponses.stream() - .map(SongResponse::getMemberPart) - .map(MemberPartResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); + .map(SongResponse::getMemberPart) + .map(MemberPartResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); } @DisplayName("이후 노래를 1. 좋아요 순 내림차순, 2. id 내림차순으로 조회한다.") @@ -428,7 +431,6 @@ void findSongByIdForAfterSwipe() { addLikeToEachKillingParts(secondSong, member2); addLikeToEachKillingParts(standardSong, member2); addLikeToEachKillingParts(firstSong, member2); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); addMemberPartToSong(10, 5, firstSong, member); addMemberPartToSong(10, 5, secondSong, member); @@ -437,22 +439,22 @@ void findSongByIdForAfterSwipe() { addMemberPartToSong(10, 5, fifthSong, member); // 정렬 순서: 2L, 4L, 1L, 5L, 3L - saveAndClearEntityManager(); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final List afterResponses = songService.findSongByIdForAfterSwipe(standardSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertThat(afterResponses.stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); assertThat(afterResponses.stream() - .map(SongResponse::getMemberPart) - .map(MemberPartResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); + .map(SongResponse::getMemberPart) + .map(MemberPartResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); } } @@ -479,9 +481,9 @@ void findSongsByGenre() { addLikeToEachKillingParts(song1, member); addLikeToEachKillingParts(song1, secondMember); addLikeToEachKillingParts(song3, member); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); // 정렬 순서: 2L, 1L, 3L, 5L, 4L + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); saveAndClearEntityManager(); // when @@ -491,7 +493,7 @@ void findSongsByGenre() { assertAll( () -> assertThat(response).hasSize(5), () -> assertThat(response.stream() - .map(HighLikedSongResponse::getId).toList()) + .map(HighLikedSongResponse::getId).toList()) .containsExactly(2L, 1L, 3L, 5L, 4L) ); } @@ -504,13 +506,13 @@ void findSongById() { final Song song = registerNewSong("title"); final Member member = createAndSaveMember("email@email.com", "nickname"); addLikeToEachKillingParts(song, member); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); addMemberPartToSong(10, 5, song, member); saveAndClearEntityManager(); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final SongResponse response = songService.findSongById(song.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertAll( @@ -521,19 +523,19 @@ void findSongById() { () -> assertThat(response.getKillingParts()).hasSize(3), () -> assertThat(response.getKillingParts().get(0)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(0).getId()) + song.getLikeCountSortedKillingParts().get(0).getId()) .hasFieldOrPropertyWithValue("rank", 1) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getKillingParts().get(1)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(1).getId()) + song.getLikeCountSortedKillingParts().get(1).getId()) .hasFieldOrPropertyWithValue("rank", 2) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getKillingParts().get(2)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(2).getId()) + song.getLikeCountSortedKillingParts().get(2).getId()) .hasFieldOrPropertyWithValue("rank", 3) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getMemberPart().getId()).isNotNull() @@ -559,8 +561,8 @@ void findRecentRegisteredSongsForCarousel() { // then assertThat(songs.stream() - .map(RecentSongCarouselResponse::getId) - .toList()) + .map(RecentSongCarouselResponse::getId) + .toList()) .containsExactly(7L, 6L, 5L, 4L, 3L); } } diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java index 2070e9e4..5eef80e2 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java @@ -18,9 +18,12 @@ import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; +import shook.shook.song.domain.InMemorySongs; +import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; import shook.shook.song.domain.killingpart.repository.KillingPartRepository; +import shook.shook.song.domain.repository.SongRepository; @Sql("classpath:/killingpart/initialize_killing_part_song.sql") @SpringBootTest @@ -28,6 +31,7 @@ class KillingPartLikeConcurrencyTest { private static KillingPart SAVED_KILLING_PART; private static Member SAVED_MEMBER; + private static Song SAVED_SONG; @Autowired private KillingPartRepository killingPartRepository; @@ -41,14 +45,22 @@ class KillingPartLikeConcurrencyTest { @Autowired private PlatformTransactionManager transactionManager; + @Autowired + private InMemorySongs inMemorySongs; + + @Autowired + private SongRepository songRepository; + private KillingPartLikeService likeService; private TransactionTemplate transactionTemplate; @BeforeEach void setUp() { + SAVED_SONG = songRepository.findById(1L).get(); SAVED_KILLING_PART = killingPartRepository.findById(1L).get(); SAVED_MEMBER = memberRepository.findById(1L).get(); - likeService = new KillingPartLikeService(killingPartRepository, memberRepository, killingPartLikeRepository); + likeService = new KillingPartLikeService(killingPartRepository, memberRepository, killingPartLikeRepository, + inMemorySongs); transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); } @@ -59,6 +71,7 @@ void likeByMultiplePeople() throws InterruptedException { // given final Member first = SAVED_MEMBER; final Member second = memberRepository.save(new Member("second@gmail.com", "second")); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when ExecutorService executorService = Executors.newFixedThreadPool(2); @@ -67,24 +80,28 @@ void likeByMultiplePeople() throws InterruptedException { final KillingPartLikeRequest request = new KillingPartLikeRequest(true); executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), request); - latch.countDown(); - return null; - })) + transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), + request); + latch.countDown(); + return null; + })) ); executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), second.getId(), request); - latch.countDown(); - return null; - })) + transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), second.getId(), + request); + latch.countDown(); + return null; + })) ); latch.await(); Thread.sleep(1000); // then - final KillingPart killingPart = killingPartRepository.findById(SAVED_KILLING_PART.getId()).get(); + final KillingPart killingPart = inMemorySongs.getSongById(SAVED_SONG.getId()).getKillingParts().stream() + .filter(kp -> kp.getId().equals(SAVED_KILLING_PART.getId())) + .findAny().get(); assertThat(killingPart.getLikeCount()).isEqualTo(2); } @@ -102,26 +119,23 @@ void likeByOnePersonMultipleTimes() throws InterruptedException { final KillingPartLikeRequest likeRequest = new KillingPartLikeRequest(true); final KillingPartLikeRequest unlikeRequest = new KillingPartLikeRequest(false); - executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), likeRequest); - latch.countDown(); - return null; - })) + executorService.execute(() -> transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), likeRequest); + latch.countDown(); + return null; + })) ); - executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), unlikeRequest); - latch.countDown(); - return null; - })) + executorService.execute(() -> transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), unlikeRequest); + latch.countDown(); + return null; + })) ); - executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), likeRequest); - latch.countDown(); - return null; - })) + executorService.execute(() -> transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), likeRequest); + latch.countDown(); + return null; + })) ); latch.await(); diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java index 3d79a5b1..f3fd844b 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java @@ -14,10 +14,13 @@ import shook.shook.member.domain.repository.MemberRepository; import shook.shook.member.exception.MemberException; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; +import shook.shook.song.domain.InMemorySongs; +import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.KillingPartLike; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; import shook.shook.song.domain.killingpart.repository.KillingPartRepository; +import shook.shook.song.domain.repository.SongRepository; import shook.shook.song.exception.killingpart.KillingPartException; import shook.shook.support.UsingJpaTest; @@ -28,6 +31,7 @@ class KillingPartLikeServiceTest extends UsingJpaTest { private static final long UNSAVED_KILLING_PART_ID = Long.MAX_VALUE; private static KillingPart SAVED_KILLING_PART; private static Member SAVED_MEMBER; + private static Song SAVED_SONG; @Autowired private KillingPartRepository killingPartRepository; @@ -38,14 +42,21 @@ class KillingPartLikeServiceTest extends UsingJpaTest { @Autowired private MemberRepository memberRepository; + @Autowired + private SongRepository songRepository; + private KillingPartLikeService likeService; + private InMemorySongs inMemorySongs; + @BeforeEach void setUp() { + SAVED_SONG = songRepository.findById(1L).get(); SAVED_KILLING_PART = killingPartRepository.findById(1L).get(); SAVED_MEMBER = memberRepository.findById(1L).get(); - likeService = new KillingPartLikeService(killingPartRepository, memberRepository, - killingPartLikeRepository); + inMemorySongs = new InMemorySongs(); + likeService = new KillingPartLikeService(killingPartRepository, memberRepository, killingPartLikeRepository, + inMemorySongs); } @DisplayName("킬링파트 좋아요를 누른다.") @@ -57,15 +68,18 @@ class Create { void create_newLike() { // given // when + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Optional updatedKillingPart = inMemorySongs.getSongById(SAVED_SONG.getId()) + .getKillingParts().stream() + .filter(killingPart -> killingPart.getId().equals(SAVED_KILLING_PART.getId())) + .findAny(); assertThat(savedLike).isPresent() .get() @@ -80,22 +94,25 @@ void create_newLike() { @Test void create_updateLike_exist() { // given + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Optional updatedKillingPart = inMemorySongs.getSongById(SAVED_SONG.getId()) + .getKillingParts().stream() + .filter(killingPart -> killingPart.getId().equals(SAVED_KILLING_PART.getId())) + .findAny(); assertThat(savedLike).isPresent() .get() @@ -110,20 +127,23 @@ void create_updateLike_exist() { @Test void create_noAction() { // given + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Optional updatedKillingPart = inMemorySongs.getSongById(SAVED_SONG.getId()) + .getKillingParts().stream() + .filter(killingPart -> killingPart.getId().equals(SAVED_KILLING_PART.getId())) + .findAny(); assertThat(savedLike).isPresent() .get() @@ -141,7 +161,7 @@ void create_KillingPartException() { // when, then assertThatThrownBy( () -> likeService.updateLikeStatus(UNSAVED_KILLING_PART_ID, SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true))) + new KillingPartLikeRequest(true))) .isInstanceOf(KillingPartException.PartNotExistException.class); } @@ -152,7 +172,7 @@ void create_memberNotExist() { // when, then assertThatThrownBy( () -> likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), UNSAVED_MEMBER_ID, - new KillingPartLikeRequest(true))) + new KillingPartLikeRequest(true))) .isInstanceOf(MemberException.MemberNotExistException.class); } } @@ -167,7 +187,7 @@ void delete_noAction() { // given // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // then @@ -186,58 +206,54 @@ void delete_noAction() { @Test void delete_alreadyDeleted_noAction() { // given + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Song savedSong = inMemorySongs.getSongById(SAVED_SONG.getId()); assertThat(savedLike).isPresent() .get() .hasFieldOrPropertyWithValue("isDeleted", true); - assertThat(updatedKillingPart).isPresent() - .get() - .hasFieldOrPropertyWithValue("likeCount", 0); + assertThat(savedSong.getTotalLikeCount()).isZero(); } @DisplayName("좋아요 데이터가 존재하는 경우, 상태가 변경된다.") @Test void create_noAction() { // given + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Song savedSong = inMemorySongs.getSongById(SAVED_SONG.getId()); assertThat(savedLike).isPresent() .get() .hasFieldOrPropertyWithValue("isDeleted", true); - assertThat(updatedKillingPart).isPresent() - .get() - .hasFieldOrPropertyWithValue("likeCount", 0); + assertThat(savedSong.getTotalLikeCount()).isZero(); } @DisplayName("존재하지 않는 킬링파트면 예외가 발생한다.") @@ -247,7 +263,7 @@ void create_KillingPartException() { // when, then assertThatThrownBy( () -> likeService.updateLikeStatus(UNSAVED_KILLING_PART_ID, SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true))) + new KillingPartLikeRequest(true))) .isInstanceOf(KillingPartException.PartNotExistException.class); } @@ -258,7 +274,7 @@ void create_memberNotExist() { // when, then assertThatThrownBy( () -> likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), UNSAVED_MEMBER_ID, - new KillingPartLikeRequest(false))) + new KillingPartLikeRequest(false))) .isInstanceOf(MemberException.MemberNotExistException.class); } } diff --git a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java index 872023c8..5f6abd39 100644 --- a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java +++ b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java @@ -39,12 +39,12 @@ void setUp() { @Test void recreate() { // given - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); likeAllKillingPartsInSong(songs.get(0)); likeAllKillingPartsInSong(songs.get(1)); // when - inMemorySongs.recreate(songs); + inMemorySongs.refreshSongs(songs); // 정렬 순서: 2L, 1L, 4L // then @@ -67,14 +67,14 @@ private void likeAllKillingPartsInSong(final Song song) { @Test void getSongById() { // given - final List songs = songRepository.findAllWithKillingParts(); - inMemorySongs.recreate(songs); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when + final List allSongs = inMemorySongs.getSongs(); final Song foundSong = inMemorySongs.getSongById(4L); // then - final Song expectedSong = songs.get(0); + final Song expectedSong = allSongs.get(0); assertThat(foundSong).isEqualTo(expectedSong); } @@ -82,14 +82,14 @@ void getSongById() { @Test void getPrevLikedSongs() { // given - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); final Song firstSong = songs.get(0); final Song secondSong = songs.get(1); final Song thirdSong = songs.get(2); final Song fourthSong = songs.get(3); likeAllKillingPartsInSong(firstSong); likeAllKillingPartsInSong(secondSong); - inMemorySongs.recreate(songs); // second, first, fourth, third + inMemorySongs.refreshSongs(songs); // second, first, fourth, third // when final List prevLikedSongs = inMemorySongs.getPrevLikedSongs(thirdSong, 2); @@ -106,14 +106,14 @@ void getPrevLikedSongs() { @Test void getNextLikedSongs() { // given - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); final Song firstSong = songs.get(0); final Song secondSong = songs.get(1); final Song thirdSong = songs.get(2); final Song fourthSong = songs.get(3); likeAllKillingPartsInSong(firstSong); likeAllKillingPartsInSong(secondSong); - inMemorySongs.recreate(songs); // second, first, fourth, third + inMemorySongs.refreshSongs(songs); // second, first, fourth, third // when final List nextLikedSongs = inMemorySongs.getNextLikedSongs(secondSong, 2); @@ -129,14 +129,14 @@ void getNextLikedSongs() { @DisplayName("특정 장르 노래에 대해 1. 좋아요 수가 더 적거나 2. 좋아요 수가 같은 경우 id가 더 작은 노래 목록을 조회한다.") @Test void getSortedSongsByGenre() { - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); final Song firstSong = songs.get(0); final Song secondSong = songs.get(1); final Song thirdSong = songs.get(2); final Song fourthSong = songs.get(3); likeAllKillingPartsInSong(firstSong); likeAllKillingPartsInSong(secondSong); - inMemorySongs.recreate(songs); // first, fourth, third + inMemorySongs.refreshSongs(songs); // first, fourth, third // when final List prevLikedSongs = inMemorySongs.getPrevLikedSongByGenre(firstSong, Genre.DANCE, 2); @@ -151,14 +151,14 @@ void getSortedSongsByGenre() { @DisplayName("특정 장르 노래에 대해 1. 좋아요 수가 더 많거나 2. 좋아요 수가 같은 경우 id가 더 큰 노래 목록을 조회한다.") @Test void getPrevLikedSongByGenre() { - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); final Song firstSong = songs.get(0); final Song secondSong = songs.get(1); final Song thirdSong = songs.get(2); final Song fourthSong = songs.get(3); likeAllKillingPartsInSong(firstSong); likeAllKillingPartsInSong(secondSong); - inMemorySongs.recreate(songs); // first, fourth, third + inMemorySongs.refreshSongs(songs); // first, fourth, third // when final List prevLikedSongs = inMemorySongs.getNextLikedSongByGenre(thirdSong, Genre.DANCE, 2); diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java index b4e68827..ab405e0a 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.List; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -135,7 +135,7 @@ void getLikes() { KILLING_PART.unlike(newLike); // when - final List likes = LIKES.getLikes(); + final Set likes = LIKES.getLikes(); // then assertThat(LIKES.getSize()).isEqualTo(1); diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java index 19299528..70e4db6f 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java @@ -110,40 +110,40 @@ void findAllBySong() { ); } - @DisplayName("한 킬링파트에 UPDATE + 1로 좋아요 수를 증가시킨다.") - @Test - void increaseLikeCount() { - // given - killingPartRepository.saveAll(KILLING_PARTS.getKillingParts()); - final KillingPart killingPart = killingPartRepository.findById(FIRST_KILLING_PART.getId()).get(); - final int initialLikeCount = killingPart.getLikeCount(); - - // when - saveAndClearEntityManager(); - killingPartRepository.increaseLikeCount(killingPart.getId()); - - // then - final KillingPart foundKillingPart = killingPartRepository.findById(killingPart.getId()).get(); - - assertThat(foundKillingPart.getLikeCount()).isEqualTo(initialLikeCount + 1); - } - - @DisplayName("한 킬링파트에 UPDATE - 1로 좋아요 수를 감소시킨다.") - @Test - void decreaseLikeCount() { - // given - killingPartRepository.saveAll(KILLING_PARTS.getKillingParts()); - killingPartRepository.increaseLikeCount(FIRST_KILLING_PART.getId()); - final KillingPart killingPart = killingPartRepository.findById(FIRST_KILLING_PART.getId()).get(); - final int initialLikeCount = killingPart.getLikeCount(); - - // when - saveAndClearEntityManager(); - killingPartRepository.decreaseLikeCount(killingPart.getId()); - - // then - final KillingPart foundKillingPart = killingPartRepository.findById(killingPart.getId()).get(); - - assertThat(foundKillingPart.getLikeCount()).isEqualTo(initialLikeCount - 1); - } +// @DisplayName("한 킬링파트에 UPDATE + 1로 좋아요 수를 증가시킨다.") +// @Test +// void increaseLikeCount() { +// // given +// killingPartRepository.saveAll(KILLING_PARTS.getKillingParts()); +// final KillingPart killingPart = killingPartRepository.findById(FIRST_KILLING_PART.getId()).get(); +// final int initialLikeCount = killingPart.getLikeCount(); +// +// // when +// saveAndClearEntityManager(); +// killingPartRepository.increaseLikeCount(killingPart.getId()); +// +// // then +// final KillingPart foundKillingPart = killingPartRepository.findById(killingPart.getId()).get(); +// +// assertThat(foundKillingPart.getLikeCount()).isEqualTo(initialLikeCount + 1); +// } +// +// @DisplayName("한 킬링파트에 UPDATE - 1로 좋아요 수를 감소시킨다.") +// @Test +// void decreaseLikeCount() { +// // given +// killingPartRepository.saveAll(KILLING_PARTS.getKillingParts()); +// killingPartRepository.increaseLikeCount(FIRST_KILLING_PART.getId()); +// final KillingPart killingPart = killingPartRepository.findById(FIRST_KILLING_PART.getId()).get(); +// final int initialLikeCount = killingPart.getLikeCount(); +// +// // when +// saveAndClearEntityManager(); +// killingPartRepository.decreaseLikeCount(killingPart.getId()); +// +// // then +// final KillingPart foundKillingPart = killingPartRepository.findById(killingPart.getId()).get(); +// +// assertThat(foundKillingPart.getLikeCount()).isEqualTo(initialLikeCount - 1); +// } } diff --git a/backend/src/test/java/shook/shook/song/ui/HighLikedSongControllerTest.java b/backend/src/test/java/shook/shook/song/ui/HighLikedSongControllerTest.java index 653d2995..f9cedc93 100644 --- a/backend/src/test/java/shook/shook/song/ui/HighLikedSongControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/HighLikedSongControllerTest.java @@ -51,16 +51,15 @@ void setUp() { @Test void showHighLikedSongs() { //given + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); // 정렬 순서 1L 2L 4L 3L - inMemorySongsScheduler.recreateCachedSong(); - //when final List responses = RestAssured.given().log().all() .when().log().all() @@ -94,16 +93,15 @@ void showHighLikedSongs() { void showHighLikedSongsWithGenre() { // given final String genre = "DANCE"; + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); // 정렬 순서 1L 4L 3L - inMemorySongsScheduler.recreateCachedSong(); - // when final List responses = RestAssured.given().log().all() .queryParam("genre", genre) @@ -118,11 +116,11 @@ void showHighLikedSongsWithGenre() { assertAll( () -> assertThat(responses).hasSize(3), () -> assertThat(responses.stream() - .map(HighLikedSongResponse::getId) - .toList()) + .map(HighLikedSongResponse::getId) + .toList()) .containsExactly(1L, 4L, 3L), () -> assertThat(responses.stream() - .map(HighLikedSongResponse::getTotalLikeCount)) + .map(HighLikedSongResponse::getTotalLikeCount)) .containsExactly(2L, 0L, 0L) ); } diff --git a/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java b/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java index 9016cf81..73392533 100644 --- a/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java @@ -25,6 +25,7 @@ import shook.shook.song.application.dto.MyPartsResponse; import shook.shook.song.application.killingpart.KillingPartLikeService; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; +import shook.shook.song.domain.InMemorySongs; import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.repository.KillingPartRepository; @@ -46,6 +47,9 @@ void setUp() { private static final long SAVED_MEMBER_ID = 1L; private static final String SAVED_MEMBER_NICKNAME = "nickname"; + @Autowired + private InMemorySongs inMemorySongs; + @Autowired private TokenProvider tokenProvider; @@ -73,6 +77,8 @@ class GetLikedKillingParts { @Test void likedKillingPartExistWithOneDeletedLikeExist() { //given + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); + final String accessToken = tokenProvider.createAccessToken(SAVED_MEMBER_ID, SAVED_MEMBER_NICKNAME); diff --git a/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java b/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java index 276805da..bf77d037 100644 --- a/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java @@ -57,6 +57,7 @@ void findSongById() { final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, MEMBER_NICKNAME); final Long songId = 1L; + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, @@ -65,7 +66,6 @@ void findSongById() { new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_3, MEMBER_ID, new KillingPartLikeRequest(true)); - inMemorySongsScheduler.recreateCachedSong(); memberPartService.register(songId, MEMBER_ID, new MemberPartRegisterRequest(0, 10)); diff --git a/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java b/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java index 494b106b..fe25d891 100644 --- a/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java @@ -25,7 +25,6 @@ import shook.shook.song.application.killingpart.dto.HighLikedSongResponse; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; -@SuppressWarnings("NonAsciiCharacters") @Sql("classpath:/killingpart/initialize_killing_part_song.sql") @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class SongSwipeControllerTest { @@ -62,6 +61,7 @@ void showSongById() { final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); final Long songId = 2L; + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -69,7 +69,6 @@ void showSongById() { likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); - inMemorySongsScheduler.recreateCachedSong(); // 정렬 순서: 1L, 2L, 4L, 3L //when final SongSwipeResponse response = RestAssured.given().log().all() @@ -97,6 +96,8 @@ void showSongsBeforeSongWithId() { // given final Long songId = 2L; final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -105,8 +106,6 @@ void showSongsBeforeSongWithId() { memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L, 2L - inMemorySongsScheduler.recreateCachedSong(); - //when final List response = RestAssured.given().log().all() .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) @@ -214,6 +213,7 @@ void findSongsByGenreForSwipe() { final String genre = "DANCE"; final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -224,8 +224,6 @@ void findSongsByGenreForSwipe() { memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L - inMemorySongsScheduler.recreateCachedSong(); - //when final SongSwipeResponse response = RestAssured.given().log().all() .queryParam("genre", genre) @@ -258,6 +256,7 @@ void showPrevSongsWithGenre() { final String genre = "DANCE"; final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -266,8 +265,6 @@ void showPrevSongsWithGenre() { memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L - inMemorySongsScheduler.recreateCachedSong(); - //when final List response = RestAssured.given().log().all() .queryParam("genre", genre) @@ -295,6 +292,7 @@ void showNextSongsWithGenre() { final String genre = "DANCE"; final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -305,8 +303,6 @@ void showNextSongsWithGenre() { memberPartService.register(4L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L - inMemorySongsScheduler.recreateCachedSong(); - //when final List response = RestAssured.given().log().all() .queryParam("genre", genre)