backend/test/scenarios/apis/user.js

342 lines
8.8 KiB
JavaScript
Raw Permalink Normal View History

2025-11-07 07:44:23 +00:00
/**
* 使用者資訊相關 API 場景模組
*
* 此模組提供可重複使用的使用者資訊相關場景包括
* - 取得使用者資訊
* - 更新使用者資訊
* - 修改密碼
* - 驗證碼流程email/phone
*
* 使用方式
* import { getUserInfo, updateUserInfo } from './scenarios/apis/user.js';
*/
import http from 'k6/http';
import { check } from 'k6';
import { Rate, Trend } from 'k6/metrics';
// 可選的自定義指標
const getUserInfoSuccessRate = new Rate('user_get_info_success');
const updateUserInfoSuccessRate = new Rate('user_update_info_success');
const getUserInfoDuration = new Trend('user_get_info_duration');
const updateUserInfoDuration = new Trend('user_update_info_duration');
/**
* 取得當前登入的使用者資訊
* @param {Object} options - 配置選項
* @param {string} options.baseUrl - API 基礎 URL
* @param {string} options.accessToken - Access Token
* @param {Object} options.customMetrics - 自定義指標對象可選
* @returns {Object} 使用者資訊結果
*/
export function getUserInfo(options = {}) {
const {
baseUrl = __ENV.BASE_URL || 'https://localhost:8888',
accessToken,
customMetrics = null,
} = options;
if (!accessToken) {
throw new Error('accessToken is required');
}
const url = `${baseUrl}/api/v1/user/me`;
const params = {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
tags: {
name: 'user_get_info',
api: 'user',
method: 'get_user_info',
},
};
const startTime = Date.now();
const res = http.get(url, params);
const duration = Date.now() - startTime;
const success = check(res, {
'get user info status is 200': (r) => r.status === 200,
'get user info has uid': (r) => {
try {
const body = JSON.parse(r.body);
return body.uid && body.uid.length > 0;
} catch {
return false;
}
},
'get user info has user_status': (r) => {
try {
const body = JSON.parse(r.body);
return body.user_status !== undefined;
} catch {
return false;
}
},
}, { name: 'get_user_info_checks' });
if (customMetrics) {
customMetrics.getUserInfoSuccessRate?.add(success);
customMetrics.getUserInfoDuration?.add(duration);
} else {
getUserInfoSuccessRate.add(success);
getUserInfoDuration.add(duration);
}
let result = null;
if (res.status === 200) {
try {
result = JSON.parse(res.body);
} catch (e) {
console.error('Failed to parse get user info response:', e);
}
}
return {
success,
status: res.status,
response: result,
};
}
/**
* 更新當前登入的使用者資訊
* @param {Object} options - 配置選項
* @param {string} options.baseUrl - API 基礎 URL
* @param {string} options.accessToken - Access Token
* @param {Object} options.updateData - 要更新的資料可選欄位
* @param {Object} options.customMetrics - 自定義指標對象可選
* @returns {Object} 更新結果
*/
export function updateUserInfo(options = {}) {
const {
baseUrl = __ENV.BASE_URL || 'https://localhost:8888',
accessToken,
updateData = {},
customMetrics = null,
} = options;
if (!accessToken) {
throw new Error('accessToken is required');
}
const url = `${baseUrl}/api/v1/user/me`;
const payload = JSON.stringify(updateData);
const params = {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
tags: {
name: 'user_update_info',
api: 'user',
method: 'update_user_info',
},
};
const startTime = Date.now();
const res = http.put(url, payload, params);
const duration = Date.now() - startTime;
const success = check(res, {
'update user info status is 200': (r) => r.status === 200,
'update user info returns updated data': (r) => {
try {
const body = JSON.parse(r.body);
return body.uid && body.uid.length > 0;
} catch {
return false;
}
},
}, { name: 'update_user_info_checks' });
if (customMetrics) {
customMetrics.updateUserInfoSuccessRate?.add(success);
customMetrics.updateUserInfoDuration?.add(duration);
} else {
updateUserInfoSuccessRate.add(success);
updateUserInfoDuration.add(duration);
}
let result = null;
if (res.status === 200) {
try {
result = JSON.parse(res.body);
} catch (e) {
console.error('Failed to parse update user info response:', e);
}
}
return {
success,
status: res.status,
response: result,
};
}
/**
* 修改當前登入使用者的密碼
* @param {Object} options - 配置選項
* @param {string} options.baseUrl - API 基礎 URL
* @param {string} options.accessToken - Access Token
* @param {string} options.currentPassword - 當前密碼
* @param {string} options.newPassword - 新密碼
* @returns {Object} 修改結果
*/
export function updatePassword(options = {}) {
const {
baseUrl = __ENV.BASE_URL || 'https://localhost:8888',
accessToken,
currentPassword,
newPassword,
} = options;
if (!accessToken || !currentPassword || !newPassword) {
throw new Error('accessToken, currentPassword, and newPassword are required');
}
const url = `${baseUrl}/api/v1/user/me/password`;
const payload = JSON.stringify({
current_password: currentPassword,
new_password: newPassword,
new_password_confirm: newPassword,
});
const params = {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
tags: {
name: 'user_update_password',
api: 'user',
method: 'update_password',
},
};
const res = http.put(url, payload, params);
const success = check(res, {
'update password status is 200': (r) => r.status === 200,
}, { name: 'update_password_checks' });
return {
success,
status: res.status,
};
}
/**
* 請求發送驗證碼用於驗證 email/phone
* @param {Object} options - 配置選項
* @param {string} options.baseUrl - API 基礎 URL
* @param {string} options.accessToken - Access Token
* @param {string} options.purpose - 驗證目的email_verification/phone_verification
* @returns {Object} 請求結果
*/
export function requestVerificationCode(options = {}) {
const {
baseUrl = __ENV.BASE_URL || 'https://localhost:8888',
accessToken,
purpose = 'email_verification',
} = options;
if (!accessToken) {
throw new Error('accessToken is required');
}
if (!['email_verification', 'phone_verification'].includes(purpose)) {
throw new Error('purpose must be email_verification or phone_verification');
}
const url = `${baseUrl}/api/v1/user/me/verifications`;
const payload = JSON.stringify({
purpose: purpose,
});
const params = {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
tags: {
name: 'user_request_verification_code',
api: 'user',
method: 'request_verification_code',
purpose: purpose,
},
};
const res = http.post(url, payload, params);
const success = check(res, {
'request verification code status is 200': (r) => r.status === 200,
}, { name: 'request_verification_code_checks' });
return {
success,
status: res.status,
};
}
/**
* 提交驗證碼以完成驗證
* @param {Object} options - 配置選項
* @param {string} options.baseUrl - API 基礎 URL
* @param {string} options.accessToken - Access Token
* @param {string} options.purpose - 驗證目的email_verification/phone_verification
* @param {string} options.verifyCode - 驗證碼
* @returns {Object} 提交結果
*/
export function submitVerificationCode(options = {}) {
const {
baseUrl = __ENV.BASE_URL || 'https://localhost:8888',
accessToken,
purpose = 'email_verification',
verifyCode,
} = options;
if (!accessToken || !verifyCode) {
throw new Error('accessToken and verifyCode are required');
}
if (!['email_verification', 'phone_verification'].includes(purpose)) {
throw new Error('purpose must be email_verification or phone_verification');
}
const url = `${baseUrl}/api/v1/user/me/verifications`;
const payload = JSON.stringify({
purpose: purpose,
verify_code: verifyCode,
});
const params = {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
tags: {
name: 'user_submit_verification_code',
api: 'user',
method: 'submit_verification_code',
purpose: purpose,
},
};
const res = http.put(url, payload, params);
const success = check(res, {
'submit verification code status is 200': (r) => r.status === 200,
}, { name: 'submit_verification_code_checks' });
return {
success,
status: res.status,
};
}