Skip to content

Commit

Permalink
feat: 관리자 로그인 구현
Browse files Browse the repository at this point in the history
- 아이디와 패스워드가 설정된 값과 일치하면  관리자 토큰 발급

#156
  • Loading branch information
amaran-th committed Nov 19, 2023
1 parent 8a95686 commit d44fb36
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 1 deletion.
14 changes: 14 additions & 0 deletions backend/emm-sale/src/documentTest/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,20 @@ include::{snippets}/delete-notifications/http-response.adoc[]

== 관리자 API

=== `POST` : 관리자 로그인

.HTTP request
include::{snippets}/admin-login-snippet/http-request.adoc[]

.HTTP request 설명
include::{snippets}/admin-login-snippet/request-fields.adoc[]

.HTTP response
include::{snippets}/admin-login-snippet/http-response.adoc[]

.HTTP response 설명
include::{snippets}/admin-login-snippet/response-fields.adoc[]

=== `POST` : 행사 생성

.HTTP request
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
package com.emmsale;

import static org.mockito.ArgumentMatchers.any;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.emmsale.admin.login.api.AdminLoginApi;
import com.emmsale.admin.login.application.dto.AdminLoginRequest;
import com.emmsale.admin.login.application.dto.AdminTokenResponse;
import com.emmsale.login.api.LoginApi;
import com.emmsale.login.application.dto.TokenResponse;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.payload.RequestFieldsSnippet;
import org.springframework.restdocs.payload.ResponseFieldsSnippet;
import org.springframework.restdocs.request.RequestParametersSnippet;
import org.springframework.test.web.servlet.ResultActions;

