Skip to content
Closed
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
263 changes: 263 additions & 0 deletions k6/11-vacancy-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
/**
* 빈자리 ꡬ독 (Vacancy) λΆ€ν•˜ν…ŒμŠ€νŠΈ - Redis SADD/SMEMBERS μ‘λ‹΅μ‹œκ°„ μΈ‘μ •
* μ‹€ν–‰: k6 run -e PHASE=<subscribe|smembers|ttl> ... 11-vacancy-test.js
*
* μ‹œν—˜ 절차 (원본 μŠ€νŽ™):
* 1) 100λͺ… κ΅¬λ…μžλ₯Ό SADD ν›„ SMEMBERS 평균 μ‘λ‹΅μ‹œκ°„ μΈ‘μ •
* 2) μ‹œκ°„λŒ€ + 1μ‹œκ°„ ν›„ ν‚€κ°€ μžλ™ μ‚­μ œλ˜λŠ”μ§€ 확인
*
* μΈ‘μ • 경둜:
* - SADD : POST /api/v1/vacancy λ₯Ό 톡해 λ°±μ—”λ“œ λ‚΄λΆ€μ—μ„œ 호좜 (μ‹€μ œ 운영 경둜)
* -> backend/src/main/java/com/catchtable/vacancy/service/VacancyService.java:56
* - SMEMBERS : λ°±μ—”λ“œκ°€ HTTP 둜 λ…ΈμΆœν•˜μ§€ μ•ŠμŒ (Kafka Consumer λ‚΄λΆ€ 호좜).
* k6/experimental/redis λͺ¨λ“ˆλ‘œ Redis 에 직접 λΆ™μ–΄μ„œ μΈ‘μ •.
* -> backend/src/main/java/com/catchtable/notification/service/NotificationKafkaConsumer.java:155
* - TTL : k6/experimental/redis 의 TTL/EXISTS 둜 직접 확인.
*
* Redis ν‚€ 포맷 (λ°±μ—”λ“œ generateRedisKey κΈ°μ€€):
* wait:store:{storeId}:{remainDate}:{remainTime}
* 예) wait:store:1:2026-05-29:18:00
*
* =============================================================================
* μ‹€ν–‰ ν™˜κ²½ - 1μ•ˆ vs 2μ•ˆ
* =============================================================================
* λ³Έ μ‹œλ‚˜λ¦¬μ˜€λŠ” 두 κ°€μ§€ μ‹€ν–‰ ν™˜κ²½μ„ μ§€μ›ν•œλ‹€. λͺ…λ Ή μ˜ˆμ‹œλŠ” 각 PHASE μ•ˆμ—μ„œ
* [1μ•ˆ] [2μ•ˆ] 으둜 ꡬ뢄해 ν‘œκΈ°.
*
* [1μ•ˆ] EC2 μ•ˆμ—μ„œ k6 μ‹€ν–‰ (ꢌμž₯)
* - URL = http://localhost:8080 (λ°±μ—”λ“œμ™€ 같은 호슀트)
* - REDIS_ADDR = catchtable-redis:6379 (docker network μ»¨ν…Œμ΄λ„ˆλͺ…)
* - ν•„μš” μž‘μ—… : EC2 접속 (SSH λ˜λŠ” AWS SSM) + k6 μ„€μΉ˜
* - 운영 ꡬ성 : λ³€κ²½ μ—†μŒ
* - μΈ‘μ • 정확도 : λ†’μŒ (RTT 거의 0)
* - PHASE 2/3 λŠ” docker network `catchtable-net` 에 λΆ™μ–΄μ•Ό ν•˜λ―€λ‘œ
* `docker run --network catchtable-net grafana/k6 run -` 방식 μ‚¬μš©.
*
* [2μ•ˆ] λ‘œμ»¬μ—μ„œ k6 μ‹€ν–‰
* - URL = https://<μš΄μ˜λ„λ©”μΈ>
* - REDIS_ADDR = <ec2-public-ip>:6379
* - κ΄€λ¦¬μž μž‘μ—… : docker-compose.prod.yml 의 redis μ„Ήμ…˜ expose -> ports
* λ³€κ²½ + μ»¨ν…Œμ΄λ„ˆ μž¬μ‹œμž‘ + λ³΄μ•ˆκ·Έλ£Ή 6379 ν—ˆμš©
* - 운영 ꡬ성 : λ³€κ²½ (ν…ŒμŠ€νŠΈ μ’…λ£Œ ν›„ 원볡 ν•„μˆ˜)
* - μΈ‘μ • 정확도 : 둜컬 ↔ EC2 RTT λ…Έμ΄μ¦ˆ μ„žμž„
*
* =============================================================================
* PHASE 1: 100λͺ… ꡬ독 (SADD λΆ€ν•˜ 생성)
* =============================================================================
*
* [사전 μ€€λΉ„]
* 1. λ°±μ—”λ“œ μ½”λ“œ μž„μ‹œ μˆ˜μ • (ν…ŒμŠ€νŠΈ λλ‚˜λ©΄ λ°˜λ“œμ‹œ 원볡!)
* 파일: backend/src/test/java/com/catchtable/loadtest/VacancyLoadTestTokenGenerator.java
* generateTokens() μœ„μ˜ `@Transactional` μ–΄λ…Έν…Œμ΄μ…˜μ„ 주석 μ²˜λ¦¬ν•œλ‹€.
* ( @Transactional 이 μ‚΄μ•„μžˆμœΌλ©΄ μ‹œλ“œ μœ μ €κ°€ λ‘€λ°±λ˜μ–΄ k6 μš”μ²­μ—μ„œ USER_NOT_FOUND λ°œμƒ )
*
* 2. μ‹œλ“œ μœ μ € 100λͺ… + 토큰 100개 생성 (backend λ””λ ‰ν† λ¦¬μ—μ„œ)
Comment on lines +49 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

VacancyLoadTestTokenGenerator에 @Commit μ–΄λ…Έν…Œμ΄μ…˜μ„ μ μš©ν•˜λ©΄ ν…ŒμŠ€νŠΈ μ‹€ν–‰ μ‹œ μ½”λ“œλ₯Ό μž„μ‹œλ‘œ μˆ˜μ •ν•˜κ³  원볡할 ν•„μš”κ°€ μ—†μ–΄μ§‘λ‹ˆλ‹€. λ”°λΌμ„œ 이 κ°€μ΄λ“œ 뢀뢄을 μ œκ±°ν•˜κ³  단계λ₯Ό λ‹¨μˆœν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Suggested change
* 1. λ°±μ—”λ“œ μ½”λ“œ μž„μ‹œ μˆ˜μ • (ν…ŒμŠ€νŠΈ λλ‚˜λ©΄ λ°˜λ“œμ‹œ 원볡!)
* 파일: backend/src/test/java/com/catchtable/loadtest/VacancyLoadTestTokenGenerator.java
* generateTokens() μœ„μ˜ `@Transactional` μ–΄λ…Έν…Œμ΄μ…˜μ„ 주석 μ²˜λ¦¬ν•œλ‹€.
* ( @Transactional 이 μ‚΄μ•„μžˆμœΌλ©΄ μ‹œλ“œ μœ μ €κ°€ λ‘€λ°±λ˜μ–΄ k6 μš”μ²­μ—μ„œ USER_NOT_FOUND λ°œμƒ )
*
* 2. μ‹œλ“œ μœ μ € 100λͺ… + 토큰 100개 생성 (backend λ””λ ‰ν† λ¦¬μ—μ„œ)
* 1. μ‹œλ“œ μœ μ € 100λͺ… + 토큰 100개 생성 (backend λ””λ ‰ν† λ¦¬μ—μ„œ)

* ./gradlew test \
* --tests "com.catchtable.loadtest.VacancyLoadTestTokenGenerator.generateTokens" \
* -DrunLoadTokenGen=true
*
* 3. ν…ŒμŠ€νŠΈ λŒ€μƒ StoreRemain μ€€λΉ„
* - remainTeam = 0 인 (μž”μ—¬ 0) StoreRemain 의 id κ°€ ν•„μš”ν•˜λ‹€.
* - DB μ—μ„œ 미리 ν•˜λ‚˜ 골라두고, κ·Έ row 의 remainDate / remainTime 으둜 Redis ν‚€λ₯Ό λ§Œλ“ λ‹€.
*
* [μ‹€ν–‰]
* [1μ•ˆ] EC2 μ•ˆμ—μ„œ (Bash)
* k6 run -e PHASE=subscribe \
* -e URL=http://localhost:8080 \
* -e TOKENS=$(cat build/load-test-tokens.csv) \
* -e REMAIN_ID=<id> -e N=100 \
* k6/11-vacancy-test.js
*
* [2μ•ˆ] λ‘œμ»¬μ—μ„œ (PowerShell)
* $tokens = Get-Content backend\build\load-test-tokens.csv -Raw
* k6 run -e PHASE=subscribe `
* -e URL=https://<μš΄μ˜λ„λ©”μΈ> `
* -e TOKENS=$tokens `
* -e REMAIN_ID=<id> -e N=100 `
* backend\k6\11-vacancy-test.js
*
* [2μ•ˆ] λ‘œμ»¬μ—μ„œ (Bash)
* k6 run -e PHASE=subscribe \
* -e URL=https://<μš΄μ˜λ„λ©”μΈ> \
* -e TOKENS=$(cat backend/build/load-test-tokens.csv) \
* -e REMAIN_ID=<id> -e N=100 \
* backend/k6/11-vacancy-test.js
*
* =============================================================================
* PHASE 2: SMEMBERS 평균 μ‘λ‹΅μ‹œκ°„ μΈ‘μ •
* =============================================================================
*
* [사전 μ€€λΉ„]
* 1. PHASE 1 이 λλ‚˜μ„œ Redis 킀에 100λͺ…이 λ“€μ–΄μžˆμ–΄μ•Ό ν•œλ‹€.
* 2. λ°±μ—”λ“œ λ‘œκ·Έμ—μ„œ Redis ν‚€λ₯Ό ν™•μΈν•œλ‹€.
* 예) [빈자리 μ•Œλ¦Ό 등둝] Redis SADD μ™„λ£Œ: key=wait:store:1:2026-05-29:18:00, userId=...
*
* [μ‹€ν–‰]
* [1μ•ˆ] EC2 μ•ˆμ—μ„œ (docker 둜 k6 μ»¨ν…Œμ΄λ„ˆλ₯Ό catchtable-net 에 λΆ™μž„)
* docker run --rm -i --network catchtable-net \
* -e PHASE=smembers \
* -e REDIS_ADDR=catchtable-redis:6379 \
* -e REDIS_KEY=wait:store:1:2026-05-29:18:00 \
* -e N=1000 \
* grafana/k6 run - < k6/11-vacancy-test.js
*
* [2μ•ˆ] λ‘œμ»¬μ—μ„œ
* k6 run -e PHASE=smembers \
* -e REDIS_ADDR=<ec2-public-ip>:6379 \
* -e REDIS_KEY=wait:store:1:2026-05-29:18:00 \
* -e N=1000 \
* backend/k6/11-vacancy-test.js
*
* (Redis 에 νŒ¨μŠ€μ›Œλ“œκ°€ κ±Έλ €μžˆλ‹€λ©΄ -e REDIS_PASSWORD=... μΆ”κ°€. ν˜„μž¬ μš΄μ˜μ€ λ―Έμ„€μ •)
* N = SMEMBERS 호좜 반볡 횟수 (1000회 정도면 뢄포가 μ•ˆμ •λ¨).
* κ²°κ³Ό: smembers_duration_ms 의 avg / p95 / p99 κ°€ 평균 μ‘λ‹΅μ‹œκ°„.
*
* =============================================================================
* PHASE 3: TTL μžλ™ μ‚­μ œ 확인
* =============================================================================
*
* [μ‹€ν–‰ - PHASE 1 직후]
* [1μ•ˆ] EC2 μ•ˆμ—μ„œ
* docker run --rm -i --network catchtable-net \
* -e PHASE=ttl \
* -e REDIS_ADDR=catchtable-redis:6379 \
* -e REDIS_KEY=wait:store:1:2026-05-29:18:00 \
* grafana/k6 run - < k6/11-vacancy-test.js
*
* [2μ•ˆ] λ‘œμ»¬μ—μ„œ
* k6 run -e PHASE=ttl \
* -e REDIS_ADDR=<ec2-public-ip>:6379 \
* -e REDIS_KEY=wait:store:1:2026-05-29:18:00 \
* backend/k6/11-vacancy-test.js
*
* 확인 사항:
* - exists == 1
* - ttl 초 λ‹¨μœ„ 값이 (remainDate+remainTime+1h - now) 와 일치
*
* [μ‹€ν–‰ - 만료 μ‹œκ° (remainDate+remainTime+1h) 이후]
* 동일 λͺ…λ Ή λ‹€μ‹œ μ‹€ν–‰.
* 확인: exists == 0 (μžλ™ μ‚­μ œ 성곡)
*
* =============================================================================
* ν…ŒμŠ€νŠΈ μ’…λ£Œ ν›„ 정리 (ν•„μˆ˜)
* =============================================================================
* - VacancyLoadTestTokenGenerator.java 의 `@Transactional` 주석 원볡.
* - μ‹œλ“œ 데이터 정리:
Comment on lines +144 to +145

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ν…ŒμŠ€νŠΈ μ’…λ£Œ ν›„ @Transactional 주석을 μ›λ³΅ν•˜λŠ” μˆ˜λ™ 단계가 더 이상 ν•„μš”ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ κ°€μ΄λ“œμ—μ„œ μ œκ±°ν•©λ‹ˆλ‹€.

Suggested change
* - VacancyLoadTestTokenGenerator.java 의 `@Transactional` 주석 원볡.
* - μ‹œλ“œ 데이터 정리:
* - μ‹œλ“œ 데이터 정리:

* DELETE FROM vacancy WHERE user_id IN (SELECT id FROM users WHERE google_id LIKE 'loadtest-google-%');
* DELETE FROM users WHERE google_id LIKE 'loadtest-google-%';
* - Redis μž”μ‘΄ ν‚€ 정리:
* redis-cli --scan --pattern 'wait:store:*' | xargs redis-cli DEL
*/

