pub trait Timestamps {
fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>>;
fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>>;
fn touch(&mut self);
fn set_created_at(&mut self);
}
#[cfg(test)]
mod tests {
use chrono::{Duration, Utc};
use super::Timestamps;
#[derive(Debug, Default)]
struct TimestampedRecord {
created_at: Option<chrono::DateTime<Utc>>,
updated_at: Option<chrono::DateTime<Utc>>,
}
impl Timestamps for TimestampedRecord {
fn created_at(&self) -> Option<chrono::DateTime<Utc>> {
self.created_at
}
fn updated_at(&self) -> Option<chrono::DateTime<Utc>> {
self.updated_at
}
fn touch(&mut self) {
self.updated_at = Some(Utc::now());
}
fn set_created_at(&mut self) {
self.created_at = Some(Utc::now());
}
}
#[tokio::test]
async fn touch_updates_timestamp() {
let mut record = TimestampedRecord {
updated_at: Some(Utc::now() - Duration::minutes(5)),
..Default::default()
};
let before = record.updated_at().expect("updated_at should be set");
record.touch();
assert!(record.updated_at().expect("updated_at should be set") >= before);
}
#[tokio::test]
async fn set_created_at_sets_timestamp() {
let mut record = TimestampedRecord::default();
record.set_created_at();
assert!(record.created_at().is_some());
}
#[tokio::test]
async fn created_and_updated_timestamps_are_independent() {
let mut record = TimestampedRecord::default();
record.set_created_at();
let created_at = record.created_at().expect("created_at should be set");
record.touch();
assert_eq!(
record.created_at().expect("created_at should remain set"),
created_at
);
assert!(record.updated_at().is_some());
}
#[tokio::test]
async fn default_state_has_no_timestamps() {
let record = TimestampedRecord::default();
assert!(record.created_at().is_none());
assert!(record.updated_at().is_none());
}
#[tokio::test]
async fn touch_does_not_set_created_at_when_absent() {
let mut record = TimestampedRecord::default();
record.touch();
assert!(record.created_at().is_none());
assert!(record.updated_at().is_some());
}
#[tokio::test]
async fn set_created_at_does_not_set_updated_at_when_absent() {
let mut record = TimestampedRecord::default();
record.set_created_at();
assert!(record.created_at().is_some());
assert!(record.updated_at().is_none());
}
#[tokio::test]
async fn repeated_touch_updates_only_updated_at() {
let created_at = Utc::now() - Duration::days(1);
let mut record = TimestampedRecord {
created_at: Some(created_at),
updated_at: Some(Utc::now() - Duration::minutes(5)),
};
record.touch();
let first_update = record.updated_at().expect("updated_at should be set");
record.touch();
let second_update = record.updated_at().expect("updated_at should stay set");
assert_eq!(record.created_at(), Some(created_at));
assert!(second_update >= first_update);
}
#[tokio::test]
async fn repeated_set_created_at_updates_only_created_at() {
let updated_at = Utc::now() - Duration::hours(1);
let mut record = TimestampedRecord {
created_at: Some(Utc::now() - Duration::days(1)),
updated_at: Some(updated_at),
};
record.set_created_at();
let first_created_at = record.created_at().expect("created_at should be set");
record.set_created_at();
let second_created_at = record.created_at().expect("created_at should stay set");
assert_eq!(record.updated_at(), Some(updated_at));
assert!(second_created_at >= first_created_at);
}
}