Skip to content

Commit

Permalink
Add article creation feature to API and integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gunkim committed Feb 17, 2025
1 parent 53b29c6 commit df4a402
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package io.github.gunkim.realworld.web.api.article

import io.github.gunkim.realworld.config.request.JsonRequest
import io.github.gunkim.realworld.domain.article.Article
import io.github.gunkim.realworld.domain.article.service.CreateArticleService
import io.github.gunkim.realworld.domain.article.service.FavoriteArticleService
import io.github.gunkim.realworld.domain.article.service.GetArticleService
import io.github.gunkim.realworld.domain.user.service.FollowUserService
import io.github.gunkim.realworld.share.AuthenticatedUser
import io.github.gunkim.realworld.web.api.article.model.request.CreateArticleRequest
import io.github.gunkim.realworld.web.api.article.model.request.GetArticlesRequest
import io.github.gunkim.realworld.web.api.article.model.response.ArticleResponse
import io.github.gunkim.realworld.web.api.article.model.response.ArticleResponseWrapper
import io.github.gunkim.realworld.web.api.article.model.response.ArticlesResponse
import java.util.UUID
import org.springframework.http.HttpStatus
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController

@RequestMapping("/api/articles")
Expand All @@ -26,11 +32,19 @@ interface ArticleResource {

@GetMapping("/{slug}")
fun getArticle(@PathVariable slug: String): ArticleResponseWrapper

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun createArticle(
@JsonRequest("article") request: CreateArticleRequest,
@AuthenticationPrincipal authenticatedUser: AuthenticatedUser,
): ArticleResponseWrapper
}

@RestController
class ArticlesController(
private val getArticleService: GetArticleService,
private val createArticleService: CreateArticleService,
private val favoriteArticleService: FavoriteArticleService,
private val followUserService: FollowUserService,
) : ArticleResource {
Expand Down Expand Up @@ -62,6 +76,22 @@ class ArticlesController(
).let(::ArticleResponseWrapper)
}

override fun createArticle(
request: CreateArticleRequest,
authenticatedUser: AuthenticatedUser,
): ArticleResponseWrapper {
val article = createArticleService.createArticle(
request.title,
request.description,
request.body,
request.tagList,
authenticatedUser.uuid
)

return ArticleResponse.create(article)
.let(::ArticleResponseWrapper)
}

private fun articlesResponse(
articles: List<Article>,
authenticatedUser: AuthenticatedUser?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.gunkim.realworld.web.api.article.model.request

data class CreateArticleRequest(
val title: String,
val description: String,
val body: String,
val tagList: List<String> = listOf(),
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package io.github.gunkim.realworld.web.api.article.model.response

import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonTypeInfo.As.WRAPPER_OBJECT
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
import com.fasterxml.jackson.annotation.JsonTypeName
import io.github.gunkim.realworld.domain.article.Article
import io.github.gunkim.realworld.web.api.user.model.response.ProfileResponse
import java.time.Instant
Expand Down Expand Up @@ -47,5 +44,11 @@ data class ArticleResponse(
article = article,
favoritesCount = favoritesCount
)

fun create(article: Article) = from(
article = article,
favoritesCount = 0,
favorited = false
)
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package io.github.gunkim.realworld.web.api

import com.fasterxml.jackson.databind.ObjectMapper
import io.github.gunkim.realworld.domain.article.Article
import io.github.gunkim.realworld.domain.article.service.CreateArticleService
import io.github.gunkim.realworld.domain.user.model.User
import io.github.gunkim.realworld.share.IntegrationTest
import io.github.gunkim.realworld.web.api.article.model.request.CreateArticleRequest
import io.kotest.core.annotation.Tags
import io.kotest.core.spec.DisplayName
import io.kotest.core.test.TestCase
import org.springframework.http.HttpHeaders
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.post

@Tags("Integration Test")
@DisplayName("Articles Controller - Integration Test")
class ArticlesControllerIntegrationTest(
private val createArticleService: CreateArticleService,
private val objectMapper: ObjectMapper,
) : IntegrationTest() {
lateinit var token: String
lateinit var articles: List<Article>
lateinit var author: User
lateinit var authUser: User

override suspend fun beforeEach(testCase: TestCase) {
val (_, token) = createUser("[email protected]", "gunkim", "password")
val (authUser, token) = createUser("[email protected]", "gunkim", "password")
val (author, _) = createUser("[email protected]", "author gunkim", "password")
val articles = listOf(
createArticleService.createArticle(
Expand All @@ -33,6 +38,7 @@ class ArticlesControllerIntegrationTest(
)

this.token = token
this.authUser = authUser
this.articles = articles
this.author = author
}
Expand Down Expand Up @@ -72,5 +78,34 @@ class ArticlesControllerIntegrationTest(
jsonPath("$.article.slug") { value(articles[0].slug) }
}.andDo { print() }
}

"POST /api/articles - Create an article" {
val request = CreateArticleRequest(
title = "New Article",
description = "New Article Description",
body = "This is the body of the new article.",
tagList = listOf("tag1", "tag3")
)

val requestBody = mapOf("article" to request)
val requestJson = objectMapper.writeValueAsString(requestBody)

mockMvc.post("/api/articles") {
contentType = org.springframework.http.MediaType.APPLICATION_JSON
header(HttpHeaders.AUTHORIZATION, token)
content = requestJson
}.andExpect {
status { isCreated() }
jsonPath("$.article.title") { value(request.title) }
jsonPath("$.article.description") { value(request.description) }
jsonPath("$.article.body") { value(request.body) }
jsonPath("$.article.tagList[0]") { value(request.tagList[0]) }
jsonPath("$.article.tagList[1]") { value(request.tagList[1]) }
jsonPath("$.article.author.username") { value(authUser.name) }
jsonPath("$.article.slug") { exists() }
jsonPath("$.article.createdAt") { exists() }
jsonPath("$.article.updatedAt") { exists() }
}.andDo { print() }
}
}
}

0 comments on commit df4a402

Please sign in to comment.