import http from 'k6/http';
import redis from 'k6/experimental/redis';
import { check } from 'k6';
import { Counter, Trend } from 'k6/metrics';

const BASE_URL = __ENV.URL || 'http://localhost:8080';
const PHASE = __ENV.PHASE || 'subscribe';
const N = Number(__ENV.N || 100);

// PHASE=subscribe 용
const TOKENS = (__ENV.TOKENS ? __ENV.TOKENS.split(',') : []).filter(Boolean);
const REMAIN_ID = Number(__ENV.REMAIN_ID || 0);

// PHASE=smembers / ttl 용
const REDIS_ADDR = __ENV.REDIS_ADDR || 'localhost:6379';
const REDIS_KEY = __ENV.REDIS_KEY || '';
const REDIS_PASSWORD = __ENV.REDIS_PASSWORD || '';

// λ©”νŠΈλ¦­
const status2xx = new Counter('status_2xx');
const status4xx = new Counter('status_4xx');
const status5xx = new Counter('status_5xx');
const subscribeDuration = new Trend('subscribe_duration_ms', true);
const smembersDuration = new Trend('smembers_duration_ms', true);
const smembersSize = new Trend('smembers_set_size');

// Redis ν΄λΌμ΄μ–ΈνŠΈ (smembers / ttl phase μ—μ„œλ§Œ μ‚¬μš©)
// URL ν˜•μ‹: redis://[:password@]host:port
const REDIS_URL = (() => {
const auth = REDIS_PASSWORD ? `:${encodeURIComponent(REDIS_PASSWORD)}@` : '';
return `redis://${auth}${REDIS_ADDR}`;
})();
const redisClient = new redis.Client(REDIS_URL);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

