use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct ObsEvent {
pub pc_id: String,
pub at: DateTime<Utc>,
pub kind: String,
pub source: String,
#[serde(default)]
pub event_record_id: Option<String>,
#[serde(default)]
pub payload: serde_json::Value,
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
use serde_json::json;
#[test]
fn obs_event_round_trips_through_json() {
let t = Utc.with_ymd_and_hms(2026, 5, 28, 10, 41, 0).unwrap();
let e = ObsEvent {
pc_id: "pc-01".into(),
at: t,
kind: "logon".into(),
source: "winlog:Security".into(),
event_record_id: Some("1234567".into()),
payload: json!({ "user": "yukimemi", "logon_type": 2 }),
};
let s = serde_json::to_string(&e).unwrap();
let back: ObsEvent = serde_json::from_str(&s).unwrap();
assert_eq!(back, e);
}
#[test]
fn obs_event_null_payload_is_valid() {
let e = ObsEvent {
pc_id: "pc-01".into(),
at: Utc.with_ymd_and_hms(2026, 5, 28, 0, 0, 0).unwrap(),
kind: "boot".into(),
source: "winlog:System".into(),
event_record_id: Some("99".into()),
payload: serde_json::Value::Null,
};
let s = serde_json::to_string(&e).unwrap();
let back: ObsEvent = serde_json::from_str(&s).unwrap();
assert_eq!(back, e);
}
#[test]
fn obs_event_missing_event_record_id_deserialises() {
let s = r#"{
"pc_id": "pc-01",
"at": "2026-05-28T10:00:00Z",
"kind": "agent_started",
"source": "agent:internal",
"payload": null
}"#;
let e: ObsEvent = serde_json::from_str(s).unwrap();
assert_eq!(e.event_record_id, None);
assert_eq!(e.kind, "agent_started");
}
#[test]
fn obs_event_missing_payload_defaults_to_null() {
let s = r#"{
"pc_id": "pc-01",
"at": "2026-05-28T10:00:00Z",
"kind": "boot",
"source": "winlog:System",
"event_record_id": "1"
}"#;
let e: ObsEvent = serde_json::from_str(s).unwrap();
assert_eq!(e.payload, serde_json::Value::Null);
}
}