diff --git a/src/main/java/com/swyp/picke/domain/battle/converter/BattleConverter.java b/src/main/java/com/swyp/picke/domain/battle/converter/BattleConverter.java index 83c1a69d..cb0280df 100644 --- a/src/main/java/com/swyp/picke/domain/battle/converter/BattleConverter.java +++ b/src/main/java/com/swyp/picke/domain/battle/converter/BattleConverter.java @@ -8,6 +8,7 @@ import com.swyp.picke.domain.battle.enums.BattleCreatorType; import com.swyp.picke.domain.tag.entity.Tag; import com.swyp.picke.domain.tag.enums.TagType; +import com.swyp.picke.domain.user.enums.PhilosopherType; import com.swyp.picke.domain.user.entity.User; import com.swyp.picke.domain.user.enums.UserBattleStep; import com.swyp.picke.domain.user.enums.VoteSide; @@ -165,6 +166,8 @@ private List toTagResponses(List tags, TagType targetTyp if (tags == null) return List.of(); return tags.stream() .filter(tag -> targetType == null || tag.getType() == targetType) + .filter(tag -> tag.getType() != TagType.PHILOSOPHER + || PhilosopherType.fromLabel(tag.getName()) != null) .map(tag -> new BattleTagResponse(tag.getId(), tag.getName(), tag.getType())) .toList(); } diff --git a/src/main/java/com/swyp/picke/domain/battle/service/BattleQueryService.java b/src/main/java/com/swyp/picke/domain/battle/service/BattleQueryService.java index 0cb8ef5b..8c6f8846 100644 --- a/src/main/java/com/swyp/picke/domain/battle/service/BattleQueryService.java +++ b/src/main/java/com/swyp/picke/domain/battle/service/BattleQueryService.java @@ -13,6 +13,7 @@ import org.springframework.transaction.annotation.Transactional; import com.swyp.picke.domain.tag.enums.TagType; +import com.swyp.picke.domain.user.enums.PhilosopherType; import java.util.List; import java.util.Map; @@ -84,6 +85,7 @@ public Optional getTopPhilosopherTagNameFromOptions(List optionIds return optionTags.stream() .filter(bot -> bot.getTag().getType() == TagType.PHILOSOPHER) + .filter(bot -> PhilosopherType.fromLabel(bot.getTag().getName()) != null) .collect(Collectors.groupingBy( bot -> bot.getTag().getName(), Collectors.counting() diff --git a/src/main/java/com/swyp/picke/domain/battle/service/BattleServiceImpl.java b/src/main/java/com/swyp/picke/domain/battle/service/BattleServiceImpl.java index 4fb08eee..bc43e7b0 100644 --- a/src/main/java/com/swyp/picke/domain/battle/service/BattleServiceImpl.java +++ b/src/main/java/com/swyp/picke/domain/battle/service/BattleServiceImpl.java @@ -15,6 +15,7 @@ import com.swyp.picke.domain.battle.util.BattleOptionDisplay; import com.swyp.picke.domain.user.dto.response.UserBattleStatusResponse; import com.swyp.picke.domain.user.enums.UserBattleStep; +import com.swyp.picke.domain.user.enums.PhilosopherType; import com.swyp.picke.domain.battle.repository.BattleOptionRepository; import com.swyp.picke.domain.battle.repository.BattleOptionTagRepository; import com.swyp.picke.domain.battle.repository.BattleRepository; @@ -649,6 +650,8 @@ private void saveBattleOptionTags(BattleOption option, List tagIds) { tagRepository.findAllById(tagIds).stream() .filter(tag -> tag.getDeletedAt() == null) .filter(tag -> tag.getType() == TagType.PHILOSOPHER || tag.getType() == TagType.VALUE) + .filter(tag -> tag.getType() != TagType.PHILOSOPHER + || PhilosopherType.fromLabel(tag.getName()) != null) .forEach(tag -> battleOptionTagRepository.save( BattleOptionTag.builder().battleOption(option).tag(tag).build())); } diff --git a/src/main/java/com/swyp/picke/domain/tag/service/TagServiceImpl.java b/src/main/java/com/swyp/picke/domain/tag/service/TagServiceImpl.java index a9df9a47..b134d8c7 100644 --- a/src/main/java/com/swyp/picke/domain/tag/service/TagServiceImpl.java +++ b/src/main/java/com/swyp/picke/domain/tag/service/TagServiceImpl.java @@ -12,6 +12,7 @@ import com.swyp.picke.domain.tag.entity.Tag; import com.swyp.picke.domain.tag.enums.TagType; import com.swyp.picke.domain.tag.repository.TagRepository; +import com.swyp.picke.domain.user.enums.PhilosopherType; import com.swyp.picke.global.common.exception.CustomException; import com.swyp.picke.global.common.exception.ErrorCode; import lombok.RequiredArgsConstructor; @@ -46,13 +47,16 @@ public TagListResponse getTags(TagType type) { List tags = (type != null) ? tagRepository.findAllByTypeAndDeletedAtIsNull(type) : tagRepository.findAllByDeletedAtIsNull(); - return TagConverter.toListResponse(tags); + return TagConverter.toListResponse(tags.stream() + .filter(this::isSupportedPhilosopherTag) + .toList()); } @Override @Transactional @PreAuthorize("hasRole('ADMIN')") public TagResponse createTag(TagRequest request) { + validatePhilosopherTag(request.name(), request.type()); validateDuplicateTag(request.name(), request.type()); Tag newTag = TagConverter.toEntity(request); @@ -66,6 +70,7 @@ public TagResponse createTag(TagRequest request) { @PreAuthorize("hasRole('ADMIN')") public TagResponse updateTag(Long tagId, TagRequest request) { Tag tag = findTagById(tagId); + validatePhilosopherTag(request.name(), request.type()); boolean typeChanged = tag.getType() != request.type(); if (!tag.getName().equals(request.name()) || tag.getType() != request.type()) { @@ -105,6 +110,17 @@ private void validateDuplicateTag(String name, TagType type) { } } + private void validatePhilosopherTag(String name, TagType type) { + if (type == TagType.PHILOSOPHER && PhilosopherType.fromLabel(name) == null) { + throw new CustomException(ErrorCode.BAD_REQUEST); + } + } + + private boolean isSupportedPhilosopherTag(Tag tag) { + return tag.getType() != TagType.PHILOSOPHER + || PhilosopherType.fromLabel(tag.getName()) != null; + } + private boolean isTagInUse(Tag tag) { return battleTagRepository.existsByTag(tag) || battleOptionTagRepository.existsByTag(tag); } diff --git a/src/main/java/com/swyp/picke/domain/user/entity/UserTendencyScore.java b/src/main/java/com/swyp/picke/domain/user/entity/UserTendencyScore.java index 7cb2ae64..59c37cfc 100644 --- a/src/main/java/com/swyp/picke/domain/user/entity/UserTendencyScore.java +++ b/src/main/java/com/swyp/picke/domain/user/entity/UserTendencyScore.java @@ -1,5 +1,6 @@ package com.swyp.picke.domain.user.entity; +import com.swyp.picke.domain.user.enums.ValueAxis; import com.swyp.picke.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -57,4 +58,22 @@ public void update(int principle, int reason, int individual, this.inner = inner; this.ideal = ideal; } + + public void applyValueTag(String tagName) { + applyValueTag(tagName, 1); + } + + public void applyValueTag(String tagName, int multiplier) { + ValueAxis.resolve(tagName).ifPresent(value -> { + int direction = value.direction() * multiplier; + switch (value.axis()) { + case PRINCIPLE -> principle += direction; + case REASON -> reason += direction; + case INDIVIDUAL -> individual += direction; + case CHANGE -> change += direction; + case INNER -> inner += direction; + case IDEAL -> ideal += direction; + } + }); + } } diff --git a/src/main/java/com/swyp/picke/domain/user/enums/PhilosopherType.java b/src/main/java/com/swyp/picke/domain/user/enums/PhilosopherType.java index 25c24a0f..4e55536b 100644 --- a/src/main/java/com/swyp/picke/domain/user/enums/PhilosopherType.java +++ b/src/main/java/com/swyp/picke/domain/user/enums/PhilosopherType.java @@ -52,29 +52,11 @@ public enum PhilosopherType { "BUDDHA", "CONFUCIUS", 22, 38, 68, 88, 94, 70, "images/philosophers/laozi.png"), - BUDDHA("붓다", "내면형", "외부의 소음에서 벗어나 마음속 깊은 평화와 고요를 찾는 수행자", + BUDDHA("석가모니", "내면형", "외부의 소음에서 벗어나 마음속 깊은 평화와 고요를 찾는 수행자", List.of("#평화", "#내면", "#명상", "#해탈"), "LAOZI", "ARISTOTLE", 35, 55, 42, 48, 96, 62, - "images/philosophers/buddha.png"), - AQUINAS("토마스 아퀴나스", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/aquinas.png"), - CAMUS("카뮈", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/camus.png"), - CHOE_HANGI("최한기", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/choe_hangi.png"), - DESCARTES("데카르트", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/descartes.png"), - EPICURUS("에피쿠로스", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/epicurus.png"), - FROMM("에리히 프롬", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/fromm.png"), - HOBBES("홉스", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/hobbes.png"), - HUME("흄", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/hume.png"), - JEONG_YAKYONG("정약용", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/jeong_yakyong.png"), - JUNG("융", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/jung.png"), - LEIBNIZ("라이프니츠", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/leibniz.png"), - MENCIUS("맹자", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/mencius.png"), - MILL("존 스튜어트 밀", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/mill.png"), - RAWLS("롤스", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/rawls.png"), - SCHOPENHAUER("쇼펜하우어", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/schopenhauer.png"), - XUNZI("순자", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/xunzi.png"), - YI_HWANG("이황", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/yi_hwang.png"), - YI_I("이이", null, null, null, null, 0, 0, 0, 0, 0, 0, "images/philosophers/yi_i.png"); + "images/philosophers/buddha.png"); private final String label; private final String typeName; @@ -111,16 +93,6 @@ public enum PhilosopherType { this.imageKey = imageKey; } - PhilosopherType(String label, String typeName, String description, - String bestMatchName, String worstMatchName, - int principle, int reason, int individual, - int change, int inner, int ideal, - String imageKey) { - this(label, typeName, description, List.of(), - bestMatchName, worstMatchName, - principle, reason, individual, change, inner, ideal, imageKey); - } - public PhilosopherType getBestMatch() { return bestMatchName != null ? valueOf(bestMatchName) : null; } diff --git a/src/main/java/com/swyp/picke/domain/user/enums/ValueAxis.java b/src/main/java/com/swyp/picke/domain/user/enums/ValueAxis.java new file mode 100644 index 00000000..f0a9106c --- /dev/null +++ b/src/main/java/com/swyp/picke/domain/user/enums/ValueAxis.java @@ -0,0 +1,34 @@ +package com.swyp.picke.domain.user.enums; + +import java.util.Arrays; +import java.util.Optional; + +public enum ValueAxis { + PRINCIPLE("원칙", "결과"), + REASON("이성", "감성"), + INDIVIDUAL("개인", "관계"), + CHANGE("변화", "전통"), + INNER("내면", "구조"), + IDEAL("이상", "현실"); + + private final String positiveTag; + private final String negativeTag; + + ValueAxis(String positiveTag, String negativeTag) { + this.positiveTag = positiveTag; + this.negativeTag = negativeTag; + } + + public static Optional resolve(String tagName) { + return Arrays.stream(values()) + .filter(axis -> axis.positiveTag.equals(tagName) || axis.negativeTag.equals(tagName)) + .findFirst() + .map(axis -> new ValueDirection( + axis, + axis.positiveTag.equals(tagName) ? 1 : -1 + )); + } + + public record ValueDirection(ValueAxis axis, int direction) { + } +} diff --git a/src/main/java/com/swyp/picke/domain/vote/service/BattleVoteServiceImpl.java b/src/main/java/com/swyp/picke/domain/vote/service/BattleVoteServiceImpl.java index 8497d6b8..b38872e3 100644 --- a/src/main/java/com/swyp/picke/domain/vote/service/BattleVoteServiceImpl.java +++ b/src/main/java/com/swyp/picke/domain/vote/service/BattleVoteServiceImpl.java @@ -3,12 +3,16 @@ import com.swyp.picke.domain.battle.entity.Battle; import com.swyp.picke.domain.battle.entity.BattleOption; import com.swyp.picke.domain.battle.repository.BattleOptionRepository; +import com.swyp.picke.domain.battle.repository.BattleOptionTagRepository; import com.swyp.picke.domain.battle.service.BattleService; +import com.swyp.picke.domain.tag.enums.TagType; import com.swyp.picke.domain.user.dto.response.UserBattleStatusResponse; import com.swyp.picke.domain.user.entity.User; +import com.swyp.picke.domain.user.entity.UserTendencyScore; import com.swyp.picke.domain.user.enums.CreditType; import com.swyp.picke.domain.user.enums.UserBattleStep; import com.swyp.picke.domain.user.repository.UserRepository; +import com.swyp.picke.domain.user.repository.UserTendencyScoreRepository; import com.swyp.picke.domain.user.service.CreditService; import com.swyp.picke.domain.user.service.UserBattleService; import com.swyp.picke.domain.vote.converter.VoteConverter; @@ -43,7 +47,9 @@ public class BattleVoteServiceImpl implements BattleVoteService { private final BattleVoteRepository battleVoteRepository; private final BattleService battleService; private final BattleOptionRepository battleOptionRepository; + private final BattleOptionTagRepository battleOptionTagRepository; private final UserRepository userRepository; + private final UserTendencyScoreRepository userTendencyScoreRepository; private final UserBattleService userBattleService; private final CreditService creditService; private final ApplicationEventPublisher eventPublisher; @@ -165,13 +171,27 @@ public VoteResultResponse postVote(Long battleId, Long userId, VoteRequest reque throw new CustomException(ErrorCode.PRE_VOTE_REQUIRED); } + BattleOption previousOption = vote.getPostVoteOption(); vote.doPostVote(option); + userTendencyScoreRepository.findByUserId(userId).ifPresent(score -> { + if (previousOption != null) { + applyValueTags(score, previousOption, -1); + } + applyValueTags(score, option, 1); + }); userBattleService.upsertStep(user, battle, UserBattleStep.COMPLETED); eventPublisher.publishEvent(new VoteUpdatedEvent(battleId)); return new VoteResultResponse(vote.getId(), UserBattleStep.COMPLETED); } + private void applyValueTags(UserTendencyScore score, BattleOption option, int multiplier) { + battleOptionTagRepository.findByBattleOption(option).stream() + .map(optionTag -> optionTag.getTag()) + .filter(tag -> tag.getType() == TagType.VALUE) + .forEach(tag -> score.applyValueTag(tag.getName(), multiplier)); + } + @Override @Transactional public void deleteVotesByBattleId(Long battleId) { diff --git a/src/test/java/com/swyp/picke/domain/battle/service/BattleQueryServiceTest.java b/src/test/java/com/swyp/picke/domain/battle/service/BattleQueryServiceTest.java new file mode 100644 index 00000000..6ba5ca9e --- /dev/null +++ b/src/test/java/com/swyp/picke/domain/battle/service/BattleQueryServiceTest.java @@ -0,0 +1,54 @@ +package com.swyp.picke.domain.battle.service; + +import com.swyp.picke.domain.battle.entity.BattleOptionTag; +import com.swyp.picke.domain.battle.repository.BattleOptionRepository; +import com.swyp.picke.domain.battle.repository.BattleOptionTagRepository; +import com.swyp.picke.domain.battle.repository.BattleRepository; +import com.swyp.picke.domain.battle.repository.BattleTagRepository; +import com.swyp.picke.domain.tag.entity.Tag; +import com.swyp.picke.domain.tag.enums.TagType; +import org.junit.jupiter.api.DisplayName; +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 java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class BattleQueryServiceTest { + + @Mock + private BattleRepository battleRepository; + @Mock + private BattleOptionRepository battleOptionRepository; + @Mock + private BattleTagRepository battleTagRepository; + @Mock + private BattleOptionTagRepository battleOptionTagRepository; + + @InjectMocks + private BattleQueryService battleQueryService; + + @Test + @DisplayName("철학자 유형 집계에서 제거된 철학자 태그를 처리하지 않는다") + void getTopPhilosopher_ignoresUnsupportedTags() { + BattleOptionTag supported = optionTag("플라톤"); + BattleOptionTag removed = optionTag("데카르트"); + when(battleOptionTagRepository.findByBattleOptionIdIn(List.of(1L))) + .thenReturn(List.of(removed, removed, supported)); + + assertThat(battleQueryService.getTopPhilosopherTagNameFromOptions(List.of(1L))) + .contains("플라톤"); + } + + private BattleOptionTag optionTag(String name) { + return BattleOptionTag.builder() + .tag(Tag.builder().name(name).type(TagType.PHILOSOPHER).build()) + .build(); + } +} diff --git a/src/test/java/com/swyp/picke/domain/tag/service/TagServiceImplTest.java b/src/test/java/com/swyp/picke/domain/tag/service/TagServiceImplTest.java new file mode 100644 index 00000000..edbfb9d7 --- /dev/null +++ b/src/test/java/com/swyp/picke/domain/tag/service/TagServiceImplTest.java @@ -0,0 +1,65 @@ +package com.swyp.picke.domain.tag.service; + +import com.swyp.picke.domain.admin.dto.tag.request.TagRequest; +import com.swyp.picke.domain.battle.repository.BattleOptionTagRepository; +import com.swyp.picke.domain.battle.repository.BattleRepository; +import com.swyp.picke.domain.battle.repository.BattleTagRepository; +import com.swyp.picke.domain.tag.dto.response.TagListResponse; +import com.swyp.picke.domain.tag.entity.Tag; +import com.swyp.picke.domain.tag.enums.TagType; +import com.swyp.picke.domain.tag.repository.TagRepository; +import com.swyp.picke.global.common.exception.CustomException; +import com.swyp.picke.global.common.exception.ErrorCode; +import org.junit.jupiter.api.DisplayName; +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 java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TagServiceImplTest { + + @Mock + private TagRepository tagRepository; + @Mock + private BattleTagRepository battleTagRepository; + @Mock + private BattleOptionTagRepository battleOptionTagRepository; + @Mock + private BattleRepository battleRepository; + + @InjectMocks + private TagServiceImpl tagService; + + @Test + @DisplayName("철학자 태그 목록에서 지원하지 않는 철학자를 제외한다") + void getTags_filtersUnsupportedPhilosophers() { + Tag supported = Tag.builder().name("소크라테스").type(TagType.PHILOSOPHER).build(); + Tag removed = Tag.builder().name("데카르트").type(TagType.PHILOSOPHER).build(); + when(tagRepository.findAllByTypeAndDeletedAtIsNull(TagType.PHILOSOPHER)) + .thenReturn(List.of(supported, removed)); + + TagListResponse response = tagService.getTags(TagType.PHILOSOPHER); + + assertThat(response.items()).extracting("name").containsExactly("소크라테스"); + assertThat(response.totalCount()).isEqualTo(1); + } + + @Test + @DisplayName("지원하지 않는 철학자 태그는 생성할 수 없다") + void createTag_rejectsUnsupportedPhilosopher() { + TagRequest request = new TagRequest("데카르트", TagType.PHILOSOPHER); + + assertThatThrownBy(() -> tagService.createTag(request)) + .isInstanceOf(CustomException.class) + .extracting("errorCode") + .isEqualTo(ErrorCode.BAD_REQUEST); + } +} diff --git a/src/test/java/com/swyp/picke/domain/user/entity/UserTendencyScoreTest.java b/src/test/java/com/swyp/picke/domain/user/entity/UserTendencyScoreTest.java new file mode 100644 index 00000000..0aaf3099 --- /dev/null +++ b/src/test/java/com/swyp/picke/domain/user/entity/UserTendencyScoreTest.java @@ -0,0 +1,54 @@ +package com.swyp.picke.domain.user.entity; + +import com.swyp.picke.domain.user.enums.ValueAxis; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class UserTendencyScoreTest { + + @Test + @DisplayName("기준 가치관 6개는 각 축에 +1로 반영된다") + void appliesPositiveValueTags() { + UserTendencyScore score = emptyScore(); + + for (String tag : new String[]{"원칙", "이성", "개인", "변화", "내면", "이상"}) { + score.applyValueTag(tag); + } + + assertThat(score.getPrinciple()).isEqualTo(1); + assertThat(score.getReason()).isEqualTo(1); + assertThat(score.getIndividual()).isEqualTo(1); + assertThat(score.getChange()).isEqualTo(1); + assertThat(score.getInner()).isEqualTo(1); + assertThat(score.getIdeal()).isEqualTo(1); + } + + @Test + @DisplayName("반대 가치관 6개는 대응하는 기존 축에 -1로 반영된다") + void appliesNegativeValueTags() { + UserTendencyScore score = emptyScore(); + + for (String tag : new String[]{"결과", "감성", "관계", "전통", "구조", "현실"}) { + score.applyValueTag(tag); + } + + assertThat(score.getPrinciple()).isEqualTo(-1); + assertThat(score.getReason()).isEqualTo(-1); + assertThat(score.getIndividual()).isEqualTo(-1); + assertThat(score.getChange()).isEqualTo(-1); + assertThat(score.getInner()).isEqualTo(-1); + assertThat(score.getIdeal()).isEqualTo(-1); + } + + @Test + @DisplayName("육각형 축은 6개만 유지한다") + void keepsSixAxes() { + assertThat(ValueAxis.values()).hasSize(6); + } + + private UserTendencyScore emptyScore() { + return UserTendencyScore.builder().build(); + } +} diff --git a/src/test/java/com/swyp/picke/domain/user/enums/PhilosopherTypeTest.java b/src/test/java/com/swyp/picke/domain/user/enums/PhilosopherTypeTest.java new file mode 100644 index 00000000..370a9b42 --- /dev/null +++ b/src/test/java/com/swyp/picke/domain/user/enums/PhilosopherTypeTest.java @@ -0,0 +1,36 @@ +package com.swyp.picke.domain.user.enums; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +class PhilosopherTypeTest { + + @Test + @DisplayName("철학자 유형은 지정된 10명만 존재한다") + void containsOnlySupportedPhilosophers() { + Set labels = Arrays.stream(PhilosopherType.values()) + .map(PhilosopherType::getLabel) + .collect(Collectors.toSet()); + + assertThat(labels).containsExactlyInAnyOrder( + "노자", "플라톤", "석가모니", "칸트", "아리스토텔레스", + "마르크스", "소크라테스", "공자", "사르트르", "니체" + ); + assertThat(PhilosopherType.values()).hasSize(10); + } + + @Test + @DisplayName("제거된 철학자 이름은 유형으로 처리하지 않는다") + void doesNotResolveRemovedPhilosophers() { + assertThat(PhilosopherType.fromLabel("데카르트")).isNull(); + assertThat(PhilosopherType.fromLabel("롤스")).isNull(); + assertThat(PhilosopherType.fromLabel("붓다")).isNull(); + assertThat(PhilosopherType.fromLabel("석가모니")).isEqualTo(PhilosopherType.BUDDHA); + } +} diff --git a/src/test/java/com/swyp/picke/domain/vote/service/BattleVoteServiceImplTest.java b/src/test/java/com/swyp/picke/domain/vote/service/BattleVoteServiceImplTest.java index 07cd98eb..545207c4 100644 --- a/src/test/java/com/swyp/picke/domain/vote/service/BattleVoteServiceImplTest.java +++ b/src/test/java/com/swyp/picke/domain/vote/service/BattleVoteServiceImplTest.java @@ -5,14 +5,20 @@ import com.swyp.picke.domain.battle.enums.BattleOptionLabel; import com.swyp.picke.domain.battle.enums.BattleStatus; import com.swyp.picke.domain.battle.repository.BattleOptionRepository; +import com.swyp.picke.domain.battle.repository.BattleOptionTagRepository; +import com.swyp.picke.domain.battle.entity.BattleOptionTag; import com.swyp.picke.domain.battle.service.BattleService; +import com.swyp.picke.domain.tag.entity.Tag; +import com.swyp.picke.domain.tag.enums.TagType; import com.swyp.picke.domain.user.dto.response.UserBattleStatusResponse; import com.swyp.picke.domain.user.entity.User; +import com.swyp.picke.domain.user.entity.UserTendencyScore; import com.swyp.picke.domain.user.enums.CreditType; import com.swyp.picke.domain.user.enums.UserBattleStep; import com.swyp.picke.domain.user.enums.UserRole; import com.swyp.picke.domain.user.enums.UserStatus; import com.swyp.picke.domain.user.repository.UserRepository; +import com.swyp.picke.domain.user.repository.UserTendencyScoreRepository; import com.swyp.picke.domain.user.service.CreditService; import com.swyp.picke.domain.user.service.UserBattleService; import com.swyp.picke.domain.vote.dto.request.VoteRequest; @@ -20,6 +26,7 @@ import com.swyp.picke.domain.vote.entity.BattleVote; import com.swyp.picke.domain.vote.repository.BattleVoteRepository; import java.time.LocalDate; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -47,9 +54,15 @@ class BattleVoteServiceImplTest { @Mock private BattleOptionRepository battleOptionRepository; + @Mock + private BattleOptionTagRepository battleOptionTagRepository; + @Mock private UserRepository userRepository; + @Mock + private UserTendencyScoreRepository userTendencyScoreRepository; + @Mock private UserBattleService userBattleService; @@ -180,6 +193,76 @@ void postVote_completesUserBattleStep() { verify(userBattleService).upsertStep(user, battle, UserBattleStep.COMPLETED); } + @Test + @DisplayName("대표 6개에 없는 반대 가치관도 실제 사후투표 입력 시 -로 계산한다") + void postVote_appliesNegativeValueTag() { + Battle battle = battle(100L, null); + User user = user(10L); + BattleOption option = option(202L, battle, BattleOptionLabel.B); + BattleVote vote = BattleVote.builder() + .user(user) + .battle(battle) + .preVoteOption(option) + .build(); + UserTendencyScore score = UserTendencyScore.builder().user(user).build(); + Tag structure = Tag.builder().name("구조").type(TagType.VALUE).build(); + BattleOptionTag optionTag = BattleOptionTag.builder() + .battleOption(option) + .tag(structure) + .build(); + + when(battleService.findById(100L)).thenReturn(battle); + when(userRepository.findById(10L)).thenReturn(Optional.of(user)); + when(battleOptionRepository.findById(202L)).thenReturn(Optional.of(option)); + when(battleVoteRepository.findByBattleAndUser(battle, user)).thenReturn(Optional.of(vote)); + when(userBattleService.getUserBattleStatus(user, battle)) + .thenReturn(new UserBattleStatusResponse(100L, UserBattleStep.POST_VOTE)); + when(userTendencyScoreRepository.findByUserId(10L)).thenReturn(Optional.of(score)); + when(battleOptionTagRepository.findByBattleOption(option)).thenReturn(List.of(optionTag)); + + battleVoteService.postVote(100L, 10L, new VoteRequest(202L)); + + assertThat(score.getInner()).isEqualTo(-1); + } + + @Test + @DisplayName("사후투표를 변경하면 기존 태그 점수를 제거하고 새 태그를 반영한다") + void postVote_replacesPreviousValueTagScore() { + Battle battle = battle(100L, null); + User user = user(10L); + BattleOption previousOption = option(201L, battle, BattleOptionLabel.A); + BattleOption newOption = option(202L, battle, BattleOptionLabel.B); + BattleVote vote = BattleVote.builder() + .user(user) + .battle(battle) + .preVoteOption(previousOption) + .postVoteOption(previousOption) + .build(); + UserTendencyScore score = UserTendencyScore.builder().user(user).inner(1).build(); + BattleOptionTag innerTag = BattleOptionTag.builder() + .battleOption(previousOption) + .tag(Tag.builder().name("내면").type(TagType.VALUE).build()) + .build(); + BattleOptionTag structureTag = BattleOptionTag.builder() + .battleOption(newOption) + .tag(Tag.builder().name("구조").type(TagType.VALUE).build()) + .build(); + + when(battleService.findById(100L)).thenReturn(battle); + when(userRepository.findById(10L)).thenReturn(Optional.of(user)); + when(battleOptionRepository.findById(202L)).thenReturn(Optional.of(newOption)); + when(battleVoteRepository.findByBattleAndUser(battle, user)).thenReturn(Optional.of(vote)); + when(userBattleService.getUserBattleStatus(user, battle)) + .thenReturn(new UserBattleStatusResponse(100L, UserBattleStep.COMPLETED)); + when(userTendencyScoreRepository.findByUserId(10L)).thenReturn(Optional.of(score)); + when(battleOptionTagRepository.findByBattleOption(previousOption)).thenReturn(List.of(innerTag)); + when(battleOptionTagRepository.findByBattleOption(newOption)).thenReturn(List.of(structureTag)); + + battleVoteService.postVote(100L, 10L, new VoteRequest(202L)); + + assertThat(score.getInner()).isEqualTo(-1); + } + private Battle battle(Long id, LocalDate targetDate) { Battle battle = Battle.builder() .title("battle")