use crate::{
EventParams, EventType, UUID_LENGTH, encode_event, encode_event_data, encode_event_data_into,
};
#[test]
fn encode_event_with_all_fields() {
let device_id = [0x01u8; UUID_LENGTH];
let session_id = [0x02u8; UUID_LENGTH];
let payload = br#"{"page":"/home"}"#;
let bytes = encode_event(&EventParams {
event_type: EventType::Track,
timestamp: 1706000000000,
service: Some("website"),
device_id: Some(&device_id),
session_id: Some(&session_id),
event_name: Some("Page Viewed"),
payload: Some(payload),
});
let root_offset = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
assert!(root_offset < bytes.len());
let table_start = root_offset;
assert_eq!(bytes[table_start + 28], EventType::Track.as_u8());
let ts = u64::from_le_bytes([
bytes[table_start + 20],
bytes[table_start + 21],
bytes[table_start + 22],
bytes[table_start + 23],
bytes[table_start + 24],
bytes[table_start + 25],
bytes[table_start + 26],
bytes[table_start + 27],
]);
assert_eq!(ts, 1706000000000);
let found = bytes.windows(UUID_LENGTH).any(|w| w == device_id);
assert!(found, "device_id not found");
let found = bytes.windows(UUID_LENGTH).any(|w| w == session_id);
assert!(found, "session_id not found");
let svc = b"website";
let found = bytes.windows(svc.len()).any(|w| w == svc.as_slice());
assert!(found, "service not found");
let name = b"Page Viewed";
let found = bytes.windows(name.len()).any(|w| w == name.as_slice());
assert!(found, "event_name not found");
let found = bytes
.windows(payload.len())
.any(|w| w == payload.as_slice());
assert!(found, "payload not found");
}
#[test]
fn encode_event_minimal() {
let bytes = encode_event(&EventParams {
event_type: EventType::Identify,
timestamp: 0,
service: None,
device_id: None,
session_id: None,
event_name: None,
payload: None,
});
let root_offset = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
assert!(root_offset < bytes.len());
assert_eq!(bytes[root_offset + 28], EventType::Identify.as_u8());
}
#[test]
fn encode_event_with_service() {
let bytes = encode_event(&EventParams {
event_type: EventType::Track,
timestamp: 1000,
service: Some("my-backend"),
device_id: None,
session_id: None,
event_name: Some("Click"),
payload: None,
});
let root_offset = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
assert!(root_offset < bytes.len());
let svc = b"my-backend";
let found = bytes.windows(svc.len()).any(|w| w == svc.as_slice());
assert!(found, "service string not found in encoded event");
let vtable_start = root_offset
- i32::from_le_bytes([
bytes[root_offset],
bytes[root_offset + 1],
bytes[root_offset + 2],
bytes[root_offset + 3],
]) as usize;
let field2 = u16::from_le_bytes([bytes[vtable_start + 8], bytes[vtable_start + 9]]);
assert_eq!(
field2, 32,
"vtable field 2 (service) should point to offset 32"
);
let service_off = u32::from_le_bytes([
bytes[root_offset + 32],
bytes[root_offset + 33],
bytes[root_offset + 34],
bytes[root_offset + 35],
]);
assert_ne!(
service_off, 0,
"service offset should be non-zero when service is present"
);
}
#[test]
fn encode_event_without_service() {
let bytes = encode_event(&EventParams {
event_type: EventType::Track,
timestamp: 1000,
service: None,
device_id: None,
session_id: None,
event_name: None,
payload: None,
});
let root_offset = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
let vtable_start = root_offset
- i32::from_le_bytes([
bytes[root_offset],
bytes[root_offset + 1],
bytes[root_offset + 2],
bytes[root_offset + 3],
]) as usize;
let field2 = u16::from_le_bytes([bytes[vtable_start + 8], bytes[vtable_start + 9]]);
assert_eq!(
field2, 0,
"vtable field 2 (service) should be 0 when absent"
);
}
#[test]
fn encode_event_data_single() {
let device_id = [0xAA; UUID_LENGTH];
let event = encode_event(&EventParams {
event_type: EventType::Track,
timestamp: 1000,
service: None,
device_id: Some(&device_id),
session_id: None,
event_name: Some("Click"),
payload: None,
});
let data = encode_event_data(&[event]);
let root = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
assert!(root < data.len());
let found = data.windows(UUID_LENGTH).any(|w| w == [0xAA; UUID_LENGTH]);
assert!(found, "device_id not found in event_data");
}
#[test]
fn encode_event_data_multiple() {
let events: Vec<Vec<u8>> = (0..5)
.map(|i| {
let device_id = [i as u8; UUID_LENGTH];
encode_event(&EventParams {
event_type: EventType::Track,
timestamp: 1000 + i,
service: None,
device_id: Some(&device_id),
session_id: None,
event_name: Some(&format!("Event{i}")),
payload: None,
})
})
.collect();
let data = encode_event_data(&events);
let root = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
assert!(root < data.len());
for i in 0..5 {
let name = format!("Event{i}");
let found = data.windows(name.len()).any(|w| w == name.as_bytes());
assert!(found, "Event name '{}' not found in event_data", name);
}
}
#[test]
fn encode_event_data_into_matches_encode_event_data() {
let device_ids: Vec<[u8; UUID_LENGTH]> = (0..5).map(|i| [i as u8; UUID_LENGTH]).collect();
let session_ids: Vec<[u8; UUID_LENGTH]> =
(0..5).map(|i| [(i + 10) as u8; UUID_LENGTH]).collect();
let names: Vec<String> = (0..5).map(|i| format!("Event{i}")).collect();
let payloads: Vec<Vec<u8>> = (0..5)
.map(|i| format!(r#"{{"idx":{i}}}"#).into_bytes())
.collect();
let params: Vec<EventParams<'_>> = (0..5)
.map(|i| EventParams {
event_type: EventType::Track,
timestamp: 1000 + i as u64,
service: None,
device_id: Some(&device_ids[i]),
session_id: Some(&session_ids[i]),
event_name: Some(&names[i]),
payload: Some(&payloads[i]),
})
.collect();
let mut buf = Vec::new();
let range = encode_event_data_into(&mut buf, ¶ms);
let into_bytes = &buf[range];
let root =
u32::from_le_bytes([into_bytes[0], into_bytes[1], into_bytes[2], into_bytes[3]]) as usize;
assert!(root < into_bytes.len());
for (i, did) in device_ids.iter().enumerate() {
let name = format!("Event{i}");
assert!(
into_bytes.windows(name.len()).any(|w| w == name.as_bytes()),
"Event name '{}' not found in encode_event_data_into output",
name,
);
assert!(
into_bytes.windows(UUID_LENGTH).any(|w| w == *did),
"device_id {} not found in encode_event_data_into output",
i,
);
let payload = format!(r#"{{"idx":{i}}}"#);
assert!(
into_bytes
.windows(payload.len())
.any(|w| w == payload.as_bytes()),
"payload {} not found in encode_event_data_into output",
i,
);
}
}
#[test]
fn encode_event_data_into_reuses_buffer() {
let mut buf = Vec::new();
let params = [EventParams {
event_type: EventType::Track,
timestamp: 100,
service: None,
device_id: None,
session_id: None,
event_name: Some("First"),
payload: None,
}];
let range1 = encode_event_data_into(&mut buf, ¶ms);
let len1 = range1.end - range1.start;
assert!(len1 > 0);
let params2 = [EventParams {
event_type: EventType::Identify,
timestamp: 200,
service: None,
device_id: None,
session_id: None,
event_name: Some("Second"),
payload: None,
}];
let range2 = encode_event_data_into(&mut buf, ¶ms2);
assert_eq!(range2.start, range1.end);
assert!(buf.len() > len1);
let into_bytes = &buf[range2.clone()];
let root =
u32::from_le_bytes([into_bytes[0], into_bytes[1], into_bytes[2], into_bytes[3]]) as usize;
assert!(root < into_bytes.len());
}
#[test]
fn encode_event_types() {
for (et, val) in [
(EventType::Unknown, 0),
(EventType::Track, 1),
(EventType::Identify, 2),
(EventType::Group, 3),
(EventType::Alias, 4),
(EventType::Context, 6),
] {
assert_eq!(et.as_u8(), val);
}
}
fn event_vtable_slot(bytes: &[u8], field: usize) -> u16 {
let root_offset = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
let soffset = i32::from_le_bytes([
bytes[root_offset],
bytes[root_offset + 1],
bytes[root_offset + 2],
bytes[root_offset + 3],
]);
let vtable_start = (root_offset as i64 - soffset as i64) as usize;
let slot_offset = vtable_start + 4 + field * 2;
u16::from_le_bytes([bytes[slot_offset], bytes[slot_offset + 1]])
}
#[test]
fn test_encode_event_session_id_none_absent() {
let bytes = encode_event(&EventParams {
event_type: EventType::Track,
timestamp: 1000,
service: None,
device_id: None,
session_id: None,
event_name: None,
payload: None,
});
let slot = event_vtable_slot(&bytes, 4);
assert_eq!(
slot, 0,
"session_id vtable slot must be 0 when session_id is None"
);
let sentinel = [0x55u8; UUID_LENGTH];
assert!(
!bytes.windows(UUID_LENGTH).any(|w| w == sentinel),
"sentinel bytes must not appear in frame when session_id is None"
);
}
#[test]
fn test_encode_event_session_id_some_present() {
let sentinel = [0x55u8; UUID_LENGTH];
let bytes = encode_event(&EventParams {
event_type: EventType::Track,
timestamp: 1000,
service: None,
device_id: None,
session_id: Some(&sentinel),
event_name: None,
payload: None,
});
let slot = event_vtable_slot(&bytes, 4);
assert_ne!(
slot, 0,
"session_id vtable slot must be non-zero when session_id is Some"
);
assert!(
bytes.windows(UUID_LENGTH).any(|w| w == sentinel),
"sentinel session_id bytes must appear in the encoded event"
);
}