Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a3c9a37
#250 [Fix] 투표 비율 계산 기준을 preVoteOption에서 postVoteOption으로 수정 (#252)
HYH0804 May 27, 2026
a38c141
#254 [Refactor] 관점 등록/수정 시 GPT 검수 코드 제거 (#256)
HYH0804 Jun 3, 2026
cee2c5d
#255 [Refactor] VoteStatsResponse OptionStat에 title 필드 추가 (#257)
HYH0804 Jun 3, 2026
53533b8
#258 [Feat] 출석체크 API 추가
si-zero Jun 3, 2026
77db7a1
#260 [Feat] 스웨거 어드민/유저 분리
si-zero Jun 4, 2026
a9d4293
[Feat] 알림기능 (#263)
HYH0804 Jun 11, 2026
b5a681b
#262 [Fix] APNs 자격증명 S3 다운로드 시 임시파일 충돌 오류 수정
HYH0804 Jun 11, 2026
d281fa3
#264 [Fix] AdMob SSV 포인트 미적립 버그 수정
si-zero Jun 11, 2026
968d6a3
#262 merge (#266)
HYH0804 Jun 14, 2026
d041631
[Chore] adMob 크롤링 파일 설정: adMob 크롤링 파일 설정 #269
si-zero Jun 16, 2026
ec25882
#270 [Feat] 설정 파일 추가
si-zero Jun 16, 2026
4c45999
[Chore] JWT필터 설정 #271
si-zero Jun 16, 2026
e0c9c1c
#275 [Fix] AdMob SSV 콜백 파라미터 optional 처리 및 서명 검증 활성화
si-zero Jun 18, 2026
ab2cb78
#277 [Fix] AdMob SSV 콜백 URL 검증 요청 처리 실패
si-zero Jun 18, 2026
04d947c
#279 [Fix] AdMob SSV 엔드포인트 검증 실패 시 항상 200 반환
si-zero Jun 18, 2026
d9ea3b5
#283 [Fix] /me/content-activities voteSide null 반환 버그
si-zero Jun 19, 2026
ec27b01
#285 [Feat] content-activities 응답에 option_title 필드 추가
si-zero Jun 20, 2026
6663e7a
#287 [Fix] AdMob SSV 서명 검증 오류로 인한 포인트 미지급 버그
si-zero Jun 24, 2026
39aca84
#289 [Fix] 오늘의 배틀 개수 변경
si-zero Jul 5, 2026
11e2da1
#293 [Fix] 사전투표 철학자 아이콘 미노출
si-zero Jul 5, 2026
aa449f0
#297 [Fix] 댓글 작성 시각이 9시간 전으로 표시되는 버그
si-zero Jul 6, 2026
3259a1a
#301 [Fix] robots.txt 파일 부재로 AdMob app-ads.txt 크롤링 차단
si-zero Jul 6, 2026
e91819b
#305 [Fix] S3 오디오 조각 재다운로드 시 FileAlreadyExistsException으로 TTS 파이프라인 실패
si-zero Jul 6, 2026
d64d5ca
#309 [Fix] JwtFilter 자체 화이트리스트에 /robots.txt 누락으로 401 발생
si-zero Jul 6, 2026
ff16d85
#316 [Fix] 포인트 정책표 대비 코드 불일치 4건 수정
si-zero Jul 7, 2026
f50c749
Merge branch 'main' into dev
si-zero Jul 7, 2026
d9e28a1
#321 [Feat] 알림 미읽음 여부 확인 API 추가
si-zero Jul 7, 2026
4279341
#313 [Hotfix] Railway 배포 환경 FFmpeg 자동 설치 적용 (#320)
jucheonsu Jul 7, 2026
f77048c
#327 [Fix] 알림 전체 읽음 처리 후 모두 읽음 신호가 전달되지 않던 문제 수정
si-zero Jul 13, 2026
99886f9
#331 [Docs] 출석체크 API 명세서를 실제 코드에 맞춰 갱신
si-zero Jul 13, 2026
087b8c0
#333 [Fix] app-ads.txt가 text/html로 응답되어 AdMob 크롤러가 무시하는 문제 수정
si-zero Jul 13, 2026
d33ff1f
#337 [Feat] 인앱 알림 테스트용 API 추가
si-zero Jul 14, 2026
17f35a4
#341 [Fix] 미문서화/중복 포인트 지급 로직 정리
si-zero Jul 15, 2026
acbe078
관리자 페이지를 새 admin-web(Next.js) 앱으로 리다이렉트 (#346)
Jul 15, 2026
de75bdf
#349 [Feat] admin.web.url env 주입 + 기본값 picke.up.railway.app (#350)
Jul 15, 2026
9493351
#352 [Fix] JwtFilter 화이트리스트 /api/v1/auth 전체 매칭으로 logout 인증 우회, 500 에러 발생
si-zero Jul 16, 2026
00bfcb3
#356 [Feat] 이용약관/개인정보처리방침 페이지 백엔드 서빙
si-zero Jul 20, 2026
99c936d
#361 [Feat] 관리자용 테스트 계정 로컬 로그인 기능 추가
si-zero Jul 24, 2026
f0a837d
#360 [Refactor] 1인 1관점 제한을 해제하고 1인 다수 관점 등록 허용
si-zero Jul 24, 2026
9fb601f
#366 [Feat] 내 콘텐츠 활동 내역(댓글 탭)에 내가 작성한 관점 포함
si-zero Jul 24, 2026
6a1abde
#370 [Fix] 사전투표만 한 기록도 내 배틀 기록에 노출되는 버그
si-zero Jul 24, 2026
80f5b9c
[Feat] 관점 200자 제한 해제 (#375)
si-zero Aug 2, 2026
f9bc278
#378 [Feat] 관리자 예약 푸시알림(제목/소제목/발송시간) 설정 및 자동발송
si-zero Aug 8, 2026
99f4765
#382 [Fix] 내 콘텐츠 활동 댓글 정보 매핑 오류 수정 (#383)
jucheonsu Aug 9, 2026
79cb355
#387 [Refactor] 철학자 태그 제한 및 가치관 육각형 계산 보정 (#388)
jucheonsu Aug 13, 2026
c77dbab
Merge: dev -> main 병합
jucheonsu Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -165,6 +166,8 @@ private List<BattleTagResponse> toTagResponses(List<Tag> 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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -84,6 +85,7 @@ public Optional<String> getTopPhilosopherTagNameFromOptions(List<Long> 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -649,6 +650,8 @@ private void saveBattleOptionTags(BattleOption option, List<Long> 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()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -46,13 +47,16 @@ public TagListResponse getTags(TagType type) {
List<Tag> 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);
Expand All @@ -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()) {
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/com/swyp/picke/domain/user/enums/ValueAxis.java
Original file line number Diff line number Diff line change
@@ -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<ValueDirection> 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) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading
Loading