use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LastCheck {
pub latest_known: String,
pub checked_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Default)]
pub struct UpdateStatusState {
inner: Arc<RwLock<Option<LastCheck>>>,
}
impl UpdateStatusState {
pub fn record(&self, check: LastCheck) {
let mut slot = match self.inner.write() {
Ok(slot) => slot,
Err(poisoned) => poisoned.into_inner(),
};
match slot.as_ref() {
Some(current) if current.checked_at > check.checked_at => {
tracing::info!(
operation = "update_check.record",
kept = %current.checked_at,
offered = %check.checked_at,
"an older check completed after a newer one; keeping the newer observation"
);
}
_ => *slot = Some(check),
}
}
#[must_use]
pub fn last(&self) -> Option<LastCheck> {
let slot = match self.inner.read() {
Ok(slot) => slot,
Err(poisoned) => poisoned.into_inner(),
};
slot.clone()
}
}
#[cfg(test)]
mod tests {
use super::{LastCheck, UpdateStatusState};
#[test]
fn a_fresh_server_has_honestly_never_checked() {
assert_eq!(UpdateStatusState::default().last(), None);
}
fn ordered_checks() -> Result<(LastCheck, LastCheck), Box<dyn std::error::Error>> {
let first = LastCheck {
latest_known: "0.13.7".to_owned(),
checked_at: "2026-08-11T01:00:00Z".parse()?,
};
let second = LastCheck {
latest_known: "0.14.0".to_owned(),
checked_at: "2026-08-11T01:00:30Z".parse()?,
};
Ok((first, second))
}
#[test]
fn record_replaces_and_is_visible_through_every_clone() -> Result<(), Box<dyn std::error::Error>>
{
let state = UpdateStatusState::default();
let reader = state.clone();
let (first, second) = ordered_checks()?;
state.record(first.clone());
assert_eq!(reader.last(), Some(first));
state.record(second.clone());
assert_eq!(reader.last(), Some(second));
Ok(())
}
#[test]
fn an_out_of_order_older_check_never_shadows_a_newer_one()
-> Result<(), Box<dyn std::error::Error>> {
let state = UpdateStatusState::default();
let (older, newer) = ordered_checks()?;
state.record(newer.clone());
state.record(older);
assert_eq!(state.last(), Some(newer));
Ok(())
}
}