PHASEκ°€ subscribe일 λ•ŒλŠ” Redisλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ, Redis ν΄λΌμ΄μ–ΈνŠΈλ₯Ό 무쑰건 μƒμ„±ν•˜κΈ°λ³΄λ‹€λŠ” ν•΄λ‹Ή νŽ˜μ΄μ¦ˆκ°€ 아닐 λ•Œλ§Œ μ‘°κ±΄λΆ€λ‘œ μƒμ„±ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ Redis 연결이 λΆˆκ°€λŠ₯ν•œ ν™˜κ²½μ—μ„œ subscribe λΆ€ν•˜ ν…ŒμŠ€νŠΈλ§Œ μ‹€ν–‰ν•  λ•Œ λ°œμƒν•  수 μžˆλŠ” λΆˆν•„μš”ν•œ μ—°κ²° μ‹œλ„λ‚˜ 였λ₯˜λ₯Ό λ°©μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Suggested change
const redisClient = new redis.Client(REDIS_URL);
const redisClient = PHASE !== 'subscribe' ? new redis.Client(REDIS_URL) : null;


export const options = (() => {
if (PHASE === 'subscribe') {
return { vus: N, iterations: N };
}
if (PHASE === 'smembers') {
// 단일 VU κ°€ N 번 반볡 - SMEMBERS 자체 μ‘λ‹΅μ‹œκ°„ λΆ„ν¬λ§Œ λ³Έλ‹€.
// λ™μ‹œμ„± λΆ€ν•˜λ₯Ό 보렀면 vus λ₯Ό 늘릴 것.
return { vus: 1, iterations: N };
}
if (PHASE === 'ttl') {
return { vus: 1, iterations: 1 };
}
throw new Error(`Unknown PHASE: ${PHASE}`);
})();

