use actix_web::test; use quoter::auth::{ Author, authenticate_request, extract_token_from_request, secure_token_validation, }; use std::time::Duration; /// Тест извлечения токена из различных источников #[test] async fn test_extract_token_from_request() { // Тест Bearer токена в Authorization header let req = test::TestRequest::default() .insert_header(("authorization", "Bearer test-jwt-token-123")) .to_http_request(); let token = extract_token_from_request(&req); assert_eq!(token, Some("test-jwt-token-123".to_string())); // Тест кастомного заголовка X-Session-Token let req = test::TestRequest::default() .insert_header(("x-session-token", "custom-session-token-456")) .to_http_request(); let token = extract_token_from_request(&req); assert_eq!(token, Some("custom-session-token-456".to_string())); // Тест Cookie let req = test::TestRequest::default() .insert_header(("cookie", "session_token=cookie-token-789; other=value")) .to_http_request(); let token = extract_token_from_request(&req); assert_eq!(token, Some("cookie-token-789".to_string())); // Тест отсутствия токена let req = test::TestRequest::default().to_http_request(); let token = extract_token_from_request(&req); assert_eq!(token, None); } /// Тест валидации формата токена #[test] async fn test_token_format_validation() { // Тест пустого токена let result = secure_token_validation("", None, Duration::from_secs(5)).await; assert!(result.is_err()); // Тест слишком короткого токена let result = secure_token_validation("short", None, Duration::from_secs(5)).await; assert!(result.is_err()); // Тест невалидного JWT (не 3 части) let result = secure_token_validation("invalid.jwt", None, Duration::from_secs(5)).await; assert!(result.is_err()); } /// Тест создания валидного JWT токена для тестов fn create_test_jwt_token(user_id: &str, username: Option<&str>) -> String { use jsonwebtoken::{EncodingKey, Header, encode}; use serde::Serialize; #[derive(Serialize)] struct Claims { user_id: String, username: Option, exp: usize, iat: usize, } let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() as usize; let claims = Claims { user_id: user_id.to_string(), username: username.map(|s| s.to_string()), exp: now + 3600, // Истекает через час iat: now, }; let secret = std::env::var("JWT_SECRET_KEY") .or_else(|_| std::env::var("JWT_SECRET_KEY")) .unwrap_or_else(|_| "your-secret-key".to_string()); let key = EncodingKey::from_secret(secret.as_ref()); encode(&Header::default(), &claims, &key).unwrap() } /// Тест валидации валидного JWT токена без Redis #[tokio::test] async fn test_valid_jwt_token_without_redis() { let token = create_test_jwt_token("test-user-123", Some("testuser")); let result = secure_token_validation(&token, None, Duration::from_secs(5)).await; assert!(result.is_ok()); let author = result.unwrap(); assert_eq!(author.user_id, "test-user-123"); assert_eq!(author.username, Some("testuser".to_string())); assert_eq!(author.token_type, Some("jwt".to_string())); assert_eq!(author.auth_data, Some("jwt_only".to_string())); } /// Тест валидации истекшего JWT токена #[tokio::test] async fn test_expired_jwt_token() { use jsonwebtoken::{EncodingKey, Header, encode}; use serde::Serialize; #[derive(Serialize)] struct Claims { user_id: String, username: Option, exp: usize, iat: usize, } let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() as usize; let claims = Claims { user_id: "test-user-123".to_string(), username: Some("testuser".to_string()), exp: now - 3600, // Истек час назад iat: now - 7200, // Создан 2 часа назад }; let secret = std::env::var("JWT_SECRET_KEY") .or_else(|_| std::env::var("JWT_SECRET_KEY")) .unwrap_or_else(|_| "your-secret-key".to_string()); let key = EncodingKey::from_secret(secret.as_ref()); let token = encode(&Header::default(), &claims, &key).unwrap(); let result = secure_token_validation(&token, None, Duration::from_secs(5)).await; assert!(result.is_err()); } /// Тест универсальной функции аутентификации #[tokio::test] async fn test_authenticate_request() { let token = create_test_jwt_token("test-user-456", Some("anotheruser")); // Тест с Bearer токеном let req = test::TestRequest::default() .insert_header(("authorization", format!("Bearer {}", token))) .to_http_request(); let result = authenticate_request(&req, None, Duration::from_secs(5)).await; assert!(result.is_ok()); let author = result.unwrap(); assert_eq!(author.user_id, "test-user-456"); assert_eq!(author.username, Some("anotheruser".to_string())); // Тест без токена let req = test::TestRequest::default().to_http_request(); let result = authenticate_request(&req, None, Duration::from_secs(5)).await; assert!(result.is_err()); } /// Тест производительности аутентификации #[tokio::test] async fn test_authentication_performance() { use std::time::Instant; let token = create_test_jwt_token("perf-user", Some("perfuser")); let iterations = 1000; let start = Instant::now(); for _ in 0..iterations { let req = test::TestRequest::default() .insert_header(("authorization", format!("Bearer {}", token))) .to_http_request(); let result = authenticate_request(&req, None, Duration::from_secs(1)).await; assert!(result.is_ok()); } let duration = start.elapsed(); let avg_time = duration.as_micros() as f64 / iterations as f64; println!( "Authentication performance: {} operations in {:?}, avg: {:.2} μs per auth", iterations, duration, avg_time ); // Проверяем, что аутентификация достаточно быстрая (< 1ms на операцию) assert!( avg_time < 1000.0, "Authentication too slow: {:.2} μs per operation", avg_time ); } /// Тест обработки различных заголовков #[test] async fn test_header_variations() { // Тест с пробелами в Bearer токене let req = test::TestRequest::default() .insert_header(("authorization", "Bearer token-with-spaces ")) .to_http_request(); let token = extract_token_from_request(&req); assert_eq!(token, Some("token-with-spaces".to_string())); // Тест с Authorization без Bearer let req = test::TestRequest::default() .insert_header(("authorization", "Basic dGVzdDp0ZXN0")) .to_http_request(); let token = extract_token_from_request(&req); assert_eq!(token, None); // Тест с несколькими cookies let req = test::TestRequest::default() .insert_header(( "cookie", "first=value1; session_token=my-token; last=value2", )) .to_http_request(); let token = extract_token_from_request(&req); assert_eq!(token, Some("my-token".to_string())); } /// Тест граничных случаев #[tokio::test] async fn test_edge_cases() { // Тест с очень длинным токеном let long_token = "a".repeat(5000); let result = secure_token_validation(&long_token, None, Duration::from_secs(1)).await; assert!(result.is_err()); // Должен быть невалидным JWT // Тест с нулевым таймаутом let token = create_test_jwt_token("timeout-user", None); let result = secure_token_validation(&token, None, Duration::from_secs(0)).await; // Должен работать, так как JWT валидация не требует Redis assert!(result.is_ok()); // Тест с очень большим таймаутом let result = secure_token_validation(&token, None, Duration::from_secs(3600)).await; assert!(result.is_ok()); } /// Тест сериализации Author структуры #[test] async fn test_author_serialization() { let author = Author { user_id: "test-123".to_string(), username: Some("testuser".to_string()), token_type: Some("jwt".to_string()), created_at: Some("1640995200".to_string()), last_activity: Some("1640995260".to_string()), auth_data: Some("jwt_only".to_string()), device_info: None, }; // Тест JSON сериализации let json = serde_json::to_string(&author).unwrap(); assert!(json.contains("test-123")); assert!(json.contains("testuser")); assert!(json.contains("jwt")); // Тест десериализации let deserialized: Author = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.user_id, author.user_id); assert_eq!(deserialized.username, author.username); assert_eq!(deserialized.token_type, author.token_type); } /// Тест безопасности токенов #[test] async fn test_token_security() { // Тест с подозрительными символами let suspicious_tokens = vec![ "'; DROP TABLE users; --", "", "../../../etc/passwd", "\\x00\\x01\\x02", "SELECT * FROM users WHERE id = 1", ]; for suspicious_token in suspicious_tokens { let req = test::TestRequest::default() .insert_header(("authorization", format!("Bearer {}", suspicious_token))) .to_http_request(); let token = extract_token_from_request(&req); assert_eq!(token, Some(suspicious_token.to_string())); // Токен должен быть отклонен при валидации let result = secure_token_validation(suspicious_token, None, Duration::from_secs(1)).await; assert!( result.is_err(), "Suspicious token should be rejected: {}", suspicious_token ); } } /// Интеграционный тест с мокированным Redis #[tokio::test] async fn test_integration_with_mock_redis() { // Этот тест демонстрирует, как можно тестировать с мокированным Redis // В реальном проекте здесь был бы мок Redis соединения let token = create_test_jwt_token("integration-user", Some("integrationuser")); let req = test::TestRequest::default() .insert_header(("authorization", format!("Bearer {}", token))) .to_http_request(); // Тестируем без Redis (None) let result = authenticate_request(&req, None, Duration::from_secs(5)).await; assert!(result.is_ok()); let author = result.unwrap(); assert_eq!(author.user_id, "integration-user"); assert_eq!(author.username, Some("integrationuser".to_string())); assert_eq!(author.auth_data, Some("jwt_only".to_string())); } /// Тест обработки ошибок аутентификации #[tokio::test] async fn test_authentication_error_handling() { // Тест с невалидным JWT let req = test::TestRequest::default() .insert_header(("authorization", "Bearer invalid.jwt.token")) .to_http_request(); let result = authenticate_request(&req, None, Duration::from_secs(5)).await; assert!(result.is_err()); // Проверяем, что ошибка имеет правильный тип match result { Err(e) => { let response = e.error_response(); assert_eq!(response.status(), actix_web::http::StatusCode::UNAUTHORIZED); } Ok(_) => panic!("Expected error, got success"), } } /// Тест множественных токенов в одном запросе #[test] async fn test_multiple_token_sources() { // Если есть несколько источников токенов, должен использоваться первый найденный let req = test::TestRequest::default() .insert_header(("authorization", "Bearer bearer-token")) .insert_header(("x-session-token", "session-token")) .insert_header(("cookie", "session_token=cookie-token")) .to_http_request(); let token = extract_token_from_request(&req); // Должен вернуть Bearer токен (приоритет) assert_eq!(token, Some("bearer-token".to_string())); } /// Тест валидации с различными алгоритмами JWT #[tokio::test] async fn test_jwt_algorithm_validation() { // Тест создания токена с неправильным алгоритмом use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; use serde::Serialize; #[derive(Serialize)] struct Claims { user_id: String, exp: usize, } let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() as usize; let claims = Claims { user_id: "test-user".to_string(), exp: now + 3600, }; // Создаем токен с RS256 вместо HS256 let mut header = Header::default(); header.alg = Algorithm::RS256; let secret = "wrong-secret"; let key = EncodingKey::from_secret(secret.as_ref()); // Этот токен должен быть отклонен, так как мы используем неправильный алгоритм if let Ok(token) = encode(&header, &claims, &key) { let result = secure_token_validation(&token, None, Duration::from_secs(5)).await; assert!(result.is_err()); } }