-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 유저 태그 수정 및 삭제 시 이미지 태그 반영 #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package com.capturecat.core.service.tag; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| import com.capturecat.core.domain.tag.ImageTagRepository; | ||
| import com.capturecat.core.domain.tag.Tag; | ||
| import com.capturecat.core.domain.tag.TagRepository; | ||
| import com.capturecat.core.domain.user.User; | ||
| import com.capturecat.core.domain.user.UserRepository; | ||
| import com.capturecat.core.service.auth.LoginUser; | ||
| import com.capturecat.core.support.error.CoreException; | ||
| import com.capturecat.core.support.error.ErrorType; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class ImageTagService { | ||
|
|
||
| private final ImageTagRepository imageTagRepository; | ||
| private final UserRepository userRepository; | ||
| private final TagRepository tagRepository; | ||
|
|
||
| @Transactional | ||
| public void update(LoginUser loginUser, Long oldTagId, Long newTagId) { | ||
| User user = userRepository.findByUsername(loginUser.getUsername()) | ||
| .orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND)); | ||
|
|
||
| imageTagRepository.updateImageTagsForUser(user.getId(), oldTagId, newTagId); | ||
| } | ||
|
Comment on lines
+25
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate that newTagId exists before updating. The Apply this diff to add validation: @Transactional
public void update(LoginUser loginUser, Long oldTagId, Long newTagId) {
User user = userRepository.findByUsername(loginUser.getUsername())
.orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND));
+ tagRepository.findById(newTagId)
+ .orElseThrow(() -> new CoreException(ErrorType.TAG_NOT_FOUND));
imageTagRepository.updateImageTagsForUser(user.getId(), oldTagId, newTagId);
}
🤖 Prompt for AI Agents |
||
|
|
||
| @Transactional | ||
| public void delete(LoginUser loginUser, Long tagId) { | ||
| User user = userRepository.findByUsername(loginUser.getUsername()) | ||
| .orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND)); | ||
| Tag tag = tagRepository.findById(tagId) | ||
| .orElseThrow(() -> new CoreException(ErrorType.TAG_NOT_FOUND)); | ||
|
|
||
| imageTagRepository.deleteByTagAndUser(tag, user); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| package com.capturecat.core.service.tag; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
| import static org.mockito.ArgumentMatchers.anyLong; | ||
| import static org.mockito.ArgumentMatchers.anyString; | ||
| import static org.mockito.BDDMockito.given; | ||
| import static org.mockito.Mockito.times; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.mockito.Mockito.verifyNoInteractions; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.extension.ExtendWith; | ||
| import org.mockito.InjectMocks; | ||
| import org.mockito.Mock; | ||
| import org.mockito.junit.jupiter.MockitoExtension; | ||
|
|
||
| import com.capturecat.core.DummyObject; | ||
| import com.capturecat.core.domain.tag.ImageTagRepository; | ||
| import com.capturecat.core.domain.tag.Tag; | ||
| import com.capturecat.core.domain.tag.TagFixture; | ||
| import com.capturecat.core.domain.tag.TagRepository; | ||
| import com.capturecat.core.domain.user.User; | ||
| import com.capturecat.core.domain.user.UserRepository; | ||
| import com.capturecat.core.service.auth.LoginUser; | ||
| import com.capturecat.core.support.error.CoreException; | ||
|
|
||
| @ExtendWith(MockitoExtension.class) | ||
| class ImageTagServiceTest { | ||
|
|
||
| @Mock | ||
| ImageTagRepository imageTagRepository; | ||
|
|
||
| @Mock | ||
| UserRepository userRepository; | ||
|
|
||
| @Mock | ||
| TagRepository tagRepository; | ||
|
|
||
| @InjectMocks | ||
| ImageTagService imageTagService; | ||
|
|
||
| @Test | ||
| void 업데이트_사용자_존재하면_레포지토리_호출한다() { | ||
| // given | ||
| User user = DummyObject.newMockUser(123L); | ||
|
|
||
| given(userRepository.findByUsername(anyString())).willReturn(Optional.of(user)); | ||
|
|
||
| // when | ||
| imageTagService.update(new LoginUser(user), 10L, 20L); | ||
|
|
||
| // then | ||
| verify(imageTagRepository, times(1)) | ||
| .updateImageTagsForUser(user.getId(), 10L, 20L); | ||
| } | ||
|
|
||
| @Test | ||
| void 업데이트_사용자_없으면_CoreException_던진다() { | ||
| // given | ||
| User user = DummyObject.newUser("noUser"); | ||
|
|
||
| given(userRepository.findByUsername(anyString())).willReturn(Optional.empty()); | ||
|
|
||
| // when & then | ||
| assertThatThrownBy(() -> imageTagService.update(new LoginUser(user), 1L, 2L)) | ||
| .isInstanceOf(CoreException.class); | ||
| verifyNoInteractions(imageTagRepository); | ||
| } | ||
|
|
||
| @Test | ||
| void 삭제_사용자_태그_존재하면_레포지토리_호출한다() { | ||
| // given | ||
| User user = DummyObject.newUser("test"); | ||
|
|
||
| given(userRepository.findByUsername(anyString())).willReturn(Optional.of(user)); | ||
|
|
||
| Tag tag = TagFixture.createTag(10L, "testTag"); | ||
| given(tagRepository.findById(anyLong())).willReturn(Optional.of(tag)); | ||
|
|
||
| // when | ||
| imageTagService.delete(new LoginUser(user), tag.getId()); | ||
|
|
||
| // then | ||
| verify(imageTagRepository, times(1)).deleteByTagAndUser(tag, user); | ||
| } | ||
|
|
||
| @Test | ||
| void 삭제_사용자_없으면_CoreException_던진다() { | ||
| // given | ||
| User user = DummyObject.newUser("noUser"); | ||
|
|
||
| given(userRepository.findByUsername(anyString())).willReturn(Optional.empty()); | ||
|
|
||
| // when & then | ||
| assertThatThrownBy(() -> imageTagService.delete(new LoginUser(user), 1L)) | ||
| .isInstanceOf(CoreException.class); | ||
| verifyNoInteractions(imageTagRepository, tagRepository); | ||
| } | ||
|
|
||
| @Test | ||
| void 삭제_태그_없으면_CoreException_던진다() { | ||
| // given | ||
| User user = DummyObject.newUser("test"); | ||
|
|
||
| given(userRepository.findByUsername(anyString())).willReturn(Optional.of(user)); | ||
| given(tagRepository.findById(anyLong())).willReturn(Optional.empty()); | ||
|
|
||
| // when & then | ||
| assertThatThrownBy(() -> imageTagService.delete(new LoginUser(user), 2L)) | ||
| .isInstanceOf(CoreException.class); | ||
| verifyNoInteractions(imageTagRepository); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverse the delete operation order to avoid potential errors.
Deleting the user tag before the image tags can cause
TAG_NOT_FOUNDexception inImageTagService.deleteat line 37-38, which attempts to load the tag after it may have been deleted. This creates fragile transaction-dependent coupling.Apply this diff to delete image tags first:
@DeleteMapping public ApiResponse<?> delete(@AuthenticationPrincipal LoginUser loginUser, @RequestParam Long tagId) { - userTagService.delete(loginUser, tagId); imageTagService.delete(loginUser, tagId); + userTagService.delete(loginUser, tagId); return ApiResponse.success(); }📝 Committable suggestion
🤖 Prompt for AI Agents