use super::*;
use crate::event::{Event, EventKind, JsonValueInput, ProjectionStateContract, StateExtent};
use crate::store::StoreConfig;
fn unavailable_freshness() -> ProjectionRunFreshnessStatus {
ProjectionRunFreshnessStatus::Unavailable {
reason: "projection_failed".to_owned(),
}
}
fn unavailable_output() -> ProjectionRunOutputHash {
ProjectionRunOutputHash::Unavailable {
reason: "projection_failed".to_owned(),
}
}
fn unavailable_cache() -> ProjectionRunCacheStatus {
ProjectionRunCacheStatus::Unavailable {
reason: "projection_failed".to_owned(),
}
}
fn sample_frontier() -> ProjectionRunInputFrontier {
ProjectionRunInputFrontier {
kind: ProjectionRunFrontierKind::Visible,
wall_ms: 5,
global_sequence: 9,
}
}
#[test]
fn append_common_findings_emits_every_unavailable_finding() {
let mut findings = Vec::new();
append_common_findings(
&mut findings,
&unavailable_freshness(),
None,
&unavailable_output(),
&unavailable_cache(),
);
assert!(
findings.contains(&ProjectionRunFinding::ObservedFreshnessUnavailable),
"PROPERTY: an Unavailable observed-freshness must emit ObservedFreshnessUnavailable"
);
assert!(
findings.contains(&ProjectionRunFinding::InputFrontierUnknown),
"PROPERTY: a missing input frontier with a non-NotApplicable freshness must emit InputFrontierUnknown"
);
assert!(
findings.contains(&ProjectionRunFinding::OutputHashUnavailable),
"PROPERTY: an Unavailable output hash must emit OutputHashUnavailable"
);
assert!(
findings.contains(&ProjectionRunFinding::CacheStatusUnavailable),
"PROPERTY: an Unavailable cache status must emit CacheStatusUnavailable"
);
assert!(
findings.contains(&ProjectionRunFinding::PartialVisibilityNotApplicable),
"PROPERTY: PartialVisibilityNotApplicable is always appended"
);
assert!(
!findings.contains(&ProjectionRunFinding::StaleUsed),
"PROPERTY: an Unavailable (not StaleAllowed) freshness must NOT emit StaleUsed"
);
}
#[test]
fn append_common_findings_stays_silent_on_a_fully_fresh_run() {
let mut findings = Vec::new();
append_common_findings(
&mut findings,
&ProjectionRunFreshnessStatus::Fresh,
Some(sample_frontier()),
&ProjectionRunOutputHash::NotApplicable,
&ProjectionRunCacheStatus::Hit,
);
assert_eq!(
findings,
vec![ProjectionRunFinding::PartialVisibilityNotApplicable],
"PROPERTY: a fresh run with a known frontier emits only PartialVisibilityNotApplicable"
);
}
#[test]
fn append_common_findings_emits_stale_used_only_for_stale_allowed() {
let mut findings = Vec::new();
append_common_findings(
&mut findings,
&ProjectionRunFreshnessStatus::StaleAllowed,
Some(sample_frontier()),
&ProjectionRunOutputHash::NotApplicable,
&ProjectionRunCacheStatus::Hit,
);
assert_eq!(
findings,
vec![
ProjectionRunFinding::StaleUsed,
ProjectionRunFinding::PartialVisibilityNotApplicable,
],
"PROPERTY: StaleAllowed freshness emits StaleUsed exactly once, before the trailing finding"
);
}
#[test]
fn append_common_findings_suppresses_input_frontier_unknown_for_not_applicable() {
let mut findings = Vec::new();
append_common_findings(
&mut findings,
&ProjectionRunFreshnessStatus::NotApplicable,
None,
&ProjectionRunOutputHash::NotApplicable,
&ProjectionRunCacheStatus::Hit,
);
assert_eq!(
findings,
vec![ProjectionRunFinding::PartialVisibilityNotApplicable],
"PROPERTY: NotApplicable freshness suppresses InputFrontierUnknown even with no frontier"
);
}
#[test]
fn append_common_findings_reports_input_frontier_unknown_when_missing_and_applicable() {
let mut findings = Vec::new();
append_common_findings(
&mut findings,
&ProjectionRunFreshnessStatus::Fresh,
None,
&ProjectionRunOutputHash::NotApplicable,
&ProjectionRunCacheStatus::Hit,
);
assert_eq!(
findings,
vec![
ProjectionRunFinding::InputFrontierUnknown,
ProjectionRunFinding::PartialVisibilityNotApplicable,
],
"PROPERTY: a missing frontier with applicable freshness emits InputFrontierUnknown"
);
}
#[test]
fn map_requested_freshness_translates_each_variant_verbatim() {
assert_eq!(
map_requested_freshness(&Freshness::Consistent),
ProjectionRunRequestedFreshness::Consistent,
"PROPERTY: Consistent maps to Consistent"
);
assert_eq!(
map_requested_freshness(&Freshness::MaybeStale { max_stale_ms: 4242 }),
ProjectionRunRequestedFreshness::MaybeStale { max_stale_ms: 4242 },
"PROPERTY: MaybeStale carries its max_stale_ms bound verbatim"
);
}
#[test]
fn map_observed_freshness_translates_each_variant() {
assert_eq!(
map_observed_freshness(ProjectionObservedFreshness::Fresh),
ProjectionRunFreshnessStatus::Fresh,
);
assert_eq!(
map_observed_freshness(ProjectionObservedFreshness::StaleAllowed),
ProjectionRunFreshnessStatus::StaleAllowed,
);
assert_eq!(
map_observed_freshness(ProjectionObservedFreshness::NotApplicable),
ProjectionRunFreshnessStatus::NotApplicable,
);
}
#[test]
fn map_cache_status_translates_each_variant_and_preserves_reason() {
assert_eq!(
map_cache_status(ProjectionCacheObservation::Hit),
ProjectionRunCacheStatus::Hit,
);
assert_eq!(
map_cache_status(ProjectionCacheObservation::Miss),
ProjectionRunCacheStatus::Miss,
);
assert_eq!(
map_cache_status(ProjectionCacheObservation::Bypassed),
ProjectionRunCacheStatus::Bypassed,
);
assert_eq!(
map_cache_status(ProjectionCacheObservation::Unavailable {
reason: "cache_get_failed",
}),
ProjectionRunCacheStatus::Unavailable {
reason: "cache_get_failed".to_owned(),
},
"PROPERTY: the Unavailable reason string is carried through, not dropped"
);
}
#[test]
fn map_input_frontier_carries_hlc_fields_into_a_visible_boundary() {
let frontier = map_input_frontier(HlcPoint {
wall_ms: 77,
global_sequence: 88,
});
assert_eq!(frontier.kind, ProjectionRunFrontierKind::Visible);
assert_eq!(
frontier.wall_ms, 77,
"PROPERTY: wall_ms is taken from HlcPoint.wall_ms"
);
assert_eq!(
frontier.global_sequence, 88,
"PROPERTY: global_sequence is taken from HlcPoint.global_sequence"
);
}
#[test]
fn output_hash_for_state_is_not_applicable_for_absent_state() {
assert_eq!(
output_hash_for_state::<u32>(None),
ProjectionRunOutputHash::NotApplicable,
"PROPERTY: no state means the output hash is NotApplicable"
);
}
#[test]
fn output_hash_for_state_hashes_present_state_canonically() {
let value = 12_345_u32;
let bytes = crate::canonical::to_bytes(&value).expect("canonical encode of u32");
let expected = crate::evidence::content_hash(&bytes);
assert_eq!(
output_hash_for_state(Some(&value)),
ProjectionRunOutputHash::Known(expected),
"PROPERTY: a present state hashes to content_hash(canonical(value))"
);
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct RegistryProbe;
impl crate::event::EventSourced for RegistryProbe {
type Input = JsonValueInput;
const STATE_CONTRACT: ProjectionStateContract =
ProjectionStateContract::single_entity("projection-run-registry-probe");
fn from_events(events: &[Event<serde_json::Value>]) -> Option<Self> {
(!events.is_empty()).then_some(Self)
}
fn apply_event(&mut self, _event: &Event<serde_json::Value>) {}
fn relevant_event_kinds() -> &'static [EventKind] {
&[]
}
fn state_extent(&self) -> StateExtent {
StateExtent::single_entity()
}
}
#[test]
fn registry_new_is_empty() {
let registry: ProjectionEvidenceRegistry = ProjectionEvidenceRegistry::new();
assert!(
!registry.contains("anything"),
"PROPERTY: a fresh registry contains no projection ids"
);
assert_eq!(
registry.projection_ids().count(),
0,
"PROPERTY: a fresh registry iterates zero projection ids"
);
}
#[test]
fn registry_contains_reflects_registration() {
let mut registry: ProjectionEvidenceRegistry = ProjectionEvidenceRegistry::new();
registry.register::<RegistryProbe>("proj.registered");
assert!(
registry.contains("proj.registered"),
"PROPERTY: a registered projection id is reported present (kills contains -> false)"
);
assert!(
!registry.contains("proj.absent"),
"PROPERTY: an unregistered projection id is reported absent (kills contains -> true)"
);
}
#[test]
fn registry_projection_ids_are_sorted() {
let mut registry: ProjectionEvidenceRegistry = ProjectionEvidenceRegistry::new();
registry.register::<RegistryProbe>("charlie");
registry.register::<RegistryProbe>("alpha");
registry.register::<RegistryProbe>("bravo");
let ids: Vec<&str> = registry.projection_ids().collect();
assert_eq!(
ids,
vec!["alpha", "bravo", "charlie"],
"PROPERTY: projection_ids yields the registered ids in sorted order"
);
}
#[test]
fn registry_run_dispatches_only_registered_projections() {
let dir = tempfile::tempdir().expect("temp dir");
let store = Store::open(StoreConfig::new(dir.path())).expect("open store");
let entity = "entity:registry-run";
let mut registry: ProjectionEvidenceRegistry = ProjectionEvidenceRegistry::new();
registry.register::<RegistryProbe>("probe.projection");
let absent = registry.run("probe.absent", &store, entity, &Freshness::Consistent);
assert!(
absent.is_none(),
"PROPERTY: an unregistered projection id dispatches to None"
);
let report = registry
.run("probe.projection", &store, entity, &Freshness::Consistent)
.expect("registered projection id must dispatch (kills run -> None)")
.expect("empty-entity evidence run succeeds");
assert_eq!(
report.body.schema_version, PROJECTION_RUN_REPORT_SCHEMA_VERSION,
"PROPERTY: the dispatched runner returns a real projection-run report body"
);
store.close().expect("close store");
}