use freenet::test_utils::{
Task, TestContext, TodoList, create_large_todo_list, create_minimal_state,
create_oversized_todo_list, load_contract, make_get, make_put, make_update,
};
use freenet_macros::freenet_test;
use freenet_stdlib::{
client_api::{ClientRequest, ContractResponse, HostResponse, WebApi},
prelude::*,
};
use std::time::Duration;
use tokio::time::timeout;
use tokio_tungstenite::connect_async;
#[freenet_test(
health_check_readiness = true,
nodes = ["gateway"],
timeout_secs = 60,
startup_wait_secs = 10,
tokio_flavor = "multi_thread"
)]
async fn test_minimal_state_put_get(ctx: &mut TestContext) -> TestResult {
let gateway = ctx.gateway()?;
const TEST_CONTRACT: &str = "test-contract-integration";
let contract = load_contract(TEST_CONTRACT, vec![].into())?;
let contract_key = contract.key();
let minimal_state = create_minimal_state();
let wrapped_state = WrappedState::from(minimal_state);
let (ws_stream, _) = connect_async(&gateway.ws_url()).await?;
let mut client = WebApi::start(ws_stream);
make_put(&mut client, wrapped_state.clone(), contract.clone(), false).await?;
let put_result = timeout(Duration::from_secs(30), client.recv()).await;
match put_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::PutResponse { key }))) => {
assert_eq!(key, contract_key);
}
other => panic!("Unexpected PUT response: {:?}", other),
}
make_get(&mut client, contract_key, true, false).await?;
let get_result = timeout(Duration::from_secs(10), client.recv()).await;
match get_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::GetResponse {
state: recv_state,
..
}))) => {
assert_eq!(recv_state, wrapped_state, "Minimal state should round-trip");
}
other => panic!("Unexpected GET response: {:?}", other),
}
client
.send(ClientRequest::Disconnect { cause: None })
.await?;
Ok(())
}
#[freenet_test(
health_check_readiness = true,
nodes = ["gateway"],
timeout_secs = 90,
startup_wait_secs = 10,
tokio_flavor = "multi_thread"
)]
async fn test_large_state_put_get(ctx: &mut TestContext) -> TestResult {
let gateway = ctx.gateway()?;
const TEST_CONTRACT: &str = "test-contract-integration";
let contract = load_contract(TEST_CONTRACT, vec![].into())?;
let contract_key = contract.key();
let large_state = create_large_todo_list();
let wrapped_state = WrappedState::from(large_state.clone());
tracing::info!("Testing with large state size: {} bytes", large_state.len());
let (ws_stream, _) = connect_async(&gateway.ws_url()).await?;
let mut client = WebApi::start(ws_stream);
make_put(&mut client, wrapped_state.clone(), contract.clone(), false).await?;
let put_result = timeout(Duration::from_secs(60), client.recv()).await;
match put_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::PutResponse { key }))) => {
assert_eq!(key, contract_key);
tracing::info!("Large state PUT successful");
}
other => panic!("Unexpected PUT response: {:?}", other),
}
make_get(&mut client, contract_key, true, false).await?;
let get_result = timeout(Duration::from_secs(30), client.recv()).await;
match get_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::GetResponse {
state: recv_state,
..
}))) => {
assert_eq!(recv_state, wrapped_state, "Large state should round-trip");
tracing::info!(
"Large state GET successful, verified {} bytes",
large_state.len()
);
}
other => panic!("Unexpected GET response: {:?}", other),
}
client
.send(ClientRequest::Disconnect { cause: None })
.await?;
Ok(())
}
#[freenet_test(
health_check_readiness = true,
nodes = ["gateway"],
timeout_secs = 60,
startup_wait_secs = 10,
tokio_flavor = "multi_thread"
)]
async fn test_empty_state_put_get(ctx: &mut TestContext) -> TestResult {
let gateway = ctx.gateway()?;
const TEST_CONTRACT: &str = "test-contract-integration";
let contract = load_contract(TEST_CONTRACT, vec![].into())?;
let contract_key = contract.key();
let empty_state = freenet::test_utils::create_empty_todo_list();
let wrapped_state = WrappedState::from(empty_state);
let (ws_stream, _) = connect_async(&gateway.ws_url()).await?;
let mut client = WebApi::start(ws_stream);
make_put(&mut client, wrapped_state.clone(), contract.clone(), false).await?;
let put_result = timeout(Duration::from_secs(30), client.recv()).await;
match put_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::PutResponse { key }))) => {
assert_eq!(key, contract_key);
}
other => panic!("Unexpected PUT response: {:?}", other),
}
make_get(&mut client, contract_key, true, false).await?;
let get_result = timeout(Duration::from_secs(10), client.recv()).await;
match get_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::GetResponse {
state: recv_state,
..
}))) => {
assert_eq!(recv_state, wrapped_state, "Empty state should round-trip");
let state_str = String::from_utf8_lossy(recv_state.as_ref());
assert!(
state_str.contains("\"tasks\":[]"),
"Empty state should have zero tasks"
);
}
other => panic!("Unexpected GET response: {:?}", other),
}
client
.send(ClientRequest::Disconnect { cause: None })
.await?;
Ok(())
}
#[freenet_test(
health_check_readiness = true,
nodes = ["gateway"],
timeout_secs = 120,
startup_wait_secs = 10,
tokio_flavor = "multi_thread"
)]
async fn test_oversized_state_handling(ctx: &mut TestContext) -> TestResult {
let gateway = ctx.gateway()?;
const TEST_CONTRACT: &str = "test-contract-integration";
let contract = load_contract(TEST_CONTRACT, vec![].into())?;
let oversized_state = create_oversized_todo_list();
let wrapped_state = WrappedState::from(oversized_state.clone());
tracing::info!(
"Testing oversized state handling: {} bytes ({:.2} MB)",
oversized_state.len(),
oversized_state.len() as f64 / (1024.0 * 1024.0)
);
assert!(
oversized_state.len() > 10_000_000,
"State should exceed 10MB for this test"
);
let (ws_stream, _) = connect_async(&gateway.ws_url()).await?;
let mut client = WebApi::start(ws_stream);
make_put(&mut client, wrapped_state.clone(), contract.clone(), false).await?;
let put_result = timeout(Duration::from_secs(60), client.recv()).await;
match put_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::PutResponse { key }))) => {
tracing::warn!(
"Oversized state was accepted (key: {}). System may not enforce size limits.",
key
);
}
Ok(Err(err)) => {
tracing::info!(
"Oversized state was rejected with error (expected): {:?}",
err
);
}
Err(_timeout) => {
tracing::info!("Oversized state PUT timed out (acceptable behavior)");
}
Ok(Ok(other)) => {
tracing::info!("Oversized state got unexpected response: {:?}", other);
}
}
let _disconnect = client.send(ClientRequest::Disconnect { cause: None }).await;
Ok(())
}
#[freenet_test(
health_check_readiness = true,
nodes = ["gateway"],
timeout_secs = 90,
startup_wait_secs = 10,
tokio_flavor = "multi_thread"
)]
async fn test_update_no_state_change(ctx: &mut TestContext) -> TestResult {
let gateway = ctx.gateway()?;
const TEST_CONTRACT: &str = "test-contract-integration";
let contract = load_contract(TEST_CONTRACT, vec![].into())?;
let contract_key = contract.key();
let todo_list = TodoList {
tasks: vec![],
version: 1,
};
let initial_state = serde_json::to_vec(&todo_list)?;
let wrapped_state = WrappedState::from(initial_state);
let (ws_stream, _) = connect_async(&gateway.ws_url()).await?;
let mut client = WebApi::start(ws_stream);
make_put(&mut client, wrapped_state.clone(), contract.clone(), false).await?;
let put_result = timeout(Duration::from_secs(30), client.recv()).await;
match put_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::PutResponse { key }))) => {
assert_eq!(key, contract_key);
tracing::info!("Initial PUT successful");
}
other => panic!("Unexpected PUT response: {:?}", other),
}
tracing::info!("Sending UPDATE with identical state to trigger NoChange scenario");
make_update(&mut client, contract_key, wrapped_state.clone()).await?;
let update_result = timeout(Duration::from_secs(30), client.recv()).await;
match update_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::UpdateResponse {
key,
summary: _,
}))) => {
assert_eq!(key, contract_key);
tracing::info!("UPDATE with no state change completed successfully");
}
Ok(Err(err)) => {
tracing::info!("UPDATE returned error (acceptable): {:?}", err);
}
Err(_timeout) => {
panic!("UPDATE operation timed out - possible regression of bug #1734");
}
other => panic!("Unexpected UPDATE response: {:?}", other),
}
client
.send(ClientRequest::Disconnect { cause: None })
.await?;
Ok(())
}
#[freenet_test(
health_check_readiness = true,
nodes = ["gateway"],
timeout_secs = 90,
startup_wait_secs = 10,
tokio_flavor = "multi_thread"
)]
async fn test_state_at_boundary_size(ctx: &mut TestContext) -> TestResult {
let gateway = ctx.gateway()?;
const TEST_CONTRACT: &str = "test-contract-integration";
let contract = load_contract(TEST_CONTRACT, vec![].into())?;
let contract_key = contract.key();
const TARGET_SIZE: usize = 950 * 1024; let mut tasks = Vec::new();
let mut current_size = 0;
while current_size < TARGET_SIZE {
let task = Task {
id: tasks.len() as u64,
title: format!("Boundary test task {}", tasks.len()),
description: "X".repeat(100), completed: false,
priority: 3,
};
current_size += 150;
tasks.push(task);
}
let boundary_state = TodoList { tasks, version: 1 };
let state_bytes = serde_json::to_vec(&boundary_state)?;
let wrapped_state = WrappedState::from(state_bytes.clone());
tracing::info!(
"Testing boundary state: {} bytes ({:.2} KB)",
state_bytes.len(),
state_bytes.len() as f64 / 1024.0
);
let (ws_stream, _) = connect_async(&gateway.ws_url()).await?;
let mut client = WebApi::start(ws_stream);
make_put(&mut client, wrapped_state.clone(), contract.clone(), false).await?;
let put_result = timeout(Duration::from_secs(60), client.recv()).await;
match put_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::PutResponse { key }))) => {
assert_eq!(key, contract_key);
tracing::info!("Boundary state PUT successful");
}
other => panic!("Unexpected PUT response for boundary state: {:?}", other),
}
make_get(&mut client, contract_key, true, false).await?;
let get_result = timeout(Duration::from_secs(30), client.recv()).await;
match get_result {
Ok(Ok(HostResponse::ContractResponse(ContractResponse::GetResponse {
state: recv_state,
..
}))) => {
assert_eq!(
recv_state, wrapped_state,
"Boundary state should round-trip"
);
tracing::info!(
"Boundary state GET successful, verified {} bytes",
state_bytes.len()
);
}
other => panic!("Unexpected GET response for boundary state: {:?}", other),
}
client
.send(ClientRequest::Disconnect { cause: None })
.await?;
Ok(())
}