177 lines
6.2 KiB
Rust
177 lines
6.2 KiB
Rust
|
|
//! 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
|
|||
|
|
);
|
|||
|
|
}
|