254 lines
12 KiB
Rust
254 lines
12 KiB
Rust
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/persistence.rs
|
|
// version: 1
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum PortResponse {
|
|
Outcome(ksp_store_lib::RawEntityWriteOutcome, ksp_store_lib::RawObservationWriteOutcome),
|
|
Conflict,
|
|
StoreFailure,
|
|
}
|
|
|
|
struct FakePersistencePort {
|
|
calls: std::sync::atomic::AtomicUsize,
|
|
network: ksp_store_lib::RawNetworkId,
|
|
normal_mode_seen: std::sync::atomic::AtomicBool,
|
|
response: PortResponse,
|
|
}
|
|
|
|
impl FakePersistencePort {
|
|
fn new(network: ksp_store_lib::RawNetworkId, response: PortResponse) -> Self {
|
|
return Self {
|
|
calls: std::sync::atomic::AtomicUsize::new(0),
|
|
network,
|
|
normal_mode_seen: std::sync::atomic::AtomicBool::new(false),
|
|
response,
|
|
};
|
|
}
|
|
}
|
|
|
|
impl crate::RawTransactionIngestPersistencePort for FakePersistencePort {
|
|
fn network_matches(&self, network: &ksp_store_lib::RawNetworkId) -> bool {
|
|
return &self.network == network;
|
|
}
|
|
|
|
fn persist_acquisition<'a>(
|
|
&'a self,
|
|
_transaction: ksp_store_lib::RawTransaction,
|
|
_observation: ksp_store_lib::RawTransactionObservation,
|
|
mode: ksp_store_lib::RawTransactionAcquisitionMode,
|
|
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>> {
|
|
self.calls.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
|
|
self.normal_mode_seen.store(mode == ksp_store_lib::RawTransactionAcquisitionMode::Normal, std::sync::atomic::Ordering::Release);
|
|
let response = self.response;
|
|
return std::boxed::Box::pin(async move {
|
|
return match response {
|
|
PortResponse::Outcome(entity, observation) => std::result::Result::Ok(ksp_store_lib::RawAcquisitionWriteOutcome::new(entity, observation)),
|
|
PortResponse::Conflict => std::result::Result::Err(ksp_core_lib::Error::new(ksp_store_lib::ERROR_CODE_RAW_CONFLICT, "synthetic conflict")),
|
|
PortResponse::StoreFailure => std::result::Result::Err(ksp_core_lib::Error::new(
|
|
ksp_core_lib::ErrorCode::new("synthetic_store", "write_failed"),
|
|
"synthetic remote failure text that must not escape",
|
|
)),
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
fn acquisition(network_name: &str, signature_byte: u8) -> std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition> {
|
|
let network = match ksp_store_lib::RawNetworkId::new(network_name) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let signature = ksp_store_lib::RawTransactionSignature::new([signature_byte; 64]);
|
|
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
|
|
network,
|
|
signature,
|
|
42,
|
|
std::option::Option::Some(1_700_000_000),
|
|
"AQID",
|
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
|
);
|
|
let transaction = match ksp_raw_transaction_lib::canonicalize_raw_transaction(material) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let provider = match ksp_store_lib::RawProvenanceCode::new("deterministic-harness") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let protocol = match ksp_store_lib::RawProvenanceCode::new("internal") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let method = match ksp_store_lib::RawProvenanceCode::new("persistence") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_700_000_000_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let provenance = ksp_store_lib::RawAcquisitionProvenance::new(provider, protocol, method, ksp_store_lib::RawAcquisitionOrigin::Live, received_at);
|
|
return std::option::Option::Some(ksp_raw_transaction_lib::assemble_raw_transaction_acquisition(
|
|
transaction,
|
|
ksp_store_lib::RawObservationKey::new([signature_byte; 32]),
|
|
provenance,
|
|
));
|
|
}
|
|
|
|
fn network(name: &str) -> std::option::Option<ksp_store_lib::RawNetworkId> {
|
|
return match ksp_store_lib::RawNetworkId::new(name) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_007_normal_mode_classifies_new_idempotent_and_purged_outcomes() {
|
|
let cases = [
|
|
(
|
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::Inserted),
|
|
crate::RawTransactionIngestEntityPersistence::Inserted,
|
|
crate::RawTransactionIngestObservationPersistence::Inserted,
|
|
),
|
|
(
|
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent, ksp_store_lib::RawObservationWriteOutcome::Inserted),
|
|
crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
|
|
crate::RawTransactionIngestObservationPersistence::Inserted,
|
|
),
|
|
(
|
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent, ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent),
|
|
crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
|
|
crate::RawTransactionIngestObservationPersistence::AlreadyPresent,
|
|
),
|
|
(
|
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::SkippedPurged, ksp_store_lib::RawObservationWriteOutcome::NotRecorded),
|
|
crate::RawTransactionIngestEntityPersistence::SkippedPurged,
|
|
crate::RawTransactionIngestObservationPersistence::NotRecorded,
|
|
),
|
|
];
|
|
for (index, (response, expected_entity, expected_observation)) in cases.into_iter().enumerate() {
|
|
let network = match network("mainnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let port = FakePersistencePort::new(network, response);
|
|
let acquisition = match acquisition("mainnet", index as u8 + 1) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = crate::persist_raw_transaction_ingest_acquisition(&port, acquisition).await;
|
|
assert!(result.is_ok());
|
|
let outcome = match result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert_eq!(outcome.entity(), expected_entity);
|
|
assert_eq!(outcome.observation(), expected_observation);
|
|
assert!(port.normal_mode_seen.load(std::sync::atomic::Ordering::Acquire));
|
|
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_007_content_conflict_maps_to_terminal_worker_code_without_remote_text() {
|
|
let network = match network("mainnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let port = FakePersistencePort::new(network, PortResponse::Conflict);
|
|
let acquisition = match acquisition("mainnet", 9) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = crate::persist_raw_transaction_ingest_acquisition(&port, acquisition).await;
|
|
let error = match result {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(value) => value,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_CONTENT_CONFLICT);
|
|
assert!(error.context().is_empty());
|
|
assert!(!std::format!("{error:?}").contains("synthetic conflict"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_007_store_failure_keeps_only_safe_lower_error_code() {
|
|
let network = match network("mainnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let port = FakePersistencePort::new(network, PortResponse::StoreFailure);
|
|
let acquisition = match acquisition("mainnet", 10) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = crate::persist_raw_transaction_ingest_acquisition(&port, acquisition).await;
|
|
let error = match result {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(value) => value,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_STORE_FAILED);
|
|
assert_eq!(error.context().len(), 2);
|
|
assert_eq!(error.context()[0].value(), "synthetic_store");
|
|
assert_eq!(error.context()[1].value(), "write_failed");
|
|
assert!(!std::format!("{error:?}").contains("synthetic remote failure text"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_007_rehydrated_or_impossible_store_outcomes_are_runtime_invalid() {
|
|
let cases = [
|
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Rehydrated, ksp_store_lib::RawObservationWriteOutcome::Inserted),
|
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent),
|
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::SkippedPurged, ksp_store_lib::RawObservationWriteOutcome::Inserted),
|
|
];
|
|
for (index, response) in cases.into_iter().enumerate() {
|
|
let network = match network("mainnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let port = FakePersistencePort::new(network, response);
|
|
let acquisition = match acquisition("mainnet", index as u8 + 20) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = crate::persist_raw_transaction_ingest_acquisition(&port, acquisition).await;
|
|
let error = match result {
|
|
std::result::Result::Ok(_) => continue,
|
|
std::result::Result::Err(value) => value,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert_eq!(error.context().len(), 1);
|
|
assert_eq!(error.context()[0].value(), "persistence.store_outcome_invalid");
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_007_store_network_mismatch_is_rejected_before_write() {
|
|
let network = match network("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let port = FakePersistencePort::new(
|
|
network,
|
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::Inserted),
|
|
);
|
|
let acquisition = match acquisition("mainnet", 30) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = crate::persist_raw_transaction_ingest_acquisition(&port, acquisition).await;
|
|
let error = match result {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(value) => value,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert_eq!(error.context()[0].value(), "persistence.store_network_mismatch");
|
|
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 0);
|
|
return;
|
|
}
|