use freenet::{
config::{ConfigArgs, NetworkArgs, SecretArgs, WebsocketApiArgs},
dev_tool::TransportKeypair,
local_node::OperationMode,
};
use std::net::{Ipv4Addr, TcpListener};
use testresult::TestResult;
use tracing::{Instrument, Level, info, span};
async fn create_test_config(
token_ttl_seconds: u64,
cleanup_interval_seconds: u64,
) -> anyhow::Result<(ConfigArgs, tempfile::TempDir)> {
let temp_dir = tempfile::tempdir()?;
let key = TransportKeypair::new();
let transport_keypair = temp_dir.path().join("private.pem");
key.save(&transport_keypair)?;
let ws_socket = TcpListener::bind("127.0.0.1:0")?;
let network_socket = TcpListener::bind("127.0.0.1:0")?;
let config = ConfigArgs {
mode: Some(OperationMode::Local),
ws_api: WebsocketApiArgs {
address: Some(Ipv4Addr::LOCALHOST.into()),
ws_api_port: Some(ws_socket.local_addr()?.port()),
token_ttl_seconds: Some(token_ttl_seconds),
token_cleanup_interval_seconds: Some(cleanup_interval_seconds),
allowed_host: None,
allowed_source_cidrs: None,
},
network_api: NetworkArgs {
address: Some(Ipv4Addr::LOCALHOST.into()),
network_port: Some(network_socket.local_addr()?.port()),
transient_budget: None,
transient_ttl_secs: None,
..Default::default()
},
config_paths: freenet::config::ConfigPathsArgs {
config_dir: Some(temp_dir.path().to_path_buf()),
data_dir: Some(temp_dir.path().to_path_buf()),
log_dir: Some(temp_dir.path().to_path_buf()),
},
secrets: SecretArgs {
transport_keypair: Some(transport_keypair),
..Default::default()
},
..Default::default()
};
Ok((config, temp_dir))
}
#[test_log::test(tokio::test)]
async fn test_token_configuration() -> TestResult {
let span = span!(Level::INFO, "test_token_configuration");
async move {
const TOKEN_TTL_SECS: u64 = 2;
const CLEANUP_INTERVAL_SECS: u64 = 1;
info!("Creating test configuration with custom token TTL and cleanup interval");
let (config, _temp_dir) = create_test_config(TOKEN_TTL_SECS, CLEANUP_INTERVAL_SECS).await?;
assert_eq!(
config.ws_api.token_ttl_seconds,
Some(TOKEN_TTL_SECS),
"Token TTL should be configured"
);
assert_eq!(
config.ws_api.token_cleanup_interval_seconds,
Some(CLEANUP_INTERVAL_SECS),
"Token cleanup interval should be configured"
);
info!("Building node configuration");
let built_config = config.build().await?;
assert_eq!(
built_config.ws_api.token_ttl_seconds, TOKEN_TTL_SECS,
"Built config should have correct token TTL"
);
assert_eq!(
built_config.ws_api.token_cleanup_interval_seconds, CLEANUP_INTERVAL_SECS,
"Built config should have correct cleanup interval"
);
info!("✓ Configuration accepted custom token values");
info!("✓ Token TTL: {} seconds", TOKEN_TTL_SECS);
info!("✓ Cleanup interval: {} seconds", CLEANUP_INTERVAL_SECS);
Ok(())
}
.instrument(span)
.await
}
#[test_log::test(tokio::test)]
async fn test_default_token_configuration() -> TestResult {
let span = span!(Level::INFO, "test_default_token_configuration");
async move {
info!("Creating test configuration without custom token settings");
let temp_dir = tempfile::tempdir()?;
let key = TransportKeypair::new();
let transport_keypair = temp_dir.path().join("private.pem");
key.save(&transport_keypair)?;
let ws_socket = TcpListener::bind("127.0.0.1:0")?;
let network_socket = TcpListener::bind("127.0.0.1:0")?;
let config = ConfigArgs {
mode: Some(OperationMode::Local),
ws_api: WebsocketApiArgs {
address: Some(Ipv4Addr::LOCALHOST.into()),
ws_api_port: Some(ws_socket.local_addr()?.port()),
token_ttl_seconds: None,
token_cleanup_interval_seconds: None,
allowed_host: None,
allowed_source_cidrs: None,
},
network_api: NetworkArgs {
address: Some(Ipv4Addr::LOCALHOST.into()),
network_port: Some(network_socket.local_addr()?.port()),
transient_budget: None,
transient_ttl_secs: None,
..Default::default()
},
config_paths: freenet::config::ConfigPathsArgs {
config_dir: Some(temp_dir.path().to_path_buf()),
data_dir: Some(temp_dir.path().to_path_buf()),
log_dir: Some(temp_dir.path().to_path_buf()),
},
secrets: SecretArgs {
transport_keypair: Some(transport_keypair),
..Default::default()
},
..Default::default()
};
info!("Building node configuration");
let built_config = config.build().await?;
assert_eq!(
built_config.ws_api.token_ttl_seconds,
86400, "Default token TTL should be 24 hours (86400 seconds)"
);
assert_eq!(
built_config.ws_api.token_cleanup_interval_seconds,
300, "Default cleanup interval should be 5 minutes (300 seconds)"
);
info!("✓ Default token configuration values applied correctly");
info!(" - Token TTL: 86400 seconds (24 hours)");
info!(" - Cleanup interval: 300 seconds (5 minutes)");
Ok(())
}
.instrument(span)
.await
}
#[test_log::test(tokio::test)]
async fn test_token_cleanup_removes_expired_tokens() -> TestResult {
use freenet::{
config::WebsocketApiConfig,
dev_tool::{AuthToken, ClientId},
server::{OriginContract, serve_client_api_for_test},
test_utils,
};
use std::time::Duration;
use tokio::time::sleep;
let span = span!(Level::INFO, "test_token_cleanup_removes_expired_tokens");
async move {
const TOKEN_TTL_SECS: u64 = 2;
const CLEANUP_INTERVAL_SECS: u64 = 1;
info!(
"Starting client API with short token TTL ({} seconds) and cleanup interval ({} seconds)",
TOKEN_TTL_SECS, CLEANUP_INTERVAL_SECS
);
let ws_socket = TcpListener::bind("127.0.0.1:0")?;
let ws_port = ws_socket.local_addr()?.port();
drop(ws_socket);
let config = WebsocketApiConfig {
address: Ipv4Addr::LOCALHOST.into(),
port: ws_port,
token_ttl_seconds: TOKEN_TTL_SECS,
token_cleanup_interval_seconds: CLEANUP_INTERVAL_SECS,
allowed_hosts: Vec::new(),
allowed_source_cidrs: Vec::new(),
};
let (gw, _ws_proxy) = serve_client_api_for_test(config).await?;
let origin_contracts = gw.origin_contracts();
info!("Creating test tokens and contract IDs");
let token1 = AuthToken::generate();
let token2 = AuthToken::generate();
let token3 = AuthToken::generate();
let contract1 = test_utils::load_contract("test-contract-integration", vec![].into())?;
let contract2 = test_utils::load_contract("test-contract-integration", vec![1u8].into())?;
let contract3 = test_utils::load_contract("test-contract-integration", vec![2u8].into())?;
let contract_id1 = contract1.key().into();
let contract_id2 = contract2.key().into();
let contract_id3 = contract3.key().into();
let client_id1 = ClientId::next();
let client_id2 = ClientId::next();
let client_id3 = ClientId::next();
origin_contracts.insert(
token1.clone(),
OriginContract::new(contract_id1, client_id1),
);
origin_contracts.insert(
token2.clone(),
OriginContract::new(contract_id2, client_id2),
);
origin_contracts.insert(
token3.clone(),
OriginContract::new(contract_id3, client_id3),
);
info!("Inserted 3 tokens into origin_contracts map");
assert_eq!(
origin_contracts.len(),
3,
"Should have 3 tokens before expiration"
);
let wait_time = Duration::from_secs(TOKEN_TTL_SECS + CLEANUP_INTERVAL_SECS + 1);
info!(
"Waiting {} seconds for tokens to expire and cleanup task to run",
wait_time.as_secs()
);
sleep(wait_time).await;
let remaining_count = origin_contracts.len();
info!("After cleanup: {} tokens remaining", remaining_count);
assert_eq!(
remaining_count, 0,
"All tokens should be removed after expiration"
);
assert!(
!origin_contracts.contains_key(&token1),
"Token 1 should be removed"
);
assert!(
!origin_contracts.contains_key(&token2),
"Token 2 should be removed"
);
assert!(
!origin_contracts.contains_key(&token3),
"Token 3 should be removed"
);
info!("✓ Cleanup task successfully removed all expired tokens");
Ok(())
}
.instrument(span)
.await
}