1use crate::events::{DraftHumanEvent, EventMetadata, GameEvent};
18use crate::log::entry::LogEntry;
19use crate::parsers::api_common;
20
21const DRAFT_NOTIFY_MARKER: &str = "Draft.Notify";
26
27const MAKE_PICK_MARKER: &str = "EventPlayerDraftMakePick";
32
33pub fn try_parse(
45 entry: &LogEntry,
46 timestamp: Option<chrono::DateTime<chrono::Utc>>,
47) -> Option<GameEvent> {
48 let body = &entry.body;
49
50 if let Some(payload) = try_parse_draft_notify(body) {
52 let metadata = EventMetadata::new(timestamp, body.as_bytes().to_vec());
53 return Some(GameEvent::DraftHuman(DraftHumanEvent::new(
54 metadata, payload,
55 )));
56 }
57
58 if let Some(payload) = try_parse_make_pick(body) {
60 let metadata = EventMetadata::new(timestamp, body.as_bytes().to_vec());
61 return Some(GameEvent::DraftHuman(DraftHumanEvent::new(
62 metadata, payload,
63 )));
64 }
65
66 None
67}
68
69fn try_parse_draft_notify(body: &str) -> Option<serde_json::Value> {
77 if !body.contains(DRAFT_NOTIFY_MARKER) {
78 return None;
79 }
80
81 let parsed = api_common::parse_json_from_body(body, "Draft.Notify")?;
82
83 if parsed.get("PackCards").is_none() && parsed.get("SelfPack").is_none() {
86 return None;
87 }
88
89 let draft_id = parsed
90 .get("draftId")
91 .and_then(serde_json::Value::as_str)
92 .unwrap_or("");
93
94 let pack_idx = parsed
95 .get("SelfPack")
96 .and_then(serde_json::Value::as_i64)
97 .unwrap_or(0);
98
99 let selection_idx = parsed
100 .get("SelfPick")
101 .and_then(serde_json::Value::as_i64)
102 .unwrap_or(0);
103
104 let pack_cards = extract_pack_cards_from_string(&parsed);
105
106 Some(serde_json::json!({
107 "type": "draft_human_notify",
108 "draft_id": draft_id,
109 "pack_number": pack_idx,
110 "pick_number": selection_idx,
111 "pack_cards": pack_cards,
112 "raw_draft_notify": parsed,
113 }))
114}
115
116fn try_parse_make_pick(body: &str) -> Option<serde_json::Value> {
127 if !body.contains(MAKE_PICK_MARKER) {
128 return None;
129 }
130
131 if api_common::is_api_response(body, MAKE_PICK_MARKER) {
134 return None;
135 }
136
137 let parsed = api_common::parse_json_from_body(body, "EventPlayerDraftMakePick")?;
138
139 let request_payload = parsed
141 .get("request")
142 .and_then(serde_json::Value::as_str)
143 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok());
144
145 let pick_info = parsed
148 .get("PickInfo")
149 .or(request_payload.as_ref())
150 .unwrap_or(&parsed);
151
152 let card_id = pick_info
153 .get("CardId")
154 .and_then(serde_json::Value::as_i64)
155 .or_else(|| {
156 pick_info
158 .get("GrpIds")
159 .and_then(|v| v.as_array())
160 .and_then(|arr| arr.first())
161 .and_then(serde_json::Value::as_i64)
162 })?;
163
164 let pack_idx = pick_info
165 .get("PackNumber")
166 .or_else(|| pick_info.get("Pack"))
167 .and_then(serde_json::Value::as_i64)
168 .unwrap_or(0);
169
170 let selection_idx = pick_info
171 .get("PickNumber")
172 .or_else(|| pick_info.get("Pick"))
173 .and_then(serde_json::Value::as_i64)
174 .unwrap_or(0);
175
176 let event_name = parsed
177 .get("EventName")
178 .or_else(|| pick_info.get("EventName"))
179 .and_then(serde_json::Value::as_str)
180 .unwrap_or("");
181
182 let card_ids = pick_info
184 .get("CardIds")
185 .and_then(|v| v.as_array())
186 .map(|arr| {
187 arr.iter()
188 .filter_map(serde_json::Value::as_i64)
189 .collect::<Vec<_>>()
190 })
191 .unwrap_or_default();
192
193 let draft_id = pick_info
194 .get("DraftId")
195 .or_else(|| pick_info.get("draftId"))
196 .or_else(|| parsed.get("DraftId"))
197 .or_else(|| parsed.get("draftId"))
198 .and_then(serde_json::Value::as_str)
199 .unwrap_or("");
200
201 Some(serde_json::json!({
202 "type": "draft_human_pick",
203 "draft_id": draft_id,
204 "event_name": event_name,
205 "card_id": card_id,
206 "pack_number": pack_idx,
207 "pick_number": selection_idx,
208 "card_ids": card_ids,
209 "raw_make_pick": parsed,
210 }))
211}
212
213fn extract_pack_cards_from_string(parsed: &serde_json::Value) -> Vec<i64> {
219 if let Some(pack_cards) = parsed.get("PackCards") {
220 if let Some(s) = pack_cards.as_str() {
222 return parse_comma_separated_ids(s);
223 }
224
225 if let Some(arr) = pack_cards.as_array() {
227 return arr
228 .iter()
229 .filter_map(|v| {
230 v.as_i64()
231 .or_else(|| v.as_str().and_then(|s| s.parse::<i64>().ok()))
232 })
233 .collect();
234 }
235 }
236
237 Vec::new()
238}
239
240fn parse_comma_separated_ids(s: &str) -> Vec<i64> {
244 s.split(',')
245 .filter_map(|segment| segment.trim().parse::<i64>().ok())
246 .collect()
247}
248
249#[cfg(test)]
254#[allow(deprecated)]
255mod tests {
256 use super::*;
257 use crate::events::PerformanceClass;
258 use crate::parsers::test_helpers::{
259 draft_human_payload, test_timestamp, unity_entry, EntryHeader,
260 };
261
262 mod draft_notify {
265 use super::*;
266
267 #[test]
268 fn test_try_parse_draft_notify_basic() {
269 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
270 {\n\
271 \"draftId\": \"abc-123-def\",\n\
272 \"SelfPack\": 0,\n\
273 \"SelfPick\": 0,\n\
274 \"PackCards\": \"12345,67890,11111\"\n\
275 }";
276 let entry = unity_entry(body);
277 let result = try_parse(&entry, Some(test_timestamp()));
278
279 assert!(result.is_some());
280 let event = result.as_ref().unwrap_or_else(|| unreachable!());
281 let payload = draft_human_payload(event);
282
283 assert_eq!(payload["type"], "draft_human_notify");
284 assert_eq!(payload["draft_id"], "abc-123-def");
285 assert_eq!(payload["pack_number"], 0);
286 assert_eq!(payload["pick_number"], 0);
287 assert_eq!(
288 payload["pack_cards"],
289 serde_json::json!([12345, 67890, 11111])
290 );
291 }
292
293 #[test]
294 fn test_try_parse_draft_notify_second_pack() {
295 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
296 {\n\
297 \"draftId\": \"draft-456\",\n\
298 \"SelfPack\": 1,\n\
299 \"SelfPick\": 5,\n\
300 \"PackCards\": \"22222,33333,44444,55555\"\n\
301 }";
302 let entry = unity_entry(body);
303 let result = try_parse(&entry, Some(test_timestamp()));
304
305 assert!(result.is_some());
306 let event = result.as_ref().unwrap_or_else(|| unreachable!());
307 let payload = draft_human_payload(event);
308
309 assert_eq!(payload["pack_number"], 1);
310 assert_eq!(payload["pick_number"], 5);
311 assert_eq!(payload["draft_id"], "draft-456");
312 assert_eq!(
313 payload["pack_cards"],
314 serde_json::json!([22222, 33333, 44444, 55555])
315 );
316 }
317
318 #[test]
319 fn test_try_parse_draft_notify_last_pick_single_card() {
320 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
321 {\n\
322 \"draftId\": \"draft-789\",\n\
323 \"SelfPack\": 2,\n\
324 \"SelfPick\": 13,\n\
325 \"PackCards\": \"99999\"\n\
326 }";
327 let entry = unity_entry(body);
328 let result = try_parse(&entry, Some(test_timestamp()));
329
330 assert!(result.is_some());
331 let event = result.as_ref().unwrap_or_else(|| unreachable!());
332 let payload = draft_human_payload(event);
333
334 assert_eq!(payload["pack_number"], 2);
335 assert_eq!(payload["pick_number"], 13);
336 assert_eq!(payload["pack_cards"], serde_json::json!([99999]));
337 }
338
339 #[test]
340 fn test_try_parse_draft_notify_empty_pack_cards() {
341 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
342 {\n\
343 \"draftId\": \"draft-empty\",\n\
344 \"SelfPack\": 0,\n\
345 \"SelfPick\": 0,\n\
346 \"PackCards\": \"\"\n\
347 }";
348 let entry = unity_entry(body);
349 let result = try_parse(&entry, Some(test_timestamp()));
350
351 assert!(result.is_some());
352 let event = result.as_ref().unwrap_or_else(|| unreachable!());
353 let payload = draft_human_payload(event);
354
355 assert_eq!(payload["pack_cards"], serde_json::json!([]));
356 }
357
358 #[test]
359 fn test_try_parse_draft_notify_array_format_pack_cards() {
360 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
361 {\n\
362 \"draftId\": \"draft-arr\",\n\
363 \"SelfPack\": 0,\n\
364 \"SelfPick\": 0,\n\
365 \"PackCards\": [12345, 67890]\n\
366 }";
367 let entry = unity_entry(body);
368 let result = try_parse(&entry, Some(test_timestamp()));
369
370 assert!(result.is_some());
371 let event = result.as_ref().unwrap_or_else(|| unreachable!());
372 let payload = draft_human_payload(event);
373
374 assert_eq!(payload["pack_cards"], serde_json::json!([12345, 67890]));
375 }
376
377 #[test]
378 fn test_try_parse_draft_notify_missing_draft_id() {
379 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
380 {\n\
381 \"SelfPack\": 0,\n\
382 \"SelfPick\": 0,\n\
383 \"PackCards\": \"12345\"\n\
384 }";
385 let entry = unity_entry(body);
386 let result = try_parse(&entry, Some(test_timestamp()));
387
388 assert!(result.is_some());
389 let event = result.as_ref().unwrap_or_else(|| unreachable!());
390 let payload = draft_human_payload(event);
391
392 assert_eq!(payload["draft_id"], "");
393 }
394
395 #[test]
396 fn test_try_parse_draft_notify_preserves_raw_payload() {
397 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
398 {\n\
399 \"draftId\": \"draft-raw\",\n\
400 \"SelfPack\": 0,\n\
401 \"SelfPick\": 0,\n\
402 \"PackCards\": \"12345\",\n\
403 \"ExtraField\": \"preserved\"\n\
404 }";
405 let entry = unity_entry(body);
406 let result = try_parse(&entry, Some(test_timestamp()));
407
408 assert!(result.is_some());
409 let event = result.as_ref().unwrap_or_else(|| unreachable!());
410 let payload = draft_human_payload(event);
411
412 assert_eq!(payload["raw_draft_notify"]["ExtraField"], "preserved");
413 }
414
415 #[test]
416 fn test_try_parse_draft_notify_with_timestamp_in_header() {
417 let body = "[UnityCrossThreadLogger]2/25/2026 12:00:00 PM \
418 Draft.Notify\n\
419 {\n\
420 \"draftId\": \"draft-ts\",\n\
421 \"SelfPack\": 0,\n\
422 \"SelfPick\": 0,\n\
423 \"PackCards\": \"12345\"\n\
424 }";
425 let entry = unity_entry(body);
426 let result = try_parse(&entry, Some(test_timestamp()));
427
428 assert!(result.is_some());
429 let event = result.as_ref().unwrap_or_else(|| unreachable!());
430 let payload = draft_human_payload(event);
431
432 assert_eq!(payload["type"], "draft_human_notify");
433 }
434 }
435
436 mod make_pick {
439 use super::*;
440
441 #[test]
442 fn test_try_parse_make_pick_basic() {
443 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
444 {\n\
445 \"EventName\": \"PremierDraft_MKM_20260201\",\n\
446 \"PickInfo\": {\n\
447 \"CardId\": 12345,\n\
448 \"PackNumber\": 0,\n\
449 \"PickNumber\": 0\n\
450 }\n\
451 }";
452 let entry = unity_entry(body);
453 let result = try_parse(&entry, Some(test_timestamp()));
454
455 assert!(result.is_some());
456 let event = result.as_ref().unwrap_or_else(|| unreachable!());
457 let payload = draft_human_payload(event);
458
459 assert_eq!(payload["type"], "draft_human_pick");
460 assert_eq!(payload["event_name"], "PremierDraft_MKM_20260201");
461 assert_eq!(payload["card_id"], 12345);
462 assert_eq!(payload["pack_number"], 0);
463 assert_eq!(payload["pick_number"], 0);
464 }
465
466 #[test]
467 fn test_try_parse_make_pick_later_in_draft() {
468 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
469 {\n\
470 \"EventName\": \"TradDraft_DSK_20260115\",\n\
471 \"PickInfo\": {\n\
472 \"CardId\": 67890,\n\
473 \"PackNumber\": 1,\n\
474 \"PickNumber\": 7\n\
475 }\n\
476 }";
477 let entry = unity_entry(body);
478 let result = try_parse(&entry, Some(test_timestamp()));
479
480 assert!(result.is_some());
481 let event = result.as_ref().unwrap_or_else(|| unreachable!());
482 let payload = draft_human_payload(event);
483
484 assert_eq!(payload["card_id"], 67890);
485 assert_eq!(payload["pack_number"], 1);
486 assert_eq!(payload["pick_number"], 7);
487 assert_eq!(payload["event_name"], "TradDraft_DSK_20260115");
488 }
489
490 #[test]
491 fn test_try_parse_make_pick_with_card_ids() {
492 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
493 {\n\
494 \"EventName\": \"PremierDraft_MKM_20260201\",\n\
495 \"PickInfo\": {\n\
496 \"CardId\": 11111,\n\
497 \"PackNumber\": 0,\n\
498 \"PickNumber\": 0,\n\
499 \"CardIds\": [11111, 22222, 33333]\n\
500 }\n\
501 }";
502 let entry = unity_entry(body);
503 let result = try_parse(&entry, Some(test_timestamp()));
504
505 assert!(result.is_some());
506 let event = result.as_ref().unwrap_or_else(|| unreachable!());
507 let payload = draft_human_payload(event);
508
509 assert_eq!(payload["card_id"], 11111);
510 assert_eq!(
511 payload["card_ids"],
512 serde_json::json!([11111, 22222, 33333])
513 );
514 }
515
516 #[test]
517 fn test_try_parse_make_pick_flat_format() {
518 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
521 {\n\
522 \"CardId\": 55555,\n\
523 \"PackNumber\": 2,\n\
524 \"PickNumber\": 10\n\
525 }";
526 let entry = unity_entry(body);
527 let result = try_parse(&entry, Some(test_timestamp()));
528
529 assert!(result.is_some());
530 let event = result.as_ref().unwrap_or_else(|| unreachable!());
531 let payload = draft_human_payload(event);
532
533 assert_eq!(payload["card_id"], 55555);
534 assert_eq!(payload["pack_number"], 2);
535 assert_eq!(payload["pick_number"], 10);
536 }
537
538 #[test]
539 fn test_try_parse_make_pick_missing_card_id_returns_none() {
540 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
541 {\n\
542 \"PickInfo\": {\n\
543 \"PackNumber\": 0,\n\
544 \"PickNumber\": 0\n\
545 }\n\
546 }";
547 let entry = unity_entry(body);
548 let result = try_parse(&entry, Some(test_timestamp()));
549
550 assert!(result.is_none());
551 }
552
553 #[test]
554 fn test_try_parse_make_pick_preserves_raw_payload() {
555 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
556 {\n\
557 \"PickInfo\": {\n\
558 \"CardId\": 12345,\n\
559 \"PackNumber\": 0,\n\
560 \"PickNumber\": 0,\n\
561 \"ExtraField\": \"kept\"\n\
562 }\n\
563 }";
564 let entry = unity_entry(body);
565 let result = try_parse(&entry, Some(test_timestamp()));
566
567 assert!(result.is_some());
568 let event = result.as_ref().unwrap_or_else(|| unreachable!());
569 let payload = draft_human_payload(event);
570
571 assert_eq!(payload["raw_make_pick"]["PickInfo"]["ExtraField"], "kept");
572 }
573
574 #[test]
575 fn test_try_parse_make_pick_outbound_request_format() {
576 let body = "[UnityCrossThreadLogger]==> EventPlayerDraftMakePick\n\
577 {\n\
578 \"id\": \"b0114c5d-0462-4855-a7ab-d06ede720f93\",\n\
579 \"request\": \"{\\\"DraftId\\\":\\\"0784e646\\\",\\\"GrpIds\\\":[100486],\\\"Pack\\\":1,\\\"Pick\\\":2}\"\n\
580 }";
581 let entry = unity_entry(body);
582 let result = try_parse(&entry, Some(test_timestamp()));
583
584 assert!(result.is_some());
585 let event = result.as_ref().unwrap_or_else(|| unreachable!());
586 let payload = draft_human_payload(event);
587
588 assert_eq!(payload["type"], "draft_human_pick");
589 assert_eq!(payload["card_id"], 100_486);
590 assert_eq!(payload["pack_number"], 1);
591 assert_eq!(payload["pick_number"], 2);
592 assert_eq!(payload["draft_id"], "0784e646");
593 assert_eq!(payload["event_name"], ""); assert_eq!(
595 payload["raw_make_pick"]["id"],
596 "b0114c5d-0462-4855-a7ab-d06ede720f93"
597 );
598 }
599
600 #[test]
601 fn test_try_parse_make_pick_ignores_success_response() {
602 let body = "[UnityCrossThreadLogger]3/11/2026 9:44:16 PM\n\
603 <== EventPlayerDraftMakePick(b0114c5d-0462-4855-a7ab-d06ede720f93)\n\
604 {\"IsPickSuccessful\":true}";
605 let entry = unity_entry(body);
606 let result = try_parse(&entry, Some(test_timestamp()));
607
608 assert!(result.is_none());
609 }
610
611 #[test]
612 fn test_try_parse_make_pick_with_timestamp_in_header() {
613 let body = "[UnityCrossThreadLogger]2/25/2026 12:00:00 PM \
614 EventPlayerDraftMakePick\n\
615 {\n\
616 \"PickInfo\": {\n\
617 \"CardId\": 77777,\n\
618 \"PackNumber\": 0,\n\
619 \"PickNumber\": 1\n\
620 }\n\
621 }";
622 let entry = unity_entry(body);
623 let result = try_parse(&entry, Some(test_timestamp()));
624
625 assert!(result.is_some());
626 let event = result.as_ref().unwrap_or_else(|| unreachable!());
627 let payload = draft_human_payload(event);
628
629 assert_eq!(payload["card_id"], 77777);
630 }
631 }
632
633 mod metadata {
636 use super::*;
637
638 #[test]
639 fn test_try_parse_preserves_raw_bytes_notify() {
640 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
641 {\"SelfPack\": 0, \"SelfPick\": 0, \
642 \"PackCards\": \"12345\"}";
643 let entry = unity_entry(body);
644 let result = try_parse(&entry, Some(test_timestamp()));
645
646 assert!(result.is_some());
647 let event = result.as_ref().unwrap_or_else(|| unreachable!());
648 assert_eq!(event.metadata().raw_bytes(), body.as_bytes());
649 }
650
651 #[test]
652 fn test_try_parse_preserves_raw_bytes_make_pick() {
653 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
654 {\"PickInfo\": {\"CardId\": 1, \"PackNumber\": 0, \
655 \"PickNumber\": 0}}";
656 let entry = unity_entry(body);
657 let result = try_parse(&entry, Some(test_timestamp()));
658
659 assert!(result.is_some());
660 let event = result.as_ref().unwrap_or_else(|| unreachable!());
661 assert_eq!(event.metadata().raw_bytes(), body.as_bytes());
662 }
663
664 #[test]
665 fn test_try_parse_stores_timestamp_notify() {
666 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
667 {\"SelfPack\": 0, \"SelfPick\": 0, \
668 \"PackCards\": \"12345\"}";
669 let entry = unity_entry(body);
670 let ts = Some(test_timestamp());
671 let result = try_parse(&entry, ts);
672
673 assert!(result.is_some());
674 let event = result.as_ref().unwrap_or_else(|| unreachable!());
675 assert_eq!(event.metadata().timestamp(), ts);
676 }
677
678 #[test]
679 fn test_try_parse_stores_timestamp_make_pick() {
680 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
681 {\"PickInfo\": {\"CardId\": 1, \"PackNumber\": 0, \
682 \"PickNumber\": 0}}";
683 let entry = unity_entry(body);
684 let ts = Some(test_timestamp());
685 let result = try_parse(&entry, ts);
686
687 assert!(result.is_some());
688 let event = result.as_ref().unwrap_or_else(|| unreachable!());
689 assert_eq!(event.metadata().timestamp(), ts);
690 }
691 }
692
693 mod non_matching {
696 use super::*;
697
698 #[test]
699 fn test_try_parse_unrelated_entry_returns_none() {
700 let body = "[UnityCrossThreadLogger]greToClientEvent\n{\"data\": 1}";
701 let entry = unity_entry(body);
702 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
703 }
704
705 #[test]
706 fn test_try_parse_empty_body_returns_none() {
707 let body = "[UnityCrossThreadLogger]";
708 let entry = unity_entry(body);
709 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
710 }
711
712 #[test]
713 fn test_try_parse_bot_draft_entry_returns_none() {
714 let body = "[UnityCrossThreadLogger]BotDraft_DraftPick\n\
716 {\n\
717 \"PickInfo\": {\n\
718 \"CardId\": 12345,\n\
719 \"PackNumber\": 0,\n\
720 \"PickNumber\": 0\n\
721 }\n\
722 }";
723 let entry = unity_entry(body);
724 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
725 }
726
727 #[test]
728 fn test_try_parse_game_result_business_event_returns_none() {
729 let body = "[UnityCrossThreadLogger]LogBusinessEvents\n\
732 {\n\
733 \"WinningType\": \"WinLoss\",\n\
734 \"WinningTeamId\": 1\n\
735 }";
736 let entry = unity_entry(body);
737 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
738 }
739
740 #[test]
741 fn test_try_parse_malformed_json_notify_returns_none() {
742 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
743 {\"PackCards\": broken!!!}";
744 let entry = unity_entry(body);
745 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
746 }
747
748 #[test]
749 fn test_try_parse_malformed_json_make_pick_returns_none() {
750 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
751 {not valid json}";
752 let entry = unity_entry(body);
753 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
754 }
755
756 #[test]
757 fn test_try_parse_malformed_json_business_event_returns_none() {
758 let body = "[UnityCrossThreadLogger]LogBusinessEvents\n\
759 {\"PickGrpId\": broken!!!}";
760 let entry = unity_entry(body);
761 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
762 }
763
764 #[test]
765 fn test_try_parse_marker_only_no_json_notify_returns_none() {
766 let body = "[UnityCrossThreadLogger]Draft.Notify";
767 let entry = unity_entry(body);
768 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
769 }
770
771 #[test]
772 fn test_try_parse_marker_only_no_json_make_pick_returns_none() {
773 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick";
774 let entry = unity_entry(body);
775 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
776 }
777
778 #[test]
779 fn test_try_parse_draft_notify_no_pack_or_self_pack_returns_none() {
780 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
782 {\"unrelatedField\": \"value\"}";
783 let entry = unity_entry(body);
784 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
785 }
786
787 #[test]
788 fn test_try_parse_connection_manager_header_returns_none() {
789 let entry = LogEntry {
790 header: EntryHeader::ConnectionManager,
791 body: "[ConnectionManager]some connection message".to_owned(),
792 };
793 assert!(try_parse(&entry, Some(test_timestamp())).is_none());
794 }
795 }
796
797 mod performance_class {
800 use super::*;
801
802 #[test]
803 fn test_draft_human_notify_is_durable_per_event() {
804 let body = "[UnityCrossThreadLogger]Draft.Notify\n\
805 {\"SelfPack\": 0, \"SelfPick\": 0, \
806 \"PackCards\": \"12345\"}";
807 let entry = unity_entry(body);
808 let result = try_parse(&entry, Some(test_timestamp()));
809
810 assert!(result.is_some());
811 let event = result.as_ref().unwrap_or_else(|| unreachable!());
812 assert_eq!(event.performance_class(), PerformanceClass::DurablePerEvent);
813 }
814
815 #[test]
816 fn test_draft_human_pick_is_durable_per_event() {
817 let body = "[UnityCrossThreadLogger]EventPlayerDraftMakePick\n\
818 {\"PickInfo\": {\"CardId\": 1, \"PackNumber\": 0, \
819 \"PickNumber\": 0}}";
820 let entry = unity_entry(body);
821 let result = try_parse(&entry, Some(test_timestamp()));
822
823 assert!(result.is_some());
824 let event = result.as_ref().unwrap_or_else(|| unreachable!());
825 assert_eq!(event.performance_class(), PerformanceClass::DurablePerEvent);
826 }
827 }
828
829 mod helpers {
832 use super::*;
833
834 #[test]
835 fn test_parse_comma_separated_ids_basic() {
836 assert_eq!(
837 parse_comma_separated_ids("12345,67890,11111"),
838 vec![12345, 67890, 11111]
839 );
840 }
841
842 #[test]
843 fn test_parse_comma_separated_ids_with_spaces() {
844 assert_eq!(
845 parse_comma_separated_ids("12345, 67890, 11111"),
846 vec![12345, 67890, 11111]
847 );
848 }
849
850 #[test]
851 fn test_parse_comma_separated_ids_single() {
852 assert_eq!(parse_comma_separated_ids("12345"), vec![12345]);
853 }
854
855 #[test]
856 fn test_parse_comma_separated_ids_empty() {
857 let result: Vec<i64> = parse_comma_separated_ids("");
858 assert!(result.is_empty());
859 }
860
861 #[test]
862 fn test_parse_comma_separated_ids_with_invalid() {
863 assert_eq!(
864 parse_comma_separated_ids("12345,abc,67890"),
865 vec![12345, 67890]
866 );
867 }
868 }
869}