// ---- PHASE 1: subscribe ----
function subscribe() {
if (TOKENS.length === 0) throw new Error('TOKENS env required');
if (!REMAIN_ID) throw new Error('REMAIN_ID env required');

const vu = __VU - 1;
const token = TOKENS[vu % TOKENS.length];

const res = http.post(
`${BASE_URL}/api/v1/vacancy`,
JSON.stringify({ remainId: REMAIN_ID }),
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
tags: { endpoint: 'subscribe' },
}
);

subscribeDuration.add(res.timings.duration);
if (res.status >= 200 && res.status < 300) status2xx.add(1);
else if (res.status >= 400 && res.status < 500) status4xx.add(1);
else if (res.status >= 500) status5xx.add(1);

check(res, {
'subscribe 2xx': (r) => r.status >= 200 && r.status < 300,
});
}

// ---- PHASE 2: smembers ----
async function smembers() {
if (!REDIS_KEY) throw new Error('REDIS_KEY env required');

const start = Date.now();
const members = await redisClient.smembers(REDIS_KEY);
const duration = Date.now() - start;

smembersDuration.add(duration);
smembersSize.add(members.length);
}

// ---- PHASE 3: ttl ----
async function ttl() {
if (!REDIS_KEY) throw new Error('REDIS_KEY env required');

const exists = await redisClient.exists(REDIS_KEY);
const ttlSec = await redisClient.ttl(REDIS_KEY);

console.log(`[TTL CHECK] key=${REDIS_KEY}`);
console.log(` exists = ${exists} (1 = ν‚€ 쑴재, 0 = μžλ™ μ‚­μ œλ¨)`);
console.log(` ttl = ${ttlSec}s (-1 = TTL μ—†μŒ, -2 = ν‚€ μ—†μŒ)`);

check(null, {
'key state observed': () => true,
});
}

