Skip to content

Commit

Permalink
Merge pull request #96 from AlbertImKr/refactor-rest-api
Browse files Browse the repository at this point in the history
[Refactor] JSON으로 전환
  • Loading branch information
AlbertImKr authored Mar 6, 2025
2 parents 1d16230 + 282784b commit 96ee602
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 75 deletions.
12 changes: 8 additions & 4 deletions http/url-shortener.http
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
### Origin URL 저장
POST {{baseUrl}}/shorten-url/create
Content-Type: text/plain
Content-Type: application/json

https://www.naver.com
{
"originUrl": "https://www.naver.com"
}

### 짧은 키워드로 Origin URL 조회
POST {{baseUrl}}/shorten-url/search
Content-Type: text/plain
Content-Type: application/json

1
{
"shortenUrl": "1"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package community.whatever.onembackendkotlin

data class ShortenUrlCreateRequest(val originUrl: String)

data class ShortenUrlSearchRequest(val shortenUrl: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package community.whatever.onembackendkotlin

data class ShortenedUrlResponse(val shortenedUrl: String)

data class OriginUrlResponse(val originUrl: String)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package community.whatever.onembackendkotlin

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
Expand All @@ -8,12 +9,12 @@ import org.springframework.web.bind.annotation.RestController
class UrlShortenController(private val urlShortenService: UrlShortenService) {

@PostMapping("/shorten-url/search")
fun shortenUrlSearch(@RequestBody key: String): String {
return urlShortenService.getOriginUrl(key)
fun shortenUrlSearch(@RequestBody key: ShortenUrlSearchRequest): ResponseEntity<OriginUrlResponse> {
return ResponseEntity.ok(urlShortenService.getOriginUrl(key))
}

@PostMapping("/shorten-url/create")
fun shortenUrlCreate(@RequestBody originUrl: String): String {
return urlShortenService.saveShortenUrl(originUrl)
fun shortenUrlCreate(@RequestBody originUrl: ShortenUrlCreateRequest): ResponseEntity<ShortenedUrlResponse> {
return ResponseEntity.ok(urlShortenService.saveShortenUrl(originUrl))
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
package community.whatever.onembackendkotlin

interface UrlShortenService {
/**
* 아이디를 통해 원본 URL을 찾는다.
*
* @param id ShortenedUrl의 id이다.
* @return ShortenedUrl의 id
* @throws UrlNotFoundException ShortenedUrl의 id에 해당하는 ShortenedUrl이 존재하지 않으면 발생한다.
*/
fun getOriginUrl(id: String): String

/**
* URL이 존재하면 저장된 URL의 id를 반환하고 존재하지 않으면 새롭게 URL을 저장하고 id를 반환한다.
*
* @param originUrl 단축할 URL이다.
* @return 저장된 URL의 id이다.
*/
fun saveShortenUrl(originUrl: String): String
fun getOriginUrl(request: ShortenUrlSearchRequest): OriginUrlResponse
fun saveShortenUrl(request: ShortenUrlCreateRequest): ShortenedUrlResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import org.springframework.stereotype.Service

@Service
class UrlShortenServiceImpl(private val shortenedUrlRepository: ShortenedUrlRepository) : UrlShortenService {
override fun getOriginUrl(id: String): String {
return shortenedUrlRepository.findById(id)?.originUrl ?: throw UrlNotFoundException()
override fun getOriginUrl(request: ShortenUrlSearchRequest): OriginUrlResponse {
val id = request.shortenUrl
return OriginUrlResponse(shortenedUrlRepository.findById(id)?.originUrl ?: throw UrlNotFoundException())
}

override fun saveShortenUrl(originUrl: String): String {
override fun saveShortenUrl(request: ShortenUrlCreateRequest): ShortenedUrlResponse {
val originUrl = request.originUrl
if (shortenedUrlRepository.existsByOriginUrl(originUrl)) {
return shortenedUrlRepository.findByOriginUrl(originUrl)!!.id!!
return ShortenedUrlResponse(shortenedUrlRepository.findByOriginUrl(originUrl)!!.id!!)
}
return shortenedUrlRepository.save(ShortenedUrl(originUrl)).id!!
return ShortenedUrlResponse(shortenedUrlRepository.save(ShortenedUrl(originUrl)).id!!)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.awaitility.kotlin.untilAsserted
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.springframework.beans.factory.annotation.Autowired
Expand Down Expand Up @@ -42,32 +43,27 @@ class UrlShortenControllerIntegrationTest {
)
fun `등록된 원본 URL을 키로 검색하면 해당 원본 URL을 반환한다`(originUrl: String) = runTest {
// given
val key = createUrl(originUrl)
.expectBody(String::class.java)
val key = createUrl(ShortenUrlCreateRequest(originUrl))
.expectBody(ShortenedUrlResponse::class.java)
.returnResult()
.responseBody!!

// when
val result = searchShortenUrl(key)
val result = searchShortenUrl(ShortenUrlSearchRequest(key.shortenedUrl))

// then
result.expectStatus().isOk()
.expectBody(String::class.java)
.isEqualTo(originUrl)
.expectBody(OriginUrlResponse::class.java)
.isEqualTo(OriginUrlResponse(originUrl))
}

@ParameterizedTest
@CsvSource(
"https://www.google.com",
"https://www.naver.com",
"https://www.daum.net"
)
fun `등록되지 않은 원본 URL을 키로 검색하면 404를 반환한다`(originUrl: String) = runTest {
@Test
fun `등록되지 않은 원본 URL을 키로 검색하면 404를 반환한다`() = runTest {
// given
val key = "non-exist-key"

// when
val result = searchShortenUrl(key)
val result = searchShortenUrl(ShortenUrlSearchRequest(key))

// then
result.expectStatus()
Expand All @@ -87,14 +83,14 @@ class UrlShortenControllerIntegrationTest {
)
fun `원본 URL을 등록하면 키를 반환한다`(originUrl: String) = runTest {
// when
val result = createUrl(originUrl)
val result = createUrl(ShortenUrlCreateRequest(originUrl))

// then
val key = result.expectStatus().isOk()
.expectBody(String::class.java)
val shortenedUrlResponse = result.expectStatus().isOk()
.expectBody(ShortenedUrlResponse::class.java)
.returnResult()
.responseBody!!
assertThat(key).isNotBlank()
assertThat(shortenedUrlResponse.shortenedUrl).isNotBlank()
}

@ParameterizedTest
Expand All @@ -104,7 +100,8 @@ class UrlShortenControllerIntegrationTest {
val originUrls = (1..count).map { "https://www.google.com/$it" }

// when
val keys = originUrls.map { async { createUrlAndReturnKey(it) } }
val keys = originUrls.map { async { createUrlAndReturnKey(ShortenUrlCreateRequest(it)) } }
.map { it.await() }

// then
await untilAsserted { assertThat(keys).hasSize(count).doesNotHaveDuplicates() }
Expand All @@ -118,10 +115,10 @@ class UrlShortenControllerIntegrationTest {
)
fun `등록된 원본 URL을 다시 등록하면 기존 키를 반환한다`(originUrl: String) = runTest {
// given
val key = createUrlAndReturnKey(originUrl)
val key = createUrlAndReturnKey(ShortenUrlCreateRequest(originUrl))

// when
val result = createUrl(originUrl)
val result = createUrl(ShortenUrlCreateRequest(originUrl))

// then
result.expectStatus().isOk()
Expand All @@ -130,17 +127,17 @@ class UrlShortenControllerIntegrationTest {
}
}

private fun searchShortenUrl(key: String) = webTestClient.post()
private fun searchShortenUrl(request: ShortenUrlSearchRequest) = webTestClient.post()
.uri("/shorten-url/search")
.bodyValue(key)
.bodyValue(request)
.exchange()

private fun createUrl(originUrl: String) = webTestClient.post()
private fun createUrl(request: ShortenUrlCreateRequest) = webTestClient.post()
.uri("/shorten-url/create")
.bodyValue(originUrl)
.bodyValue(request)
.exchange()

private fun createUrlAndReturnKey(originUrl: String) = createUrl(originUrl)
private fun createUrlAndReturnKey(request: ShortenUrlCreateRequest) = createUrl(request)
.expectBody(String::class.java)
.returnResult()
.responseBody!!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package community.whatever.onembackendkotlin

import com.fasterxml.jackson.databind.ObjectMapper
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
Expand All @@ -19,18 +20,20 @@ class UrlShortenControllerTest(
@MockitoBean
private lateinit var urlShortenService: UrlShortenService

private val objectMapper = ObjectMapper()

@Test
fun `원본 URL 등록 시 키를 반환한다`() {
// given
val originUrl = "https://www.google.com"
val key = "123"
val originUrl = ShortenUrlCreateRequest("https://www.google.com")
val key = ShortenedUrlResponse("123")
given(urlShortenService.saveShortenUrl(originUrl)).willReturn(key)

// when
val result = mockMvc.perform(
post("/shorten-url/create")
.contentType(MediaType.APPLICATION_JSON)
.content(originUrl)
.content(objectMapper.writeValueAsString(originUrl))
)
.andExpect(status().isOk)
.andReturn()
Expand All @@ -44,59 +47,59 @@ class UrlShortenControllerTest(
@Test
fun `키로 검색하면 원본 URL을 반환한다`() {
// given
val originUrl = "https://www.google.com"
val key = "123"
val originUrl = OriginUrlResponse("https://www.google.com")
val key = ShortenUrlSearchRequest("123")
given(urlShortenService.getOriginUrl(key)).willReturn(originUrl)

// when
val result = mockMvc.perform(
post("/shorten-url/search")
.contentType(MediaType.APPLICATION_JSON)
.content(key)
.content(objectMapper.writeValueAsString(key))
)
.andExpect(status().isOk)
.andReturn()
.response
.contentAsString

// then
assertThat(originUrl).isEqualTo(result)
assertThat(result).isEqualTo(objectMapper.writeValueAsString(originUrl))
}

@Test
fun `유요하지 않은 키로 검색하면 404 에러를 반환한다`() {
// given
val invalidKey = "invalid-key"
val invalidKey = ShortenUrlSearchRequest("invalid")
given(urlShortenService.getOriginUrl(invalidKey)).willThrow(UrlNotFoundException())

// when
mockMvc.perform(
post("/shorten-url/search")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidKey)
.content(objectMapper.writeValueAsString(invalidKey))
)
.andExpect(status().isNotFound)
}

@Test
fun `중복된 원본 URL을 등록하면 기존 키를 반환한다`() {
// given
val originUrl = "https://www.google.com"
val key = "123"
val originUrl = ShortenUrlCreateRequest("https://www.google.com")
val key = ShortenedUrlResponse("123")
given(urlShortenService.saveShortenUrl(originUrl)).willReturn(key)

// when
val result = mockMvc.perform(
post("/shorten-url/create")
.contentType(MediaType.APPLICATION_JSON)
.content(originUrl)
.content(objectMapper.writeValueAsString(originUrl))
)
.andExpect(status().isOk)
.andReturn()
.response
.contentAsString

// then
assertThat(key).isEqualTo(result)
assertThat(result).isEqualTo(objectMapper.writeValueAsString(key))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ class UrlShortenServiceImplTest {
every { shortenedUrlRepository.save(shortenedUrl) } returns savedShortenedUrl

// when
val result = urlShortenService.saveShortenUrl(originUrl)
val result = urlShortenService.saveShortenUrl(ShortenUrlCreateRequest(originUrl))

// then
assertThat(result).isEqualTo(id)
assertThat(result).isEqualTo(ShortenedUrlResponse(id))
verify { shortenedUrlRepository.save(shortenedUrl) }
}

Expand All @@ -54,10 +54,10 @@ class UrlShortenServiceImplTest {
every { shortenedUrlRepository.findByOriginUrl(originUrl) } returns savedShortenedUrl

// when
val result = urlShortenService.saveShortenUrl(originUrl)
val result = urlShortenService.saveShortenUrl(ShortenUrlCreateRequest(originUrl))

// then
assertThat(result).isEqualTo(id)
assertThat(result).isEqualTo(ShortenedUrlResponse(id))
verify { shortenedUrlRepository.findByOriginUrl(originUrl) }
}
}
Expand All @@ -83,10 +83,10 @@ class UrlShortenServiceImplTest {
every { shortenedUrlRepository.findById(id) } returns shortenedUrl

// when
val result = urlShortenService.getOriginUrl(id)
val result = urlShortenService.getOriginUrl(ShortenUrlSearchRequest(id))

// then
assertThat(result).isEqualTo(originUrl)
assertThat(result).isEqualTo(OriginUrlResponse(originUrl))
verify { shortenedUrlRepository.findById(id) }
}

Expand All @@ -96,7 +96,8 @@ class UrlShortenServiceImplTest {
every { shortenedUrlRepository.findById(id) } returns null

// when, then
assertThatThrownBy { urlShortenService.getOriginUrl(id) }.isInstanceOf(UrlNotFoundException::class.java)
assertThatThrownBy { urlShortenService.getOriginUrl(ShortenUrlSearchRequest(id)) }
.isInstanceOf(UrlNotFoundException::class.java)
verify { shortenedUrlRepository.findById(id) }
}
}
Expand Down

0 comments on commit 96ee602

Please sign in to comment.