use chrono::{DateTime, Utc};
pub struct DeterminismContext {
current_recorded_at: DateTime<Utc>,
}
impl DeterminismContext {
#[must_use]
pub const fn new(workflow_started_recorded_at: DateTime<Utc>) -> Self {
Self {
current_recorded_at: workflow_started_recorded_at,
}
}
#[must_use]
pub const fn now(&self) -> DateTime<Utc> {
self.current_recorded_at
}
pub const fn advance_to_recorded_at(&mut self, recorded_at: DateTime<Utc>) {
self.current_recorded_at = recorded_at;
}
}
#[cfg(test)]
mod tests {
use chrono::{DateTime, TimeZone, Utc};
use super::DeterminismContext;
type TestResult<T = ()> = Result<T, Box<dyn std::error::Error>>;
fn timestamp(seconds: i64) -> TestResult<DateTime<Utc>> {
Utc.timestamp_opt(seconds, 0)
.single()
.ok_or_else(|| format!("invalid fixed timestamp {seconds}").into())
}
#[test]
fn now_starts_at_workflow_started_and_advances_with_recorded_events() -> TestResult {
let started_at = timestamp(1_700_000_000)?;
let first_event_at = timestamp(1_700_000_010)?;
let second_event_at = timestamp(1_700_000_020)?;
let mut context = DeterminismContext::new(started_at);
assert_eq!(context.now(), started_at);
context.advance_to_recorded_at(first_event_at);
assert_eq!(context.now(), first_event_at);
context.advance_to_recorded_at(second_event_at);
assert_eq!(context.now(), second_event_at);
Ok(())
}
#[test]
fn identical_recorded_sequences_have_identical_now_values() -> TestResult {
let started_at = timestamp(1_700_100_000)?;
let events = [
timestamp(1_700_100_001)?,
timestamp(1_700_100_005)?,
timestamp(1_700_100_030)?,
];
let mut first = DeterminismContext::new(started_at);
let mut second = DeterminismContext::new(started_at);
assert_eq!(first.now(), second.now());
for recorded_at in events {
first.advance_to_recorded_at(recorded_at);
second.advance_to_recorded_at(recorded_at);
assert_eq!(first.now(), second.now());
}
Ok(())
}
}