Files
erp/integration-tests/test_common.rs
iven 841766b168
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
fix(用户管理): 修复用户列表页面加载失败问题
修复用户列表页面加载失败导致测试超时的问题,确保页面元素正确渲染
2026-04-19 08:46:28 +08:00

204 lines
6.0 KiB
Rust

use serde::{Deserialize, Serialize};
use uuid::Uuid;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestContext {
pub tenant_id: Uuid,
pub user_id: Uuid,
pub access_token: String,
pub refresh_token: String,
pub base_url: String,
pub created_resources: HashMap<String, Vec<CreatedResource>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreatedResource {
pub id: Uuid,
pub name: String,
pub resource_type: String,
}
impl TestContext {
pub fn new() -> Self {
Self {
tenant_id: Uuid::nil(),
user_id: Uuid::nil(),
access_token: String::new(),
refresh_token: String::new(),
base_url: std::env::var("ERP_API_URL")
.unwrap_or_else(|_| "http://localhost:3000".to_string()),
created_resources: HashMap::new(),
}
}
pub fn store_resource(&mut self, resource_type: &str, id: Uuid, name: &str) {
self.created_resources
.entry(resource_type.to_string())
.or_insert_with(Vec::new)
.push(CreatedResource {
id,
name: name.to_string(),
resource_type: resource_type.to_string(),
});
}
pub fn get_resource(&self, resource_type: &str, name: &str) -> Option<Uuid> {
self.created_resources
.get(resource_type)
.and_then(|resources| {
resources
.iter()
.find(|r| r.name == name)
.map(|r| r.id)
})
}
pub fn auth_header(&self) -> String {
format!("Bearer {}", self.access_token)
}
}
impl Default for TestContext {
fn default() -> Self {
Self::new()
}
}
pub struct HttpClient {
base_url: String,
}
impl HttpClient {
pub fn new(base_url: &str) -> Self {
Self {
base_url: base_url.to_string(),
}
}
pub async fn get(&self, path: &str, token: Option<&str>) -> TestResponse {
let client = reqwest::Client::new();
let mut req = client.get(format!("{}{}", self.base_url, path));
if let Some(t) = token {
req = req.header("Authorization", format!("Bearer {}", t));
}
req.send().await.into()
}
pub async fn post<T: Serialize>(&self, path: &str, body: &T, token: Option<&str>) -> TestResponse {
let client = reqwest::Client::new();
let mut req = client.post(format!("{}{}", self.base_url, path));
req = req.header("Content-Type", "application/json");
if let Some(t) = token {
req = req.header("Authorization", format!("Bearer {}", t));
}
req.json(body).send().await.into()
}
pub async fn put<T: Serialize>(&self, path: &str, body: &T, token: Option<&str>) -> TestResponse {
let client = reqwest::Client::new();
let mut req = client.put(format!("{}{}", self.base_url, path));
req = req.header("Content-Type", "application/json");
if let Some(t) = token {
req = req.header("Authorization", format!("Bearer {}", t));
}
req.json(body).send().await.into()
}
pub async fn delete(&self, path: &str, token: Option<&str>) -> TestResponse {
let client = reqwest::Client::new();
let mut req = client.delete(format!("{}{}", self.base_url, path));
if let Some(t) = token {
req = req.header("Authorization", format!("Bearer {}", t));
}
req.send().await.into()
}
}
#[derive(Debug)]
pub struct TestResponse {
pub status: u16,
pub body: serde_json::Value,
pub elapsed_ms: u64,
}
impl From<reqwest::Response> for TestResponse {
fn from(resp: reqwest::Response) -> Self {
let status = resp.status().as_u16();
let body = resp.json().unwrap_or(serde_json::json!({}));
Self {
status,
body,
elapsed_ms: 0,
}
}
}
pub async fn login(client: &HttpClient, username: &str, password: &str) -> Result<(String, String), String> {
#[derive(Serialize)]
struct LoginReq { username: String, password: String }
#[derive(Deserialize)]
struct LoginResp {
data: LoginData
}
#[derive(Deserialize)]
struct LoginData {
access_token: String,
refresh_token: String,
}
let resp = client.post("/api/v1/auth/login", &LoginReq {
username: username.to_string(),
password: password.to_string(),
}, None).await;
if resp.status == 200 {
let data: LoginResp = serde_json::from_value(resp.body)
.map_err(|e| format!("parse error: {}", e))?;
Ok((data.data.access_token, data.data.refresh_token))
} else {
Err(format!("login failed: {} - {:?}", resp.status, resp.body))
}
}
pub async fn refresh_token(client: &HttpClient, refresh_token: &str) -> Result<(String, String), String> {
#[derive(Serialize)]
struct RefreshReq { refresh_token: String }
#[derive(Deserialize)]
struct RefreshResp { data: RefreshData }
#[derive(Deserialize)]
struct RefreshData {
access_token: String,
refresh_token: String,
}
let resp = client.post("/api/v1/auth/refresh", &RefreshReq {
refresh_token: refresh_token.to_string()
}, None).await;
if resp.status == 200 {
let data: RefreshResp = serde_json::from_value(resp.body)
.map_err(|e| format!("parse error: {}", e))?;
Ok((data.data.access_token, data.data.refresh_token))
} else {
Err(format!("refresh failed: {} - {:?}", resp.status, resp.body))
}
}
pub fn validate_uuid(id: &str) -> bool {
Uuid::parse_str(id).is_ok()
}
pub fn validate_email(email: &str) -> bool {
email.contains('@') && email.contains('.')
}
pub fn validate_pagination(body: &serde_json::Value) -> Option<(u64, u64, u64)> {
let data = body.get("data")?;
let total = data.get("total")?.as_u64()?;
let page = data.get("page")?.as_u64()?;
let page_size = data.get("page_size")?.as_u64()?;
Some((total, page, page_size))
}