204 lines
6.0 KiB
Rust
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))
|
|
} |