Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

테스트 코드 추가 및 리팩토링 #28

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
94bf3e4
[WIP] FollowServiceTest 추가
Feb 7, 2025
cfb488e
FollowController Validate 어노테이션 추가
Feb 7, 2025
0732814
상수->변수로 이동
Feb 10, 2025
47a9a0f
FollowGroup Transactional 적용
Feb 10, 2025
c4ca026
[AUTH] token 변조 시 Exception 처리 추가
Feb 10, 2025
3f4a463
사용하지 않는 변수 정리
Feb 10, 2025
4551112
[UTIL] ParseUtil 최적화
Feb 10, 2025
5173c13
FollowPair 삭제 및 FollowDto 로 통합
Feb 10, 2025
27fa715
메인 폴더와 동일 구조로 테스트 dto 폴더 이동
Feb 11, 2025
bc3b0cb
[TEST] followServiceTest 작성
Feb 11, 2025
bf263b7
불필요한 ResponseEntity 삭제
Feb 11, 2025
f80aec2
[BUG] GetFollower 팔로워 불러오지 않는 버그 수정
Feb 11, 2025
b824978
잘못된 query 주석 수정
Feb 11, 2025
fba540b
[TEST] LoginDto test 작성
Feb 11, 2025
f0a478d
[TEST] SignupRequestDto 테스트 작성
Feb 11, 2025
2208aed
[Test] UserServiceTest 작성
Feb 11, 2025
a434685
[TEST] FollowService Test 코드 추가
Feb 11, 2025
21df289
[TEST] FollowGroupService 테스트 추가
Feb 11, 2025
9a18155
[DOMAIN] @Valid 적용
Feb 11, 2025
1320403
Valid 에러 발생 시 클라이언트로 파싱하여 반환하도록 추가
Feb 11, 2025
c5da0fd
[TEST] 도메인 누락된 테스트 추가
Feb 11, 2025
a38e1ce
[domain] 팔로워/팔로잉 불러오기 기능 및 테스트 코드 추가
Feb 11, 2025
2de0fb9
테스트코드 보완 적용
Feb 24, 2025
048223b
불필요한 입력값 없도록 오버로딩 함수 추가
Feb 24, 2025
1deb6a7
Update README.md
gru3530 Feb 24, 2025
cd409bf
회원가입 시 글자수 제한 추가
Feb 25, 2025
54b1608
Merge branch 'main' into feature/enterTestCode
gru3530 Feb 25, 2025
1eeb1c4
Github Action 적용
Feb 25, 2025
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
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI Pipeline

on:
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: 코드 체크아웃
uses: actions/checkout@v3

- name: JDK 설정
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'

- name: Gradle 캐시 설정
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle

- name: Gradle Wrapper 실행 권한 부여
run: chmod +x ./gradlew

- name: 종속성 설치
run: ./gradlew build -x test

- name: 테스트 실행
run: ./gradlew test
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# 🌟 Stargram - Instagram Clone Project

> **Stargram은 인스타그램의 핵심 기능을 구현한 SNS 플랫폼입니다.**
> **사용자는 사진, 동영상과 텍스트를 공유하며, 댓글과 좋아요를 통해 소통할 수 있습니다.**


> **사용자는 사진/동영상과 텍스트를 공유하며, 댓글과 좋아요를 통해 소통할 수 있습니다.**

---

Expand Down Expand Up @@ -69,7 +67,7 @@ Stargram은 **Instagram의 핵심 기능을 모방한 SNS 플랫폼**으로, 사
Stargram
│── src
│ ├── main
│ │ ├── java/com/stargram
│ │ ├── java.com.flab.stargram
│ │ │ ├── config # 프로젝트 설정 (예외 처리, 보안, 키 관리 등)
│ │ │ ├── controller # REST API 컨트롤러
│ │ │ ├── domain # 비즈니스 로직
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.mysql:mysql-connector-j'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
implementation 'org.springframework.boot:spring-boot-starter-validation'

compileOnly 'org.projectlombok:lombok'

Expand Down
6 changes: 4 additions & 2 deletions src/main/java/com/flab/stargram/config/KeyPairConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@

