/** * 使用者資訊相關 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, }; }