Some checks failed
Deploy quoter Microservice on push / deploy (push) Failing after 36m41s
### 🔒 FIX: JWT Token Grace Period - **✅ Добавлен grace period для истекших токенов**: 60 секунд - Изменена логика проверки JWT `exp` в `auth.rs` - Токены принимаются в течение 60 секунд после истечения - Это даёт клиенту время автоматически обновить токен через `refreshToken()` - Логирование разделено: `info` для grace period, `warn` для полного истечения - Решает проблему "Invalid or expired token" при параллельных запросах - Формула: `if exp + 60 < current_time` → reject, иначе accept - Предотвращает race condition: upload начался до истечения, закончился после
216 lines
8.3 KiB
Rust
216 lines
8.3 KiB
Rust
use redis::{AsyncCommands, Client};
|
||
use std::env;
|
||
|
||
#[tokio::test]
|
||
async fn test_redis_url_parsing() {
|
||
// Тестируем различные форматы Redis URL
|
||
|
||
let test_urls = vec![
|
||
// Простой URL без пароля
|
||
"redis://localhost:6379",
|
||
// URL с паролем (должен использовать дефолтное имя пользователя 'redis')
|
||
"redis://:password@localhost:6379",
|
||
// URL с пользователем и паролем
|
||
"redis://user:password@localhost:6379",
|
||
// URL с длинным hex паролем (как в Dokku) - должен использовать дефолтное имя 'redis'
|
||
"redis://:dbc5f9f9007c555e209964454c9d6abecae5f1db72e490acd5c94354dc012282@dokku-redis-discoursio-redis:6379",
|
||
// URL с IP адресом
|
||
"redis://172.17.0.3:6379",
|
||
// URL с exposed портом
|
||
"redis://localhost:12389",
|
||
// Тест с явным указанием дефолтного пользователя 'redis'
|
||
"redis://redis:password@localhost:6379",
|
||
// Тест с пустым пользователем и паролем (должен работать как без аутентификации)
|
||
"redis://:@localhost:6379",
|
||
];
|
||
|
||
for url in test_urls {
|
||
// Парсим URL для детального вывода
|
||
if let Ok(parsed) = url::Url::parse(url) {
|
||
println!(
|
||
"Testing Redis URL: {}",
|
||
url.replace(url.split('@').next().unwrap_or(""), "***")
|
||
);
|
||
println!(" Host: {}", parsed.host_str().unwrap_or("none"));
|
||
println!(" Port: {}", parsed.port().unwrap_or(0));
|
||
println!(" Username: '{}'", parsed.username());
|
||
println!(
|
||
" Password: '{}'",
|
||
if parsed.password().is_some() {
|
||
"***"
|
||
} else {
|
||
"none"
|
||
}
|
||
);
|
||
} else {
|
||
println!("Testing Redis URL: {}", url);
|
||
}
|
||
|
||
match Client::open(url) {
|
||
Ok(client) => {
|
||
println!("✅ URL parsed successfully");
|
||
|
||
// Попробуем подключиться (с коротким таймаутом)
|
||
match tokio::time::timeout(
|
||
std::time::Duration::from_secs(2),
|
||
client.get_multiplexed_async_connection(),
|
||
)
|
||
.await
|
||
{
|
||
Ok(Ok(_)) => println!("✅ Connection successful"),
|
||
Ok(Err(e)) => println!("❌ Connection failed: {}", e),
|
||
Err(_) => println!("⏰ Connection timeout"),
|
||
}
|
||
}
|
||
Err(e) => {
|
||
println!("❌ URL parsing failed: {}", e);
|
||
}
|
||
}
|
||
println!("---");
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_redis_connection_with_env() {
|
||
// Тестируем с реальным REDIS_URL из окружения
|
||
if let Ok(redis_url) = env::var("REDIS_URL") {
|
||
println!(
|
||
"Testing with real REDIS_URL: {}",
|
||
redis_url.replace(redis_url.split('@').next().unwrap_or(""), "***")
|
||
);
|
||
|
||
// Парсим реальный URL для детального вывода
|
||
if let Ok(parsed) = url::Url::parse(&redis_url) {
|
||
println!(" Host: {}", parsed.host_str().unwrap_or("none"));
|
||
println!(" Port: {}", parsed.port().unwrap_or(0));
|
||
println!(" Username: '{}'", parsed.username());
|
||
println!(
|
||
" Password: '{}'",
|
||
if parsed.password().is_some() {
|
||
"***"
|
||
} else {
|
||
"none"
|
||
}
|
||
);
|
||
}
|
||
|
||
match Client::open(redis_url) {
|
||
Ok(client) => {
|
||
println!("✅ Real URL parsed successfully");
|
||
|
||
match tokio::time::timeout(
|
||
std::time::Duration::from_secs(5),
|
||
client.get_multiplexed_async_connection(),
|
||
)
|
||
.await
|
||
{
|
||
Ok(Ok(mut conn)) => {
|
||
println!("✅ Real connection successful");
|
||
|
||
// Попробуем выполнить простую команду
|
||
match tokio::time::timeout(
|
||
std::time::Duration::from_secs(2),
|
||
conn.ping::<String>(),
|
||
)
|
||
.await
|
||
{
|
||
Ok(Ok(result)) => println!("✅ PING successful: {}", result),
|
||
Ok(Err(e)) => println!("❌ PING failed: {}", e),
|
||
Err(_) => println!("⏰ PING timeout"),
|
||
}
|
||
}
|
||
Ok(Err(e)) => println!("❌ Real connection failed: {}", e),
|
||
Err(_) => println!("⏰ Real connection timeout"),
|
||
}
|
||
}
|
||
Err(e) => {
|
||
println!("❌ Real URL parsing failed: {}", e);
|
||
}
|
||
}
|
||
} else {
|
||
println!("⚠️ REDIS_URL not set, skipping real connection test");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_redis_url_components() {
|
||
// Тестируем парсинг компонентов URL
|
||
let test_url = "redis://:dbc5f9f9007c555e209964454c9d6abecae5f1db72e490acd5c94354dc012282@dokku-redis-discoursio-redis:6379";
|
||
|
||
if let Ok(parsed) = url::Url::parse(test_url) {
|
||
println!("✅ URL parsed successfully");
|
||
println!(" Scheme: {}", parsed.scheme());
|
||
println!(" Host: {}", parsed.host_str().unwrap_or("none"));
|
||
println!(" Port: {}", parsed.port().unwrap_or(0));
|
||
println!(" Username: '{}'", parsed.username());
|
||
println!(
|
||
" Password: {}",
|
||
if parsed.password().is_some() {
|
||
"***"
|
||
} else {
|
||
"none"
|
||
}
|
||
);
|
||
println!(" Path: {}", parsed.path());
|
||
|
||
// Проверяем, что пустое имя пользователя означает дефолтное 'redis'
|
||
if parsed.username().is_empty() && parsed.password().is_some() {
|
||
println!(
|
||
" ⚠️ Empty username with password - Redis client should use default 'redis' user"
|
||
);
|
||
}
|
||
} else {
|
||
println!("❌ URL parsing failed");
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_redis_default_username_behavior() {
|
||
// Тестируем поведение Redis client с пустым именем пользователя
|
||
println!("Testing Redis default username behavior...");
|
||
|
||
let test_cases = vec![
|
||
(
|
||
"redis://:password@localhost:6379",
|
||
"Empty username with password",
|
||
),
|
||
(
|
||
"redis://redis:password@localhost:6379",
|
||
"Explicit redis username",
|
||
),
|
||
("redis://:@localhost:6379", "Empty username and password"),
|
||
("redis://localhost:6379", "No authentication"),
|
||
];
|
||
|
||
for (url, description) in test_cases {
|
||
println!("\n--- {} ---", description);
|
||
println!(
|
||
"URL: {}",
|
||
url.replace(url.split('@').next().unwrap_or(""), "***")
|
||
);
|
||
|
||
if let Ok(parsed) = url::Url::parse(url) {
|
||
println!(" Parsed Username: '{}'", parsed.username());
|
||
println!(
|
||
" Parsed Password: {}",
|
||
if parsed.password().is_some() {
|
||
"***"
|
||
} else {
|
||
"none"
|
||
}
|
||
);
|
||
|
||
// Redis client поведение:
|
||
// - Если username пустой, но есть password -> использует дефолтное имя 'redis'
|
||
// - Если username и password пустые -> без аутентификации
|
||
if parsed.username().is_empty() && parsed.password().is_some() {
|
||
println!(" Expected Redis behavior: Use default username 'redis'");
|
||
} else if parsed.username().is_empty() && parsed.password().is_none() {
|
||
println!(" Expected Redis behavior: No authentication");
|
||
} else {
|
||
println!(" Expected Redis behavior: Use explicit credentials");
|
||
}
|
||
}
|
||
}
|
||
}
|