roomescape-refactored/test-scripts/create-schedule-scripts.js
pricelees 06f7faf7f9 [#66] 결제 & 예약 확정 로직 수정 (#67)
<!-- 제목 양식 -->
<!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) -->

## 📝 관련 이슈 및 PR

**PR과 관련된 이슈 번호**
- #66

##  작업 내용
<!-- 어떤 작업을 했는지 알려주세요! -->
- 이전 #64 의 작업 이후, 결제 & 예약 확정 API 로직은 총 3개의 독립된 트랜잭션을 사용함.
- 검증 & 배치 충돌 방지를 위한 첫 번째 트랜잭션 이외의 다른 트랜잭션은 불필요하다고 판단함. -> PG사 성공 응답이 오면 나머지 작업은 \@Async 처리 후 바로 성공 응답

## 🧪 테스트
<!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! -->
- 변경된 로직에 대한 통합 테스트 완료
- 성능 테스트 결과 P95 응답 시간 327.01ms -> 235.52ms / 평균 응답 시간 77.85 -> 68.16ms /  최대 응답 시간 5.26 -> 4.08초 단축

## 📚 참고 자료 및 기타
<!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! -->

Reviewed-on: #67
Co-authored-by: pricelees <priceelees@gmail.com>
Co-committed-by: pricelees <priceelees@gmail.com>
2025-10-17 04:59:12 +00:00

135 lines
4.3 KiB
JavaScript

import http from 'k6/http';
import {check} from 'k6';
import exec from 'k6/execution';
import {BASE_URL, getHeaders, login, parseIdToString} from "./common.js";
import {randomIntBetween} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
const TOTAL_ITERATIONS = 200000;
export const options = {
scenarios: {
schedule_creation: {
executor: 'shared-iterations',
vus: 100,
iterations: TOTAL_ITERATIONS,
maxDuration: '30m',
},
},
thresholds: {
'http_req_duration': ['p(95)<3000'],
'http_req_failed': ['rate<0.1'],
},
};
export function setup() {
console.log('=== Setup: 테스트 데이터 및 작업 목록 준비 중 ===');
const themesRes = http.get(`${BASE_URL}/tests/themes`);
const themes = parseIdToString(themesRes).results
const accountsRes = http.get(`${BASE_URL}/tests/admin/accounts`);
const accounts = JSON.parse(accountsRes.body).results;
const dates = generateDates(7);
console.log(`총 매장 수: ${accounts.length}`);
console.log(`총 테마 수: ${themes.length}`);
console.log(`생성 기간: ${dates.length}`);
const tasks = [];
for (const account of accounts) {
const loginResult = login(account.account, account.password, 'ADMIN');
if (loginResult === null) {
console.error(`[Setup] 로그인 실패: ${account.account}`);
continue;
}
const {storeId, accessToken} = loginResult;
// 5 ~ ${themes.size} 인 random 숫자 생성
const selectedThemes = selectRandomThemes(themes, randomIntBetween(5, themes.length));
for (const theme of selectedThemes) {
for (const date of dates) {
for (const time of theme.times) {
tasks.push({
storeId,
accessToken,
date,
time: time.startFrom,
themeId: theme.id,
});
}
}
}
}
console.log(`총 생성할 스케줄 수(iterations): ${tasks.length}`);
return {tasks};
}
export default function (data) {
// 👈 3. 현재 반복 횟수가 준비된 작업 수를 초과하는지 확인
const taskIndex = exec.scenario.iterationInTest;
if (taskIndex >= data.tasks.length) {
// 첫 번째로 이 조건에 도달한 VU가 테스트를 종료
if (taskIndex === data.tasks.length) {
console.log('모든 스케쥴 생성 완료. 테스트 종료');
exec.test.abort();
}
return;
}
const task = data.tasks[taskIndex];
if (!task) {
console.log(`[VU ${__VU}] 알 수 없는 오류: task가 없습니다. (index: ${taskIndex})`);
return;
}
createSchedule(task.storeId, task.accessToken, task);
}
function createSchedule(storeId, accessToken, schedule) {
const payload = JSON.stringify({
date: schedule.date,
time: schedule.time,
themeId: schedule.themeId,
});
const params = getHeaders(accessToken, "/admin/stores/${id}/schedules")
const res = http.post(`${BASE_URL}/admin/stores/${storeId}/schedules`, payload, params);
const success = check(res, {'일정 생성 성공': (r) => r.status === 200 || r.status === 201});
if (!success) {
console.error(`일정 생성 실패 [${res.status}]: 매장=${storeId}, ${schedule.date} ${schedule.time} (테마: ${schedule.themeId}) | 응답: ${res.body}`);
}
return success;
}
function generateDates(days) {
const dates = [];
const today = new Date();
for (let i = 1; i <= days; i++) {
const date = new Date(today);
date.setDate(today.getDate() + i);
dates.push(date.toISOString().split('T')[0]);
}
return dates;
}
function selectRandomThemes(themes, count) {
const shuffled = [...themes].sort(() => 0.5 - Math.random());
return shuffled.slice(0, Math.min(count, themes.length));
}
export function teardown(data) {
if (data.tasks) {
console.log(`\n=== 테스트 완료: 총 ${data.tasks.length}개의 스케줄 생성을 시도했습니다. ===`);
} else {
console.log('\n=== 테스트 완료: setup 단계에서 오류가 발생하여 작업을 실행하지 못했습니다. ===');
}
}