use crate::events::{DraftBotEvent, EventMetadata, GameEvent};
use crate::log::entry::LogEntry;
use crate::parsers::api_common;
const DRAFT_STATUS_MARKER: &str = "BotDraftDraftStatus";
const DRAFT_PICK_MARKER: &str = "BotDraftDraftPick";
pub fn try_parse(
entry: &LogEntry,
timestamp: Option<chrono::DateTime<chrono::Utc>>,
) -> Option<GameEvent> {
let body = &entry.body;
if let Some(payload) = try_parse_pack_presentation(body) {
let metadata = EventMetadata::new(timestamp, body.as_bytes().to_vec());
return Some(GameEvent::DraftBot(DraftBotEvent::new(metadata, payload)));
}
if let Some(payload) = try_parse_draft_pick(body) {
let metadata = EventMetadata::new(timestamp, body.as_bytes().to_vec());
return Some(GameEvent::DraftBot(DraftBotEvent::new(metadata, payload)));
}
None
}
fn try_parse_pack_presentation(body: &str) -> Option<serde_json::Value> {
let parsed = if api_common::is_api_response(body, DRAFT_STATUS_MARKER) {
let top = api_common::parse_json_from_body(body, DRAFT_STATUS_MARKER)?;
api_common::parse_nested_json(&top, "Payload", Some(DRAFT_STATUS_MARKER))
} else if api_common::is_api_response(body, DRAFT_PICK_MARKER) {
let top = api_common::parse_json_from_body(body, DRAFT_PICK_MARKER)?;
api_common::parse_nested_json(&top, "Payload", Some(DRAFT_PICK_MARKER))
} else {
None
}?;
let status_val = parsed.get("DraftStatus")?;
if status_val.as_str() != Some("PickNext") {
return None;
}
let pack_idx = parsed
.get("PackNumber")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0);
let selection_idx = parsed
.get("PickNumber")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0);
let event_name = api_common::extract_event_name(&parsed);
let draft_pack = extract_draft_pack(&parsed);
Some(serde_json::json!({
"type": "draft_bot_pack",
"event_name": event_name,
"pack_number": pack_idx,
"pick_number": selection_idx,
"draft_pack": draft_pack,
"raw_draft_status": parsed,
}))
}
fn try_parse_draft_pick(body: &str) -> Option<serde_json::Value> {
let parsed = if api_common::is_api_request(body, DRAFT_PICK_MARKER) {
let top = api_common::parse_json_from_body(body, DRAFT_PICK_MARKER)?;
api_common::parse_nested_json(&top, "request", Some(DRAFT_PICK_MARKER))
} else {
None
}?;
let pick_info = parsed.get("PickInfo")?;
if pick_info.get("CardIds").is_none()
&& pick_info.get("PackNumber").is_none()
&& pick_info.get("PickNumber").is_none()
{
return None;
}
let pack_idx = pick_info
.get("PackNumber")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0);
let selection_idx = pick_info
.get("PickNumber")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0);
let card_ids: Vec<i64> = pick_info
.get("CardIds")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| {
v.as_i64()
.or_else(|| v.as_str().and_then(|s| s.parse::<i64>().ok()))
})
.collect()
})
.unwrap_or_default();
let card_id = *card_ids.first()?;
if card_id == 0 {
return None;
}
let event_name = api_common::extract_event_name(&parsed);
Some(serde_json::json!({
"type": "draft_bot_pick",
"event_name": event_name,
"card_id": card_id,
"pack_number": pack_idx,
"pick_number": selection_idx,
"raw_pick_info": parsed,
}))
}
fn extract_draft_pack(parsed: &serde_json::Value) -> Vec<i64> {
let Some(pack) = parsed.get("DraftPack").and_then(|v| v.as_array()) else {
return Vec::new();
};
pack.iter()
.filter_map(|v| {
v.as_i64()
.or_else(|| v.as_str().and_then(|s| s.parse::<i64>().ok()))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::events::PerformanceClass;
use crate::parsers::test_helpers::{
draft_bot_payload, test_timestamp, unity_entry, EntryHeader,
};
mod pack_presentation {
use super::*;
#[test]
fn test_try_parse_pack_presentation_basic() {
let body = "[UnityCrossThreadLogger]2/01/2026 10:23:51 AM\n\
<== BotDraftDraftStatus(uuid)\n\
{\"Payload\":\"{\\\"EventName\\\":\\\"QuickDraft_MKM_20260201\\\",\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":0,\\\"PickNumber\\\":0,\\\"DraftPack\\\":[\\\"12345\\\",\\\"67890\\\",\\\"11111\\\"]}\"}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["type"], "draft_bot_pack");
assert_eq!(payload["event_name"], "QuickDraft_MKM_20260201");
assert_eq!(payload["pack_number"], 0);
assert_eq!(payload["pick_number"], 0);
assert_eq!(
payload["draft_pack"],
serde_json::json!([12345, 67890, 11111])
);
}
#[test]
fn test_try_parse_pack_presentation_second_pack() {
let body = "[UnityCrossThreadLogger]1/18/2026 8:42:01 PM\n\
<== BotDraftDraftPick(uuid)\n\
{\"Payload\":\"{\\\"EventName\\\":\\\"QuickDraft_DSK_20260115\\\",\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":1,\\\"PickNumber\\\":3,\\\"DraftPack\\\":[\\\"22222\\\",\\\"33333\\\"]}\"}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["pack_number"], 1);
assert_eq!(payload["pick_number"], 3);
assert_eq!(payload["event_name"], "QuickDraft_DSK_20260115");
assert_eq!(payload["draft_pack"], serde_json::json!([22222, 33333]));
}
#[test]
fn test_try_parse_pack_presentation_third_pack_last_pick() {
let body = "[UnityCrossThreadLogger]2/12/2026 1:11:11 PM\n\
<== BotDraftDraftPick(uuid)\n\
{\"Payload\":\"{\\\"EventName\\\":\\\"QuickDraft_MKM_20260201\\\",\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":2,\\\"PickNumber\\\":13,\\\"DraftPack\\\":[\\\"44444\\\"]}\"}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["pack_number"], 2);
assert_eq!(payload["pick_number"], 13);
assert_eq!(payload["draft_pack"], serde_json::json!([44444]));
}
#[test]
fn test_try_parse_pack_presentation_integer_card_ids() {
let body = "[UnityCrossThreadLogger]2/01/2026 10:23:51 AM\n\
<== BotDraftDraftStatus(uuid)\n\
{\"Payload\":\"{\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":0,\\\"PickNumber\\\":0,\\\"DraftPack\\\":[12345, 67890]}\"}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["draft_pack"], serde_json::json!([12345, 67890]));
}
#[test]
fn test_try_parse_pack_presentation_empty_pack() {
let body = "[UnityCrossThreadLogger]2/12/2026 1:11:11 PM\n\
<== BotDraftDraftPick(uuid)\n\
{\"Payload\":\"{\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":0,\\\"PickNumber\\\":0,\\\"DraftPack\\\":[]}\"}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["draft_pack"], serde_json::json!([]));
}
#[test]
fn test_try_parse_pack_presentation_missing_draft_pack() {
let body = "[UnityCrossThreadLogger]2/12/2026 1:11:11 PM\n\
<== BotDraftDraftPick(uuid)\n\
{\"Payload\":\"{\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":0,\\\"PickNumber\\\":0}\"}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["draft_pack"], serde_json::json!([]));
}
#[test]
fn test_try_parse_pack_presentation_preserves_raw_payload() {
let body = "[UnityCrossThreadLogger]2/01/2026 10:23:51 AM\n\
<== BotDraftDraftStatus(uuid)\n\
{\n\
\"Payload\":\"{\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":0,\\\"PickNumber\\\":0,\\\"DraftPack\\\":[\\\"11111\\\"],\\\"ExtraField\\\":\\\"preserved\\\"}\"
}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["raw_draft_status"]["ExtraField"], "preserved");
}
}
mod draft_pick {
use super::*;
#[test]
fn test_try_parse_draft_pick_returns_pick() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"EventName\":\"QuickDraft_TMT_20260313\",\"PickInfo\":{\"EventName\":\"QuickDraft_TMT_20260313\",\"CardIds\":[\"12345\"],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["type"], "draft_bot_pick");
assert_eq!(payload["card_id"], 12345);
assert_eq!(payload["pack_number"], 0);
assert_eq!(payload["pick_number"], 0);
}
#[test]
fn test_try_parse_draft_pick_later_in_draft() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"EventName\":\"QuickDraft_TMT_20260313\",\"PickInfo\":{\"EventName\":\"QuickDraft_TMT_20260313\",\"CardIds\":[\"67890\"],\"PackNumber\":1,\"PickNumber\":7}}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["card_id"], 67890);
assert_eq!(payload["pack_number"], 1);
assert_eq!(payload["pick_number"], 7);
}
#[test]
fn test_try_parse_draft_pick_event_name_in_request_root_returns_name() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"EventName\":\"QuickDraft_SOS_20260430\",\"PickInfo\":{\"CardIds\":[\"12345\"],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["event_name"], "QuickDraft_SOS_20260430");
}
#[test]
fn test_try_parse_draft_pick_event_name_in_pick_info_returns_name() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"EventName\":\"QuickDraft_SOS_20260430\",\"CardIds\":[\"12345\"],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["event_name"], "QuickDraft_SOS_20260430");
}
#[test]
fn test_try_parse_draft_pick_missing_event_name_defaults_to_empty() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"CardIds\":[\"98546\"],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["event_name"], "");
}
#[test]
fn test_try_parse_draft_pick_missing_card_id_returns_none() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"EventName\":\"Test\",\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_none());
}
#[test]
fn test_try_parse_draft_pick_preserves_raw_payload() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"EventName\":\"Test\",\"CardIds\":[\"12345\"],\"PackNumber\":0,\"PickNumber\":0},\"ExtraField\":\"kept\"}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
let payload = draft_bot_payload(event);
assert_eq!(payload["raw_pick_info"]["ExtraField"], "kept");
}
}
mod metadata {
use super::*;
#[test]
fn test_try_parse_preserves_raw_bytes_pack() {
let body = "[UnityCrossThreadLogger]2/01/2026 10:23:51 AM\n\
<== BotDraftDraftStatus(uuid)\n\
{\"Payload\":\"{\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":0,\\\"PickNumber\\\":0,\\\"DraftPack\\\":[]}\"}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
assert_eq!(event.metadata().raw_bytes(), body.as_bytes());
}
#[test]
fn test_try_parse_preserves_raw_bytes_pick() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"CardIds\":[\"1\"],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
assert_eq!(event.metadata().raw_bytes(), body.as_bytes());
}
#[test]
fn test_try_parse_stores_timestamp_pack() {
let body = "[UnityCrossThreadLogger]2/01/2026 10:23:51 AM\n\
<== BotDraftDraftStatus(uuid)\n\
{\"Payload\":\"{\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":0,\\\"PickNumber\\\":0,\\\"DraftPack\\\":[]}\"}";
let entry = unity_entry(body);
let ts = Some(test_timestamp());
let result = try_parse(&entry, ts);
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
assert_eq!(event.metadata().timestamp(), ts);
}
#[test]
fn test_try_parse_stores_timestamp_pick() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"CardIds\":[\"1\"],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
let ts = Some(test_timestamp());
let result = try_parse(&entry, ts);
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
assert_eq!(event.metadata().timestamp(), ts);
}
}
mod non_matching {
use super::*;
#[test]
fn test_try_parse_unrelated_entry_returns_none() {
let body = "[UnityCrossThreadLogger]greToClientEvent\n{\"data\": 1}";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_empty_body_returns_none() {
let body = "[UnityCrossThreadLogger]";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_draft_status_not_pick_next_returns_none() {
let body = "[UnityCrossThreadLogger]2/26/2026 1:11:11 PM\n\
<== BotDraftDraftStatus(uuid)\n\
{\"Payload\":\"{\\\"DraftStatus\\\":\\\"Completed\\\",\\\"PackNumber\\\":2,\\\"PickNumber\\\":13,\\\"DraftPack\\\":[]}\"}";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_pack_presentation_wrong_status_returns_none() {
let body = "[UnityCrossThreadLogger]2/26/2026 1:11:11 PM\n\
<== BotDraftDraftPick(uuid)\n\
{\"Payload\":\"{\\\"DraftStatus\\\":\\\"Completed\\\",\\\"PackNumber\\\":2,\\\"PickNumber\\\":13,\\\"DraftPack\\\":[]}\"}";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_malformed_json_returns_none() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":broken!!!}"}"#;
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_marker_only_no_json_returns_none() {
let body = "[UnityCrossThreadLogger]==> BotDraftDraftPick";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_missing_pick_fields_returns_none() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{}}"}"#;
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_empty_card_ids_returns_none() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"CardIds\":[],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_zero_card_id_returns_none() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"CardIds\":[\"0\"],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_draft_status_marker_in_text_only_returns_none() {
let body = "[UnityCrossThreadLogger]2/26/2026 1:11:11 PM\n\
<== BotDraftDraftStatus(uuid)\n\
DraftStatus is PickNext";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_draft_pick_missing_payload_returns_none() {
let body = "[UnityCrossThreadLogger]2/26/2026 1:11:11 PM\n\
<== BotDraftDraftPick(uuid)\n\
{\n\
\"Result\": \"Success\"\n\
}";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_draft_pick_malformed_payload_returns_none() {
let body = "[UnityCrossThreadLogger]2/26/2026 1:11:11 PM\n\
<== BotDraftDraftPick(uuid)\n\
{\n\
\"Payload\": \"not json\"\n\
}";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_human_draft_entry_returns_none() {
let body = "[UnityCrossThreadLogger]Draft.Notify\n\
{\n\
\"draftId\": \"abc-123\",\n\
\"SelfPack\": 0,\n\
\"SelfPick\": 0,\n\
\"PackCards\": \"12345,67890\"\n\
}";
let entry = unity_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_connection_manager_header_returns_none() {
let entry = LogEntry {
header: EntryHeader::ConnectionManager,
body: "[ConnectionManager]some connection message".to_owned(),
};
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
}
mod performance_class {
use super::*;
#[test]
fn test_draft_bot_event_is_durable_per_event() {
let body = "[UnityCrossThreadLogger]2/01/2026 10:23:51 AM\n\
<== BotDraftDraftStatus(uuid)\n\
{\"Payload\":\"{\\\"DraftStatus\\\":\\\"PickNext\\\",\\\"PackNumber\\\":0,\\\"PickNumber\\\":0,\\\"DraftPack\\\":[]}\"}";
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
assert_eq!(event.performance_class(), PerformanceClass::DurablePerEvent);
}
#[test]
fn test_draft_bot_pick_event_is_durable_per_event() {
let body = r#"[UnityCrossThreadLogger]==> BotDraftDraftPick {"id":"uuid","request":"{\"PickInfo\":{\"CardIds\":[\"1\"],\"PackNumber\":0,\"PickNumber\":0}}"}"#;
let entry = unity_entry(body);
let result = try_parse(&entry, Some(test_timestamp()));
assert!(result.is_some());
let event = result.as_ref().unwrap_or_else(|| unreachable!());
assert_eq!(event.performance_class(), PerformanceClass::DurablePerEvent);
}
}
mod helpers {
use super::*;
#[test]
fn test_extract_draft_pack_string_ids() {
let parsed = serde_json::json!({
"DraftPack": ["12345", "67890", "11111"]
});
let pack = extract_draft_pack(&parsed);
assert_eq!(pack, vec![12345, 67890, 11111]);
}
#[test]
fn test_extract_draft_pack_integer_ids() {
let parsed = serde_json::json!({
"DraftPack": [12345, 67890]
});
let pack = extract_draft_pack(&parsed);
assert_eq!(pack, vec![12345, 67890]);
}
#[test]
fn test_extract_draft_pack_empty() {
let parsed = serde_json::json!({"DraftPack": []});
let pack = extract_draft_pack(&parsed);
assert!(pack.is_empty());
}
#[test]
fn test_extract_draft_pack_missing_field() {
let parsed = serde_json::json!({"other": "data"});
let pack = extract_draft_pack(&parsed);
assert!(pack.is_empty());
}
#[test]
fn test_extract_draft_pack_mixed_types() {
let parsed = serde_json::json!({
"DraftPack": [12345, "67890", "not_a_number", 11111]
});
let pack = extract_draft_pack(&parsed);
assert_eq!(pack, vec![12345, 67890, 11111]);
}
}
}