diff --git a/backend/src/main/java/com/festago/admin/presentation/AdminController.java b/backend/src/main/java/com/festago/admin/presentation/AdminController.java index f4d1ab11a..1a26c25da 100644 --- a/backend/src/main/java/com/festago/admin/presentation/AdminController.java +++ b/backend/src/main/java/com/festago/admin/presentation/AdminController.java @@ -3,35 +3,14 @@ import com.festago.common.exception.BadRequestException; import com.festago.common.exception.ErrorCode; import com.festago.common.exception.InternalServerException; -import com.festago.festival.application.FestivalService; -import com.festago.festival.dto.FestivalCreateRequest; -import com.festago.festival.dto.FestivalResponse; -import com.festago.festival.dto.FestivalUpdateRequest; -import com.festago.school.application.SchoolService; -import com.festago.school.dto.SchoolCreateRequest; -import com.festago.school.dto.SchoolResponse; -import com.festago.school.dto.SchoolUpdateRequest; -import com.festago.stage.application.StageService; -import com.festago.stage.dto.StageCreateRequest; -import com.festago.stage.dto.StageResponse; -import com.festago.stage.dto.StageUpdateRequest; -import com.festago.ticket.application.TicketService; -import com.festago.ticket.dto.TicketCreateRequest; -import com.festago.ticket.dto.TicketCreateResponse; import io.swagger.v3.oas.annotations.Hidden; -import jakarta.validation.Valid; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.boot.info.BuildProperties; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -41,109 +20,8 @@ @RequiredArgsConstructor public class AdminController { - private final FestivalService festivalService; - private final StageService stageService; - private final TicketService ticketService; - private final SchoolService schoolService; private final Optional properties; - /** - * @deprecated 새로운 Festival CRUD 기능이 안정되면 삭제 - */ - @Deprecated(forRemoval = true) - @PostMapping("/festivals") - public ResponseEntity createFestival(@RequestBody @Valid FestivalCreateRequest request) { - FestivalResponse response = festivalService.create(request); - return ResponseEntity.ok() - .body(response); - } - - /** - * @deprecated 새로운 Festival CRUD 기능이 안정되면 삭제 - */ - @Deprecated(forRemoval = true) - @PatchMapping("/festivals/{festivalId}") - public ResponseEntity updateFestival(@RequestBody @Valid FestivalUpdateRequest request, - @PathVariable Long festivalId) { - festivalService.update(festivalId, request); - return ResponseEntity.ok() - .build(); - } - - /** - * @deprecated 새로운 Festival CRUD 기능이 안정되면 삭제 - */ - @Deprecated(forRemoval = true) - @DeleteMapping("/festivals/{festivalId}") - public ResponseEntity deleteFestival(@PathVariable Long festivalId) { - festivalService.delete(festivalId); - return ResponseEntity.ok() - .build(); - } - - @PostMapping("/stages") - public ResponseEntity createStage(@RequestBody @Valid StageCreateRequest request) { - StageResponse response = stageService.create(request); - return ResponseEntity.ok() - .body(response); - } - - @PatchMapping("/stages/{stageId}") - public ResponseEntity updateStage(@RequestBody @Valid StageUpdateRequest request, - @PathVariable Long stageId) { - stageService.update(stageId, request); - return ResponseEntity.ok() - .build(); - } - - @DeleteMapping("/stages/{stageId}") - public ResponseEntity deleteStage(@PathVariable Long stageId) { - stageService.delete(stageId); - return ResponseEntity.ok() - .build(); - } - - @PostMapping("/tickets") - public ResponseEntity createTicket(@RequestBody @Valid TicketCreateRequest request) { - TicketCreateResponse response = ticketService.create(request); - return ResponseEntity.ok() - .body(response); - } - - /** - * @deprecated API 버저닝이 적용되면 해당 메서드 삭제 - */ - @Deprecated(forRemoval = true) - @PostMapping("/schools") - public ResponseEntity createSchool(@RequestBody @Valid SchoolCreateRequest request) { - SchoolResponse response = schoolService.create(request); - return ResponseEntity.ok() - .body(response); - } - - /** - * @deprecated API 버저닝이 적용되면 해당 메서드 삭제 - */ - @Deprecated(forRemoval = true) - @PatchMapping("/schools/{schoolId}") - public ResponseEntity updateSchool(@RequestBody @Valid SchoolUpdateRequest request, - @PathVariable Long schoolId) { - schoolService.update(schoolId, request); - return ResponseEntity.ok() - .build(); - } - - /** - * @deprecated API 버저닝이 적용되면 해당 메서드 삭제 - */ - @Deprecated(forRemoval = true) - @DeleteMapping("/schools/{schoolId}") - public ResponseEntity deleteSchool(@PathVariable Long schoolId) { - schoolService.delete(schoolId); - return ResponseEntity.ok() - .build(); - } - @GetMapping("/version") public ResponseEntity getVersion() { return properties.map(it -> ResponseEntity.ok(it.getTime().atZone(ZoneId.of("Asia/Seoul")).toString())) diff --git a/backend/src/main/java/com/festago/bookmark/application/SchoolBookmarkCommandService.java b/backend/src/main/java/com/festago/bookmark/application/SchoolBookmarkCommandService.java new file mode 100644 index 000000000..0e6c4f7c7 --- /dev/null +++ b/backend/src/main/java/com/festago/bookmark/application/SchoolBookmarkCommandService.java @@ -0,0 +1,47 @@ +package com.festago.bookmark.application; + +import com.festago.bookmark.domain.Bookmark; +import com.festago.bookmark.domain.BookmarkType; +import com.festago.bookmark.repository.BookmarkRepository; +import com.festago.common.exception.ErrorCode; +import com.festago.common.exception.NotFoundException; +import com.festago.school.repository.SchoolRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class SchoolBookmarkCommandService { + + private static final int BOOKMARK_MAXIMUM_COUNT = 12; + + private final BookmarkRepository bookmarkRepository; + private final SchoolRepository schoolRepository; + + public Long save(Long schoolId, Long memberId) { + validate(schoolId, memberId); + return bookmarkRepository.save(new Bookmark(BookmarkType.SCHOOL, schoolId, memberId)) + .getId(); + } + + private void validate(Long schoolId, Long memberId) { + if (!schoolRepository.existsById(schoolId)) { + throw new NotFoundException(ErrorCode.SCHOOL_NOT_FOUND); + } + + if (bookmarkRepository.existsByBookmarkTypeAndMemberIdAndResourceId(BookmarkType.SCHOOL, memberId, schoolId)) { + throw new IllegalArgumentException("이미 저장된 북마크입니다."); + } + + long bookmarkCount = bookmarkRepository.countByMemberIdAndBookmarkType(memberId, BookmarkType.SCHOOL); + if (bookmarkCount >= BOOKMARK_MAXIMUM_COUNT) { + throw new IllegalArgumentException("북마크는 저장 갯수를 초과하였습니다."); + } + } + + public void delete(Long schoolId, Long memberId) { + bookmarkRepository.deleteByBookmarkTypeAndMemberIdAndResourceId(BookmarkType.SCHOOL, memberId, schoolId); + } +} diff --git a/backend/src/main/java/com/festago/bookmark/application/SchoolBookmarkV1QueryService.java b/backend/src/main/java/com/festago/bookmark/application/SchoolBookmarkV1QueryService.java new file mode 100644 index 000000000..5834b86eb --- /dev/null +++ b/backend/src/main/java/com/festago/bookmark/application/SchoolBookmarkV1QueryService.java @@ -0,0 +1,21 @@ +package com.festago.bookmark.application; + +import com.festago.bookmark.dto.v1.SchoolBookmarkV1Response; +import com.festago.bookmark.repository.SchoolBookmarkV1QuerydslRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class SchoolBookmarkV1QueryService { + + private final SchoolBookmarkV1QuerydslRepository schoolBookmarkV1QuerydslRepository; + + public List findAllByMemberId(Long memberId) { + return schoolBookmarkV1QuerydslRepository.findAllByMemberId(memberId); + } +} + diff --git a/backend/src/main/java/com/festago/bookmark/dto/v1/SchoolBookmarkV1Response.java b/backend/src/main/java/com/festago/bookmark/dto/v1/SchoolBookmarkV1Response.java new file mode 100644 index 000000000..3b9a219e2 --- /dev/null +++ b/backend/src/main/java/com/festago/bookmark/dto/v1/SchoolBookmarkV1Response.java @@ -0,0 +1,15 @@ +package com.festago.bookmark.dto.v1; + +import com.festago.school.dto.v1.SchoolSearchV1Response; +import com.querydsl.core.annotations.QueryProjection; +import java.time.LocalDateTime; + +public record SchoolBookmarkV1Response( + SchoolSearchV1Response school, + LocalDateTime bookmarkCreatedAt +) { + + @QueryProjection + public SchoolBookmarkV1Response { + } +} diff --git a/backend/src/main/java/com/festago/bookmark/presentation/SchoolBookmarkV1Controller.java b/backend/src/main/java/com/festago/bookmark/presentation/SchoolBookmarkV1Controller.java new file mode 100644 index 000000000..7d1a5fe1d --- /dev/null +++ b/backend/src/main/java/com/festago/bookmark/presentation/SchoolBookmarkV1Controller.java @@ -0,0 +1,28 @@ +package com.festago.bookmark.presentation; + +import com.festago.auth.annotation.Member; +import com.festago.bookmark.application.SchoolBookmarkV1QueryService; +import com.festago.bookmark.dto.v1.SchoolBookmarkV1Response; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/bookmarks/schools") +@Tag(name = "학교 북마크 API V1") +public class SchoolBookmarkV1Controller { + + private final SchoolBookmarkV1QueryService schoolBookmarkV1QueryService; + + @GetMapping + @Operation(description = "특정한 회원의 학교 북마크 목록을 반환한다", summary = "회원 학교 북마크 목록 조회") + public ResponseEntity> findAllByMemberId(@Member Long memberId) { + return ResponseEntity.ok(schoolBookmarkV1QueryService.findAllByMemberId(memberId)); + } +} diff --git a/backend/src/main/java/com/festago/bookmark/repository/SchoolBookmarkV1QuerydslRepository.java b/backend/src/main/java/com/festago/bookmark/repository/SchoolBookmarkV1QuerydslRepository.java new file mode 100644 index 000000000..033548394 --- /dev/null +++ b/backend/src/main/java/com/festago/bookmark/repository/SchoolBookmarkV1QuerydslRepository.java @@ -0,0 +1,31 @@ +package com.festago.bookmark.repository; + +import static com.festago.bookmark.domain.QBookmark.bookmark; +import static com.festago.school.domain.QSchool.school; + +import com.festago.bookmark.domain.Bookmark; +import com.festago.bookmark.domain.BookmarkType; +import com.festago.bookmark.dto.v1.QSchoolBookmarkV1Response; +import com.festago.bookmark.dto.v1.SchoolBookmarkV1Response; +import com.festago.common.querydsl.QueryDslRepositorySupport; +import com.festago.school.dto.v1.QSchoolSearchV1Response; +import java.util.List; +import org.springframework.stereotype.Repository; + +@Repository +public class SchoolBookmarkV1QuerydslRepository extends QueryDslRepositorySupport { + + protected SchoolBookmarkV1QuerydslRepository() { + super(Bookmark.class); + } + + public List findAllByMemberId(Long memberId) { + return select(new QSchoolBookmarkV1Response( + new QSchoolSearchV1Response(school.id, school.name, school.logoUrl), bookmark.createdAt)) + .from(bookmark) + .innerJoin(school).on(school.id.eq(bookmark.resourceId) + .and(bookmark.memberId.eq(memberId)) + .and(bookmark.bookmarkType.eq(BookmarkType.SCHOOL))) + .fetch(); + } +} diff --git a/backend/src/main/java/com/festago/school/application/SchoolService.java b/backend/src/main/java/com/festago/school/application/SchoolService.java deleted file mode 100644 index 0c3f27e19..000000000 --- a/backend/src/main/java/com/festago/school/application/SchoolService.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.festago.school.application; - -import com.festago.common.exception.BadRequestException; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.NotFoundException; -import com.festago.school.domain.School; -import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateRequest; -import com.festago.school.dto.SchoolResponse; -import com.festago.school.dto.SchoolUpdateRequest; -import com.festago.school.dto.SchoolsResponse; -import com.festago.school.repository.SchoolRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional -@RequiredArgsConstructor -public class SchoolService { - - private final SchoolRepository schoolRepository; - - @Transactional(readOnly = true) - public SchoolsResponse findAll() { - return SchoolsResponse.from(schoolRepository.findAll()); - } - - @Transactional(readOnly = true) - public SchoolResponse findById(Long id) { - return SchoolResponse.from(findSchool(id)); - } - - private School findSchool(Long id) { - return schoolRepository.findById(id) - .orElseThrow(() -> new NotFoundException(ErrorCode.SCHOOL_NOT_FOUND)); - } - - public SchoolResponse create(SchoolCreateRequest request) { - validateSchool(request); - String domain = request.domain(); - String name = request.name(); - School school = schoolRepository.save(new School(domain, name, SchoolRegion.서울)); - return SchoolResponse.from(school); - } - - private void validateSchool(SchoolCreateRequest request) { - if (schoolRepository.existsByDomainOrName(request.domain(), request.name())) { - throw new BadRequestException(ErrorCode.DUPLICATE_SCHOOL); - } - } - - public void update(Long schoolId, SchoolUpdateRequest request) { - School school = findSchool(schoolId); - school.changeName(request.name()); - school.changeDomain(request.domain()); - } - - public void delete(Long schoolId) { - // TODO 지금은 외래키 제약조건 때문에 참조하는 다른 엔티티가 있으면 예외가 발생하지만, 추후 이미 가입된 학생이 있다는 등 예외가 필요할듯 - try { - schoolRepository.deleteById(schoolId); - schoolRepository.flush(); - } catch (DataIntegrityViolationException e) { - throw new BadRequestException(ErrorCode.DELETE_CONSTRAINT_SCHOOL); - } - } -} diff --git a/backend/src/main/java/com/festago/school/dto/v1/SchoolSearchV1Response.java b/backend/src/main/java/com/festago/school/dto/v1/SchoolSearchV1Response.java new file mode 100644 index 000000000..be8d86636 --- /dev/null +++ b/backend/src/main/java/com/festago/school/dto/v1/SchoolSearchV1Response.java @@ -0,0 +1,14 @@ +package com.festago.school.dto.v1; + +import com.querydsl.core.annotations.QueryProjection; + +public record SchoolSearchV1Response( + Long id, + String name, + String logoUrl +) { + + @QueryProjection + public SchoolSearchV1Response { + } +} diff --git a/backend/src/main/java/com/festago/school/presentation/SchoolController.java b/backend/src/main/java/com/festago/school/presentation/SchoolController.java deleted file mode 100644 index b100a7a5c..000000000 --- a/backend/src/main/java/com/festago/school/presentation/SchoolController.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.festago.school.presentation; - -import com.festago.school.application.SchoolService; -import com.festago.school.dto.SchoolResponse; -import com.festago.school.dto.SchoolsResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @deprecated API 버저닝이 적용되면 해당 클래스 삭제 - */ -@Deprecated(forRemoval = true) -@RestController -@RequestMapping("/schools") -@Tag(name = "학교 정보 요청") -@RequiredArgsConstructor -public class SchoolController { - - private final SchoolService schoolService; - - @GetMapping - @Operation(description = "모든 학교 정보를 조회한다.", summary = "전체 학교 조회") - public ResponseEntity findAll() { - return ResponseEntity.ok() - .body(schoolService.findAll()); - } - - @GetMapping("/{schoolId}") - @Operation(description = "단일 학교 정보를 조회한다.", summary = "단일 학교 조회") - public ResponseEntity findById(@PathVariable Long schoolId) { - return ResponseEntity.ok() - .body(schoolService.findById(schoolId)); - } -} diff --git a/backend/src/main/java/com/festago/school/repository/SchoolRepository.java b/backend/src/main/java/com/festago/school/repository/SchoolRepository.java index 157a5fd60..dc3705a1b 100644 --- a/backend/src/main/java/com/festago/school/repository/SchoolRepository.java +++ b/backend/src/main/java/com/festago/school/repository/SchoolRepository.java @@ -3,26 +3,24 @@ import com.festago.common.exception.ErrorCode; import com.festago.common.exception.NotFoundException; import com.festago.school.domain.School; -import com.festago.school.domain.SchoolRegion; -import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.Repository; -public interface SchoolRepository extends JpaRepository { +public interface SchoolRepository extends Repository { + + School save(School school); + + Optional findById(Long schoolId); + + void deleteById(Long id); + + boolean existsById(Long id); default School getOrThrow(Long schoolId) { return findById(schoolId) .orElseThrow(() -> new NotFoundException(ErrorCode.SCHOOL_NOT_FOUND)); } - /** - * @deprecated API 버저닝이 적용되면 해당 메서드 삭제 - */ - @Deprecated(forRemoval = true) - boolean existsByDomainOrName(String domain, String name); - - List findAllByRegion(SchoolRegion schoolRegion); - boolean existsByDomain(String domain); boolean existsByName(String name); diff --git a/backend/src/test/java/com/festago/bookmark/application/SchoolBookmarkCommandServiceTest.java b/backend/src/test/java/com/festago/bookmark/application/SchoolBookmarkCommandServiceTest.java new file mode 100644 index 000000000..535bcd68d --- /dev/null +++ b/backend/src/test/java/com/festago/bookmark/application/SchoolBookmarkCommandServiceTest.java @@ -0,0 +1,106 @@ +package com.festago.bookmark.application; + +import static com.festago.bookmark.domain.BookmarkType.SCHOOL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.festago.bookmark.domain.Bookmark; +import com.festago.bookmark.domain.BookmarkType; +import com.festago.bookmark.repository.BookmarkRepository; +import com.festago.bookmark.repository.MemoryBookmarkRepository; +import com.festago.common.exception.ErrorCode; +import com.festago.common.exception.NotFoundException; +import com.festago.school.repository.MemorySchoolRepository; +import com.festago.school.repository.SchoolRepository; +import com.festago.support.SchoolFixture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class SchoolBookmarkCommandServiceTest { + + BookmarkRepository bookmarkRepository; + SchoolRepository schoolRepository; + SchoolBookmarkCommandService schoolBookmarkCommandService; + + @BeforeEach + void setUp() { + bookmarkRepository = new MemoryBookmarkRepository(); + schoolRepository = new MemorySchoolRepository(); + schoolBookmarkCommandService = new SchoolBookmarkCommandService(bookmarkRepository, schoolRepository); + } + + @Nested + class 학교_북마크_추가시 { + + @Test + void 해당하는_학교가_없으면_예외() { + // when && then + assertThatThrownBy(() -> schoolBookmarkCommandService.save(-1L, 1L)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorCode.SCHOOL_NOT_FOUND.getMessage()); + } + + @Test + void 학교_북마크_갯수가_이미_12개_이상이면_예외() { + // given + Long memberId = 1L; + for (long i = 0; i < 12; i++) { + Long schoolId = schoolRepository.save(SchoolFixture.school().build()).getId(); + bookmarkRepository.save(new Bookmark(BookmarkType.SCHOOL, schoolId, memberId)); + } + + Long schoolId = schoolRepository.save(SchoolFixture.school().build()).getId(); + + // when && then + assertThatThrownBy(() -> schoolBookmarkCommandService.save(schoolId, memberId)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("북마크는 저장 갯수를 초과하였습니다."); + } + + @Test + void 이미_해당하는_북마크가_저장됐다면_예외() { + // given + Long schoolId = schoolRepository.save(SchoolFixture.school().build()).getId(); + bookmarkRepository.save(new Bookmark(BookmarkType.SCHOOL, schoolId, 1L)); + + // when && then + assertThatThrownBy(() -> schoolBookmarkCommandService.save(schoolId, 1L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이미 저장된 북마크입니다."); + } + + @Test + void 북마크_저장_성공() { + // given + Long memberId = 1L; + Long schoolId = schoolRepository.save(SchoolFixture.school().build()).getId(); + + // when + Long actual = schoolBookmarkCommandService.save(schoolId, memberId); + + // then + assertThat(actual).isPositive(); + } + } + + @Test + void 북마크를_삭제한다() { + // given + Long memberId = 1L; + Long schoolId = schoolRepository.save(SchoolFixture.school().build()).getId(); + bookmarkRepository.save(new Bookmark(SCHOOL, schoolId, memberId)); + + // when + schoolBookmarkCommandService.delete(schoolId, memberId); + + // then + var actual = bookmarkRepository.existsByBookmarkTypeAndMemberIdAndResourceId(SCHOOL, memberId, + schoolId); + assertThat(actual).isFalse(); + } +} diff --git a/backend/src/test/java/com/festago/bookmark/application/SchoolBookmarkV1QueryServiceIntegrationTest.java b/backend/src/test/java/com/festago/bookmark/application/SchoolBookmarkV1QueryServiceIntegrationTest.java new file mode 100644 index 000000000..cb8af0e4c --- /dev/null +++ b/backend/src/test/java/com/festago/bookmark/application/SchoolBookmarkV1QueryServiceIntegrationTest.java @@ -0,0 +1,84 @@ +package com.festago.bookmark.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import com.festago.bookmark.domain.Bookmark; +import com.festago.bookmark.domain.BookmarkType; +import com.festago.bookmark.dto.v1.SchoolBookmarkV1Response; +import com.festago.bookmark.repository.BookmarkRepository; +import com.festago.member.repository.MemberRepository; +import com.festago.school.dto.v1.SchoolSearchV1Response; +import com.festago.school.repository.SchoolRepository; +import com.festago.support.ApplicationIntegrationTest; +import com.festago.support.MemberFixture; +import com.festago.support.SchoolFixture; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class SchoolBookmarkV1QueryServiceIntegrationTest extends ApplicationIntegrationTest { + + @Autowired + SchoolBookmarkV1QueryService schoolBookmarkV1QueryService; + + @Autowired + SchoolRepository schoolRepository; + + @Autowired + MemberRepository memberRepository; + + @Autowired + BookmarkRepository bookmarkRepository; + + @Test + void 특정_회원의_북마크_목록들을_검색한다() { + // given + var 회원A_ID = saveMember("socialId_A"); + var 회원B_ID = saveMember("socialId_B"); + + var 학교A_ID = saveSchool("A대학교", "a.ac.kr", "https://www.festago.com/A.png"); + var 학교B_ID = saveSchool("B대학교", "b.ac.kr", "https://www.festago.com/B.png"); + + saveBookmark(학교A_ID, 회원A_ID); + saveBookmark(학교B_ID, 회원A_ID); + + saveBookmark(학교A_ID, 회원B_ID); + saveBookmark(학교B_ID, 회원B_ID); + + // when + var actual = schoolBookmarkV1QueryService.findAllByMemberId(회원A_ID); + + // then + assertSoftly(softly -> { + softly.assertThat(actual).hasSize(2); + softly.assertThat(actual).allSatisfy(it -> assertThat(it).hasNoNullFieldsOrProperties()); + softly.assertThat(actual).map(SchoolBookmarkV1Response::school) + .containsExactly( + new SchoolSearchV1Response(학교A_ID, "A대학교", "https://www.festago.com/A.png"), + new SchoolSearchV1Response(학교B_ID, "B대학교", "https://www.festago.com/B.png") + ); + }); + } + + private Long saveMember(String socialId) { + return memberRepository.save(MemberFixture.member() + .socialId(socialId) + .build()).getId(); + } + + private Long saveSchool(String name, String domain, String logoUrl) { + return schoolRepository.save(SchoolFixture.school() + .name(name) + .domain(domain) + .logoUrl(logoUrl) + .build()).getId(); + } + + private void saveBookmark(Long schoolId, Long memberId) { + bookmarkRepository.save(new Bookmark(BookmarkType.SCHOOL, schoolId, memberId)); + } +} diff --git a/backend/src/test/java/com/festago/bookmark/presentation/SchoolBookmarkV1ControllerTest.java b/backend/src/test/java/com/festago/bookmark/presentation/SchoolBookmarkV1ControllerTest.java new file mode 100644 index 000000000..3362c4cc7 --- /dev/null +++ b/backend/src/test/java/com/festago/bookmark/presentation/SchoolBookmarkV1ControllerTest.java @@ -0,0 +1,55 @@ +package com.festago.bookmark.presentation; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.festago.support.CustomWebMvcTest; +import com.festago.support.WithMockAuth; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@CustomWebMvcTest +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class SchoolBookmarkV1ControllerTest { + + @Autowired + MockMvc mockMvc; + + @Nested + class 학교_북마크_목록_조회 { + + final String uri = "/api/v1/bookmarks/schools"; + + @Nested + @DisplayName("GET " + uri) + class 올바른_주소로 { + + @Test + @WithMockAuth + void 요청을_보내면_200_응답을_반환한다() throws Exception { + // when & then + mockMvc.perform(get(uri) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + void 인증을_안했으면_4xx_응답을_반환한다() throws Exception { + // when & then + mockMvc.perform(get(uri) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + } + } +} diff --git a/backend/src/test/java/com/festago/presentation/AdminControllerTest.java b/backend/src/test/java/com/festago/presentation/AdminControllerTest.java deleted file mode 100644 index 4a207fa04..000000000 --- a/backend/src/test/java/com/festago/presentation/AdminControllerTest.java +++ /dev/null @@ -1,414 +0,0 @@ -package com.festago.presentation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.anyLong; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.festago.auth.application.AdminAuthService; -import com.festago.auth.domain.Role; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.NotFoundException; -import com.festago.common.exception.dto.ErrorResponse; -import com.festago.festival.application.FestivalService; -import com.festago.festival.dto.FestivalCreateRequest; -import com.festago.festival.dto.FestivalResponse; -import com.festago.school.application.SchoolService; -import com.festago.school.dto.SchoolCreateRequest; -import com.festago.school.dto.SchoolResponse; -import com.festago.school.dto.SchoolUpdateRequest; -import com.festago.stage.application.StageService; -import com.festago.stage.dto.StageCreateRequest; -import com.festago.stage.dto.StageResponse; -import com.festago.stage.dto.StageUpdateRequest; -import com.festago.support.CustomWebMvcTest; -import com.festago.support.WithMockAuth; -import com.festago.ticket.application.TicketService; -import com.festago.ticket.domain.TicketType; -import com.festago.ticket.dto.TicketCreateRequest; -import com.festago.ticket.dto.TicketCreateResponse; -import jakarta.servlet.http.Cookie; -import java.nio.charset.StandardCharsets; -import java.time.LocalDate; -import java.time.LocalDateTime; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -@CustomWebMvcTest -@DisplayNameGeneration(ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -class AdminControllerTest { - - @Autowired - MockMvc mockMvc; - - @Autowired - ObjectMapper objectMapper; - - @Autowired - FestivalService festivalService; - - @Autowired - StageService stageService; - - @Autowired - TicketService ticketService; - - @Autowired - AdminAuthService adminAuthService; - - @Autowired - SchoolService schoolService; - - @Test - @WithMockAuth - void 토큰의_Role이_어드민이_아니면_404_NotFound() throws Exception { - // when & then - mockMvc.perform(get("/admin") - .cookie(new Cookie("token", "token"))) - .andExpect(status().isNotFound()); - } - - @Test - void 쿠키에_토큰이_없으면_404_NotFound() throws Exception { - // when & then - mockMvc.perform(get("/admin")) - .andExpect(status().isNotFound()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 축제_생성() throws Exception { - // given - String festivalName = "테코 대학교"; - String startDate = "2023-08-02"; - String endDate = "2023-08-03"; - String thumbnail = "https://picsum.photos/536/354"; - - FestivalCreateRequest request = new FestivalCreateRequest( - festivalName, - LocalDate.parse(startDate), - LocalDate.parse(endDate), - "", - 1L); - - FestivalResponse expected = new FestivalResponse( - 1L, - 1L, - festivalName, - LocalDate.parse(startDate), - LocalDate.parse(endDate), - thumbnail); - - given(festivalService.create(any())) - .willReturn(expected); - - // when && then - String content = mockMvc.perform(post("/admin/api/festivals") - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - FestivalResponse actual = objectMapper.readValue(content, FestivalResponse.class); - assertThat(actual).isEqualTo(expected); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 존재_하지_않는_축제_무대_생성_예외() throws Exception { - // given - String startTime = "2023-07-27T18:00:00"; - String lineUp = "글렌, 애쉬, 오리, 푸우"; - String ticketOpenTime = "2023-07-26T18:00:00"; - long festivalId = 1L; - - StageCreateRequest request = new StageCreateRequest( - LocalDateTime.parse(startTime), - lineUp, - LocalDateTime.parse(ticketOpenTime), - festivalId); - - NotFoundException exception = new NotFoundException(ErrorCode.FESTIVAL_NOT_FOUND); - ErrorResponse expected = ErrorResponse.from(exception); - - given(stageService.create(any())) - .willThrow(exception); - - // when && then - String content = mockMvc.perform(post("/admin/api/stages") - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isNotFound()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - ErrorResponse actual = objectMapper.readValue(content, ErrorResponse.class); - assertThat(actual).isEqualTo(expected); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 무대_생성() throws Exception { - // given - String startTime = "2023-07-27T18:00:00"; - String lineUp = "글렌, 애쉬, 오리, 푸우"; - String ticketOpenTime = "2023-07-26T18:00:00"; - long festivalId = 1L; - - StageCreateRequest request = new StageCreateRequest( - LocalDateTime.parse(startTime), - lineUp, - LocalDateTime.parse(ticketOpenTime), - festivalId); - - StageResponse expected = new StageResponse(festivalId, festivalId, LocalDateTime.parse(startTime), - LocalDateTime.parse(ticketOpenTime), lineUp); - - given(stageService.create(any())) - .willReturn(expected); - - // when && then - String content = mockMvc.perform(post("/admin/api/stages") - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - StageResponse actual = objectMapper.readValue(content, StageResponse.class); - assertThat(actual).isEqualTo(expected); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 무대_수정() throws Exception { - // given - String startTime = "2023-07-27T18:00:00"; - String ticketOpenTime = "2023-07-26T18:00:00"; - String lineUp = "글렌, 애쉬, 오리, 푸우"; - - StageUpdateRequest request = new StageUpdateRequest(LocalDateTime.parse(startTime), - LocalDateTime.parse(ticketOpenTime), lineUp); - - // when & then - mockMvc.perform(patch("/admin/api/stages/{id}", 1L) - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isOk()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 무대_삭제() throws Exception { - // when & then - mockMvc.perform(delete("/admin/api/stages/{id}", 1L) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isOk()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 존재_하지_않는_무대_티켓_예외() throws Exception { - // given - String entryTime = "2023-07-27T18:00:00"; - - TicketCreateRequest request = new TicketCreateRequest(1L, - TicketType.VISITOR, - 100, - LocalDateTime.parse(entryTime) - ); - - NotFoundException exception = new NotFoundException(ErrorCode.STAGE_NOT_FOUND); - - ErrorResponse expected = ErrorResponse.from(exception); - - given(ticketService.create(any())) - .willThrow(exception); - - // when && then - String content = mockMvc.perform(post("/admin/api/tickets") - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isNotFound()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - ErrorResponse actual = objectMapper.readValue(content, ErrorResponse.class); - assertThat(actual).isEqualTo(expected); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 티켓_생성() throws Exception { - // given - long ticketId = 1L; - String entryTime = "2023-07-27T18:00:00"; - int totalAmount = 100; - long stageId = 1L; - TicketType ticketType = TicketType.VISITOR; - - TicketCreateRequest request = new TicketCreateRequest(stageId, - ticketType, - totalAmount, - LocalDateTime.parse(entryTime) - ); - - TicketCreateResponse expected = new TicketCreateResponse(ticketId); - - given(ticketService.create(any())) - .willReturn(expected); - - // when && then - String content = mockMvc.perform(post("/admin/api/tickets") - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - TicketCreateResponse actual = objectMapper.readValue(content, TicketCreateResponse.class); - assertThat(actual).isEqualTo(expected); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 학교_생성() throws Exception { - // given - String domain = "teco.ac.kr"; - String name = "테코대학교"; - - SchoolCreateRequest request = new SchoolCreateRequest(domain, name); - SchoolResponse expected = new SchoolResponse(1L, domain, name); - given(schoolService.create(any(SchoolCreateRequest.class))) - .willReturn(expected); - - // when & then - String content = mockMvc.perform(post("/admin/api/schools") - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - SchoolResponse actual = objectMapper.readValue(content, SchoolResponse.class); - assertThat(actual).isEqualTo(expected); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 학교_생성_name_null이면_에외() throws Exception { - // given - SchoolCreateRequest request = new SchoolCreateRequest("teco.ac.kr", null); - - // when & then - mockMvc.perform(post("/admin/api/schools") - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isBadRequest()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 학교_생성_domain_null이면_에외() throws Exception { - // given - SchoolCreateRequest request = new SchoolCreateRequest(null, "테코대학교"); - - // when & then - mockMvc.perform(post("/admin/api/schools") - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isBadRequest()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 학교_수정() throws Exception { - // given - SchoolUpdateRequest request = new SchoolUpdateRequest("teco.ac.kr", "테코대학교"); - - // when & then - mockMvc.perform(patch("/admin/api/schools/{id}", 1L) - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isOk()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 학교_수정_name_null이면_에외() throws Exception { - // given - SchoolUpdateRequest request = new SchoolUpdateRequest("teco.ac.kr", null); - - // when & then - mockMvc.perform(patch("/admin/api/schools/{id}", 1L) - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isBadRequest()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 학교_수정_domain_null이면_에외() throws Exception { - // given - SchoolUpdateRequest request = new SchoolUpdateRequest(null, "테코대학교"); - - // when & then - mockMvc.perform(patch("/admin/api/schools/{id}", 1L) - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isBadRequest()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 존재_하지_않는_학교_수정_예외() throws Exception { - // given - SchoolUpdateRequest request = new SchoolUpdateRequest("teco.ac.kr", "테코대학교"); - - willThrow(new NotFoundException(ErrorCode.SCHOOL_NOT_FOUND)) - .given(schoolService).update(anyLong(), any(SchoolUpdateRequest.class)); - - // when & then - mockMvc.perform(patch("/admin/api/schools/{id}", 1L) - .content(objectMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isNotFound()); - } - - @Test - @WithMockAuth(role = Role.ADMIN) - void 학교_삭제() throws Exception { - // when & then - mockMvc.perform(delete("/admin/api/schools/{id}", 1L) - .contentType(MediaType.APPLICATION_JSON) - .cookie(new Cookie("token", "token"))) - .andExpect(status().isOk()); - } -} diff --git a/backend/src/test/java/com/festago/presentation/SchoolControllerTest.java b/backend/src/test/java/com/festago/presentation/SchoolControllerTest.java deleted file mode 100644 index a14bcc944..000000000 --- a/backend/src/test/java/com/festago/presentation/SchoolControllerTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.festago.presentation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.festago.school.application.SchoolService; -import com.festago.school.dto.SchoolResponse; -import com.festago.school.dto.SchoolsResponse; -import com.festago.support.CustomWebMvcTest; -import java.nio.charset.StandardCharsets; -import java.util.List; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -@CustomWebMvcTest -@DisplayNameGeneration(ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -class SchoolControllerTest { - - @Autowired - MockMvc mockMvc; - - @Autowired - ObjectMapper objectMapper; - - @Autowired - SchoolService schoolService; - - @Test - void 모든_학교_정보_조회() throws Exception { - // given - SchoolsResponse expected = new SchoolsResponse( - List.of( - new SchoolResponse(1L, "pooh.ac.kr", "푸우대학"), - new SchoolResponse(2L, "ash.ac.kr", "애쉬대학") - )); - - given(schoolService.findAll()) - .willReturn(expected); - - // when & then - String content = mockMvc.perform(get("/schools") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - SchoolsResponse actual = objectMapper.readValue(content, SchoolsResponse.class); - assertThat(actual).isEqualTo(expected); - } - - @Test - void 단일_학교_정보_조회() throws Exception { - // given - SchoolResponse expected = new SchoolResponse(1L, "teco.ac.kr", "테코대학"); - - given(schoolService.findById(expected.id())) - .willReturn(expected); - - // when & then - String content = mockMvc.perform(get("/schools/{id}", 1L) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(StandardCharsets.UTF_8); - SchoolResponse actual = objectMapper.readValue(content, SchoolResponse.class); - assertThat(actual).isEqualTo(expected); - } -} diff --git a/backend/src/test/java/com/festago/school/intergration/SchoolServiceIntegrationTest.java b/backend/src/test/java/com/festago/school/intergration/SchoolServiceIntegrationTest.java deleted file mode 100644 index 65e1c97f6..000000000 --- a/backend/src/test/java/com/festago/school/intergration/SchoolServiceIntegrationTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.festago.school.intergration; - - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.festago.common.exception.BadRequestException; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.NotFoundException; -import com.festago.school.application.SchoolService; -import com.festago.school.domain.School; -import com.festago.school.domain.SchoolRegion; -import com.festago.school.dto.SchoolCreateRequest; -import com.festago.school.dto.SchoolResponse; -import com.festago.school.dto.SchoolUpdateRequest; -import com.festago.school.dto.SchoolsResponse; -import com.festago.school.repository.SchoolRepository; -import com.festago.support.ApplicationIntegrationTest; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(ReplaceUnderscores.class) -public class SchoolServiceIntegrationTest extends ApplicationIntegrationTest { - - @Autowired - SchoolService schoolService; - - @Autowired - SchoolRepository schoolRepository; - - @Nested - class 학교_생성시 { - - @Test - void 성공() { - // given - SchoolCreateRequest expected = new SchoolCreateRequest("domain.com", "name"); - - // when - SchoolResponse actual = schoolService.create(expected); - - // then - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(actual.id()).isPositive(); - softly.assertThat(actual.domain()).isEqualTo(expected.domain()); - softly.assertThat(actual.name()).isEqualTo(expected.name()); - }); - } - - @Test - void 중복된_도메인이_존재하면_예외() { - // given - School savedSchool = schoolRepository.save(new School("domain.com", "name", SchoolRegion.서울)); - - // when && then - assertThatThrownBy( - () -> schoolService.create(new SchoolCreateRequest(savedSchool.getDomain(), "otherName"))) - .isInstanceOf(BadRequestException.class) - .hasMessage(ErrorCode.DUPLICATE_SCHOOL.getMessage()); - } - - @Test - void 중복된_이름이_존재하면_예외() { - // given - School savedSchool = schoolRepository.save(new School("domain.com", "name", SchoolRegion.서울)); - - // when && then - assertThatThrownBy(() -> schoolService.create(new SchoolCreateRequest("otherDomain.com", savedSchool.getName()))) - .isInstanceOf(BadRequestException.class) - .hasMessage(ErrorCode.DUPLICATE_SCHOOL.getMessage()); - } - } - - @Test - void 모든_학교를_조회한다() { - // given - schoolRepository.save(new School("domain.com", "name1", SchoolRegion.서울)); - schoolRepository.save(new School("domain.kr", "name2", SchoolRegion.서울)); - schoolRepository.save(new School("domain.jp", "name3", SchoolRegion.서울)); - - // when - SchoolsResponse actual = schoolService.findAll(); - - // then - assertThat(actual.schools()).hasSize(3); - } - - @Test - void 단건_학교를_조회한다() { - // given - School expected = schoolRepository.save(new School("domain.com", "name", SchoolRegion.서울)); - - // when - SchoolResponse actual = schoolService.findById(expected.getId()); - - // then - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(actual.id()).isPositive(); - softly.assertThat(actual.domain()).isEqualTo(expected.getDomain()); - softly.assertThat(actual.name()).isEqualTo(expected.getName()); - }); - } - - @Test - void 단건_조회시_없으면_예외() { - // when && then - assertThatThrownBy(() -> schoolService.findById(-1L)) - .isInstanceOf(NotFoundException.class) - .hasMessage(ErrorCode.SCHOOL_NOT_FOUND.getMessage()); - } - - @Test - void 학교_정보를_수정한다() { - // given - Long savedSchoolId = schoolRepository.save(new School("domain.com", "name", SchoolRegion.서울)).getId(); - SchoolUpdateRequest expected = new SchoolUpdateRequest("newDomain.com", "newName"); - - // when - schoolService.update(savedSchoolId, expected); - - // then - School actual = schoolRepository.findById(savedSchoolId).get(); - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(actual.getId()).isEqualTo(savedSchoolId); - softly.assertThat(actual.getDomain()).isEqualTo(expected.domain()); - softly.assertThat(actual.getName()).isEqualTo(expected.name()); - }); - } - - @Test - void 학교를_삭제한다() { - // given - Long savedSchoolId = schoolRepository.save(new School("domain.com", "name", SchoolRegion.서울)).getId(); - - // when - schoolService.delete(savedSchoolId); - - // then - assertThat(schoolRepository.findById(savedSchoolId)).isEmpty(); - } -} diff --git a/backend/src/test/java/com/festago/school/repository/MemorySchoolRepository.java b/backend/src/test/java/com/festago/school/repository/MemorySchoolRepository.java new file mode 100644 index 000000000..6bd01b50f --- /dev/null +++ b/backend/src/test/java/com/festago/school/repository/MemorySchoolRepository.java @@ -0,0 +1,60 @@ +package com.festago.school.repository; + +import com.festago.school.domain.School; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import lombok.SneakyThrows; + +public class MemorySchoolRepository implements SchoolRepository { + + private final Map db = new HashMap<>(); + private final AtomicLong id = new AtomicLong(1L); + + @SneakyThrows + @Override + public School save(School school) { + Field idField = school.getClass() + .getDeclaredField("id"); + idField.setAccessible(true); + idField.set(school, id.getAndIncrement()); + db.put(school.getId(), school); + return school; + } + + @Override + public Optional findById(Long schoolId) { + return Optional.ofNullable(db.get(schoolId)); + } + + @Override + public void deleteById(Long id) { + db.remove(id); + } + + @Override + public boolean existsById(Long id) { + return db.get(id) != null; + } + + @Override + public boolean existsByDomain(String domain) { + return db.values().stream() + .anyMatch(it -> it.getDomain().equals(domain)); + } + + @Override + public boolean existsByName(String name) { + return db.values().stream() + .anyMatch(it -> it.getName().equals(name)); + } + + @Override + public Optional findByName(String name) { + return db.values().stream() + .filter(it -> it.getName().equals(name)) + .findFirst(); + } +} diff --git a/backend/src/test/java/com/festago/school/repository/MemorySchoolRepositoryTest.java b/backend/src/test/java/com/festago/school/repository/MemorySchoolRepositoryTest.java new file mode 100644 index 000000000..f1898ff7f --- /dev/null +++ b/backend/src/test/java/com/festago/school/repository/MemorySchoolRepositoryTest.java @@ -0,0 +1,54 @@ +package com.festago.school.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.festago.school.domain.School; +import com.festago.school.domain.SchoolRegion; +import com.festago.support.SchoolFixture; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class MemorySchoolRepositoryTest { + + SchoolRepository schoolRepository; + + @BeforeEach + void setUp() { + schoolRepository = new MemorySchoolRepository(); + } + + @Test + void 학교를_저장한다() { + // given + School school = schoolRepository.save(SchoolFixture.school().build()); + + // when && then + assertThat(school.getId()).isPositive(); + } + + @Test + void 특정_필드로_조회한다() { + // given + schoolRepository.save(SchoolFixture.school() + .region(SchoolRegion.서울) + .name("학교이름") + .domain("knu.ac.kr") + .build() + ); + + // when && then + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(schoolRepository.findByName("학교이름")).isNotEmpty(); + softly.assertThat(schoolRepository.findByName("없는학교")).isEmpty(); + softly.assertThat(schoolRepository.existsByName("학교이름")).isTrue(); + softly.assertThat(schoolRepository.existsByName("없는학교")).isFalse(); + softly.assertThat(schoolRepository.existsByDomain("knu.ac.kr")).isTrue(); + softly.assertThat(schoolRepository.existsByDomain("no.ac.kr")).isFalse(); + }); + } +} diff --git a/backend/src/test/java/com/festago/school/repository/SchoolRepositoryTest.java b/backend/src/test/java/com/festago/school/repository/SchoolRepositoryTest.java deleted file mode 100644 index 8cfa80478..000000000 --- a/backend/src/test/java/com/festago/school/repository/SchoolRepositoryTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.festago.school.repository; - -import com.festago.school.domain.School; -import com.festago.school.domain.SchoolRegion; -import com.festago.support.RepositoryTest; -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -@DisplayNameGeneration(ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -@RepositoryTest -class SchoolRepositoryTest { - - @Autowired - SchoolRepository schoolRepository; - - @Test - void 지역으로_학교를_검색한다() { - // given - School expectSchool = schoolRepository.save(new School("domain", "name", SchoolRegion.서울)); - schoolRepository.save(new School("domain2", "name2", SchoolRegion.부산)); - schoolRepository.save(new School("domain3", "name3", SchoolRegion.대구)); - - // when - List actual = schoolRepository.findAllByRegion(SchoolRegion.서울); - - // then - Assertions.assertThat(actual).containsExactly(expectSchool); - } -}