use std::error::Error;
use liminal_protocol::wire::{
ClientRequest, ConnectionIncarnation, EnrollmentReceiptCapacityScope, EnrollmentRequest,
EnrollmentToken, IdentityCapacityExceeded, IdentityCapacityScope, ReceiptCapacityExceeded,
ServerValue,
};
use crate::config::types::ParticipantConfig;
use super::ProductionParticipantHandler;
use super::tests::{dispatch, open_disk_store_for_tests, test_participant_config};
use super::tests_receipts::enroll;
pub(super) fn capacity_config(mutate: impl FnOnce(&mut ParticipantConfig)) -> ParticipantConfig {
let mut config = test_participant_config();
mutate(&mut config);
config
}
fn enrollment_request(conversation_id: u64, token: [u8; 16]) -> ClientRequest {
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id,
enrollment_token: EnrollmentToken::new(token),
})
}
fn assert_enrollment_receipt_refusal(
value: &ServerValue,
conversation_id: u64,
scope: EnrollmentReceiptCapacityScope,
limit: u64,
occupied: u64,
) -> Result<(), Box<dyn Error>> {
let ServerValue::ReceiptCapacityExceeded(ReceiptCapacityExceeded::Enrollment {
request,
scope: got_scope,
limit: got_limit,
occupied: got_occupied,
}) = value
else {
return Err(format!(
"expected the enrollment ReceiptCapacityExceeded row ({scope:?}), got: {value:?}"
)
.into());
};
assert_eq!(request.conversation_id, conversation_id);
assert_eq!(*got_scope, scope);
assert_eq!(*got_limit, limit);
assert_eq!(*got_occupied, occupied);
Ok(())
}
#[test]
fn enrollment_identity_server_scope_refuses_and_survives_restart() -> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(71, 1);
let config = capacity_config(|c| c.max_retired_identity_slots_server = 2);
{
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, config)?;
enroll(&handler, incarnation, 701, [1; 16])?;
enroll(&handler, incarnation, 702, [2; 16])?;
let refused = dispatch(&handler, incarnation, enrollment_request(703, [3; 16]))?;
let ServerValue::IdentityCapacityExceeded(IdentityCapacityExceeded {
request,
scope,
limit,
occupied,
}) = refused
else {
return Err(format!(
"third server-wide identity must refuse with IdentityCapacityExceeded, got: \
{refused:?}"
)
.into());
};
assert_eq!(request.conversation_id, 703);
assert_eq!(scope, IdentityCapacityScope::Server);
assert_eq!(limit, 2);
assert_eq!(occupied, 2);
}
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, config)?;
let refused = dispatch(&handler, incarnation, enrollment_request(703, [3; 16]))?;
let ServerValue::IdentityCapacityExceeded(IdentityCapacityExceeded {
scope,
limit,
occupied,
..
}) = refused
else {
return Err(format!(
"the server identity scope must survive a cold restart, got: {refused:?}"
)
.into());
};
assert_eq!(scope, IdentityCapacityScope::Server);
assert_eq!(limit, 2);
assert_eq!(occupied, 2);
Ok(())
}
#[test]
fn enrollment_live_receipt_server_scope_refuses_and_survives_restart() -> Result<(), Box<dyn Error>>
{
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(72, 1);
let config = capacity_config(|c| c.max_live_attach_receipts_server = 1);
{
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, config)?;
enroll(&handler, incarnation, 711, [11; 16])?;
let refused = dispatch(&handler, incarnation, enrollment_request(712, [12; 16]))?;
assert_enrollment_receipt_refusal(
&refused,
712,
EnrollmentReceiptCapacityScope::LiveReceiptServer,
1,
1,
)?;
}
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, config)?;
let refused = dispatch(&handler, incarnation, enrollment_request(712, [12; 16]))?;
assert_enrollment_receipt_refusal(
&refused,
712,
EnrollmentReceiptCapacityScope::LiveReceiptServer,
1,
1,
)
}
#[test]
fn enrollment_provenance_server_scope_refusal() -> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(73, 1);
let store = open_disk_store_for_tests(&data_dir)?;
let config = capacity_config(|c| c.max_receipt_provenance_server = 1);
let handler = ProductionParticipantHandler::new(store, config)?;
enroll(&handler, incarnation, 721, [21; 16])?;
let refused = dispatch(&handler, incarnation, enrollment_request(722, [22; 16]))?;
assert_enrollment_receipt_refusal(
&refused,
722,
EnrollmentReceiptCapacityScope::ProvenanceServer,
1,
1,
)
}
#[test]
fn enrollment_over_limit_scope_refuses_with_true_numbers() -> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(80, 1);
{
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, test_participant_config())?;
enroll(&handler, incarnation, 751, [61; 16])?;
enroll(&handler, incarnation, 752, [62; 16])?;
}
let store = open_disk_store_for_tests(&data_dir)?;
let config = capacity_config(|c| c.max_receipt_provenance_server = 1);
let handler = ProductionParticipantHandler::new(store, config)?;
let refused = dispatch(&handler, incarnation, enrollment_request(753, [63; 16]))?;
assert_enrollment_receipt_refusal(
&refused,
753,
EnrollmentReceiptCapacityScope::ProvenanceServer,
1,
2,
)
}
#[test]
fn enrollment_mixed_full_and_over_limit_refuses_the_earlier_full_scope()
-> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(81, 1);
{
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, test_participant_config())?;
enroll(&handler, incarnation, 761, [64; 16])?;
enroll(&handler, incarnation, 762, [65; 16])?;
}
let store = open_disk_store_for_tests(&data_dir)?;
let config = capacity_config(|c| {
c.max_retired_identity_slots_server = 2;
c.max_receipt_provenance_server = 1;
});
let handler = ProductionParticipantHandler::new(store, config)?;
let refused = dispatch(&handler, incarnation, enrollment_request(763, [66; 16]))?;
let ServerValue::IdentityCapacityExceeded(IdentityCapacityExceeded {
request,
scope,
limit,
occupied,
}) = refused
else {
return Err(format!(
"the earlier full identity Server scope must answer before the later over-limit \
provenance scope, got: {refused:?}"
)
.into());
};
assert_eq!(request.conversation_id, 763);
assert_eq!(scope, IdentityCapacityScope::Server);
assert_eq!(limit, 2);
assert_eq!(occupied, 2);
Ok(())
}
#[test]
fn enrollment_provenance_conversation_scope_refusal_is_scoped() -> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(74, 1);
let store = open_disk_store_for_tests(&data_dir)?;
let config = capacity_config(|c| c.max_receipt_provenance_per_conversation = 1);
let handler = ProductionParticipantHandler::new(store, config)?;
enroll(&handler, incarnation, 731, [31; 16])?;
let refused = dispatch(
&handler,
ConnectionIncarnation::new(74, 2),
enrollment_request(731, [32; 16]),
)?;
assert_enrollment_receipt_refusal(
&refused,
731,
EnrollmentReceiptCapacityScope::ProvenanceConversation,
1,
1,
)?;
enroll(&handler, ConnectionIncarnation::new(74, 2), 732, [32; 16])?;
Ok(())
}