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
This commit is contained in:
2026-04-23 17:20:01 +08:00
parent c843fecbce
commit 44c320d8fa
392 changed files with 11786 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
//! 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
);
}