use std::time::Duration;
use hashgraph_like_consensus::{
error::ConsensusError, scope::ScopeID, scope_config::NetworkType,
service::DefaultConsensusService, session::ConsensusConfig, storage::ConsensusStorage,
types::CreateProposalRequest,
};
const SCOPE_NAME: &str = "test_scope";
const PROPOSAL_PAYLOAD: Vec<u8> = vec![];
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
const DEFAULT_DOUBLE_TIMEOUT: Duration = Duration::from_secs(120);
const DEFAULT_SHORT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn test_scope_config_creation() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from(SCOPE_NAME);
service
.scope(&scope)
.await
.unwrap()
.with_network_type(NetworkType::P2P)
.with_threshold(0.75)
.with_timeout(DEFAULT_DOUBLE_TIMEOUT)
.with_liveness_criteria(true)
.initialize()
.await
.unwrap();
let config = service.scope(&scope).await.unwrap().get_config();
assert_eq!(config.network_type, NetworkType::P2P);
assert_eq!(config.default_consensus_threshold, 0.75);
assert_eq!(config.default_timeout, DEFAULT_DOUBLE_TIMEOUT);
assert!(config.default_liveness_criteria_yes);
}
#[tokio::test]
async fn test_scope_config_update() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("update_test_scope");
service
.scope(&scope)
.await
.unwrap()
.with_network_type(NetworkType::Gossipsub)
.with_threshold(2.0 / 3.0)
.with_timeout(DEFAULT_TIMEOUT)
.initialize()
.await
.unwrap();
service
.scope(&scope)
.await
.unwrap()
.with_threshold(0.8)
.update()
.await
.unwrap();
let config = service.scope(&scope).await.unwrap().get_config();
assert_eq!(config.default_consensus_threshold, 0.8);
assert_eq!(config.network_type, NetworkType::Gossipsub); assert_eq!(config.default_timeout, DEFAULT_TIMEOUT); }
#[tokio::test]
async fn test_scope_config_update_multiple_fields() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("multi_update_scope");
service
.scope(&scope)
.await
.unwrap()
.with_network_type(NetworkType::P2P)
.with_threshold(0.6)
.with_timeout(DEFAULT_SHORT_TIMEOUT)
.initialize()
.await
.unwrap();
service
.scope(&scope)
.await
.unwrap()
.with_threshold(0.9)
.with_timeout(DEFAULT_DOUBLE_TIMEOUT)
.with_liveness_criteria(false)
.update()
.await
.unwrap();
let config = service.scope(&scope).await.unwrap().get_config();
assert_eq!(config.default_consensus_threshold, 0.9);
assert_eq!(config.default_timeout, DEFAULT_DOUBLE_TIMEOUT);
assert!(!config.default_liveness_criteria_yes);
assert_eq!(config.network_type, NetworkType::P2P); }
#[tokio::test]
async fn test_scope_config_presets() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("preset_test_scope");
service
.scope(&scope)
.await
.unwrap()
.p2p_preset()
.initialize()
.await
.unwrap();
let config = service.scope(&scope).await.unwrap().get_config();
assert_eq!(config.network_type, NetworkType::P2P);
assert_eq!(config.default_consensus_threshold, 2.0 / 3.0);
assert_eq!(config.default_timeout, DEFAULT_TIMEOUT);
service
.scope(&scope)
.await
.unwrap()
.gossipsub_preset()
.update()
.await
.unwrap();
let config = service.scope(&scope).await.unwrap().get_config();
assert_eq!(config.network_type, NetworkType::Gossipsub);
}
#[tokio::test]
async fn test_scope_config_validation() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("validation_test_scope");
let result = service
.scope(&scope)
.await
.unwrap()
.with_threshold(1.5) .initialize()
.await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ConsensusError::InvalidConsensusThreshold
));
let result = service
.scope(&scope)
.await
.unwrap()
.with_threshold(-0.1) .initialize()
.await;
assert!(result.is_err());
let result = service
.scope(&scope)
.await
.unwrap()
.with_timeout(Duration::from_secs(0)) .initialize()
.await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ConsensusError::InvalidTimeout
));
}
#[tokio::test]
async fn test_scope_config_new_scope_uses_defaults() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("new_scope_defaults");
let config = service.scope(&scope).await.unwrap().get_config();
assert_eq!(config.network_type, NetworkType::Gossipsub);
assert_eq!(config.default_consensus_threshold, 2.0 / 3.0);
assert_eq!(config.default_timeout, DEFAULT_TIMEOUT);
assert!(config.default_liveness_criteria_yes);
}
#[tokio::test]
async fn test_max_rounds_override_zero_validation() {
let service = DefaultConsensusService::default();
let scope_p2p = ScopeID::from("p2p_zero_rounds");
let scope_gossipsub = ScopeID::from("gossipsub_zero_rounds");
let result = service
.scope(&scope_p2p)
.await
.unwrap()
.with_network_type(NetworkType::P2P)
.with_max_rounds(Some(0))
.initialize()
.await;
assert!(
result.is_ok(),
"max_rounds_override = Some(0) should be allowed for P2P networks"
);
let config = service.scope(&scope_p2p).await.unwrap().get_config();
assert_eq!(config.max_rounds_override, Some(0));
assert_eq!(config.network_type, NetworkType::P2P);
let result = service
.scope(&scope_gossipsub)
.await
.unwrap()
.with_network_type(NetworkType::Gossipsub)
.with_max_rounds(Some(0))
.initialize()
.await;
assert!(
result.is_err(),
"max_rounds_override = Some(0) should be rejected for Gossipsub networks"
);
assert!(matches!(
result.unwrap_err(),
ConsensusError::InvalidMaxRounds
));
}
#[tokio::test]
async fn create_proposal_with_config_preserves_override_timeout() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("test_scope");
let request = CreateProposalRequest::new(
"Test".to_string(),
PROPOSAL_PAYLOAD,
vec![0u8; 20],
3,
60, true,
)
.unwrap();
let override_config = ConsensusConfig::gossipsub()
.with_timeout(Duration::from_secs(120))
.unwrap();
let proposal = service
.create_proposal_with_config(&scope, request, Some(override_config))
.await
.unwrap();
let config = service
.storage()
.get_proposal_config(&scope, proposal.proposal_id)
.await
.unwrap();
assert_eq!(config.consensus_timeout(), Duration::from_secs(120));
}
#[tokio::test]
async fn test_scope_config_convenience_profiles_and_network_defaults() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("convenience_profiles_scope");
service
.scope(&scope)
.await
.unwrap()
.strict_consensus()
.initialize()
.await
.unwrap();
let strict = service.scope(&scope).await.unwrap().get_config();
assert_eq!(strict.default_consensus_threshold, 0.9);
service
.scope(&scope)
.await
.unwrap()
.fast_consensus()
.update()
.await
.unwrap();
let fast = service.scope(&scope).await.unwrap().get_config();
assert_eq!(fast.default_consensus_threshold, 0.6);
assert_eq!(fast.default_timeout, Duration::from_secs(30));
service
.scope(&scope)
.await
.unwrap()
.with_network_defaults(NetworkType::P2P)
.update()
.await
.unwrap();
let p2p = service.scope(&scope).await.unwrap().get_config();
assert_eq!(p2p.network_type, NetworkType::P2P);
service
.scope(&scope)
.await
.unwrap()
.with_network_defaults(NetworkType::Gossipsub)
.update()
.await
.unwrap();
let gossipsub = service.scope(&scope).await.unwrap().get_config();
assert_eq!(gossipsub.network_type, NetworkType::Gossipsub);
}
#[tokio::test]
async fn test_scope_config_update_replaces_all_fields() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("replace_all_fields_scope");
service
.scope(&scope)
.await
.unwrap()
.with_network_type(NetworkType::P2P)
.initialize()
.await
.unwrap();
service
.scope(&scope)
.await
.unwrap()
.with_network_type(NetworkType::Gossipsub)
.with_threshold(0.8)
.with_timeout(Duration::from_secs(90))
.with_liveness_criteria(false)
.with_max_rounds(Some(7))
.update()
.await
.unwrap();
let built = service.scope(&scope).await.unwrap().get_config();
assert_eq!(built.network_type, NetworkType::Gossipsub);
assert_eq!(built.default_consensus_threshold, 0.8);
assert_eq!(built.default_timeout, Duration::from_secs(90));
assert!(!built.default_liveness_criteria_yes);
assert_eq!(built.max_rounds_override, Some(7));
}