@Configuration
public class KeyPairConfig {
private static final String KEY_ALGORITHM = "RSA";
private static final int RSA_KEY_SIZE = 2048;

@Bean
public KeyPair keyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGenerator.initialize(RSA_KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package com.flab.stargram.config.exception;

import java.util.Map;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.flab.stargram.entity.common.ApiResponseEnum;
import com.flab.stargram.entity.common.ApiResult;

import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

@RestControllerAdvice
public class GlobalApiExceptionHandler {
Expand All @@ -19,4 +29,38 @@ public ResponseEntity<ApiResult> handleBaseApiException(BaseApiException e) {
public ResponseEntity<ApiResult> handleGeneralException(Exception e) {
return ApiResult.error(ApiResponseEnum.FAILURE, e.getMessage());
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ApiResult> handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
Throwable cause = ex.getCause();

if (cause instanceof InvalidFormatException invalidFormatException) {
String fieldName = invalidFormatException.getPath().get(0).getFieldName();
String invalidValue = invalidFormatException.getValue().toString();
return ApiResult.error(
ApiResponseEnum.INVALID_INPUT,
ApiResponseEnum.INVALID_INPUT.getMessage(),
Map.of(fieldName, invalidValue)
);
}

return ApiResult.error(ApiResponseEnum.INVALID_INPUT, ApiResponseEnum.INVALID_INPUT.getMessage());
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResult> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
Map<String, String> errors = ex.getBindingResult().getFieldErrors().stream()
.collect(
Collectors.toMap(
FieldError::getField,
fieldError -> Optional.ofNullable(fieldError.getDefaultMessage()).orElse("Invalid value")
)
);

return ApiResult.error(
ApiResponseEnum.INVALID_INPUT,
ApiResponseEnum.INVALID_INPUT.getMessage(),
errors
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class CommentController {
private final CommentService commentService;

@PostMapping("/{postIdInput}/comment")
public ResponseEntity<ApiResult> createComment(@RequestBody CommentRequestDto dto, @PathVariable String postIdInput) {
public ApiResult createComment(@RequestBody CommentRequestDto dto, @PathVariable String postIdInput) {
dto.validateEmpty().ifInvalidThrow();

Long postId = ParseUtil.parseToLong(postIdInput);
Expand Down
35 changes: 14 additions & 21 deletions src/main/java/com/flab/stargram/controller/FollowController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.flab.stargram.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -10,40 +10,33 @@

import com.flab.stargram.entity.common.ApiResult;
import com.flab.stargram.entity.common.ParseUtil;
import com.flab.stargram.entity.dto.FollowPair;
import com.flab.stargram.entity.dto.FollowRequestDto;
import com.flab.stargram.entity.dto.FollowDto;
import com.flab.stargram.service.FollowService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/users/{inputUserId}")
@RequestMapping("/users")
@RequiredArgsConstructor
@Validated
public class FollowController {
private final FollowService followService;

@PostMapping("/follow")
public ResponseEntity<ApiResult> followUser(@RequestBody FollowRequestDto dto, @PathVariable String inputUserId) {
dto.validateEmpty().ifInvalidThrow();
FollowPair followPair = FollowPair.createFollowPairOf(inputUserId, dto);

followService.followUser(followPair);
return ApiResult.success(null);
public ApiResult followUser(@Valid @RequestBody FollowDto dto) {
followService.followUser(dto);
return ApiResult.success();
}

@PostMapping("/unfollow")
public ResponseEntity<ApiResult> unfollowUser(@RequestBody FollowRequestDto dto, @PathVariable String inputUserId) {
dto.validateEmpty().ifInvalidThrow();
FollowPair followPair = FollowPair.createFollowPairOf(inputUserId, dto);

followService.unfollowUser(followPair);
return ApiResult.success(null);
public ApiResult unfollowUser(@Valid @RequestBody FollowDto dto) {
followService.unfollowUser(dto);
return ApiResult.success();
}

@GetMapping("/followers")
public ResponseEntity<ApiResult> followUsers(@PathVariable String inputUserId) {
Long userId = ParseUtil.parseToLong(inputUserId);

return ApiResult.success(followService.getFollowers(userId));
@GetMapping("/{inputUserId}/followers")
public ApiResult followUsers(@PathVariable String inputUserId) {
return ApiResult.success(followService.getFollowerIds(ParseUtil.parseToLong(inputUserId)));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.flab.stargram.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -10,20 +10,19 @@
import com.flab.stargram.entity.dto.PostRequestDto;
import com.flab.stargram.entity.common.ApiResult;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/posts")
@RequiredArgsConstructor

@Validated
public class PostController {

private final PostService postService;

@PostMapping
public ResponseEntity<ApiResult> createPost(@RequestBody PostRequestDto dto) {
dto.validateEmpty().ifInvalidThrow();

public ApiResult createPost(@Valid @RequestBody PostRequestDto dto) {
return ApiResult.success(postService.postFeed(dto));
}
}
16 changes: 7 additions & 9 deletions src/main/java/com/flab/stargram/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
package com.flab.stargram.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.flab.stargram.domain.auth.service.AuthCookieService;
import com.flab.stargram.domain.auth.service.AuthService;
import com.flab.stargram.entity.common.ApiResult;
import com.flab.stargram.entity.dto.LoginDto;
import com.flab.stargram.entity.dto.SignUpRequestDto;
import com.flab.stargram.entity.common.ApiResult;
import com.flab.stargram.entity.model.User;
import com.flab.stargram.service.UserService;

import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@Validated
@RequiredArgsConstructor
public class UserController {

Expand All @@ -25,16 +27,12 @@ public class UserController {
private final AuthCookieService authCookieService;

@PostMapping("/signup")
public ResponseEntity<ApiResult> signUp(@RequestBody SignUpRequestDto dto, HttpServletResponse response) {
dto.validateEmpty().ifInvalidThrow();

public ApiResult signUp(@Valid @RequestBody SignUpRequestDto dto) {
return ApiResult.success(userService.signUp(dto));
}

@PostMapping("/login")
public ResponseEntity<ApiResult> login(@RequestBody LoginDto dto, HttpServletResponse response) {
dto.validateEmpty().ifInvalidThrow();

public ApiResult login(@Valid @RequestBody LoginDto dto, HttpServletResponse response) {
User user = userService.login(dto);
String token = authService.generateToken(user);
authCookieService.addAuthCookie(response, token);
Expand All @@ -43,7 +41,7 @@ public ResponseEntity<ApiResult> login(@RequestBody LoginDto dto, HttpServletRes
}

@PostMapping("/users/logout")
public ResponseEntity<ApiResult> logout(HttpServletResponse response) {
public ApiResult logout(HttpServletResponse response) {
authCookieService.removeAuthCookie(response);
return ApiResult.success(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.SignatureException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -28,7 +30,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons

try {
request.setAttribute("userId", authService.validateToken(token).getSubject());
} catch (ExpiredJwtException e) {
} catch (SignatureException | ExpiredJwtException e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/com/flab/stargram/entity/common/ApiResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@ public class ApiResult {
private final String message;
private final Object data;

public static ResponseEntity<ApiResult> success(Object data) {
ApiResult result = new ApiResult(ApiResponseEnum.SUCCESS, null, data);
return ResponseEntity.ok(result);
public static ApiResult success(Object data) {
return new ApiResult(ApiResponseEnum.SUCCESS, null, data);
}

public static ApiResult success() {
return new ApiResult(ApiResponseEnum.SUCCESS, null, null);
}

public static ResponseEntity<ApiResult> error(ApiResponseEnum apiResponseEnum, String message) {
ApiResult result = new ApiResult(apiResponseEnum, message, null);
return ResponseEntity.status(apiResponseEnum.getHttpStatus()).body(result);
}

public static ResponseEntity<ApiResult> error(ApiResponseEnum apiResponseEnum, String message, Object detail) {
ApiResult result = new ApiResult(apiResponseEnum, message, detail);
return ResponseEntity.status(apiResponseEnum.getHttpStatus()).body(result);
}
}
8 changes: 6 additions & 2 deletions src/main/java/com/flab/stargram/entity/common/ParseUtil.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.flab.stargram.entity.common;

import java.util.regex.Pattern;

import com.flab.stargram.config.exception.InvalidInputException;

public class ParseUtil {
private static final String parseLongRegex = "^[0-9]+$";
private static final Pattern parseLongPattern = Pattern.compile("^[0-9]+$");

public static Long parseToLong(String value) {
if (value.matches(parseLongRegex)) {
assert value != null : "Input value is null";

if (parseLongPattern.matcher(value).matches()) {
return Long.parseLong(value);
} else {
throw new InvalidInputException(ApiResponseEnum.INVALID_INPUT);
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/flab/stargram/entity/dto/FollowDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.flab.stargram.entity.dto;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class FollowDto {
@NotNull(message = "following ID는 필수 입니다.")
@Positive(message = "following ID는 양수로 입력해야 합니다.")
private Long followingId;

@NotNull(message = "follower ID는 필수 입니다.")
@Positive(message = "follower ID는 양수로 입력해야 합니다.")
private Long followerId;

public boolean isSameUser(){
return followingId.equals(followerId);
}
}
Loading
Loading