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
2 changes: 1 addition & 1 deletion apps/user-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

group = 'site.icebang'
version = '0.0.1-beta-stable'
version = '0.0.1-beta-STABLE'
description = 'Ice bang - fast campus team4'

java {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.permitAll()
.requestMatchers("/v0/auth/check-session")
.authenticated()
.requestMatchers(SecurityEndpoints.SUPER_ADMIN.getMatchers())
.hasAnyRole("SUPER_ADMIN")
.requestMatchers(SecurityEndpoints.DATA_ADMIN.getMatchers())
.hasRole("SUPER_ADMIN") // hasAuthority -> hasRole
.requestMatchers(SecurityEndpoints.DATA_ENGINEER.getMatchers())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public enum SecurityEndpoints {
"/js/**",
"/images/**",
"/v0/organizations/**",
"/v0/auth/register",
"/v0/check-execution-log-insert"),

// 데이터 관리 관련 엔드포인트
Expand All @@ -27,7 +26,9 @@ public enum SecurityEndpoints {
OPS("/api/scheduler/**", "/api/monitoring/**"),

// 일반 사용자 엔드포인트
USER("/user/**", "/profile/**", "/v0/auth/check-session", "/v0/workflows/**");
USER("/user/**", "/profile/**", "/v0/auth/check-session", "/v0/workflows/**"),

SUPER_ADMIN("/v0/auth/register");

private final String[] patterns;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package site.icebang.global.handler.exception;

import java.util.stream.Collectors;

import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
Expand All @@ -19,9 +22,13 @@
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<String> handleValidation(MethodArgumentNotValidException ex) {
String detail = ex.getBindingResult().toString();
return ApiResponse.error("Validation failed: " + detail, HttpStatus.BAD_REQUEST);
public ApiResponse<Void> handleValidation(MethodArgumentNotValidException ex) {
String errorMessage =
ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));

return ApiResponse.error("입력 값 검증 실패: " + errorMessage, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(Exception.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -15,6 +16,8 @@
import org.springframework.http.*;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -29,7 +32,7 @@
class AuthApiIntegrationTest extends IntegrationTestSupport {
@Test
@DisplayName("사용자 로그인 성공")
void login_success() throws Exception {
void loginSuccess() throws Exception {
// given
Map<String, String> loginRequest = new HashMap<>();
loginRequest.put("email", "admin@icebang.site");
Expand Down Expand Up @@ -81,9 +84,243 @@ void login_success() throws Exception {
.build())));
}

@Test
@DisplayName("사용자 등록 실패 - 이메일 양식 오류")
@WithUserDetails("admin@icebang.site")
void registerFailureWhenInvalidEmail() throws Exception {
// given
Map<String, Object> registerRequest = new HashMap<>();
registerRequest.put("name", "홍길동");
registerRequest.put("email", "invalid-email"); // 잘못된 이메일 형식
registerRequest.put("orgId", 1);
registerRequest.put("deptId", 1);
registerRequest.put("positionId", 1);
registerRequest.put("roleIds", Arrays.asList(1));

// when & then
mockMvc
.perform(
post(getApiUrlForDocs("/v0/auth/register"))
.contentType(MediaType.APPLICATION_JSON)
.header("Origin", "https://admin.icebang.site")
.header("Referer", "https://admin.icebang.site/")
.content(objectMapper.writeValueAsString(registerRequest)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.status").value("BAD_REQUEST"))
.andDo(
document(
"auth-register-invalid-email",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
resource(
ResourceSnippetParameters.builder()
.tag("Authentication")
.summary("사용자 회원가입 실패 - 잘못된 이메일")
.description("잘못된 이메일 형식으로 인한 회원가입 실패")
.requestFields(
fieldWithPath("name").type(JsonFieldType.STRING).description("사용자명"),
fieldWithPath("email")
.type(JsonFieldType.STRING)
.description("잘못된 형식의 이메일 주소"),
fieldWithPath("orgId").type(JsonFieldType.NUMBER).description("조직 ID"),
fieldWithPath("deptId").type(JsonFieldType.NUMBER).description("부서 ID"),
fieldWithPath("positionId")
.type(JsonFieldType.NUMBER)
.description("직책 ID"),
fieldWithPath("roleIds")
.type(JsonFieldType.ARRAY)
.description("역할 ID 목록"))
.responseFields(
fieldWithPath("success")
.type(JsonFieldType.BOOLEAN)
.description("요청 성공 여부"),
fieldWithPath("data").type(JsonFieldType.NULL).description("에러 시 null"),
fieldWithPath("message")
.type(JsonFieldType.STRING)
.description("에러 메시지"),
fieldWithPath("status")
.type(JsonFieldType.STRING)
.description("HTTP 상태"))
.build())));
}

@Test
@DisplayName("사용자 등록 실패 - 필수 필드 누락")
@WithUserDetails("admin@icebang.site")
void registerFailureWhenMissingRequiredFields() throws Exception {
// given - 필수 필드 누락
Map<String, Object> registerRequest = new HashMap<>();
registerRequest.put("email", "test@icebang.site");
// name, orgId, deptId, positionId, roleIds 누락

// when & then
mockMvc
.perform(
post(getApiUrlForDocs("/v0/auth/register"))
.contentType(MediaType.APPLICATION_JSON)
.header("Origin", "https://admin.icebang.site")
.header("Referer", "https://admin.icebang.site/")
.content(objectMapper.writeValueAsString(registerRequest)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.status").value("BAD_REQUEST"))
.andDo(
document(
"auth-register-missing-fields",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
resource(
ResourceSnippetParameters.builder()
.tag("Authentication")
.summary("사용자 회원가입 실패 - 필수 필드 누락")
.description("필수 필드 누락으로 인한 회원가입 실패")
.requestFields(
fieldWithPath("email")
.type(JsonFieldType.STRING)
.description("사용자 이메일 주소"))
.responseFields(
fieldWithPath("success")
.type(JsonFieldType.BOOLEAN)
.description("요청 성공 여부"),
fieldWithPath("data").type(JsonFieldType.NULL).description("에러 시 null"),
fieldWithPath("message")
.type(JsonFieldType.STRING)
.description("에러 메시지"),
fieldWithPath("status")
.type(JsonFieldType.STRING)
.description("HTTP 상태"))
.build())));
}

@Test
@DisplayName("사용자 등록 실패 - Authentication이 없는 경우")
void registerFailureWhenAuthenticationMissing() throws Exception {
// given
Map<String, Object> registerRequest = new HashMap<>();
registerRequest.put("name", "홍길동");
registerRequest.put("email", "hong@icebang.site");
registerRequest.put("orgId", 1);
registerRequest.put("deptId", 1);
registerRequest.put("positionId", 1);
registerRequest.put("roleIds", Arrays.asList(1, 2));

// when & then
mockMvc
.perform(
post(getApiUrlForDocs("/v0/auth/register"))
.contentType(MediaType.APPLICATION_JSON)
.header("Origin", "https://admin.icebang.site")
.header("Referer", "https://admin.icebang.site/")
.content(objectMapper.writeValueAsString(registerRequest)))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.status").value("UNAUTHORIZED"))
.andExpect(jsonPath("$.message").value("Authentication required"))
.andExpect(jsonPath("$.data").isEmpty())
.andDo(
document(
"auth-register-unauthorized",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
resource(
ResourceSnippetParameters.builder()
.tag("Authentication")
.summary("사용자 회원가입 실패 - 인증 없음")
.description("인증 정보가 없어서 회원가입 실패")
.requestFields(
fieldWithPath("name").type(JsonFieldType.STRING).description("사용자명"),
fieldWithPath("email")
.type(JsonFieldType.STRING)
.description("사용자 이메일 주소"),
fieldWithPath("orgId").type(JsonFieldType.NUMBER).description("조직 ID"),
fieldWithPath("deptId").type(JsonFieldType.NUMBER).description("부서 ID"),
fieldWithPath("positionId")
.type(JsonFieldType.NUMBER)
.description("직책 ID"),
fieldWithPath("roleIds")
.type(JsonFieldType.ARRAY)
.description("역할 ID 목록"))
.responseFields(
fieldWithPath("success")
.type(JsonFieldType.BOOLEAN)
.description("요청 성공 여부"),
fieldWithPath("data").type(JsonFieldType.NULL).description("에러 시 null"),
fieldWithPath("message")
.type(JsonFieldType.STRING)
.description("인증 에러 메시지"),
fieldWithPath("status")
.type(JsonFieldType.STRING)
.description("HTTP 상태"))
.build())));
}

@Test
@DisplayName("사용자 등록 실패 - Permission이 없는 경우")
@WithMockUser("content.choi@icebang.site")
void registerFailureWhenNoPermissionProvided() throws Exception {
// given
Map<String, Object> registerRequest = new HashMap<>();
registerRequest.put("name", "홍길동");
registerRequest.put("email", "hong@icebang.site");
registerRequest.put("orgId", 1);
registerRequest.put("deptId", 1);
registerRequest.put("positionId", 1);
registerRequest.put("roleIds", Arrays.asList(1, 2));

// when & then
mockMvc
.perform(
post(getApiUrlForDocs("/v0/auth/register"))
.contentType(MediaType.APPLICATION_JSON)
.header("Origin", "https://admin.icebang.site")
.header("Referer", "https://admin.icebang.site/")
.content(objectMapper.writeValueAsString(registerRequest)))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.status").value("FORBIDDEN"))
.andExpect(jsonPath("$.message").value("Access denied"))
.andExpect(jsonPath("$.data").isEmpty())
.andDo(
document(
"auth-register-forbidden",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
resource(
ResourceSnippetParameters.builder()
.tag("Authentication")
.summary("사용자 회원가입 실패 - 권한 부족")
.description("적절한 권한이 없어서 회원가입 실패")
.requestFields(
fieldWithPath("name").type(JsonFieldType.STRING).description("사용자명"),
fieldWithPath("email")
.type(JsonFieldType.STRING)
.description("사용자 이메일 주소"),
fieldWithPath("orgId").type(JsonFieldType.NUMBER).description("조직 ID"),
fieldWithPath("deptId").type(JsonFieldType.NUMBER).description("부서 ID"),
fieldWithPath("positionId")
.type(JsonFieldType.NUMBER)
.description("직책 ID"),
fieldWithPath("roleIds")
.type(JsonFieldType.ARRAY)
.description("역할 ID 목록"))
.responseFields(
fieldWithPath("success")
.type(JsonFieldType.BOOLEAN)
.description("요청 성공 여부"),
fieldWithPath("data").type(JsonFieldType.NULL).description("에러 시 null"),
fieldWithPath("message")
.type(JsonFieldType.STRING)
.description("권한 에러 메시지"),
fieldWithPath("status")
.type(JsonFieldType.STRING)
.description("HTTP 상태"))
.build())));
}

@Test
@DisplayName("사용자 로그아웃 성공")
void logout_success() throws Exception {
void logoutSuccess() throws Exception {
// given - 먼저 로그인
Map<String, String> loginRequest = new HashMap<>();
loginRequest.put("email", "admin@icebang.site");
Expand Down
Loading