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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ dependencies {
// Sentry
implementation 'io.sentry:sentry-spring-boot-starter:4.3.0'

// Swagger (OpenAPI)
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'

}

tasks.named('test') {
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/org/runnect/server/ServerApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class ServerApplication {
static {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.runnect.server.config.jpa;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.runnect.server.config.swagger;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

private static final String ACCESS_TOKEN_HEADER = "accessToken";
private static final String REFRESH_TOKEN_HEADER = "refreshToken";

@Bean
public OpenAPI openAPI() {
SecurityScheme accessTokenScheme = new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name(ACCESS_TOKEN_HEADER);

SecurityScheme refreshTokenScheme = new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name(REFRESH_TOKEN_HEADER);

SecurityRequirement securityRequirement = new SecurityRequirement()
.addList(ACCESS_TOKEN_HEADER)
.addList(REFRESH_TOKEN_HEADER);

return new OpenAPI()
.info(new Info()
.title("Runnect API")
.description("Runnect 서버 API 문서")
.version("v1"))
.components(new Components()
.addSecuritySchemes(ACCESS_TOKEN_HEADER, accessTokenScheme)
.addSecuritySchemes(REFRESH_TOKEN_HEADER, refreshTokenScheme))
.addSecurityItem(securityRequirement);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package org.runnect.server.auth.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.runnect.server.auth.dto.response.GetNewTokenResponseDto;
import org.runnect.server.auth.dto.response.SignInResponseDto;
import org.runnect.server.auth.dto.response.SignUpResponseDto;
import org.runnect.server.auth.service.AuthService;
import org.runnect.server.common.constant.ErrorStatus;
import org.runnect.server.config.jwt.JwtService;
import org.runnect.server.config.slack.SlackApi;
import org.runnect.server.user.exception.authException.InvalidRefreshTokenException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(AuthController.class)
class AuthControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private AuthService authService;
@MockBean
private JwtService jwtService;
@MockBean
private SlackApi slackApi;

@Nested
@DisplayName("POST /api/auth")
class SignIn {

@Test
@DisplayName("기존 유저면 200과 LOGIN 응답을 반환한다")
void 기존_유저_로그인() throws Exception {
when(authService.signIn(BDDMockito.any())).thenReturn(
SignInResponseDto.of("KAKAO", "user@runnect.io", "access-token", "refresh-token"));

mockMvc.perform(post("/api/auth")
.contentType("application/json")
.content("{\"token\":\"kakao-token\",\"provider\":\"kakao\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.email").value("user@runnect.io"));
}

@Test
@DisplayName("신규 유저면 200과 SIGNUP 응답을 반환한다")
void 신규_유저_회원가입() throws Exception {
when(authService.signIn(BDDMockito.any())).thenReturn(
SignUpResponseDto.of("KAKAO", "new@runnect.io", "러너1", "access-token", "refresh-token"));

mockMvc.perform(post("/api/auth")
.contentType("application/json")
.content("{\"token\":\"kakao-token\",\"provider\":\"kakao\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.nickname").value("러너1"));
}

@Test
@DisplayName("token이 없으면 400을 반환한다")
void 토큰_없음() throws Exception {
mockMvc.perform(post("/api/auth")
.contentType("application/json")
.content("{\"provider\":\"kakao\"}"))
.andExpect(status().isBadRequest());

BDDMockito.verifyNoInteractions(authService);
}

@Test
@DisplayName("provider가 없으면 400을 반환한다")
void 프로바이더_없음() throws Exception {
mockMvc.perform(post("/api/auth")
.contentType("application/json")
.content("{\"token\":\"kakao-token\"}"))
.andExpect(status().isBadRequest());

BDDMockito.verifyNoInteractions(authService);
}
}

@Nested
@DisplayName("GET /api/auth/getNewToken")
class GetNewToken {

@Test
@DisplayName("정상 요청이면 200과 재발급된 토큰을 반환한다")
void 정상_재발급() throws Exception {
when(authService.getNewToken("old-access", "valid-refresh"))
.thenReturn(GetNewTokenResponseDto.of("new-access", "new-refresh"));

mockMvc.perform(get("/api/auth/getNewToken")
.header("accessToken", "old-access")
.header("refreshToken", "valid-refresh"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.accessToken").value("new-access"));
}

@Test
@DisplayName("refreshToken 헤더가 없으면 400을 반환한다")
void 리프레시토큰_헤더_없음() throws Exception {
mockMvc.perform(get("/api/auth/getNewToken").header("accessToken", "old-access"))
.andExpect(status().isBadRequest());

BDDMockito.verifyNoInteractions(authService);
}

@Test
@DisplayName("refreshToken이 빈 문자열이면 400을 반환한다")
void 리프레시토큰_빈문자열() throws Exception {
mockMvc.perform(get("/api/auth/getNewToken")
.header("accessToken", "old-access")
.header("refreshToken", ""))
.andExpect(status().isBadRequest());

BDDMockito.verifyNoInteractions(authService);
}

@Test
@DisplayName("저장된 리프레시 토큰과 일치하지 않으면 401을 반환한다")
void 리프레시토큰_불일치() throws Exception {
when(authService.getNewToken("old-access", "wrong-refresh"))
.thenThrow(new InvalidRefreshTokenException(
ErrorStatus.INVALID_REFRESH_TOKEN_EXCEPTION,
ErrorStatus.INVALID_REFRESH_TOKEN_EXCEPTION.getMessage()));

mockMvc.perform(get("/api/auth/getNewToken")
.header("accessToken", "old-access")
.header("refreshToken", "wrong-refresh"))
.andExpect(status().isUnauthorized());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.runnect.server.banner.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.runnect.server.banner.dto.response.BannerResponse;
import org.runnect.server.banner.dto.response.GetBannerResponseDto;
import org.runnect.server.banner.service.BannerService;
import org.runnect.server.config.jwt.JwtService;
import org.runnect.server.config.slack.SlackApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(BannerController.class)
class BannerControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private BannerService bannerService;
@MockBean
private JwtService jwtService;
@MockBean
private SlackApi slackApi;

@Test
@DisplayName("GET /api/banner - 정상 요청이면 200과 배너 목록을 반환한다")
void 정상_조회() throws Exception {
List<BannerResponse> banners = Collections.singletonList(BannerResponse.of(0, "image.png", "https://a.com"));
when(bannerService.getBanners()).thenReturn(GetBannerResponseDto.of(banners));

mockMvc.perform(get("/api/banner"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.banners[0].index").value(0))
.andExpect(jsonPath("$.data.banners[0].imageUrl").value("image.png"));
}

@Test
@DisplayName("GET /api/banner - 배너가 없으면 200과 빈 목록을 반환한다")
void 배너_없음() throws Exception {
when(bannerService.getBanners()).thenReturn(GetBannerResponseDto.of(Collections.emptyList()));

mockMvc.perform(get("/api/banner"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.banners").isEmpty());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.runnect.server.common.controller;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.runnect.server.config.jwt.JwtService;
import org.runnect.server.config.slack.SlackApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(ServerProfileController.class)
@ActiveProfiles("test")
class ServerProfileControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private JwtService jwtService;
@MockBean
private SlackApi slackApi;

@Test
@DisplayName("GET /profile - 활성 프로파일명을 그대로 반환한다")
void 활성_프로파일_반환() throws Exception {
mockMvc.perform(get("/profile"))
.andExpect(status().isOk())
.andExpect(content().string("test"));
}
}
Loading
Loading