export default async function () {
if (PHASE === 'subscribe') subscribe();
else if (PHASE === 'smembers') await smembers();
else if (PHASE === 'ttl') await ttl();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.catchtable.loadtest;

import com.catchtable.global.security.JwtTokenProvider;
import com.catchtable.user.entity.User;
import com.catchtable.user.entity.UserRole;
import com.catchtable.user.entity.UserStatus;
import com.catchtable.user.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ν…ŒμŠ€νŠΈκ°€ μ™„λ£Œλœ ν›„ 데이터λ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— μ»€λ°‹ν•˜κΈ° μœ„ν•΄ @Commit μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ μ‚¬μš©μžκ°€ ν…ŒμŠ€νŠΈ μ‹€ν–‰ μ‹œ 맀번 @Transactional을 μˆ˜λ™μœΌλ‘œ 주석 μ²˜λ¦¬ν•˜κ³  λ‹€μ‹œ 원볡해야 ν•˜λŠ” λ²ˆκ±°λ‘œμ›€κ³Ό μ‹€μˆ˜λ₯Ό λ°©μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

org.springframework.test.annotation.Commit을 μž„ν¬νŠΈν•˜μ—¬ μ μš©ν•΄ μ£Όμ„Έμš”.

Suggested change
import org.springframework.transaction.annotation.Transactional;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;


import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
* 빈자리 λΆ€ν•˜ ν…ŒμŠ€νŠΈμš© JWT 토큰 100개 생성 μΌνšŒμ„± νƒœμŠ€ν¬.
*
* μ‹€ν–‰:
* ./gradlew test --tests "com.catchtable.loadtest.VacancyLoadTestTokenGenerator.generateTokens" -DrunLoadTokenGen=true
*
* κ²°κ³Ό:
* build/load-test-tokens.txt (ν•œ 쀄당 토큰 1개, 총 100쀄)
* build/load-test-tokens.csv (콀마 ꡬ뢄 ν•œ 쀄 - k6 -e TOKENS= 에 λ°”λ‘œ μ£Όμž… κ°€λŠ₯)
*/
@SpringBootTest
class VacancyLoadTestTokenGenerator {

private static final int DEFAULT_USER_COUNT = 100;
private static final String EMAIL_PREFIX = "loadtest+";
private static final String EMAIL_DOMAIN = "@catchtable.test";
private static final String GOOGLE_ID_PREFIX = "loadtest-google-";
private static final String NICKNAME_PREFIX = "loadtest_user_";

@Autowired
private UserRepository userRepository;

@Autowired
private JwtTokenProvider jwtTokenProvider;

@Test
@Transactional
void generateTokens() throws IOException {
Comment on lines +44 to +46

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

λ©”μ„œλ“œμ— @Commit μ–΄λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•˜μ—¬ ν…ŒμŠ€νŠΈ μ‹€ν–‰ ν›„ νŠΈλžœμž­μ…˜μ΄ μžλ™μœΌλ‘œ μ»€λ°‹λ˜λ„λ‘ μ„€μ •ν•©λ‹ˆλ‹€.

Suggested change
@Test
@Transactional
void generateTokens() throws IOException {
@Test
@Transactional
@Commit
void generateTokens() throws IOException {

// -DrunLoadTokenGen=true 일 λ•Œλ§Œ μ‹€ν–‰. CI/일반 test μ‹€ν–‰ μ‹œ μŠ€ν‚΅.
if (!"true".equals(System.getProperty("runLoadTokenGen"))) {
System.out.println("[SKIP] -DrunLoadTokenGen=true μ˜΅μ…˜ μ—†μŒ. κ±΄λ„ˆλœ€.");
return;
}

int userCount = Integer.getInteger("tokenCount", DEFAULT_USER_COUNT);
List<String> tokens = new ArrayList<>(userCount);

for (int i = 1; i <= userCount; i++) {
String googleId = GOOGLE_ID_PREFIX + i;
User user = userRepository.findByGoogleId(googleId)
.orElseGet(() -> userRepository.save(User.builder()
.googleId(googleId)
.email(EMAIL_PREFIX + i + EMAIL_DOMAIN)
.nickname(NICKNAME_PREFIX + i)
.profileImage(null)
.role(UserRole.USER)
.status(UserStatus.ACTIVE)
.build()));

String token = jwtTokenProvider.generateAccessToken(user.getId(), user.getRole());
tokens.add(token);
}

Path txt = Path.of("build", "load-test-tokens.txt");
Path csv = Path.of("build", "load-test-tokens.csv");
Files.createDirectories(txt.getParent());
Files.write(txt, tokens);
Files.writeString(csv, String.join(",", tokens));

System.out.println("[OK] " + userCount + " tokens written:");
System.out.println(" - " + txt.toAbsolutePath());
System.out.println(" - " + csv.toAbsolutePath());
}
}
Loading