Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,70 @@ env:

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

e2e-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_USER: htyuc
POSTGRES_PASSWORD: htyuc
POSTGRES_DB: htyuc_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

env:
UC_DB_URL: postgres://htyuc:htyuc@localhost:5432/htyuc_test
REDIS_HOST: localhost
REDIS_PORT: 6379
JWT_KEY: test_jwt_key_for_testing_only_1234567890
POOL_SIZE: 5
EXPIRATION_DAYS: 7
SKIP_POST_LOGIN: true
SKIP_REGISTRATION: true
print_debug: true

steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install diesel_cli
run: cargo install diesel_cli --no-default-features --features postgres

- name: Run diesel migrations
working-directory: htyuc_models
run: |
diesel setup
diesel migration run
env:
DATABASE_URL: postgres://htyuc:htyuc@localhost:5432/htyuc_test

- name: Initialize test data
run: |
PGPASSWORD=htyuc psql -h localhost -p 5432 -U htyuc -d htyuc_test -f htyuc/tests/fixtures/init_test_data.sql

- name: Run e2e tests
run: cargo test --package htyuc --test e2e_auth_tests -- --test-threads=1 --nocapture
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ tracing-appender = "^0.2"
tracing-subscriber = { version = "^0.3", features = ["env-filter", "local-time"] }
url = "^2.5"
uuid = { version = "^1.21", features = ["serde", "v4"] }
tower = { version = "^0.5", features = ["util"] }
http-body-util = "^0.1"
hyper = { version = "^1.6", features = ["full"] }
73 changes: 70 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,9 @@ cargo test
# 运行特定模块测试
cargo test --package htycommons

# 运行 E2E 认证测试(需要 PostgreSQL 和 Redis)
./scripts/run_tests.sh

# 运行测试并显示输出
print_debug=true cargo test -- --nocapture

Expand Down Expand Up @@ -472,11 +475,75 @@ cd htyuc
cargo test
```

### 集成测试
### E2E 测试

项目包含完整的端到端测试,覆盖认证相关功能:

#### 测试覆盖范围

| 功能模块 | 测试用例 |
|---------|---------|
| `login_with_password` | 成功登录、错误密码、缺少用户名/密码、用户不存在、无效域名 |
| `login_with_cert` | 无效签名、缺少/空 encrypted_data |
| `sudo` | 成功获取 sudoer token、无认证、无效 token |
| `sudo2` | 无认证、无效 token、切换到自己 |
| `verify_jwt_token` | 无效 token、登录后验证 |
| `generate_key_pair` | 无认证、登录后生成密钥对 |

#### 使用 Docker 运行测试(推荐)

```bash
# 使用脚本自动启动测试环境并运行测试
./scripts/run_tests.sh
```

该脚本会自动:
1. 启动 PostgreSQL 和 Redis 容器
2. 运行数据库迁移
3. 初始化测试数据
4. 执行 E2E 测试
5. 清理测试环境

#### 手动运行测试

```bash
# 1. 启动测试数据库和 Redis
docker compose -f docker-compose.test.yml up -d

# 2. 运行数据库迁移
cd htyuc_models
DATABASE_URL="postgres://htyuc:htyuc@localhost:5433/htyuc_test" diesel migration run
cd ..

# 3. 初始化测试数据
PGPASSWORD=htyuc psql -h localhost -p 5433 -U htyuc -d htyuc_test \
-f htyuc/tests/fixtures/init_test_data.sql

# 4. 运行测试
UC_DB_URL="postgres://htyuc:htyuc@localhost:5433/htyuc_test" \
REDIS_HOST="localhost" \
REDIS_PORT="6380" \
JWT_KEY="your_test_jwt_key" \
POOL_SIZE="5" \
cargo test --package htyuc --test e2e_auth_tests -- --test-threads=1

# 5. 清理
docker compose -f docker-compose.test.yml down -v
```

#### 使用本地数据库运行测试

如果已有本地 PostgreSQL 和 Redis 服务:

```bash
# 运行完整的集成测试
cargo test --test integration_tests
# 初始化测试数据
psql your_database -f htyuc/tests/fixtures/init_test_data.sql

