Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/main/java/eatda/controller/article/ArticleController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package eatda.controller.article;

import eatda.service.article.ArticleService;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequiredArgsConstructor
public class ArticleController {

private final ArticleService articleService;

@GetMapping("/api/articles")
public ResponseEntity<ArticlesResponse> getArticles(@RequestParam(defaultValue = "3") @Min(1) @Max(50) int size) {
return ResponseEntity.status(HttpStatus.OK)
.body(articleService.getAllArticles(size));
}
}
9 changes: 9 additions & 0 deletions src/main/java/eatda/controller/article/ArticleResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package eatda.controller.article;

public record ArticleResponse(
String title,
String subtitle,
String articleUrl,
String imageUrl
) {
}
8 changes: 8 additions & 0 deletions src/main/java/eatda/controller/article/ArticlesResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package eatda.controller.article;

import java.util.List;

public record ArticlesResponse(
List<ArticleResponse> articles
) {
}
12 changes: 12 additions & 0 deletions src/main/java/eatda/repository/article/ArticleRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package eatda.repository.article;

import eatda.domain.article.Article;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ArticleRepository extends JpaRepository<Article, Long> {

Page<Article> findAllByOrderByCreatedAtDesc(Pageable pageable);

}
33 changes: 33 additions & 0 deletions src/main/java/eatda/service/article/ArticleService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package eatda.service.article;

