use chrono::{DateTime, Utc};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompetingChangeset {
pub changeset_id: Uuid,
pub session_id: Option<Uuid>,
pub state: String,
pub updated_at: DateTime<Utc>,
}
pub const LIVE_STATES: &[&str] = &["submitted", "verifying", "approved"];
pub fn is_stale(
session_id: Uuid,
last_read: Option<DateTime<Utc>>,
competitors: &[CompetingChangeset],
) -> Option<&CompetingChangeset> {
competitors.iter().find(|cs| {
if cs.session_id == Some(session_id) {
return false;
}
if !LIVE_STATES.contains(&cs.state.as_str()) {
return false;
}
match last_read {
Some(lr) => cs.updated_at > lr,
None => true,
}
})
}
#[cfg(test)]
mod tests {
use super::*;
fn cs(session: Option<Uuid>, state: &str, at: DateTime<Utc>) -> CompetingChangeset {
CompetingChangeset {
changeset_id: Uuid::new_v4(),
session_id: session,
state: state.to_string(),
updated_at: at,
}
}
#[test]
fn no_competitors_is_safe() {
let sid = Uuid::new_v4();
assert!(is_stale(sid, None, &[]).is_none());
assert!(is_stale(sid, Some(Utc::now()), &[]).is_none());
}
#[test]
fn self_session_never_stale() {
let sid = Uuid::new_v4();
let competitors = vec![cs(Some(sid), "submitted", Utc::now())];
assert!(is_stale(sid, None, &competitors).is_none());
}
#[test]
fn draft_never_stale() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
let competitors = vec![cs(Some(other), "draft", Utc::now())];
assert!(is_stale(sid, None, &competitors).is_none());
}
#[test]
fn merged_never_stale() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
let competitors = vec![cs(Some(other), "merged", Utc::now())];
assert!(is_stale(sid, None, &competitors).is_none());
}
#[test]
fn rejected_and_closed_are_inert() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
assert!(is_stale(sid, None, &[cs(Some(other), "rejected", Utc::now())]).is_none());
assert!(is_stale(sid, None, &[cs(Some(other), "closed", Utc::now())]).is_none());
}
#[test]
fn competing_submitted_with_no_read_is_stale() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
let competitors = vec![cs(Some(other), "submitted", Utc::now())];
assert!(is_stale(sid, None, &competitors).is_some());
}
#[test]
fn competing_verifying_counts_as_live() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
let competitors = vec![cs(Some(other), "verifying", Utc::now())];
assert!(is_stale(sid, None, &competitors).is_some());
}
#[test]
fn competing_approved_counts_as_live() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
let competitors = vec![cs(Some(other), "approved", Utc::now())];
assert!(is_stale(sid, None, &competitors).is_some());
}
#[test]
fn read_after_competing_submit_is_safe() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
let submit_time = Utc::now() - chrono::Duration::seconds(10);
let read_time = Utc::now();
let competitors = vec![cs(Some(other), "submitted", submit_time)];
assert!(is_stale(sid, Some(read_time), &competitors).is_none());
}
#[test]
fn read_before_competing_submit_is_stale() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
let read_time = Utc::now() - chrono::Duration::seconds(10);
let submit_time = Utc::now();
let competitors = vec![cs(Some(other), "submitted", submit_time)];
assert!(is_stale(sid, Some(read_time), &competitors).is_some());
}
#[test]
fn read_equal_to_competing_submit_is_safe() {
let sid = Uuid::new_v4();
let other = Uuid::new_v4();
let t = Utc::now();
let competitors = vec![cs(Some(other), "submitted", t)];
assert!(is_stale(sid, Some(t), &competitors).is_none());
}
#[test]
fn first_live_competitor_wins() {
let sid = Uuid::new_v4();
let other_a = Uuid::new_v4();
let other_b = Uuid::new_v4();
let t = Utc::now();
let draft = cs(Some(other_a), "draft", t);
let live = cs(Some(other_b), "submitted", t);
let competitors = vec![draft, live.clone()];
let got = is_stale(sid, None, &competitors).expect("expected stale");
assert_eq!(got.session_id, live.session_id);
assert_eq!(got.state, "submitted");
}
#[test]
fn no_session_id_still_counts() {
let sid = Uuid::new_v4();
let competitors = vec![cs(None, "submitted", Utc::now())];
assert!(is_stale(sid, None, &competitors).is_some());
}
}