@WebMvcTest(LoginApi.class)
@WebMvcTest({LoginApi.class, AdminLoginApi.class})
class LoginApiTest extends MockMvcTestHelper {

@Test
Expand Down Expand Up @@ -62,4 +69,35 @@ void illegalLoginTest() throws Exception {
result.andExpect(status().isBadRequest())
.andDo(print());
}

@Test
@DisplayName("id와 password가 유효할 경우 200과 함께 AdminTokenResponse를 반환해 준다.")
void availableAdminLoginTest() throws Exception {
// given
final AdminLoginRequest request = new AdminLoginRequest("관리자 id", "관리자 password");
final AdminTokenResponse adminTokenResponse = new AdminTokenResponse("access_token");

BDDMockito.given(adminLoginService.createAdminToken(any())).willReturn(adminTokenResponse);

final RequestFieldsSnippet requestFields = requestFields(
fieldWithPath("id").type(JsonFieldType.STRING).description("관리자 로그인 id"),
fieldWithPath("password").type(JsonFieldType.STRING).description("관리자 로그인 password")
);

final ResponseFieldsSnippet responseFields = responseFields(
fieldWithPath("accessToken").type(JsonFieldType.STRING).description("관리자 Access Token 값")
);

// when
final ResultActions result = mockMvc.perform(
post("/admin/login")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(request))
);

// then
result.andExpect(status().isOk())
.andDo(print())
.andDo(document("admin-login-snippet", requestFields, responseFields));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.emmsale.activity.application.ActivityQueryService;
import com.emmsale.admin.activity.application.ActivityCommandService;
import com.emmsale.admin.event.application.EventCommandService;
import com.emmsale.admin.login.application.AdminLoginService;
import com.emmsale.admin.report.application.ReportQueryService;
import com.emmsale.admin.tag.application.TagCommandService;
import com.emmsale.block.application.BlockCommandService;
Expand Down Expand Up @@ -81,6 +82,8 @@ abstract class MockMvcTestHelper {
@MockBean
protected LoginService loginService;
@MockBean
protected AdminLoginService adminLoginService;
@MockBean
protected EventQueryService eventQueryService;
@MockBean
protected EventCommandService eventCommandService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.emmsale.admin.login.api;

import com.emmsale.admin.login.application.AdminLoginService;
import com.emmsale.admin.login.application.dto.AdminLoginRequest;
import com.emmsale.admin.login.application.dto.AdminTokenResponse;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/admin/login")
public class AdminLoginApi {

private final AdminLoginService adminLoginService;

public AdminLoginApi(final AdminLoginService adminLoginService) {
this.adminLoginService = adminLoginService;
}

@PostMapping
public ResponseEntity<AdminTokenResponse> login(@RequestBody final AdminLoginRequest request) {
return ResponseEntity.ok().body(adminLoginService.createAdminToken(request));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.emmsale.admin.login.application;

import com.emmsale.admin.login.application.dto.AdminLoginRequest;
import com.emmsale.admin.login.application.dto.AdminTokenResponse;
import com.emmsale.login.exception.LoginException;
import com.emmsale.login.exception.LoginExceptionType;
import com.emmsale.login.utils.JwtTokenProvider;
import javax.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
@Transactional
@RequiredArgsConstructor
public class AdminLoginService {

private final JwtTokenProvider tokenProvider;
@Value("${data.admin_login.id}")
private String adminId;
@Value("${data.admin_login.password}")
private String adminPassword;
@Value("${data.admin_login.member_id}")
private Long adminMemberId;

public AdminTokenResponse createAdminToken(final AdminLoginRequest request) {
validateNotNullRequest(request);
validateAdminLoginInformation(request);
final String accessToken = tokenProvider.createToken(String.valueOf(adminMemberId));

return new AdminTokenResponse(accessToken);
}

private void validateNotNullRequest(final AdminLoginRequest request) {
if (request == null) {
throw new LoginException(LoginExceptionType.NOT_FOUND_ADMIN_LOGIN_INFORMATION);
}
}

private void validateAdminLoginInformation(final AdminLoginRequest request) {
if (!(request.getId().equals(adminId) && request.getPassword().equals(adminPassword))) {
throw new LoginException(LoginExceptionType.INVALID_ADMIN_LOGIN_INFORMATION);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.emmsale.admin.login.application.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class AdminLoginRequest {

final String id;
final String password;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.emmsale.admin.login.application.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public class AdminTokenResponse {

private final String accessToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ public enum LoginExceptionType implements BaseExceptionType {
INVALID_ACCESS_TOKEN_TYPE(
HttpStatus.BAD_REQUEST,
"Access Token Type이 올바르지 않습니다."
),
NOT_FOUND_ADMIN_LOGIN_INFORMATION(
HttpStatus.BAD_REQUEST,
"관리자 로그인 정보를 찾을 수 없습니다."
),
INVALID_ADMIN_LOGIN_INFORMATION(
HttpStatus.BAD_REQUEST,
"관리자 아이디 또는 비밀번호가 올바르지 않습니다."
),
INVALID_ADMIN_ACCESS_TOKEN(
HttpStatus.BAD_REQUEST,
"관리자 토큰이 유효하지 않습니다."
);

private final HttpStatus httpStatus;
Expand Down
4 changes: 4 additions & 0 deletions backend/emm-sale/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ firebase:

data:
admin-url: http://localhost:3000
admin_login:
id: admin
password: 1234
member_id: 3

cloud:
aws:
Expand Down
6 changes: 6 additions & 0 deletions backend/emm-sale/src/main/resources/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ insert into member(id, name, image_url, open_profile_url, description, github_id
values (2, 'member2', 'https://imageurl.com', 'https://openprofileurl.com', '반갑습니다.', 2,
'amaran-th22', CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP());

insert into member(id, name, image_url, open_profile_url, description, github_id, github_username,
created_at,
updated_at)
values (3, 'admin', 'https://imageurl.com', 'https://openprofileurl.com', '반갑습니다.', 3,
'amaran-th22', CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP());

insert into member_activity(id, activity_id, member_id, created_at, updated_at)
values (1, 1, 1, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.emmsale.admin.login.application;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.BDDMockito.given;

import com.emmsale.admin.login.application.dto.AdminLoginRequest;
import com.emmsale.admin.login.application.dto.AdminTokenResponse;
import com.emmsale.helper.ServiceIntegrationTestHelper;
import com.emmsale.login.exception.LoginException;
import com.emmsale.login.exception.LoginExceptionType;
import com.emmsale.login.utils.JwtTokenProvider;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

@SpringBootTest
class AdminLoginServiceTest extends ServiceIntegrationTestHelper {

@Autowired
private AdminLoginService adminLoginService;
@MockBean
private JwtTokenProvider tokenProvider;
@Value("${data.admin_login.id}")
private String adminId;
@Value("${data.admin_login.password}")
private String adminPassword;
@Value("${data.admin_login.member_id}")
private Long adminMemberId;

@Test
@DisplayName("관리자 아이디, 패스워드로 사용자를 조회하여 토큰을 생성한다.")
void createAdminToken_success() {
// given
final Long memberId = adminMemberId;
final AdminLoginRequest request = new AdminLoginRequest(adminId, adminPassword);
final String expectAccessToken = "expect_access_token";
given(tokenProvider.createToken(String.valueOf(memberId))).willReturn(expectAccessToken);

// when
final AdminTokenResponse actualToken = adminLoginService.createAdminToken(request);

// then
assertEquals(expectAccessToken, actualToken.getAccessToken());
}

@Test
@DisplayName("관리자 정보 요청이 null이면 예외를 반환한다.")
void createAdminToken_fail_not_found_request() {
// given
final LoginExceptionType expectExceptionType = LoginExceptionType.NOT_FOUND_ADMIN_LOGIN_INFORMATION;

// when
final ThrowingCallable actual = () -> adminLoginService.createAdminToken(null);

// then
Assertions.assertThatThrownBy(actual)
.isInstanceOf(LoginException.class)
.hasMessage(expectExceptionType.errorMessage());
}

@Test
@DisplayName("관리자 아이디가 올바르지 않으면 예외를 반환한다.")
void createAdminToken_fail_invalid_id() {
// given
final AdminLoginRequest request = new AdminLoginRequest("invalid", adminPassword);
final LoginExceptionType expectExceptionType = LoginExceptionType.INVALID_ADMIN_LOGIN_INFORMATION;

// when
final ThrowingCallable actual = () -> adminLoginService.createAdminToken(
request);

// then
Assertions.assertThatThrownBy(actual)
.isInstanceOf(LoginException.class)
.hasMessage(expectExceptionType.errorMessage());
}

@Test
@DisplayName("관리자 패스워드가 올바르지 않으면 예외를 반환한다.")
void createAdminToken_fail_invalid_password() {
// given
final AdminLoginRequest request = new AdminLoginRequest(adminId, "invalid");
final LoginExceptionType expectExceptionType = LoginExceptionType.INVALID_ADMIN_LOGIN_INFORMATION;

// when
final ThrowingCallable actual = () -> adminLoginService.createAdminToken(
request);

// then
Assertions.assertThatThrownBy(actual)
.isInstanceOf(LoginException.class)
.hasMessage(expectExceptionType.errorMessage());
}

}

0 comments on commit d44fb36

Please sign in to comment.