import eatda.controller.article.ArticleResponse;
import eatda.controller.article.ArticlesResponse;
import eatda.repository.article.ArticleRepository;
import eatda.service.common.ImageService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ArticleService {

private final ArticleRepository articleRepository;
private final ImageService imageService;

public ArticlesResponse getAllArticles(int size) {
PageRequest pageRequest = PageRequest.of(0, size);
List<ArticleResponse> articles = articleRepository.findAllByOrderByCreatedAtDesc(pageRequest)
.stream()
.map(article -> new ArticleResponse(
article.getTitle(),
article.getSubtitle(),
article.getArticleUrl(),
imageService.getPresignedUrl(article.getImageKey())
))
.toList();

return new ArticlesResponse(articles);
}
}
5 changes: 4 additions & 1 deletion src/test/java/eatda/controller/BaseControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
import eatda.client.oauth.OauthToken;
import eatda.controller.web.jwt.JwtManager;
import eatda.domain.member.Member;
import eatda.fixture.ArticleGenerator;
import eatda.fixture.CheerGenerator;
import eatda.fixture.MemberGenerator;
import eatda.fixture.StoreGenerator;
import eatda.repository.member.MemberRepository;
import eatda.repository.store.CheerRepository;
import eatda.repository.store.StoreRepository;
import eatda.service.common.ImageService;
import eatda.service.common.ImageService;
import eatda.service.story.StoryService;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
Expand Down Expand Up @@ -59,6 +59,9 @@ public class BaseControllerTest {
@Autowired
protected CheerGenerator cheerGenerator;

@Autowired
protected ArticleGenerator articleGenerator;

@Autowired
protected MemberRepository memberRepository;

Expand Down
31 changes: 31 additions & 0 deletions src/test/java/eatda/controller/article/ArticleControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package eatda.controller.article;

import static org.assertj.core.api.Assertions.assertThat;

import eatda.controller.BaseControllerTest;
import eatda.domain.article.Article;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

public class ArticleControllerTest extends BaseControllerTest {

@Nested
class GetArticles {

@Test
void 가게의_담긴_이야기_목록을_조회할_수_있다() {
Article article1 = articleGenerator.generate("국밥의 모든 것");
Article article2 = articleGenerator.generate("순대국의 진실");

ArticlesResponse response = given()
.queryParam("size", 3)
.when()
.get("/api/articles")
.then().statusCode(200)
.extract().as(ArticlesResponse.class);

assertThat(response.articles()).hasSize(2);
assertThat(response.articles().getFirst().title()).isEqualTo("순대국의 진실");
}
}
}
5 changes: 5 additions & 0 deletions src/test/java/eatda/document/BaseDocumentTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import eatda.controller.web.jwt.JwtManager;
import eatda.exception.BusinessErrorCode;
import eatda.exception.EtcErrorCode;
import eatda.service.article.ArticleService;
import eatda.service.auth.AuthService;
import eatda.service.common.ImageService;
import eatda.service.member.MemberService;
Expand Down Expand Up @@ -57,6 +58,10 @@ public abstract class BaseDocumentTest {

@MockitoBean
protected CheerService cheerService;

@MockitoBean
protected ArticleService articleService;

@MockitoBean
protected JwtManager jwtManager;

Expand Down
1 change: 1 addition & 0 deletions src/test/java/eatda/document/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum Tag {
STORE_API("Store API"),
STORY_API("Story API"),
CHEER_API("Cheer API"),
ARTICLE_API("Article API"),
;

private final String displayName;
Expand Down
74 changes: 74 additions & 0 deletions src/test/java/eatda/document/article/ArticleDocumentTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package eatda.document.article;

import static org.mockito.Mockito.doReturn;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;

import eatda.controller.article.ArticleResponse;
import eatda.controller.article.ArticlesResponse;
import eatda.document.BaseDocumentTest;
import eatda.document.RestDocsRequest;
import eatda.document.RestDocsResponse;
import eatda.document.Tag;
import io.restassured.response.Response;
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.restassured.RestDocumentationFilter;

public class ArticleDocumentTest extends BaseDocumentTest {

@Nested
class GetArticles {

RestDocsRequest requestDocument = request()
.tag(Tag.ARTICLE_API)
.summary("가게의 담긴 이야기")
.description("게시글을 최신순으로 페이지네이션하여 조회합니다.")
.queryParameter(parameterWithName("size").description("페이지당 조회할 아티클 개수 (default = 3)"));

RestDocsResponse responseDocument = response()
.responseBodyField(
fieldWithPath("articles").description("게시글 응답 리스트"),
fieldWithPath("articles[].title").description("게시글 제목"),
fieldWithPath("articles[].subtitle").description("게시글 소제목"),
fieldWithPath("articles[].articleUrl").description("게시글 링크 URL"),
fieldWithPath("articles[].imageUrl").description("게시글 이미지 URL")
);

@Test
void 가게의_담긴_이야기_목록_조회_성공() {
ArticlesResponse mockResponse = new ArticlesResponse(List.of(
new ArticleResponse(
"국밥의 모든 것",
"뜨끈한 국물의 세계",
"https://eatda.com/article/1",
"https://s3.bucket.com/article/1.jpg"
),
new ArticleResponse(
"순대국의 진실",
"돼지부속의 미학",
"https://eatda.com/article/2",
"https://s3.bucket.com/article/2.jpg"
)
));

doReturn(mockResponse)
.when(articleService)
.getAllArticles(3);

RestDocumentationFilter document = document("article/get-articles", 200)
.request(requestDocument)
.response(responseDocument)
.build();

Response response = given(document)
.queryParam("size", 3)
.when()
.get("/api/articles");

response.then()
.statusCode(200);
}
}
}
51 changes: 51 additions & 0 deletions src/test/java/eatda/fixture/ArticleGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package eatda.fixture;

import eatda.controller.article.ArticleResponse;
import eatda.domain.article.Article;
import eatda.repository.article.ArticleRepository;
import org.springframework.stereotype.Component;

@Component
public class ArticleGenerator {

private static final String DEFAULT_TITLE = "기본 제목";
private static final String DEFAULT_SUBTITLE = "기본 소제목";
private static final String DEFAULT_URL = "https://eatda.com/article/default";
private static final String DEFAULT_IMAGE_KEY = "article/default-image.jpg";

private final ArticleRepository articleRepository;

public ArticleGenerator(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}

public Article generate() {
return generate(DEFAULT_TITLE);
}

public Article generate(String title) {
return generate(title, DEFAULT_SUBTITLE);
}

public Article generate(String title, String subtitle) {
return generate(title, subtitle, DEFAULT_URL);
}

public Article generate(String title, String subtitle, String articleUrl) {
return generate(title, subtitle, articleUrl, DEFAULT_IMAGE_KEY);
}

public Article generate(String title, String subtitle, String articleUrl, String imageKey) {
Article article = new Article(title, subtitle, articleUrl, imageKey);
return articleRepository.save(article);
}

public ArticleResponse toResponse(Article article) {
return new ArticleResponse(
article.getTitle(),
article.getSubtitle(),
article.getArticleUrl(),
"https://s3.bucket.com/" + article.getImageKey()
);
}
}
8 changes: 8 additions & 0 deletions src/test/java/eatda/service/BaseServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import eatda.DatabaseCleaner;
import eatda.client.map.MapClient;
import eatda.client.oauth.OauthClient;
import eatda.fixture.ArticleGenerator;
import eatda.fixture.CheerGenerator;
import eatda.fixture.MemberGenerator;
import eatda.fixture.StoreGenerator;
import eatda.repository.article.ArticleRepository;
import eatda.repository.member.MemberRepository;
import eatda.repository.store.CheerRepository;
import eatda.repository.store.StoreRepository;
Expand Down Expand Up @@ -45,6 +47,9 @@ public abstract class BaseServiceTest {
@Autowired
protected CheerGenerator cheerGenerator;

@Autowired
protected ArticleGenerator articleGenerator;

@Autowired
protected MemberRepository memberRepository;

Expand All @@ -54,6 +59,9 @@ public abstract class BaseServiceTest {
@Autowired
protected CheerRepository cheerRepository;

@Autowired
protected ArticleRepository articleRepository;

@BeforeEach
void mockingImageService() {
doReturn(MOCKED_IMAGE_URL).when(imageService).getPresignedUrl(anyString());
Expand Down
32 changes: 32 additions & 0 deletions src/test/java/eatda/service/article/ArticleServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package eatda.service.article;

import static org.assertj.core.api.Assertions.assertThat;

import eatda.controller.article.ArticleResponse;
import eatda.service.BaseServiceTest;
import java.util.stream.LongStream;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

public class ArticleServiceTest extends BaseServiceTest {

@Autowired
private ArticleService articleService;

@Nested
class GetAllArticles {

@Test
void 가게의_담긴_이야기를_최신순으로_조회할_수_있다() {
LongStream.rangeClosed(1, 5)
.forEach(i -> articleGenerator.generate("아티클 제목 " + i));

var response = articleService.getAllArticles(3);

assertThat(response.articles()).hasSize(3)
.extracting(ArticleResponse::title)
.containsExactly("아티클 제목 5", "아티클 제목 4", "아티클 제목 3");
}
}
}