diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt index 06f32bea..f0f59159 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -26,3 +26,16 @@ fun substringAroundKeyword(keyword: String, content: String, amount: Int): Pair< (index - frontIndex) to content.substring(frontIndex, backIndex) } } + +fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { + // Validate + if (!(pageSize > 0 && pageNum > 0 && total >= 0)) { + throw RuntimeException() + } + + return if ((pageNum - 1) * pageSize < total) { + pageNum + } else { + Math.ceil(total.toDouble() / pageSize).toInt() + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index 279a3a82..6fe51849 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.academics.service.AcademicsService import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto +import com.wafflestudio.csereal.core.academics.service.AcademicsSearchService import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -12,7 +13,8 @@ import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/academics") @RestController class AcademicsController( - private val academicsService: AcademicsService + private val academicsService: AcademicsService, + private val academicsSearchService: AcademicsSearchService ) { @AuthenticatedStaff @PostMapping("/{studentType}/{postType}") @@ -95,4 +97,24 @@ class AcademicsController( fun getScholarship(@PathVariable scholarshipId: Long): ResponseEntity { return ResponseEntity.ok(academicsService.readScholarship(scholarshipId)) } + + @GetMapping("/search/top") + fun searchTop( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) number: Int + ) = academicsSearchService.searchTopAcademics( + keyword = keyword, + number = number + ) + + @GetMapping("/search") + fun searchAcademics( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) pageSize: Int, + @RequestParam(required = true) pageNum: Int + ) = academicsSearchService.searchAcademics( + keyword = keyword, + pageSize = pageSize, + pageNum = pageNum + ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt index 2b90f96d..f3861445 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt @@ -14,13 +14,16 @@ class AcademicsEntity( @Enumerated(EnumType.STRING) var postType: AcademicsPostType, - var name: String?, + var name: String, var description: String, var year: Int?, var time: String?, @OneToMany(mappedBy = "academics", cascade = [CascadeType.ALL], orphanRemoval = true) - var attachments: MutableList = mutableListOf() + var attachments: MutableList = mutableListOf(), + + @OneToOne(mappedBy = "academics", cascade = [CascadeType.ALL], orphanRemoval = true) + var academicsSearch: AcademicsSearchEntity? = null ) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt new file mode 100644 index 00000000..ce50ce3b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt @@ -0,0 +1,128 @@ +package com.wafflestudio.csereal.core.academics.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml +import jakarta.persistence.* + +@Entity(name = "academics_search") +class AcademicsSearchEntity( + @Column(columnDefinition = "TEXT", nullable = false) + var content: String, + + @OneToOne + @JoinColumn(name = "academics_id") + val academics: AcademicsEntity? = null, + + @OneToOne + @JoinColumn(name = "course_id") + val course: CourseEntity? = null, + + @OneToOne + @JoinColumn(name = "scholarship_id") + val scholarship: ScholarshipEntity? = null + +) : BaseTimeEntity() { + companion object { + fun create(academics: AcademicsEntity): AcademicsSearchEntity { + return AcademicsSearchEntity( + academics = academics, + content = createContent(academics) + ) + } + + fun create(course: CourseEntity): AcademicsSearchEntity { + return AcademicsSearchEntity( + course = course, + content = createContent(course) + ) + } + + fun create(scholarship: ScholarshipEntity): AcademicsSearchEntity { + return AcademicsSearchEntity( + scholarship = scholarship, + content = createContent(scholarship) + ) + } + + fun createContent(academics: AcademicsEntity): String { + val sb = StringBuilder() + academics.name.let { sb.appendLine(it) } + academics.time?.let { sb.appendLine(it) } + academics.year?.let { sb.appendLine(it) } + sb.appendLine(academics.studentType.value) + sb.appendLine( + cleanTextFromHtml( + academics.description + ) + ) + + return sb.toString() + } + + fun createContent(course: CourseEntity) = + course.let { + val sb = StringBuilder() + sb.appendLine(it.studentType.value) + sb.appendLine(it.classification) + sb.appendLine(it.code) + sb.appendLine(it.name) + sb.appendLine(it.credit) + sb.appendLine(it.grade) + it.description?.let { desc -> + sb.appendLine(cleanTextFromHtml(desc)) + } + + sb.toString() + } + + fun createContent(scholarship: ScholarshipEntity) = + scholarship.let { + val sb = StringBuilder() + sb.appendLine(it.studentType.value) + sb.appendLine(it.name) + sb.appendLine( + cleanTextFromHtml(it.description) + ) + sb.toString() + } + } + + fun update(academics: AcademicsEntity) { + this.content = createContent(academics) + } + + fun update(course: CourseEntity) { + this.content = createContent(course) + } + + fun update(scholarship: ScholarshipEntity) { + this.content = createContent(scholarship) + } + + @PrePersist + @PreUpdate + fun checkType() { + if (!( + (academics != null && course == null && scholarship == null) || + (academics == null && course != null && scholarship == null) || + (academics == null && course == null && scholarship != null) + ) + ) { + throw IllegalStateException("AcademicsSearchEntity must have only one type of entity") + } + } + + fun ofType() = + when { + academics != null && course == null && scholarship == null -> AcademicsSearchType.ACADEMICS + academics == null && course != null && scholarship == null -> AcademicsSearchType.COURSE + academics == null && course == null && scholarship != null -> AcademicsSearchType.SCHOLARSHIP + else -> throw IllegalStateException("AcademicsSearchEntity must have only one type of entity") + } +} + +enum class AcademicsSearchType { + ACADEMICS, + COURSE, + SCHOLARSHIP +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt new file mode 100644 index 00000000..cd16fb10 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt @@ -0,0 +1,87 @@ +package com.wafflestudio.csereal.core.academics.database + +import com.querydsl.jpa.impl.JPAQuery +import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.core.academics.database.QAcademicsEntity.academicsEntity +import com.wafflestudio.csereal.core.academics.database.QAcademicsSearchEntity.academicsSearchEntity +import com.wafflestudio.csereal.core.academics.database.QCourseEntity.courseEntity +import com.wafflestudio.csereal.core.academics.database.QScholarshipEntity.scholarshipEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository +import com.wafflestudio.csereal.common.utils.exchangePageNum + +interface AcademicsSearchRepository : JpaRepository, AcademicsSearchCustomRepository + +interface AcademicsSearchCustomRepository { + fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> + fun searchTopAcademics(keyword: String, number: Int): List +} + +@Repository +class AcademicsSearchCustomRepositoryImpl( + private val queryFactory: JPAQueryFactory, + private val commonRepository: CommonRepository +) : AcademicsSearchCustomRepository { + override fun searchTopAcademics(keyword: String, number: Int): List { + return searchQuery(keyword) + .limit(number.toLong()) + .fetch() + } + + override fun searchAcademics( + keyword: String, + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val query = searchQuery(keyword) + val total = getSearchCount(keyword) + + val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() + val queryResult = query.offset(validOffset) + .limit(pageSize.toLong()) + .fetch() + + return queryResult to total + } + + fun searchQuery(keyword: String): JPAQuery { + val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( + keyword, + academicsSearchEntity.content + ) + + return queryFactory.selectFrom( + academicsSearchEntity + ).leftJoin( + academicsSearchEntity.academics, + academicsEntity + ).fetchJoin() + .leftJoin( + academicsSearchEntity.course, + courseEntity + ).fetchJoin() + .leftJoin( + academicsSearchEntity.scholarship, + scholarshipEntity + ).fetchJoin() + .where( + searchDoubleTemplate.gt(0.0) + ) + } + + fun getSearchCount(keyword: String): Long { + val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( + keyword, + academicsSearchEntity.content + ) + + return queryFactory.select( + academicsSearchEntity.countDistinct() + ).from(academicsSearchEntity) + .where( + searchDoubleTemplate.gt(0.0) + ).fetchOne()!! + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt index 2da6e67a..13e53321 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsStudentType.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.academics.database -enum class AcademicsStudentType { - UNDERGRADUATE, GRADUATE +enum class AcademicsStudentType(val value: String) { + UNDERGRADUATE("학부"), + GRADUATE("대학원"); } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt index 5f6727ef..baa4769c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt @@ -7,6 +7,7 @@ import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEnti import jakarta.persistence.CascadeType import jakarta.persistence.Entity import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne @Entity(name = "course") class CourseEntity( @@ -27,7 +28,10 @@ class CourseEntity( var description: String?, @OneToMany(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) - var attachments: MutableList = mutableListOf() + var attachments: MutableList = mutableListOf(), + + @OneToOne(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) + var academicsSearch: AcademicsSearchEntity? = null ) : BaseTimeEntity(), AttachmentContentEntityType { override fun bringAttachments() = attachments diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt index 3fc88cea..6aee54fd 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt @@ -12,7 +12,10 @@ class ScholarshipEntity( val name: String, @Column(columnDefinition = "text") - val description: String + val description: String, + + @OneToOne(mappedBy = "scholarship", cascade = [CascadeType.ALL], orphanRemoval = true) + var academicsSearch: AcademicsSearchEntity? = null ) : BaseTimeEntity() { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt index e3775e33..448a0632 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt @@ -5,14 +5,14 @@ import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class AcademicsDto( - val id: Long, - val name: String?, + val id: Long = -1, // TODO: Seperate to multiple DTOs or set this as nullable + val name: String, val description: String, - val year: Int?, - val time: String?, - val createdAt: LocalDateTime?, - val modifiedAt: LocalDateTime?, - val attachments: List? + val year: Int? = null, + val time: String? = null, + val createdAt: LocalDateTime? = null, + val modifiedAt: LocalDateTime? = null, + val attachments: List? = null ) { companion object { fun of(entity: AcademicsEntity, attachmentResponses: List): AcademicsDto = entity.run { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt new file mode 100644 index 00000000..36b6b79f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt @@ -0,0 +1,18 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity + +data class AcademicsSearchPageResponse( + val academics: List, + val total: Long +) { + companion object { + fun of( + academics: List, + total: Long + ) = AcademicsSearchPageResponse( + academics = academics.map(AcademicsSearchResponseElement::of), + total = total + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt new file mode 100644 index 00000000..7854a72c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt @@ -0,0 +1,43 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchType + +data class AcademicsSearchResponseElement( + val id: Long, + val name: String, + val academicsType: AcademicsSearchType +) { + companion object { + fun of(academicsSearch: AcademicsSearchEntity): AcademicsSearchResponseElement { + return when { + academicsSearch.academics != null && + academicsSearch.course == null && + academicsSearch.scholarship == null -> + AcademicsSearchResponseElement( + id = academicsSearch.academics!!.id, + name = academicsSearch.academics!!.name, + academicsType = AcademicsSearchType.ACADEMICS + ) + academicsSearch.academics == null && + academicsSearch.course != null && + academicsSearch.scholarship == null -> + AcademicsSearchResponseElement( + id = academicsSearch.course!!.id, + name = academicsSearch.course!!.name, + academicsType = AcademicsSearchType.COURSE + ) + academicsSearch.academics == null && + academicsSearch.course == null && + academicsSearch.scholarship != null -> + AcademicsSearchResponseElement( + id = academicsSearch.scholarship!!.id, + name = academicsSearch.scholarship!!.name, + academicsType = AcademicsSearchType.SCHOLARSHIP + ) + else -> throw CserealException.Csereal401("AcademicsSearchEntity의 연결이 올바르지 않습니다.") + } + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt new file mode 100644 index 00000000..232b2fe2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity + +data class AcademicsSearchTopResponse( + val topAcademics: List +) { + companion object { + fun of( + topAcademics: List + ) = AcademicsSearchTopResponse( + topAcademics = topAcademics.map(AcademicsSearchResponseElement::of) + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt new file mode 100644 index 00000000..eb2738b2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt @@ -0,0 +1,39 @@ +package com.wafflestudio.csereal.core.academics.service + +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchRepository +import com.wafflestudio.csereal.core.academics.dto.AcademicsSearchPageResponse +import com.wafflestudio.csereal.core.academics.dto.AcademicsSearchTopResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface AcademicsSearchService { + fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int): AcademicsSearchPageResponse + fun searchTopAcademics(keyword: String, number: Int): AcademicsSearchTopResponse +} + +@Service +class AcademicsSearchServiceImpl( + private val academicsSearchRepository: AcademicsSearchRepository +) : AcademicsSearchService { + @Transactional(readOnly = true) + override fun searchTopAcademics(keyword: String, number: Int) = + AcademicsSearchTopResponse.of( + academicsSearchRepository.searchTopAcademics( + keyword = keyword, + number = number + ) + ) + + @Transactional(readOnly = true) + override fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int) = + academicsSearchRepository.searchAcademics( + keyword = keyword, + pageSize = pageSize, + pageNum = pageNum + ).let { + AcademicsSearchPageResponse.of( + academics = it.first, + total = it.second + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt index 2773b6d5..7ee322cf 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -30,6 +30,10 @@ interface AcademicsService { fun readScholarship(scholarshipId: Long): ScholarshipDto } +// TODO: add Update, Delete method +// remember to update academicsSearch Field on Update method +// remember to mark delete of academicsSearch Field on Delete mark method + @Service class AcademicsServiceImpl( private val academicsRepository: AcademicsRepository, @@ -47,13 +51,18 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) - val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) + var newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, request) if (attachments != null) { attachmentService.uploadAllAttachments(newAcademics, attachments) } - academicsRepository.save(newAcademics) + // create search data + newAcademics.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + + newAcademics = academicsRepository.save(newAcademics) val attachmentResponses = attachmentService.createAttachmentResponses(newAcademics.attachments) @@ -87,6 +96,7 @@ class AcademicsServiceImpl( return academicsYearResponses } + @Transactional(readOnly = true) override fun readGeneralStudies(): GeneralStudiesPageResponse { val academicsEntity = academicsRepository.findByStudentTypeAndPostType( AcademicsStudentType.UNDERGRADUATE, @@ -103,13 +113,18 @@ class AcademicsServiceImpl( @Transactional override fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) - val newCourse = CourseEntity.of(enumStudentType, request) + var newCourse = CourseEntity.of(enumStudentType, request) if (attachments != null) { attachmentService.uploadAllAttachments(newCourse, attachments) } - courseRepository.save(newCourse) + // create search data + newCourse.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + + newCourse = courseRepository.save(newCourse) val attachmentResponses = attachmentService.createAttachmentResponses(newCourse.attachments) @@ -138,9 +153,14 @@ class AcademicsServiceImpl( @Transactional override fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) - val newScholarship = ScholarshipEntity.of(enumStudentType, request) + var newScholarship = ScholarshipEntity.of(enumStudentType, request) + + // create search data + newScholarship.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } - scholarshipRepository.save(newScholarship) + newScholarship = scholarshipRepository.save(newScholarship) return ScholarshipDto.of(newScholarship) } diff --git a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt index 54965232..a54c0d31 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt @@ -1,7 +1,9 @@ package com.wafflestudio.csereal.common.util import com.wafflestudio.csereal.common.utils.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -101,4 +103,46 @@ class UtilsTest : BehaviorSpec({ } } } + + Given("Using exchangePageNum to get valid page number") { + When("Given variables are not positive") { + val totalMinus = Triple(1, 1, -1) + val pageSizeZero = Triple(0, 1, 1) + val pageNumZero = Triple(1, 0, 1) + + Then("should throw AssertionError") { + shouldThrow { + exchangePageNum(totalMinus.first, totalMinus.second, totalMinus.third) + } + shouldThrow { + exchangePageNum(pageSizeZero.first, pageSizeZero.second, pageSizeZero.third) + } + shouldThrow { + exchangePageNum(pageNumZero.first, pageNumZero.second, pageNumZero.third) + } + } + } + + When("Given page is in the range") { + val pageSize = 10 + val total = 100L + val pageNum = 3 + + Then("Should return pageNum itself") { + val resultPageNum = exchangePageNum(pageSize, pageNum, total) + resultPageNum shouldBe pageNum + } + } + + When("Given page is out of range (bigger)") { + val pageSize = 10 + val total = 104L + val pageNum = 15 + + Then("Should return last page number") { + val resultPageNum = exchangePageNum(pageSize, pageNum, total) + resultPageNum shouldBe 11 + } + } + } }) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/academics/AcademicsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/academics/AcademicsServiceTest.kt new file mode 100644 index 00000000..d5968a7d --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/academics/AcademicsServiceTest.kt @@ -0,0 +1,78 @@ +//package com.wafflestudio.csereal.core.academics +// +//import com.wafflestudio.csereal.core.academics.database.AcademicsRepository +//import com.wafflestudio.csereal.core.academics.database.AcademicsSearchRepository +//import com.wafflestudio.csereal.core.academics.dto.AcademicsDto +//import com.wafflestudio.csereal.core.academics.service.AcademicsService +//import io.kotest.core.spec.style.BehaviorSpec +//import io.kotest.extensions.spring.SpringTestExtension +//import io.kotest.extensions.spring.SpringTestLifecycleMode +//import io.kotest.matchers.shouldBe +//import io.kotest.matchers.shouldNotBe +//import jakarta.transaction.Transactional +//import org.springframework.boot.test.context.SpringBootTest +//import org.springframework.data.repository.findByIdOrNull +//import org.springframework.test.context.ActiveProfiles +// +// +// TODO: Fix test issue +//@SpringBootTest +//@ActiveProfiles("test") +//@Transactional +//class AcademicsServiceTest ( +// private val academicsService: AcademicsService, +// private val academicsRepository: AcademicsRepository, +// private val academicsSearchRepository: AcademicsSearchRepository, +//): BehaviorSpec({ +// extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) +// +// Given("첨부파일 없는 Academics를 생성하려고 할 때") { +// val studentType = "undergraduate" +// val enumPostType = "guide" +// val request = AcademicsDto( +// name = "name", +// description = "

description

", +// year = 2023, +// time = "12:43", +// ) +// +// When("Academics를 생성한다면") { +// val returnDto = academicsService.createAcademics(studentType, enumPostType, request, null) +// +// Then("Academics가 생성되어야 한다.") { +// val id = returnDto.id +// val savedAcademics = academicsRepository.findByIdOrNull(id) +// +// savedAcademics shouldNotBe null +// savedAcademics!!.let { +// it.name shouldBe request.name +// it.description shouldBe request.description +// it.year shouldBe request.year +// it.time shouldBe request.time +// } +// } +// +// Then("검색 데이터가 생성되어야 한다.") { +// val savedAcademics = academicsRepository.findByIdOrNull(returnDto.id)!! +// val createdSearch = savedAcademics.academicsSearch?.id.let { +// academicsSearchRepository.findByIdOrNull(it) +// } +// +// createdSearch shouldNotBe null +// createdSearch!!.let { +// it.academics shouldBe savedAcademics +// it.course shouldBe null +// it.scholarship shouldBe null +// it.content shouldBe """ +// name +// 12:43 +// 2023 +// 학부생 +// description +// +// """.trimIndent() +// } +// } +// } +// } +//})