Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
@@ -0,0 +1,149 @@
use loco_rs::{controller::extractor::auth, prelude::*, tests_cfg};
use serde::{Deserialize, Serialize};
use loco_rs::model::{Authenticable, ModelError};
use crate::infra_cfg;
#[derive(Debug, Deserialize, Serialize)]
pub struct TestUserResponse {
pub pid: String,
pub user_id: i32,
pub user_email: String,
}
// Mock user struct for testing ApiToken extractor
#[derive(Debug, Clone)]
struct TestUser {
id: i32,
email: String,
}
#[async_trait::async_trait]
impl Authenticable for TestUser {
async fn find_by_claims_key(
_db: &sea_orm::DatabaseConnection,
pid: &str,
) -> Result<Self, ModelError> {
// Simple mock: return user if pid matches, otherwise not found
if pid == "test_pid_123" {
Ok(Self {
id: 1,
email: "test@example.com".to_string(),
})
} else {
Err(ModelError::EntityNotFound)
}
}
async fn find_by_api_key(
_db: &sea_orm::DatabaseConnection,
api_key: &str,
) -> Result<Self, ModelError> {
// Simple mock: return user if api_key matches, otherwise not found
if api_key == "test_api_key_123" {
Ok(Self {
id: 1,
email: "test@example.com".to_string(),
})
} else {
Err(ModelError::EntityNotFound)
}
}
}
// Test handler for ApiToken extractor
async fn api_token_handler(auth: auth::ApiToken<TestUser>) -> Result<Response> {
format::json(TestUserResponse {
pid: String::new(), // API tokens don't have PIDs
user_id: auth.user.id,
user_email: auth.user.email,
})
}
// Test ApiToken extractor with valid API key
#[tokio::test]
async fn can_extract_api_token_valid() {
let ctx = tests_cfg::app::get_app_context().await;
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", get(api_token_handler), Some(port)).await;
let client = reqwest::Client::new();
let res = client
.get(get_base_url_port(port))
.header("Authorization", "Bearer test_api_key_123")
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 200);
let body: TestUserResponse = res.json().await.expect("Valid JSON response");
assert_eq!(body.pid, ""); // API tokens don't have PIDs
assert_eq!(body.user_id, 1);
assert_eq!(body.user_email, "test@example.com");
handle.abort();
}
// Test ApiToken extractor with invalid API key
#[tokio::test]
async fn can_handle_api_token_invalid() {
let ctx = tests_cfg::app::get_app_context().await;
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", get(api_token_handler), Some(port)).await;
let client = reqwest::Client::new();
let res = client
.get(get_base_url_port(port))
.header("Authorization", "Bearer invalid_api_key")
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 401);
handle.abort();
}
// Test ApiToken extractor with missing Authorization header
#[tokio::test]
async fn can_handle_api_token_missing() {
let ctx = tests_cfg::app::get_app_context().await;
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", get(api_token_handler), Some(port)).await;
let client = reqwest::Client::new();
let res = client
.get(get_base_url_port(port))
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 401);
handle.abort();
}
// Test response serialization
#[tokio::test]
async fn test_user_response_serialization() {
let response = TestUserResponse {
pid: "test_pid".to_string(),
user_id: 1,
user_email: "test@example.com".to_string(),
};
let json = serde_json::to_string(&response).expect("Should serialize");
let deserialized: TestUserResponse = serde_json::from_str(&json).expect("Should deserialize");
assert_eq!(response.pid, deserialized.pid);
assert_eq!(response.user_id, deserialized.user_id);
assert_eq!(response.user_email, deserialized.user_email);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,206 @@
use loco_rs::{controller::extractor::auth, prelude::*, tests_cfg};
use serde::{Deserialize, Serialize};
use loco_rs::model::{Authenticable, ModelError};
use crate::infra_cfg;
#[derive(Debug, Deserialize, Serialize)]
pub struct TestUserResponse {
pub pid: String,
pub user_id: i32,
pub user_email: String,
}
// Mock user struct for testing JWTWithUser extractor
#[derive(Debug, Clone)]
struct TestUser {
id: i32,
email: String,
}
#[async_trait::async_trait]
impl Authenticable for TestUser {
async fn find_by_claims_key(
_db: &sea_orm::DatabaseConnection,
pid: &str,
) -> Result<Self, ModelError> {
// Simple mock: return user if pid matches, otherwise not found
if pid == "test_pid_123" {
Ok(Self {
id: 1,
email: "test@example.com".to_string(),
})
} else {
Err(ModelError::EntityNotFound)
}
}
async fn find_by_api_key(
_db: &sea_orm::DatabaseConnection,
api_key: &str,
) -> Result<Self, ModelError> {
// Simple mock: return user if api_key matches, otherwise not found
if api_key == "test_api_key_123" {
Ok(Self {
id: 1,
email: "test@example.com".to_string(),
})
} else {
Err(ModelError::EntityNotFound)
}
}
}
// Test handler for JWTWithUser extractor
async fn jwt_with_user_handler(auth: auth::JWTWithUser<TestUser>) -> Result<Response> {
format::json(TestUserResponse {
pid: auth.claims.pid,
user_id: auth.user.id,
user_email: auth.user.email,
})
}
// Test JWTWithUser extractor with valid token
#[tokio::test]
async fn can_extract_jwt_with_user_valid_token() {
let mut ctx = tests_cfg::app::get_app_context().await;
// Configure JWT auth
let secret = "PqRwLF2rhHe8J22oBeHy".to_string();
ctx.config.auth = Some(loco_rs::config::Auth {
jwt: Some(loco_rs::config::JWT {
location: None,
secret: secret.clone(),
expiration: 3600,
}),
});
// Create a valid JWT token with known PID
let jwt = loco_rs::auth::jwt::JWT::new(&secret);
let token = jwt
.generate_token(3600, "test_pid_123".to_string(), serde_json::Map::new())
.expect("Failed to generate token");
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", get(jwt_with_user_handler), Some(port)).await;
let client = reqwest::Client::new();
let res = client
.get(get_base_url_port(port))
.header("Authorization", format!("Bearer {token}"))
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 200);
let body: TestUserResponse = res.json().await.expect("Valid JSON response");
assert_eq!(body.pid, "test_pid_123");
assert_eq!(body.user_id, 1);
assert_eq!(body.user_email, "test@example.com");
handle.abort();
}
// Test JWTWithUser extractor with invalid token
#[tokio::test]
async fn can_handle_jwt_with_user_invalid_token() {
let mut ctx = tests_cfg::app::get_app_context().await;
// Configure JWT auth
let secret = "PqRwLF2rhHe8J22oBeHy".to_string();
ctx.config.auth = Some(loco_rs::config::Auth {
jwt: Some(loco_rs::config::JWT {
location: None,
secret: secret.clone(),
expiration: 3600,
}),
});
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", get(jwt_with_user_handler), Some(port)).await;
let client = reqwest::Client::new();
let res = client
.get(get_base_url_port(port))
.header("Authorization", "Bearer invalid_token")
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 401);
handle.abort();
}
// Test JWTWithUser extractor with non-existent user
#[tokio::test]
async fn can_handle_jwt_with_user_nonexistent_user() {
let mut ctx = tests_cfg::app::get_app_context().await;
// Configure JWT auth
let secret = "PqRwLF2rhHe8J22oBeHy".to_string();
ctx.config.auth = Some(loco_rs::config::Auth {
jwt: Some(loco_rs::config::JWT {
location: None,
secret: secret.clone(),
expiration: 3600,
}),
});
// Create a valid JWT token with unknown PID
let jwt = loco_rs::auth::jwt::JWT::new(&secret);
let token = jwt
.generate_token(3600, "unknown_pid".to_string(), serde_json::Map::new())
.expect("Failed to generate token");
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", get(jwt_with_user_handler), Some(port)).await;
let client = reqwest::Client::new();
let res = client
.get(get_base_url_port(port))
.header("Authorization", format!("Bearer {token}"))
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 401);
handle.abort();
}
// Test JWTWithUser extractor with missing token
#[tokio::test]
async fn can_handle_jwt_with_user_missing_token() {
let mut ctx = tests_cfg::app::get_app_context().await;
// Configure JWT auth
let secret = "PqRwLF2rhHe8J22oBeHy".to_string();
ctx.config.auth = Some(loco_rs::config::Auth {
jwt: Some(loco_rs::config::JWT {
location: None,
secret: secret.clone(),
expiration: 3600,
}),
});
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", get(jwt_with_user_handler), Some(port)).await;
let client = reqwest::Client::new();
let res = client
.get(get_base_url_port(port))
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 401);
handle.abort();
}
@@ -0,0 +1,7 @@
mod jwt;
#[cfg(feature = "with-db")]
mod jwt_with_user;
#[cfg(feature = "with-db")]
mod api_token;
@@ -0,0 +1,4 @@
mod auth;
mod shared_store;
mod validate;
mod view_engine;
@@ -0,0 +1,87 @@
use axum::extract::State;
use loco_rs::{controller::format, prelude::*, tests_cfg};
use rstest::rstest;
use serde::{Deserialize, Serialize};
use crate::infra_cfg;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct MySharedData {
message: String,
}
struct MySharedDataWithoutClone {
message: String,
}
#[rstest]
#[case(true)]
#[case(false)]
#[tokio::test]
async fn test_shared_store_extractor(#[case] exists: bool) {
async fn action(
State(_ctx): State<AppContext>,
SharedStore(shared_data): SharedStore<MySharedData>,
) -> Result<Response> {
format::json(&shared_data)
}
let ctx: AppContext = tests_cfg::app::get_app_context().await;
let test_data = MySharedData {
message: "Hello from SharedStore!".to_string(),
};
if exists {
ctx.shared_store.insert(test_data.clone());
}
let port = get_available_port().await;
let handle = infra_cfg::server::start_with_route(ctx, "/", get(action), Some(port)).await;
let res = reqwest::get(get_base_url_port(port))
.await
.expect("Failed to make request");
if exists {
assert_eq!(res.status(), axum::http::StatusCode::OK);
let body: MySharedData = res.json().await.expect("Failed to parse response body");
assert_eq!(body, test_data);
} else {
assert_eq!(res.status(), axum::http::StatusCode::INTERNAL_SERVER_ERROR);
}
handle.abort();
}
#[tokio::test]
async fn test_shared_store_without_clone() {
async fn action(State(ctx): State<AppContext>) -> Result<Response> {
let shared_data_ref = ctx
.shared_store
.get_ref::<MySharedDataWithoutClone>()
.ok_or_else(|| Error::InternalServerError)?;
format::text(&shared_data_ref.message)
}
let ctx: AppContext = tests_cfg::app::get_app_context().await;
let test_data = MySharedDataWithoutClone {
message: "Hello from SharedStore!".to_string(),
};
ctx.shared_store.insert(test_data);
let port = get_available_port().await;
let handle = infra_cfg::server::start_with_route(ctx, "/", get(action), Some(port)).await;
let res = reqwest::get(get_base_url_port(port))
.await
.expect("Failed to make request");
assert_eq!(res.status(), axum::http::StatusCode::OK);
let body = res.text().await.expect("Failed to parse response body");
assert_eq!(body, "Hello from SharedStore!");
handle.abort();
}
@@ -0,0 +1,90 @@
use loco_rs::{prelude::*, tests_cfg};
use serde::{Deserialize, Serialize};
use validator::Validate;
use crate::infra_cfg;
#[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Data {
#[validate(length(min = 5, message = "message_str"))]
pub name: String,
#[validate(email)]
pub email: String,
}
async fn validation_with_response(
JsonValidateWithMessage(_params): JsonValidateWithMessage<Data>,
) -> Result<Response> {
format::json(())
}
async fn simple_validation(JsonValidate(_params): JsonValidate<Data>) -> Result<Response> {
format::json(())
}
#[tokio::test]
async fn can_validation_with_response() {
let ctx = tests_cfg::app::get_app_context().await;
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", post(validation_with_response), Some(port))
.await;
let client = reqwest::Client::new();
let res = client
.post(get_base_url_port(port))
.json(&serde_json::json!({"name": "test", "email": "invalid"}))
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 400);
let res_text = res.text().await.expect("response text");
let res_json: serde_json::Value = serde_json::from_str(&res_text).expect("Valid JSON response");
let expected_json = serde_json::json!(
{
"errors":{
"email":[{"code":"email","message":null,"params":{"value":"invalid"}}],
"name":[{"code":"length","message":"message_str","params":{"min":5,"value":"test"}}]
}
});
assert_eq!(res_json, expected_json);
handle.abort();
}
#[tokio::test]
async fn can_validation_without_response() {
let ctx = tests_cfg::app::get_app_context().await;
let port = get_available_port().await;
let handle =
infra_cfg::server::start_with_route(ctx, "/", post(simple_validation), Some(port)).await;
let client = reqwest::Client::new();
let res = client
.post(get_base_url_port(port))
.json(&serde_json::json!({"name": "test", "email": "invalid"}))
.send()
.await
.expect("Valid response");
assert_eq!(res.status(), 400);
let res_text = res.text().await.expect("response text");
let res_json: serde_json::Value = serde_json::from_str(&res_text).expect("Valid JSON response");
let expected_json = serde_json::json!(
{
"error": "Bad Request"
}
);
assert_eq!(res_json, expected_json);
handle.abort();
}
@@ -0,0 +1,26 @@
use loco_rs::{prelude::*, tests_cfg};
use crate::infra_cfg;
async fn action(ViewEngine(_engine): ViewEngine<()>) -> Result<Response> {
format::json(())
}
/// When the `ViewEngine` layer (`Extension<ViewEngine<E>>`) was never
/// installed, the extractor must reject gracefully with an error response
/// instead of panicking.
#[tokio::test]
async fn missing_layer_rejects_gracefully() {
let ctx = tests_cfg::app::get_app_context().await;
let port = get_available_port().await;
let handle = infra_cfg::server::start_with_route(ctx, "/", get(action), Some(port)).await;
let res = reqwest::get(get_base_url_port(port))
.await
.expect("valid response");
assert_eq!(res.status(), axum::http::StatusCode::INTERNAL_SERVER_ERROR);
handle.abort();
}