Files
huike-back/htyts/tests/ts_e2e_authcore_http.rs
T
weli 44c320d8fa chore add core rust project files and diesel migrations
Track required workspace crates, scripts, and historical diesel migrations so the repository contains the complete runnable backend baseline.

Made-with: Cursor
2026-04-23 17:20:01 +08:00

177 lines
6.2 KiB
Rust
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.
//! HTYTS + AuthCore HTYUC 联调:需已启动 `htyuc``HTYUC_URL`),且 `JWT_KEY` 与 UC 一致。
//! UC `verify_jwt` 要求 JWT 已在 UC Redis 中(与 `token_id` 一致),故通过 `login_with_password` 取令牌(数据见 AuthCore `htyuc/tests/fixtures/init_test_data.sql`)。
//! 在 CI 中由 `.github/workflows/htyts-authcore-weekly.yml` 构建并启动 UC 后执行;不在默认 PR workflow 中跑。
use std::sync::Arc;
use std::time::Duration;
use axum::serve;
use htycommons::db::pool;
use htyts::redis_store::RedisTaskStore;
use htyts::state::TsState;
use htyts::ts_router_with_state;
use serde_json::{json, Value};
use tokio::net::TcpListener;
/// 与 AuthCore `rust.yml` e2e 及 UC 进程保持一致,便于 `verify_jwt_token` 校验同一密钥。
const JWT_KEY_AUTHCORE_E2E: &str = "test_jwt_key_for_testing_only_1234567890";
/// 与 `init_test_data.sql` 中 `hty_apps.domain` 一致,登录与后续请求的 `HtyHost` 须相同。
const UC_E2E_HTY_HOST: &str = "root";
/// 具备 `SYS_CAN_SUDO` 的测试用户(fixture)。
const UC_E2E_SUDO_USER: &str = "sudouser";
const UC_E2E_SUDO_PASS: &str = "sudopass";
fn ensure_authcore_joint_env() {
std::env::var("HTYUC_URL").expect(
"HTYUC_URL must point to running HTYUC (e.g. http://127.0.0.1:18080). \
See huiwing/scripts/ci-authcore-weekly.sh or the weekly GitHub workflow.",
);
std::env::set_var("JWT_KEY", JWT_KEY_AUTHCORE_E2E);
std::env::set_var("TOKEN_VERIFY", "true");
std::env::set_var("AUTH_CHECKING", "true");
if std::env::var("REDIS_HOST").is_err() {
std::env::set_var("REDIS_HOST", "127.0.0.1");
}
if std::env::var("REDIS_PORT").is_err() {
std::env::set_var("REDIS_PORT", "6379");
}
if std::env::var("POOL_SIZE").is_err() {
std::env::set_var("POOL_SIZE", "8");
}
if std::env::var("TS_DOMAIN").is_err() {
std::env::set_var("TS_DOMAIN", "e2e.test.local");
}
}
fn redis_url() -> String {
let host = std::env::var("REDIS_HOST").expect("REDIS_HOST");
let port = std::env::var("REDIS_PORT").expect("REDIS_PORT");
format!("redis://{host}:{port}")
}
async fn fetch_sudoer_jwt_via_uc_login() -> String {
ensure_authcore_joint_env();
let uc_base = std::env::var("HTYUC_URL").expect("HTYUC_URL");
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(120))
.build()
.expect("reqwest client");
let login_body = json!({
"username": UC_E2E_SUDO_USER,
"password": UC_E2E_SUDO_PASS,
});
let resp = client
.post(format!(
"{}/api/v1/uc/login_with_password",
uc_base.trim_end_matches('/')
))
.header("HtyHost", UC_E2E_HTY_HOST)
.json(&login_body)
.send()
.await
.expect("login_with_password");
let status = resp.status();
let text = resp.text().await.unwrap_or_default();
assert!(
status.is_success(),
"login_with_password http={status} body={text}"
);
let v: Value = serde_json::from_str(&text).expect("login json");
assert!(
v["r"].as_bool().unwrap_or(false),
"login r=false body={text}"
);
v["d"]
.as_str()
.expect("login token in d")
.to_string()
}
async fn spawn_ts_server() -> String {
ensure_authcore_joint_env();
let db_url = std::env::var("TS_DATABASE_URL").expect("TS_DATABASE_URL");
let pg = pool(&db_url);
let redis = RedisTaskStore::connect(&redis_url())
.await
.expect("RedisTaskStore::connect");
let st = Arc::new(TsState {
db: Arc::new(pg),
redis,
zombie_minutes: std::env::var("ZOMBIE_MIN")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(10),
});
let app = ts_router_with_state(st);
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("bind ephemeral port");
let addr = listener.local_addr().expect("local_addr");
tokio::spawn(async move {
if let Err(e) = serve(listener, app).await {
eprintln!("htyts authcore e2e server error: {e}");
}
});
tokio::time::sleep(Duration::from_millis(200)).await;
format!("http://127.0.0.1:{}", addr.port())
}
/// 首次 `create_task` 在缓存未命中时应走 UC `POST /api/v1/uc/verify_jwt_token`(与 `check_auth` 一致)。
/// 默认 `cargo test` 跳过;周更 CI 与本地联调使用 `cargo test -p htyts --test ts_e2e_authcore_http -- --ignored`。
#[tokio::test]
#[ignore = "needs HTYUC_URL + running HTYUC (AuthCore); see scripts/ci-authcore-weekly.sh"]
async fn create_task_with_uc_verify_jwt_token() {
let base = spawn_ts_server().await;
let sudo = fetch_sudoer_jwt_via_uc_login().await;
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(120))
.build()
.expect("reqwest client");
let create_body = json!({
"task_type": "NOOP",
"task_status": "PENDING",
"payload": { "e2e_authcore": "note" }
});
let create = client
.post(format!("{base}/api/v1/ts/create_task"))
.header("HtyHost", UC_E2E_HTY_HOST)
.header("HtySudoerToken", &sudo)
.json(&create_body)
.send()
.await
.expect("create_task");
let status = create.status();
let body_text = create.text().await.unwrap_or_default();
assert_eq!(
status,
reqwest::StatusCode::CREATED,
"create_task (cache miss → UC verify) body={}",
body_text
);
let created: Value = serde_json::from_str(&body_text).expect("create json");
assert_eq!(created["r"], true);
let create2 = client
.post(format!("{base}/api/v1/ts/create_task"))
.header("HtyHost", UC_E2E_HTY_HOST)
.header("HtySudoerToken", &sudo)
.json(&json!({
"task_type": "NOOP",
"task_status": "PENDING",
"payload": { "e2e_authcore": "second_same_sudo_cache" }
}))
.send()
.await
.expect("create_task 2");
let status2 = create2.status();
let body2 = create2.text().await.unwrap_or_default();
assert_eq!(
status2,
reqwest::StatusCode::CREATED,
"second create (Redis sudo cache hit) body={}",
body2
);
}