-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: 공지사항 조회 #20
The head ref may contain hidden characters: "19-feature-\uACF5\uC9C0\uC0AC\uD56D-\uC870\uD68C"
feature: 공지사항 조회 #20
Changes from all commits
fa69f2b
7ab8026
c42e5c8
f6a57f6
1273b66
313a2b3
5bf6e7b
c70c562
62c298e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.petqua.application.announcement | ||
|
||
import com.petqua.domain.announcement.Announcement | ||
|
||
data class AnnouncementResponse( | ||
val id: Long, | ||
val title: String, | ||
val linkUrl: String, | ||
) { | ||
companion object { | ||
fun from(announcement: Announcement): AnnouncementResponse { | ||
return AnnouncementResponse( | ||
id = announcement.id, | ||
title = announcement.title, | ||
linkUrl = announcement.linkUrl, | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.petqua.application.announcement | ||
|
||
import com.petqua.domain.announcement.AnnouncementRepository | ||
import org.springframework.cache.annotation.Cacheable | ||
import org.springframework.stereotype.Service | ||
import org.springframework.transaction.annotation.Transactional | ||
|
||
@Transactional | ||
@Service | ||
class AnnouncementService( | ||
private val announcementRepository: AnnouncementRepository, | ||
) { | ||
|
||
@Cacheable("announcements") | ||
@Transactional(readOnly = true) | ||
fun readAll(): List<AnnouncementResponse> { | ||
val announcements = announcementRepository.findAll() | ||
return announcements.map { AnnouncementResponse.from(it) } | ||
} | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.petqua.domain.announcement | ||
|
||
import com.petqua.common.domain.BaseEntity | ||
import jakarta.persistence.Column | ||
import jakarta.persistence.Entity | ||
import jakarta.persistence.GeneratedValue | ||
import jakarta.persistence.GenerationType | ||
import jakarta.persistence.Id | ||
|
||
@Entity | ||
class Announcement( | ||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
val id: Long = 0L, | ||
|
||
@Column(nullable = false) | ||
val title: String, | ||
|
||
@Column(nullable = false) | ||
val linkUrl: String, | ||
) : BaseEntity() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.petqua.domain.announcement | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository | ||
|
||
interface AnnouncementRepository : JpaRepository<Announcement, Long> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.petqua.presentation.announcement | ||
|
||
import com.petqua.application.announcement.AnnouncementResponse | ||
import com.petqua.application.announcement.AnnouncementService | ||
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 | ||
|
||
@RequestMapping("/announcements") | ||
@RestController | ||
class AnnouncementController( | ||
private val announcementService: AnnouncementService | ||
) { | ||
|
||
@GetMapping | ||
fun readAll(): ResponseEntity<List<AnnouncementResponse>> { | ||
val response = announcementService.readAll() | ||
return ResponseEntity.ok(response) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package com.petqua.application.announcement | ||
|
||
import com.petqua.domain.announcement.Announcement | ||
import com.petqua.domain.announcement.AnnouncementRepository | ||
import io.kotest.core.spec.style.BehaviorSpec | ||
import io.kotest.matchers.shouldBe | ||
import org.mockito.Mockito.atMost | ||
import org.mockito.Mockito.verify | ||
import org.springframework.boot.test.context.SpringBootTest | ||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE | ||
import org.springframework.boot.test.mock.mockito.SpyBean | ||
import org.springframework.test.context.TestConstructor | ||
import org.springframework.test.context.TestConstructor.AutowireMode.ALL | ||
|
||
@TestConstructor(autowireMode = ALL) | ||
@SpringBootTest(webEnvironment = NONE) | ||
class AnnouncementServiceTest( | ||
private var announcementService: AnnouncementService, | ||
@SpyBean private var announcementRepository: AnnouncementRepository, | ||
) : BehaviorSpec({ | ||
|
||
Given("공지사항 조회 테스트") { | ||
announcementRepository.saveAll( | ||
listOf( | ||
Announcement(title = "titleA", linkUrl = "linkUrlA"), | ||
Announcement(title = "titleB", linkUrl = "linkUrlB"), | ||
Announcement(title = "titleC", linkUrl = "linkUrlC"), | ||
) | ||
) | ||
|
||
When("공지사항을 전체 조회 하면") { | ||
val results = announcementService.readAll() | ||
|
||
Then("모든 공지사항이 조회 된다") { | ||
results.size shouldBe 3 | ||
} | ||
} | ||
|
||
When("공지사항이 캐싱 되어 있으면") { | ||
repeat(5) { announcementService.readAll() } | ||
|
||
Then("퀴리가 발생 하지 않는다") { | ||
verify(announcementRepository, atMost(1)).findAll() | ||
} | ||
} | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
package com.petqua.application | ||
package com.petqua.application.product | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헉쓰 놓쳤네요 이동 감사합니다 |
||
import com.petqua.application.product.ProductService | ||
import com.petqua.application.product.dto.ProductDetailResponse | ||
import com.petqua.application.product.dto.ProductReadRequest | ||
import com.petqua.application.product.dto.ProductsResponse | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.petqua.presentation.announcement | ||
|
||
import com.petqua.application.announcement.AnnouncementResponse | ||
import com.petqua.domain.announcement.Announcement | ||
import com.petqua.domain.announcement.AnnouncementRepository | ||
import com.petqua.test.ApiTestConfig | ||
import io.restassured.module.kotlin.extensions.Extract | ||
import io.restassured.module.kotlin.extensions.Given | ||
import io.restassured.module.kotlin.extensions.Then | ||
import io.restassured.module.kotlin.extensions.When | ||
import org.assertj.core.api.SoftAssertions.assertSoftly | ||
import org.springframework.http.HttpStatus | ||
|
||
class AnnouncementControllerTest( | ||
private val announcementRepository: AnnouncementRepository | ||
) : ApiTestConfig() { | ||
|
||
init { | ||
Given("공지 사항이 존재할 때") { | ||
val announcements = announcementRepository.saveAll( | ||
listOf( | ||
Announcement(title = "announcementsA", linkUrl = "linkUrlA"), | ||
Announcement(title = "announcementsB", linkUrl = "linkUrlB") | ||
) | ||
) | ||
|
||
When("공지 사항 목록을 조회하면") { | ||
val response = Given { | ||
log().all() | ||
} When { | ||
get("/announcements") | ||
} Then { | ||
log().all() | ||
} Extract { | ||
response() | ||
} | ||
|
||
Then("모든 공지 사항 목록이 반환된다.") { | ||
val responseData = response.`as`(Array<AnnouncementResponse>::class.java) | ||
|
||
assertSoftly { | ||
it.assertThat(response.statusCode).isEqualTo(HttpStatus.OK.value()) | ||
it.assertThat(responseData.size).isEqualTo(2) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,12 +11,16 @@ import org.springframework.boot.test.web.server.LocalServerPort | |
abstract class ApiTestConfig : BehaviorSpec() { | ||
|
||
@LocalServerPort | ||
protected val port: Int = RestAssured.port | ||
protected var port: Int = 0 | ||
|
||
@Autowired | ||
private lateinit var dataCleaner: DataCleaner | ||
|
||
init { | ||
this.beforeTest { | ||
RestAssured.port = this.port | ||
} | ||
|
||
Comment on lines
+20
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 이전이랑 무슨 차이가 있나요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이전에는 port라는 변수에 값만 설정하고, port를 사용하진 않았었는데요 @SpringBooTest에서 RandomPort로 하니까 RestAssured.port 값이 이전과 동일해서 connection 안되더라고요. @LocalServerPort로 SpringContext마다 할당된 randomPort를 RestAssured.port로 지정하였습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 너모 멋져요...! |
||
afterContainer { | ||
dataCleaner.clean() | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
깔끔하네요~~