backend/test/scenarios/apis/user.js

342 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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