# 运行测试
UC_DB_URL="postgres://user@localhost/your_database" \
REDIS_HOST="localhost" \
REDIS_PORT="6379" \
cargo test --package htyuc --test e2e_auth_tests -- --test-threads=1 --nocapture
```

### 性能测试
Expand Down
24 changes: 24 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
test-db:
image: postgres:14
environment:
POSTGRES_USER: htyuc
POSTGRES_PASSWORD: htyuc
POSTGRES_DB: htyuc_test
ports:
- "5433:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U htyuc -d htyuc_test"]
interval: 5s
timeout: 5s
retries: 5

test-redis:
image: redis:7
ports:
- "6380:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
5 changes: 5 additions & 0 deletions htyuc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ tracing = { workspace = true }
tracing-appender = { workspace = true }
tracing-subscriber = { workspace = true }
uuid = { workspace = true }

[dev-dependencies]
tower = { workspace = true }
http-body-util = { workspace = true }
hyper = { workspace = true }
125 changes: 125 additions & 0 deletions htyuc/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use axum::Router;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use http_body_util::BodyExt;
use tower::util::ServiceExt;
use serde_json::Value;
use std::sync::Once;

static INIT: Once = Once::new();

fn init_test_env() {
INIT.call_once(|| {
dotenv::dotenv().ok();

if std::env::var("UC_DB_URL").is_err() {
std::env::set_var("UC_DB_URL", "postgres://htyuc:htyuc@localhost:5433/htyuc_test");
}
if std::env::var("REDIS_HOST").is_err() {
std::env::set_var("REDIS_HOST", "localhost");
}
if std::env::var("REDIS_PORT").is_err() {
std::env::set_var("REDIS_PORT", "6380");
}
if std::env::var("JWT_KEY").is_err() {
std::env::set_var("JWT_KEY", "test_jwt_key_for_testing_only_1234567890");
}
if std::env::var("POOL_SIZE").is_err() {
std::env::set_var("POOL_SIZE", "5");
}
if std::env::var("EXPIRATION_DAYS").is_err() {
std::env::set_var("EXPIRATION_DAYS", "7");
}
if std::env::var("SKIP_POST_LOGIN").is_err() {
std::env::set_var("SKIP_POST_LOGIN", "true");
}
if std::env::var("SKIP_REGISTRATION").is_err() {
std::env::set_var("SKIP_REGISTRATION", "true");
}
});
}

pub struct TestApp {
pub router: Router,
}

impl TestApp {
pub fn new(db_url: &str) -> Self {
init_test_env();
let router = htyuc::uc_rocket(db_url);
Self { router }
}

pub async fn post_json(
&self,
uri: &str,
body: &str,
headers: Vec<(&str, &str)>,
) -> (StatusCode, Value) {
let mut request = Request::builder()
.method("POST")
.uri(uri)
.header("Content-Type", "application/json");

for (key, value) in headers {
request = request.header(key, value);
}

let request = request.body(Body::from(body.to_string())).unwrap();

let response = self
.router
.clone()
.oneshot(request)
.await
.unwrap();

let status = response.status();
let body = response.into_body().collect().await.unwrap().to_bytes();
let body: Value = serde_json::from_slice(&body).unwrap_or(Value::Null);

(status, body)
}

pub async fn get(
&self,
uri: &str,
headers: Vec<(&str, &str)>,
) -> (StatusCode, Value) {
let mut request = Request::builder()
.method("GET")
.uri(uri);

for (key, value) in headers {
request = request.header(key, value);
}

let request = request.body(Body::empty()).unwrap();

let response = self
.router
.clone()
.oneshot(request)
.await
.unwrap();

let status = response.status();
let body = response.into_body().collect().await.unwrap().to_bytes();
let body: Value = serde_json::from_slice(&body).unwrap_or(Value::Null);

(status, body)
}
}

pub struct TestCert;

impl TestCert {
pub fn generate_invalid_signature() -> String {
"invalid_signature_data_12345".to_string()
}
}

pub fn get_test_db_url() -> String {
init_test_env();
std::env::var("UC_DB_URL").unwrap_or_else(|_| "postgres://htyuc:htyuc@localhost:5433/htyuc_test".to_string())
}
Loading