use crate::{
LogEntryParams, LogEventType, LogLevel, UUID_LENGTH, encode_log_data, encode_log_data_into,
encode_log_entry,
};
#[test]
fn encode_log_entry_with_all_fields() {
let session_id = [0x03u8; UUID_LENGTH];
let payload = br#"{"code":500}"#;
let bytes = encode_log_entry(&LogEntryParams {
event_type: LogEventType::Log,
session_id: Some(&session_id),
level: LogLevel::Error,
timestamp: 1706000000000,
source: Some("web-01"),
service: Some("api"),
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], LogEventType::Log.as_u8());
assert_eq!(bytes[table_start + 29], LogLevel::Error.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 == session_id);
assert!(found, "session_id not found");
let found = bytes.windows(6).any(|w| w == b"web-01");
assert!(found, "source not found");
let found = bytes.windows(3).any(|w| w == b"api");
assert!(found, "service not found");
let found = bytes
.windows(payload.len())
.any(|w| w == payload.as_slice());
assert!(found, "payload not found");
}
#[test]
fn encode_log_entry_minimal() {
let bytes = encode_log_entry(&LogEntryParams {
event_type: LogEventType::Log,
session_id: None,
level: LogLevel::Info,
timestamp: 0,
source: None,
service: 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], LogEventType::Log.as_u8());
assert_eq!(bytes[root_offset + 29], LogLevel::Info.as_u8());
}
#[test]
fn encode_log_data_single() {
let session_id = [0xBB; UUID_LENGTH];
let log = encode_log_entry(&LogEntryParams {
event_type: LogEventType::Log,
session_id: Some(&session_id),
level: LogLevel::Warning,
timestamp: 2000,
source: None,
service: Some("worker"),
payload: None,
});
let data = encode_log_data(&[log]);
let root = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
assert!(root < data.len());
let found = data.windows(6).any(|w| w == b"worker");
assert!(found, "service not found in log_data");
}
#[test]
fn encode_log_data_multiple() {
let logs: Vec<Vec<u8>> = (0..3)
.map(|i| {
encode_log_entry(&LogEntryParams {
event_type: LogEventType::Log,
session_id: None,
level: LogLevel::Debug,
timestamp: 3000 + i,
source: Some(&format!("host-{i}")),
service: Some("svc"),
payload: None,
})
})
.collect();
let data = encode_log_data(&logs);
let root = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
assert!(root < data.len());
for i in 0..3 {
let host = format!("host-{i}");
let found = data.windows(host.len()).any(|w| w == host.as_bytes());
assert!(found, "Host '{}' not found in log_data", host);
}
}
#[test]
fn encode_log_data_into_matches_encode_log_data() {
let session_ids: Vec<[u8; UUID_LENGTH]> = (0..3).map(|i| [i as u8; UUID_LENGTH]).collect();
let sources: Vec<String> = (0..3).map(|i| format!("host-{i}")).collect();
let payloads: Vec<Vec<u8>> = (0..3)
.map(|i| format!(r#"{{"n":{i}}}"#).into_bytes())
.collect();
let params: Vec<LogEntryParams<'_>> = (0..3)
.map(|i| LogEntryParams {
event_type: LogEventType::Log,
session_id: Some(&session_ids[i]),
level: LogLevel::Error,
timestamp: 3000 + i as u64,
source: Some(&sources[i]),
service: Some("api"),
payload: Some(&payloads[i]),
})
.collect();
let mut buf = Vec::new();
let range = encode_log_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 in 0..3 {
let host = format!("host-{i}");
assert!(
into_bytes.windows(host.len()).any(|w| w == host.as_bytes()),
"source '{}' not found in encode_log_data_into output",
host,
);
let payload = format!(r#"{{"n":{i}}}"#);
assert!(
into_bytes
.windows(payload.len())
.any(|w| w == payload.as_bytes()),
"payload {} not found in encode_log_data_into output",
i,
);
}
assert!(into_bytes.windows(3).any(|w| w == b"api"));
}
#[test]
fn encode_log_data_into_reuses_buffer() {
let mut buf = Vec::new();
let params = [LogEntryParams {
event_type: LogEventType::Log,
session_id: None,
level: LogLevel::Info,
timestamp: 100,
source: Some("test"),
service: None,
payload: None,
}];
let range1 = encode_log_data_into(&mut buf, ¶ms);
assert!(range1.end > range1.start);
let params2 = [LogEntryParams {
event_type: LogEventType::Log,
session_id: None,
level: LogLevel::Debug,
timestamp: 200,
source: Some("test2"),
service: None,
payload: None,
}];
let range2 = encode_log_data_into(&mut buf, ¶ms2);
assert_eq!(range2.start, range1.end);
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 log_level_values() {
assert_eq!(LogLevel::Emergency.as_u8(), 0);
assert_eq!(LogLevel::Alert.as_u8(), 1);
assert_eq!(LogLevel::Critical.as_u8(), 2);
assert_eq!(LogLevel::Error.as_u8(), 3);
assert_eq!(LogLevel::Warning.as_u8(), 4);
assert_eq!(LogLevel::Notice.as_u8(), 5);
assert_eq!(LogLevel::Info.as_u8(), 6);
assert_eq!(LogLevel::Debug.as_u8(), 7);
assert_eq!(LogLevel::Trace.as_u8(), 8);
}
#[test]
fn log_event_type_values() {
assert_eq!(LogEventType::Unknown.as_u8(), 0);
assert_eq!(LogEventType::Log.as_u8(), 1);
assert_eq!(LogEventType::Enrich.as_u8(), 2);
}
fn log_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_log_session_id_none_absent() {
let bytes = encode_log_entry(&LogEntryParams {
event_type: LogEventType::Log,
session_id: None,
level: LogLevel::Info,
timestamp: 1000,
source: None,
service: None,
payload: None,
});
let slot = log_vtable_slot(&bytes, 1);
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 log frame when session_id is None"
);
}
#[test]
fn test_encode_log_session_id_some_present() {
let sentinel = [0x55u8; UUID_LENGTH];
let bytes = encode_log_entry(&LogEntryParams {
event_type: LogEventType::Log,
session_id: Some(&sentinel),
level: LogLevel::Info,
timestamp: 1000,
source: None,
service: None,
payload: None,
});
let slot = log_vtable_slot(&bytes, 1);
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 log entry"
);
}