Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ bin/

### Mac OS ###
.DS_Store

### .env ###
.env
56 changes: 56 additions & 0 deletions Docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
services:
db:
image: "mysql:8.3.0"
container_name: load_test_mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: root#1234
MYSQL_DATABASE: load_test_db
TZ: UTC

ports:
- "3000:3306"
deploy:
resources:
limits:
cpus: "0.5"
memory: "512MB"

redis:
image: "docker.io/bitnami/redis:7.2"
container_name: load_test_redis
restart: always
environment:
- ALLOW_EMPTY_PASSWORD=yes
- REDIS_AOF_ENABLED=yes
- REDIS_RDB_ENABLED=no
ports:
- "6299:6379"
deploy:
resources:
limits:
cpus: "0.5"
memory: "512MB"

teumteum-server:
build:
context: .
dockerfile: Dockerfile
restart: always
environment:
DB_URL: jdbc:mysql://db:3306/load_test_db
DB_USERNAME: root
DB_PASSWORD: root#1234
REDIS_HOST: redis
REDIS_PORT: 6379
JWT_SECERT_KEY: ${JWT_ACCESS_KEY}

depends_on:
- db
- redis
ports:
- "8080:8080"

networks:
teumteum_local:
driver: bridge
2 changes: 1 addition & 1 deletion src/gatling/java/protocol/Protocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class Protocol {

private static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0";

public static final HttpProtocolBuilder httpProtocol = HttpDsl.http.baseUrl("https://api.teum.org")
public static final HttpProtocolBuilder httpProtocol = HttpDsl.http.baseUrl("http://localhost:8080")
.header("Content-Type", "application/json")
.userAgentHeader(USER_AGENT);

Expand Down
3 changes: 1 addition & 2 deletions src/gatling/java/simulation/SimulationSample.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ public class SimulationSample extends Simulation {
private final ScenarioBuilder scn = scenario(this.getClass().getSimpleName())
.exec(http("get user")
.get("/users/1")
.check(status().is(200))
);
.check(status().is(200)));

{
setUp(
Expand Down
66 changes: 66 additions & 0 deletions src/gatling/java/simulation/TeumTeumApiSimulation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package simulation;

import static io.gatling.javaapi.core.CoreDsl.StringBody;
import static io.gatling.javaapi.core.CoreDsl.constantUsersPerSec;
import static io.gatling.javaapi.core.CoreDsl.jsonPath;
import static io.gatling.javaapi.core.CoreDsl.rampUsersPerSec;
import static io.gatling.javaapi.core.CoreDsl.scenario;
import static io.gatling.javaapi.http.HttpDsl.http;
import static io.gatling.javaapi.http.HttpDsl.status;
import static protocol.Protocol.httpProtocol;

import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import net.datafaker.Faker;

public class TeumTeumApiSimulation extends Simulation {

private static final Faker faker = new Faker();
private final ScenarioBuilder teumteumScn = scenario("TeumTeum 찬해지기 API 부하 테스트를 진행한다.")
.exec(session ->
session
.set("id", java.util.UUID.randomUUID().toString())
.set("name", faker.name().fullName()))

.exec(http("User 카드 등록 API 요청")
.post("/users")
.body(StringBody(
"{"
+ "\"id\": \"${id}\", "
+ "\"terms\": {\"service\": true, \"privatePolicy\": true}, "
+ "\"name\": \"${name}\", "
+ "\"birth\": \"20000402\", "
+ "\"characterId\": 2, "
+ "\"authenticated\": \"네이버\", "
+ "\"activityArea\": \"경기 시흥\", "
+ "\"mbti\": \"ENFP\", "
+ "\"status\": \"직장인\", "
+ "\"job\": {\"name\" : \"카카오 뱅크\", \"class\" : \"개발\", \"detailClass\" : \"BE 개발자\"}, "
+ "\"interests\": [\"네트워킹\", \"IT\", \"모여서 각자 일하기\"], "
+ "\"goal\": \"회사에서 좋은 사람들과 멋진 개발하기\""
+ "}"
))
.check(status().is(201))
.check(jsonPath("$.id").saveAs("userId"))
.check(jsonPath("$.accessToken").saveAs("accessToken"))
.check(jsonPath("$.refreshToken").saveAs("refreshToken")))

.exec(http("TeumTeum 친해지기 API 요청")
.post("/teum-teum/around")
.header("Authorization", "Bearer ${accessToken}")
.body(StringBody("{\"id\": ${userId}, \"latitude\": 37.5665, \"longitude\": 126.9780,"
+ " \"name\": \"test_name\", \"jobDetailClass\": \"test_job\", \"characterId\": 1}"))
.check(status().is(200))
);

{
setUp(
teumteumScn.injectOpen(
constantUsersPerSec(10).during(Duration.of(30, ChronoUnit.SECONDS)),
rampUsersPerSec(10).to(50).during(Duration.of(30, ChronoUnit.SECONDS))
).protocols(httpProtocol)
);
}
}
54 changes: 54 additions & 0 deletions src/gatling/java/simulation/UserApiSimulation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package simulation;

import static io.gatling.javaapi.core.CoreDsl.StringBody;
import static io.gatling.javaapi.core.CoreDsl.atOnceUsers;
import static io.gatling.javaapi.core.CoreDsl.jsonPath;
import static io.gatling.javaapi.core.CoreDsl.scenario;
import static io.gatling.javaapi.http.HttpDsl.http;
import static io.gatling.javaapi.http.HttpDsl.status;
import static protocol.Protocol.httpProtocol;

import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;

public class UserApiSimulation extends Simulation {

private final ScenarioBuilder UserScn = scenario("User API 부하 테스트를 진행한다.")
.exec(http("User 카드 등록 API 요청")
.post("/users")
.body(StringBody(
"{\"id\": \"test_id\", "
+ "\"terms\": {\"service\": true, \"privatePolicy\": true}, "
+ "\"name\": \"홍길동\", "
+ "\"birth\": \"1990-01-01\", "
+ "\"characterId\": 1, "
+ "\"authenticated\": \"SNS\", "
+ "\"activityArea\": \"서울\", "
+ "\"mbti\": \"INTJ\", "
+ "\"status\": \"ACTIVE\", "
+ "\"job\": {\"name\": \"개발자\", \"class\": \"IT\", \"detailClass\": \"백엔드\"}, "
+ "\"interests\": [\"코딩\", \"독서\", \"운동\"], "
+ "\"goal\": \"성장하기 위해 노력하는 개발자가 되기\"}"
))
.check(status().is(201))
.check(jsonPath("$.id").saveAs("userId"))
.check(jsonPath("$.accessToken").saveAs("accessToken"))
.check(jsonPath("$.refreshToken").saveAs("refreshToken"))

).exec(http("User 정보 조회 API 요청")
.get("/users/${userId}")
.header("Authorization", "Bearer ${accessToken}")
.check(status().is(200))

).exec(http("User 리뷰 조회 API 요청")
.get("/users/${userId}")
.header("Authorization", "Bearer ${accessToken}")
.check(status().is(200)));


{
setUp(
UserScn.injectOpen(
atOnceUsers(10)).protocols(httpProtocol));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE"));
config.addExposedHeader("Authorization");
config.addExposedHeader("Authorization-refresh");
config.setAllowCredentials(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

public record ReviewRegisterRequest(
@Valid
@Size(min = 2, max = 5)
@Size(min = 1, max = 5)
List<UserReviewRegisterRequest> reviews
) {

Expand Down
28 changes: 21 additions & 7 deletions src/main/java/net/teumteum/user/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package net.teumteum.user.service;

import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import net.teumteum.core.security.Authenticated;
import net.teumteum.core.security.service.JwtService;
import net.teumteum.core.security.service.RedisService;
import net.teumteum.core.security.service.SecurityService;
import net.teumteum.meeting.domain.Meeting;
import net.teumteum.meeting.domain.MeetingConnector;
import net.teumteum.user.domain.BalanceGameType;
import net.teumteum.user.domain.InterestQuestion;
Expand Down Expand Up @@ -109,7 +111,10 @@ public void logout(Long userId) {

@Transactional
public void registerReview(Long meetingId, Long currentUserId, ReviewRegisterRequest request) {
checkMeetingExistence(meetingId);
var meeting = getMeeting(meetingId);

checkMeetingIsClosed(meeting);
checkUserParticipationInMeeting(meeting, currentUserId);
checkUserNotRegisterSelfReview(request, currentUserId);

request.reviews()
Expand Down Expand Up @@ -157,12 +162,9 @@ private void checkUserExistence(Authenticated authenticated, String oauthId) {
);
}

private void checkMeetingExistence(Long meetingId) {
Assert.isTrue(meetingConnector.existById(meetingId),
() -> {
throw new IllegalArgumentException("meetingId에 해당하는 meeting을 찾을 수 없습니다. \"" + meetingId + "\"");
}
);
private Meeting getMeeting(Long meetingId) {
return meetingConnector.findById(meetingId)
.orElseThrow(() -> new IllegalArgumentException("meetingId에 해당하는 모임을 찾을 수 없습니다. \"" + meetingId + "\""));
}

private void checkUserNotRegisterSelfReview(ReviewRegisterRequest request, Long currentUserId) {
Expand All @@ -172,4 +174,16 @@ private void checkUserNotRegisterSelfReview(ReviewRegisterRequest request, Long
}
);
}

private void checkUserParticipationInMeeting(Meeting meeting, Long userId) {
if (!meeting.getParticipantUserIds().contains(userId)) {
throw new IllegalArgumentException("모임에 참여하지 않은 회원입니다.");
}
}

private void checkMeetingIsClosed(Meeting meeting) {
if (!LocalDateTime.now().isAfter(meeting.getPromiseDateTime())) {
throw new IllegalArgumentException("해당 모임은 아직 종료되지 않았습니다.");
}
}
}
5 changes: 5 additions & 0 deletions src/test/java/net/teumteum/integration/Repository.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ List<Meeting> saveAndGetCloseMeetingsByParticipantUserId(int size, Long particip
return meetingRepository.saveAllAndFlush(meetings);
}

Meeting saveAndGetCloseMeetingByParticipantUserIds(List<Long> participantUserIds) {
var meeting = MeetingFixture.getCloseMeetingWithParticipantIds(participantUserIds);
return meetingRepository.save(meeting);
}

List<Meeting> saveAndGetOpenMeetings(int size) {
var meetings = Stream.generate(MeetingFixture::getOpenMeeting)
.limit(size)
Expand Down
Loading