From 0b825e55f6254c8a64b724b4755577cf33a156e2 Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Tue, 9 Apr 2024 20:49:05 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=ED=83=9C=EA=B7=B8=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=97=90=20=EC=83=9D=EC=84=B1,=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=EA=B0=84=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/seong/shoutlink/domain/tag/Tag.java | 14 ++++++++++++-- .../shoutlink/domain/tag/repository/TagEntity.java | 2 +- .../domain/tag/repository/StubTagRepository.java | 2 +- .../com/seong/shoutlink/fixture/TagFixture.java | 7 ++++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/seong/shoutlink/domain/tag/Tag.java b/src/main/java/com/seong/shoutlink/domain/tag/Tag.java index afdd9e3..c33684a 100644 --- a/src/main/java/com/seong/shoutlink/domain/tag/Tag.java +++ b/src/main/java/com/seong/shoutlink/domain/tag/Tag.java @@ -1,5 +1,6 @@ package com.seong.shoutlink.domain.tag; +import java.time.LocalDateTime; import lombok.Getter; @Getter @@ -7,13 +8,22 @@ public class Tag { private Long tagId; private String name; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; public Tag(String name) { - this(null, name); + this(null, name, null, null); } - public Tag(Long tagId, String name) { + public Tag(Long tagId, String name, LocalDateTime createdAt, LocalDateTime updatedAt) { this.tagId = tagId; this.name = name; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public boolean isCreatedWithinADay() { + LocalDateTime aDayAgo = LocalDateTime.now().minusDays(1); + return createdAt.isAfter(aDayAgo); } } diff --git a/src/main/java/com/seong/shoutlink/domain/tag/repository/TagEntity.java b/src/main/java/com/seong/shoutlink/domain/tag/repository/TagEntity.java index 180206d..6bff45a 100644 --- a/src/main/java/com/seong/shoutlink/domain/tag/repository/TagEntity.java +++ b/src/main/java/com/seong/shoutlink/domain/tag/repository/TagEntity.java @@ -41,6 +41,6 @@ public static TagEntity from(HubTag hubTag) { } public Tag toDomain() { - return new Tag(tagId, name); + return new Tag(tagId, name, getCreatedAt(), getUpdatedAt()); } } diff --git a/src/test/java/com/seong/shoutlink/domain/tag/repository/StubTagRepository.java b/src/test/java/com/seong/shoutlink/domain/tag/repository/StubTagRepository.java index 2bd05ae..b82cdb5 100644 --- a/src/test/java/com/seong/shoutlink/domain/tag/repository/StubTagRepository.java +++ b/src/test/java/com/seong/shoutlink/domain/tag/repository/StubTagRepository.java @@ -33,7 +33,7 @@ public List saveAll(List hubTags) { } return memory.entrySet().stream().map(entry -> { Tag value = entry.getValue(); - return new Tag(entry.getKey(), value.getName()); + return new Tag(entry.getKey(), value.getName(), value.getCreatedAt(), value.getUpdatedAt()); }).toList(); } diff --git a/src/test/java/com/seong/shoutlink/fixture/TagFixture.java b/src/test/java/com/seong/shoutlink/fixture/TagFixture.java index 528f073..e98e675 100644 --- a/src/test/java/com/seong/shoutlink/fixture/TagFixture.java +++ b/src/test/java/com/seong/shoutlink/fixture/TagFixture.java @@ -1,10 +1,15 @@ package com.seong.shoutlink.fixture; import com.seong.shoutlink.domain.tag.Tag; +import java.time.LocalDateTime; public final class TagFixture { + public static final long TAG_ID = 1L; + public static final String TAG_NAME = "태그1"; + public static final LocalDateTime MORE_THEN_A_DAY = LocalDateTime.now().minusHours(25); + public static Tag tag() { - return new Tag(1L, "태그1"); + return new Tag(TAG_ID, TAG_NAME, MORE_THEN_A_DAY, MORE_THEN_A_DAY); } } From 87b59a005d9435a869d0b6542963f7b4661d9fc9 Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Tue, 9 Apr 2024 20:50:48 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=ED=83=9C=EA=B7=B8=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EC=83=9D=EC=84=B1=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tag/repository/TagJpaRepository.java | 7 ++ .../tag/repository/TagRepositoryImpl.java | 7 ++ .../domain/tag/service/TagRepository.java | 3 + .../domain/tag/service/TagService.java | 17 +++-- .../tag/repository/StubTagRepository.java | 6 ++ .../domain/tag/service/TagServiceTest.java | 68 ++++++++++++++++--- 6 files changed, 93 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/seong/shoutlink/domain/tag/repository/TagJpaRepository.java b/src/main/java/com/seong/shoutlink/domain/tag/repository/TagJpaRepository.java index 514c01d..86b77a7 100644 --- a/src/main/java/com/seong/shoutlink/domain/tag/repository/TagJpaRepository.java +++ b/src/main/java/com/seong/shoutlink/domain/tag/repository/TagJpaRepository.java @@ -1,5 +1,6 @@ package com.seong.shoutlink.domain.tag.repository; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -8,4 +9,10 @@ public interface TagJpaRepository extends JpaRepository { @Query("delete from HubTagEntity t where t.hubId=:hubId") long deleteByHubId(@Param("hubId") Long hubId); + + @Query("select t from HubTagEntity t" + + " where t.hubId=:hubId" + + " order by t.createdAt" + + " limit 1") + Optional findLatestTagByHubId(Long hubId); } diff --git a/src/main/java/com/seong/shoutlink/domain/tag/repository/TagRepositoryImpl.java b/src/main/java/com/seong/shoutlink/domain/tag/repository/TagRepositoryImpl.java index 81ca7c4..57c283b 100644 --- a/src/main/java/com/seong/shoutlink/domain/tag/repository/TagRepositoryImpl.java +++ b/src/main/java/com/seong/shoutlink/domain/tag/repository/TagRepositoryImpl.java @@ -5,6 +5,7 @@ import com.seong.shoutlink.domain.tag.Tag; import com.seong.shoutlink.domain.tag.service.TagRepository; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -28,4 +29,10 @@ public List saveAll(List tags) { public long deleteHubTags(Hub hub) { return tagJpaRepository.deleteByHubId(hub.getHubId()); } + + @Override + public Optional findLatestTagByHub(Hub hub) { + return tagJpaRepository.findLatestTagByHubId(hub.getHubId()) + .map(TagEntity::toDomain); + } } diff --git a/src/main/java/com/seong/shoutlink/domain/tag/service/TagRepository.java b/src/main/java/com/seong/shoutlink/domain/tag/service/TagRepository.java index 51903c4..8b795e5 100644 --- a/src/main/java/com/seong/shoutlink/domain/tag/service/TagRepository.java +++ b/src/main/java/com/seong/shoutlink/domain/tag/service/TagRepository.java @@ -4,10 +4,13 @@ import com.seong.shoutlink.domain.tag.HubTag; import com.seong.shoutlink.domain.tag.Tag; import java.util.List; +import java.util.Optional; public interface TagRepository { List saveAll(List tags); long deleteHubTags(Hub hub); + + Optional findLatestTagByHub(Hub hub); } diff --git a/src/main/java/com/seong/shoutlink/domain/tag/service/TagService.java b/src/main/java/com/seong/shoutlink/domain/tag/service/TagService.java index 97772c9..048c7aa 100644 --- a/src/main/java/com/seong/shoutlink/domain/tag/service/TagService.java +++ b/src/main/java/com/seong/shoutlink/domain/tag/service/TagService.java @@ -27,6 +27,7 @@ public class TagService { private static final int MINIMUM_TAG_CONDITION = 5; private static final int MAXIMUM_TAG_COUNT = 5; + private static final int ZERO = 0; private final TagRepository tagRepository; private final HubRepository hubRepository; @@ -37,9 +38,9 @@ public class TagService { @Transactional public CreateTagResponse autoCreateHubTags(AutoCreateTagCommand command) { Hub hub = getHub(command.hubId()); + checkTagIsCreatedWithinADay(hub); List hubLinkBundles = linkBundleRepository.findHubLinkBundles(hub); - List links = linkRepository.findAllByLinkBundlesIn( - hubLinkBundles); + List links = linkRepository.findAllByLinkBundlesIn(hubLinkBundles); List linkBundlesAndLinks = groupingLinks(links); int generateTagCount = calculateNumberOfTag(links); @@ -51,12 +52,19 @@ public CreateTagResponse autoCreateHubTags(AutoCreateTagCommand command) { .map(generatedTag -> new Tag(generatedTag.name())) .map(tag -> new HubTag(hub, tag)) .toList(); - if(!hubTags.isEmpty()) { + if (!hubTags.isEmpty()) { tagRepository.deleteHubTags(hub); } return CreateTagResponse.from(tagRepository.saveAll(hubTags)); } + private void checkTagIsCreatedWithinADay(Hub hub) { + tagRepository.findLatestTagByHub(hub) + .filter(Tag::isCreatedWithinADay) + .ifPresent(tag -> { + throw new ShoutLinkException("태그 생성된 지 하루가 지나지 않았습니다.", ErrorCode.NOT_MET_CONDITION);}); + } + private List groupingLinks(List links) { return links.stream() .collect(groupingBy( @@ -69,7 +77,8 @@ private List groupingLinks(List links) { private int calculateNumberOfTag(List links) { int totalLinkCount = links.size(); - if (totalLinkCount < MINIMUM_TAG_CONDITION) { + if (totalLinkCount < MINIMUM_TAG_CONDITION + || totalLinkCount % MINIMUM_TAG_CONDITION != ZERO) { throw new ShoutLinkException("태그 생성 조건을 충족하지 못했습니다.", ErrorCode.NOT_MET_CONDITION); } return Math.min(MAXIMUM_TAG_COUNT, totalLinkCount / MINIMUM_TAG_CONDITION); diff --git a/src/test/java/com/seong/shoutlink/domain/tag/repository/StubTagRepository.java b/src/test/java/com/seong/shoutlink/domain/tag/repository/StubTagRepository.java index b82cdb5..0703709 100644 --- a/src/test/java/com/seong/shoutlink/domain/tag/repository/StubTagRepository.java +++ b/src/test/java/com/seong/shoutlink/domain/tag/repository/StubTagRepository.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; public class StubTagRepository implements TagRepository { @@ -43,4 +44,9 @@ public long deleteHubTags(Hub hub) { memory.clear(); return size; } + + @Override + public Optional findLatestTagByHub(Hub hub) { + return memory.values().stream().findFirst(); + } } diff --git a/src/test/java/com/seong/shoutlink/domain/tag/service/TagServiceTest.java b/src/test/java/com/seong/shoutlink/domain/tag/service/TagServiceTest.java index a321210..136e98a 100644 --- a/src/test/java/com/seong/shoutlink/domain/tag/service/TagServiceTest.java +++ b/src/test/java/com/seong/shoutlink/domain/tag/service/TagServiceTest.java @@ -10,6 +10,7 @@ import com.seong.shoutlink.domain.link.Link; import com.seong.shoutlink.domain.link.repository.FakeLinkRepository; import com.seong.shoutlink.domain.linkbundle.repository.FakeLinkBundleRepository; +import com.seong.shoutlink.domain.tag.Tag; import com.seong.shoutlink.domain.tag.repository.StubTagRepository; import com.seong.shoutlink.domain.tag.service.request.AutoCreateTagCommand; import com.seong.shoutlink.domain.tag.service.response.CreateTagResponse; @@ -22,10 +23,13 @@ import com.seong.shoutlink.fixture.TagFixture; import com.seong.shoutlink.global.client.StubApiClient; import com.seong.shoutlink.global.client.ai.GeminiClient; +import java.time.LocalDateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class TagServiceTest { @@ -60,15 +64,16 @@ void setUp() { linkRepository, autoGenerativeClient); } - @Test - @DisplayName("성공: 링크가 5개 이상인 경우 자동 태그 생성한다.") - void autoCreateTag() { + @ParameterizedTest + @CsvSource({"5", "10", "15", "20", "25"}) + @DisplayName("성공: 링크가 5의 배수인 경우 자동 태그 생성한다.") + void autoCreateTag(int totalLinkSize) { //given AutoCreateTagCommand command = new AutoCreateTagCommand(1L); hubRepository.stub(HubFixture.hub(MemberFixture.member())); linkBundleRepository.stub(LinkBundleFixture.linkBundle()); - linkRepository.stub(LinkFixture.links(5).toArray(Link[]::new)); + linkRepository.stub(LinkFixture.links(totalLinkSize).toArray(Link[]::new)); //when CreateTagResponse response = tagService.autoCreateHubTags(command); @@ -96,14 +101,50 @@ void thenDeleteOldTags() { } @Test + @DisplayName("예외(notFound): 존재하지 않는 허브인 경우") + void notFound_WhenHubNotFound() { + //given + AutoCreateTagCommand command = new AutoCreateTagCommand(1L); + + //when + Exception exception = catchException(() -> tagService.autoCreateHubTags(command)); + + //then + assertThat(exception).isInstanceOf(ShoutLinkException.class) + .extracting(e -> ((ShoutLinkException) e).getErrorCode()) + .isEqualTo(ErrorCode.NOT_FOUND); + } + + @Test + @DisplayName("예외(notMetCondition): 하루 안에 생성된 태그가 존재하는 경우") + void notMetCondition_WhenLatestTagCreatedWithinADay() { + //given + AutoCreateTagCommand command = new AutoCreateTagCommand(1L); + LocalDateTime createdWithinADay = LocalDateTime.now().minusHours(23); + Tag tag = new Tag(1L, "태그", createdWithinADay, createdWithinADay); + + hubRepository.stub(HubFixture.hub(MemberFixture.member())); + tagRepository.stub(tag); + + //when + Exception exception = catchException(() -> tagService.autoCreateHubTags(command)); + + //then + assertThat(exception).isInstanceOf(ShoutLinkException.class) + .extracting(e -> ((ShoutLinkException) e).getErrorCode()) + .isEqualTo(ErrorCode.NOT_MET_CONDITION); + } + + @ParameterizedTest + @CsvSource({"0","1","2","3","4"}) @DisplayName("예외(notMetCondition): 링크가 5개 미만인 경우") - void notMetCondition_WhenTotalLinkCountLT5() { + void notMetCondition_WhenTotalLinkCountLT5(int totalLinkSize) { //given AutoCreateTagCommand command = new AutoCreateTagCommand(1L); hubRepository.stub(HubFixture.hub(MemberFixture.member())); linkBundleRepository.stub(LinkBundleFixture.linkBundle()); - linkRepository.stub(LinkFixture.links(4).toArray(Link[]::new)); + linkRepository.stub(LinkFixture.links(totalLinkSize).toArray(Link[]::new)); //when Exception exception = catchException(() -> tagService.autoCreateHubTags(command)); @@ -114,19 +155,24 @@ void notMetCondition_WhenTotalLinkCountLT5() { .isEqualTo(ErrorCode.NOT_MET_CONDITION); } - @Test - @DisplayName("예외(notFound): 존재하지 않는 허브인 경우") - void notFound_WhenHubNotFound() { + @ParameterizedTest + @CsvSource({"6","9","11","14","16","19","21","24"}) + @DisplayName("예외(notMetCondition): 링크 개수가 5의 배수가 아닐 때") + void notMetCondition_WhenTotalLinkCountIsNotMultiplesOf5(int totalLinkSize) { //given AutoCreateTagCommand command = new AutoCreateTagCommand(1L); + hubRepository.stub(HubFixture.hub(MemberFixture.member())); + linkBundleRepository.stub(LinkBundleFixture.linkBundle()); + linkRepository.stub(LinkFixture.links(totalLinkSize).toArray(Link[]::new)); + //when Exception exception = catchException(() -> tagService.autoCreateHubTags(command)); //then assertThat(exception).isInstanceOf(ShoutLinkException.class) .extracting(e -> ((ShoutLinkException) e).getErrorCode()) - .isEqualTo(ErrorCode.NOT_FOUND); + .isEqualTo(ErrorCode.NOT_MET_CONDITION); } } -} \ No newline at end of file +}