1#![no_std]
16
17extern crate alloc;
18
19pub mod auth;
20
21use alloc::collections::BTreeSet;
22use alloc::collections::{BTreeMap, BinaryHeap};
23
24use alloc::string::String;
25use alloc::vec::Vec;
26use core::cmp::Ordering;
27use serde::{Deserialize, Serialize};
28
29use serde_json::Value;
30
31#[cfg(feature = "std")]
32extern crate std;
33
34#[cfg(feature = "std")]
35pub use std::collections::HashMap;
36
37#[cfg(not(feature = "std"))]
38pub use hashbrown::HashMap;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
43pub enum StateResVersion {
44 V1,
45 V2,
46 V2_1,
47}
48
49#[derive(Debug, Clone)]
51pub enum KahnSortResult {
52 Ok(Vec<String>),
54 CycleDetected {
58 sorted: Vec<String>,
59 stuck: Vec<String>,
60 },
61}
62
63impl KahnSortResult {
64 pub fn into_sorted(self) -> Vec<String> {
67 match self {
68 KahnSortResult::Ok(v) => v,
69 KahnSortResult::CycleDetected { .. } => Vec::new(),
70 }
71 }
72
73 pub fn is_ok(&self) -> bool {
75 matches!(self, KahnSortResult::Ok(_))
76 }
77}
78
79fn deserialize_power_level<'de, D>(deserializer: D) -> Result<i64, D::Error>
82where
83 D: serde::Deserializer<'de>,
84{
85 use serde::de;
86
87 struct PowerLevelVisitor;
88
89 impl<'de> de::Visitor<'de> for PowerLevelVisitor {
90 type Value = i64;
91
92 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
93 formatter.write_str("an integer, float, or string representation of a power level")
94 }
95
96 fn visit_i64<E: de::Error>(self, v: i64) -> Result<i64, E> {
97 Ok(v)
98 }
99
100 fn visit_u64<E: de::Error>(self, v: u64) -> Result<i64, E> {
101 Ok(v as i64)
102 }
103
104 fn visit_f64<E: de::Error>(self, v: f64) -> Result<i64, E> {
105 Ok(v as i64)
106 }
107
108 fn visit_str<E: de::Error>(self, v: &str) -> Result<i64, E> {
109 Ok(v.parse::<i64>()
110 .or_else(|_| v.parse::<f64>().map(|f| f as i64))
111 .unwrap_or(0))
112 }
113 }
114
115 deserializer.deserialize_any(PowerLevelVisitor)
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, Default)]
120pub struct LeanEvent {
121 pub event_id: String,
122 #[serde(rename = "type")]
123 pub event_type: String,
124 #[serde(default)]
125 pub state_key: String,
126 #[serde(default, deserialize_with = "deserialize_power_level")]
127 pub power_level: i64,
128 pub origin_server_ts: u64,
129 #[serde(default)]
130 pub sender: String,
131 #[serde(default)]
132 pub content: Value,
133 #[serde(default)]
134 pub prev_events: Vec<String>,
135 #[serde(default)]
136 pub auth_events: Vec<String>,
137 #[serde(default)]
138 pub depth: u64, }
140
141impl PartialEq for LeanEvent {
142 fn eq(&self, other: &Self) -> bool {
143 self.event_id == other.event_id
144 }
145}
146
147impl Eq for LeanEvent {}
148
149impl Ord for LeanEvent {
150 fn cmp(&self, other: &Self) -> Ordering {
151 self.event_id.cmp(&other.event_id)
152 }
153}
154
155impl PartialOrd for LeanEvent {
156 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
157 Some(self.cmp(other))
158 }
159}
160
161impl LeanEvent {
162 pub fn cmp_by_depth(&self, other: &Self) -> Ordering {
166 self.depth
167 .cmp(&other.depth)
168 .then(self.event_id.cmp(&other.event_id))
169 }
170}
171
172#[derive(Debug, Clone, Copy)]
174struct SortPriority<'a> {
175 event: &'a LeanEvent,
176 version: StateResVersion,
177}
178
179impl<'a> PartialEq for SortPriority<'a> {
180 fn eq(&self, other: &Self) -> bool {
181 self.cmp(other) == Ordering::Equal
182 }
183}
184
185impl<'a> Eq for SortPriority<'a> {}
186
187impl<'a> Ord for SortPriority<'a> {
188 fn cmp(&self, other: &Self) -> Ordering {
189 match self.version {
190 StateResVersion::V1 => {
191 match other.event.depth.cmp(&self.event.depth) {
194 Ordering::Equal => other.event.event_id.cmp(&self.event.event_id),
195 ord => ord,
196 }
197 }
198 StateResVersion::V2 | StateResVersion::V2_1 => {
199 match other.event.power_level.cmp(&self.event.power_level) {
207 Ordering::Equal => {
208 match other
211 .event
212 .origin_server_ts
213 .cmp(&self.event.origin_server_ts)
214 {
215 Ordering::Equal => {
216 self.event.event_id.cmp(&other.event.event_id)
219 }
220 ord => ord,
221 }
222 }
223 ord => ord,
224 }
225 }
226 }
227 }
228}
229
230impl<'a> PartialOrd for SortPriority<'a> {
231 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
232 Some(self.cmp(other))
233 }
234}
235
236pub fn lean_kahn_sort_detailed(
240 events: &HashMap<String, LeanEvent>,
241 version: StateResVersion,
242) -> KahnSortResult {
243 let mut in_degree: HashMap<String, usize> = HashMap::new();
244 let mut adjacency: HashMap<String, Vec<String>> = HashMap::new();
245
246 for (id, event) in events {
247 in_degree.entry(id.clone()).or_insert(0);
248 for auth in &event.auth_events {
249 if events.contains_key(auth) {
250 adjacency.entry(auth.clone()).or_default().push(id.clone());
251 *in_degree.entry(id.clone()).or_insert(0) += 1;
252 }
253 }
254 }
255
256 let mut queue: BinaryHeap<SortPriority> = BinaryHeap::new();
257 for (id, °ree) in &in_degree {
258 if degree == 0 {
259 if let Some(event) = events.get(id) {
260 queue.push(SortPriority { event, version });
261 }
262 }
263 }
264
265 let mut result = Vec::new();
266 while let Some(priority) = queue.pop() {
267 let event = priority.event;
268 result.push(event.event_id.clone());
269 if let Some(neighbors) = adjacency.get(&event.event_id) {
270 for next_id in neighbors {
271 let degree = in_degree.get_mut(next_id).unwrap();
272 *degree -= 1;
273 if *degree == 0 {
274 queue.push(SortPriority {
275 event: events.get(next_id).unwrap(),
276 version,
277 });
278 }
279 }
280 }
281 }
282
283 if result.len() != events.len() {
285 let sorted_set: alloc::collections::BTreeSet<&String> = result.iter().collect();
286 let stuck: Vec<String> = events
287 .keys()
288 .filter(|id| !sorted_set.contains(id))
289 .cloned()
290 .collect();
291 return KahnSortResult::CycleDetected {
292 sorted: result,
293 stuck,
294 };
295 }
296
297 KahnSortResult::Ok(result)
298}
299
300pub fn lean_kahn_sort(
303 events: &HashMap<String, LeanEvent>,
304 version: StateResVersion,
305) -> Vec<String> {
306 lean_kahn_sort_detailed(events, version).into_sorted()
307}
308
309pub fn resolve_lean(
310 unconflicted_state: BTreeMap<(String, String), String>,
311 conflicted_events: HashMap<String, LeanEvent>,
312 version: StateResVersion,
313) -> BTreeMap<(String, String), String> {
314 let (mut resolved, sort_set) = match version {
316 StateResVersion::V2_1 => (BTreeMap::new(), conflicted_events.clone()),
317 _ => (unconflicted_state, conflicted_events),
318 };
319
320 let mut power_events = HashMap::new();
327 let mut non_power_events = HashMap::new();
328
329 for (id, ev) in &sort_set {
330 if ev.event_type == "m.room.member"
331 || ev.event_type == "m.room.create"
332 || ev.event_type == "m.room.power_levels"
333 || ev.event_type == "m.room.join_rules"
334 {
335 power_events.insert(id.clone(), ev.clone());
336 } else {
337 non_power_events.insert(id.clone(), ev.clone());
338 }
339 }
340
341 let sorted_power_ids = lean_kahn_sort(&power_events, version);
344 for id in &sorted_power_ids {
345 if let Some(event) = sort_set.get(id) {
346 if iterative_auth_ok(event, &resolved, &sort_set) {
347 resolved.insert(
348 (event.event_type.clone(), event.state_key.clone()),
349 event.event_id.clone(),
350 );
351 }
352 }
353 }
354
355 let mainline = build_mainline(&resolved, &sort_set);
357
358 let mut non_power_list: Vec<&LeanEvent> = non_power_events.values().collect();
360 mainline_sort(&mut non_power_list, &mainline, &sort_set);
361
362 for ev in non_power_list {
363 if iterative_auth_ok(ev, &resolved, &sort_set) {
364 resolved.insert(
365 (ev.event_type.clone(), ev.state_key.clone()),
366 ev.event_id.clone(),
367 );
368 }
369 }
370
371 resolved
372}
373
374fn iterative_auth_ok(
379 event: &LeanEvent,
380 resolved: &BTreeMap<(String, String), String>,
381 all_events: &HashMap<String, LeanEvent>,
382) -> bool {
383 if event.event_type == "m.room.member" {
385 let new_membership = event
386 .content
387 .get("membership")
388 .and_then(|v| v.as_str())
389 .unwrap_or("");
390
391 if new_membership == "join" || new_membership == "invite" {
392 let target_key = (
393 alloc::string::String::from("m.room.member"),
394 event.state_key.clone(),
395 );
396 if let Some(resolved_eid) = resolved.get(&target_key) {
397 if let Some(resolved_ev) = all_events.get(resolved_eid) {
398 let resolved_membership = resolved_ev
399 .content
400 .get("membership")
401 .and_then(|v| v.as_str())
402 .unwrap_or("");
403 if resolved_membership == "ban" && resolved_ev.sender != resolved_ev.state_key {
405 return false;
406 }
407 }
408 }
409 }
410 }
411
412 true
413}
414
415fn build_mainline(
418 resolved: &BTreeMap<(String, String), String>,
419 all_events: &HashMap<String, LeanEvent>,
420) -> Vec<String> {
421 let mut mainline = Vec::new();
422 let pl_key = (
423 alloc::string::String::from("m.room.power_levels"),
424 alloc::string::String::new(),
425 );
426 let mut current = resolved.get(&pl_key).cloned();
427
428 while let Some(eid) = current {
429 mainline.push(eid.clone());
430 current = None;
431 if let Some(ev) = all_events.get(&eid) {
432 for auth_id in &ev.auth_events {
433 if let Some(auth_ev) = all_events.get(auth_id) {
434 if auth_ev.event_type == "m.room.power_levels" {
435 current = Some(auth_id.clone());
436 break;
437 }
438 }
439 }
440 }
441 }
442
443 mainline
444}
445
446fn closest_mainline_position(
449 event: &LeanEvent,
450 mainline: &[String],
451 all_events: &HashMap<String, LeanEvent>,
452) -> usize {
453 if let Some(pos) = mainline.iter().position(|id| id == &event.event_id) {
455 return pos;
456 }
457
458 let mut visited = alloc::collections::BTreeSet::new();
460 let mut stack: Vec<String> = event.auth_events.clone();
461
462 while let Some(auth_id) = stack.pop() {
463 if !visited.insert(auth_id.clone()) {
464 continue;
465 }
466 if let Some(pos) = mainline.iter().position(|id| id == &auth_id) {
467 return pos;
468 }
469 if let Some(auth_ev) = all_events.get(&auth_id) {
470 for parent_auth in &auth_ev.auth_events {
471 stack.push(parent_auth.clone());
472 }
473 }
474 }
475
476 mainline.len()
478}
479
480fn mainline_sort(
485 events: &mut Vec<&LeanEvent>,
486 mainline: &[String],
487 all_events: &HashMap<String, LeanEvent>,
488) {
489 let positions: HashMap<String, usize> = events
491 .iter()
492 .map(|ev| {
493 (
494 ev.event_id.clone(),
495 closest_mainline_position(ev, mainline, all_events),
496 )
497 })
498 .collect();
499
500 events.sort_by(|a, b| {
501 let pos_a = positions.get(&a.event_id).copied().unwrap_or(usize::MAX);
502 let pos_b = positions.get(&b.event_id).copied().unwrap_or(usize::MAX);
503
504 match pos_b.cmp(&pos_a) {
507 Ordering::Equal => {
508 match a.origin_server_ts.cmp(&b.origin_server_ts) {
510 Ordering::Equal => a.event_id.cmp(&b.event_id),
511 ord => ord,
512 }
513 }
514 ord => ord,
515 }
516 });
517}
518
519#[derive(Debug, Clone)]
521pub struct SubgraphResult {
522 pub subgraph: HashMap<String, LeanEvent>,
524 pub missing_auth_events: Vec<String>,
526}
527
528pub fn compute_v2_1_conflicted_subgraph(
529 auth_graph: &HashMap<String, LeanEvent>,
530 conflicted_set: &[String],
531) -> HashMap<String, LeanEvent> {
532 compute_v2_1_conflicted_subgraph_bounded(auth_graph, conflicted_set, None).subgraph
533}
534
535pub fn compute_v2_1_conflicted_subgraph_bounded(
540 auth_graph: &HashMap<String, LeanEvent>,
541 conflicted_set: &[String],
542 max_auth_depth: Option<usize>,
543) -> SubgraphResult {
544 let mut backwards_reachable = BTreeSet::new();
545 let mut forwards_reachable = BTreeSet::new();
546 let mut missing_auth_events = BTreeSet::new();
547
548 let mut b_stack: Vec<(String, usize)> = conflicted_set.iter().map(|s| (s.clone(), 0)).collect();
551 while let Some((node, depth)) = b_stack.pop() {
552 if let Some(max_depth) = max_auth_depth {
554 if depth > max_depth {
555 continue;
556 }
557 }
558 if backwards_reachable.insert(node.clone()) {
559 if let Some(event) = auth_graph.get(&node) {
560 for auth_id in &event.auth_events {
561 if !auth_graph.contains_key(auth_id) {
562 missing_auth_events.insert(auth_id.clone());
563 }
564 b_stack.push((auth_id.clone(), depth + 1));
565 }
566 }
567 }
568 }
569
570 let mut children_map: HashMap<String, Vec<String>> = HashMap::new();
572 for (id, event) in auth_graph {
573 for prev in &event.auth_events {
574 children_map
575 .entry(prev.clone())
576 .or_default()
577 .push(id.clone());
578 }
579 }
580
581 let mut f_stack: Vec<String> = conflicted_set.to_vec();
583 while let Some(node) = f_stack.pop() {
584 if forwards_reachable.insert(node.clone()) {
585 if let Some(children) = children_map.get(&node) {
586 f_stack.extend(children.clone());
587 }
588 }
589 }
590
591 let mut subgraph = HashMap::new();
593 let backwards_ids: BTreeSet<String> = backwards_reachable.iter().cloned().collect();
594 let forwards_ids: BTreeSet<String> = forwards_reachable.iter().cloned().collect();
595
596 for id in backwards_ids.intersection(&forwards_ids) {
597 if let Some(event) = auth_graph.get(id) {
598 subgraph.insert(id.clone(), event.clone());
599 }
600 }
601
602 SubgraphResult {
603 subgraph,
604 missing_auth_events: missing_auth_events.into_iter().collect(),
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611 use alloc::string::ToString;
612 use alloc::vec;
613
614 #[cfg(not(feature = "std"))]
615 use hashbrown::HashMap;
616 #[cfg(feature = "std")]
617 use std::collections::HashMap;
618
619 #[test]
620 fn test_leanevent_deserialization_defaults() {
621 let json = r#"{
622 "event_id": "$test",
623 "type": "m.room.message",
624 "origin_server_ts": 12345
625 }"#;
626 let ev: LeanEvent = serde_json::from_str(json).unwrap();
627 assert_eq!(ev.event_id, "$test");
628 assert_eq!(ev.event_type, "m.room.message");
629 assert_eq!(ev.origin_server_ts, 12345);
630 assert_eq!(ev.state_key, "");
631 assert_eq!(ev.power_level, 0);
632 assert_eq!(ev.sender, "");
633 assert_eq!(ev.prev_events.len(), 0);
634 assert_eq!(ev.auth_events.len(), 0);
635 assert_eq!(ev.depth, 0);
636 }
637
638 #[test]
639 fn test_sort_priority_v2_tie_break() {
640 let e_base = LeanEvent {
641 event_id: "$1".into(),
642 power_level: 100,
643 origin_server_ts: 10,
644 ..Default::default()
645 };
646 let e_worst_pl = LeanEvent {
647 event_id: "$2".into(),
648 power_level: 50,
649 origin_server_ts: 10,
650 ..Default::default()
651 };
652 let p_base = SortPriority {
653 event: &e_base,
654 version: StateResVersion::V2,
655 };
656 let p_worst_pl = SortPriority {
657 event: &e_worst_pl,
658 version: StateResVersion::V2,
659 };
660
661 assert_eq!(p_base.cmp(&p_worst_pl), Ordering::Less); let e_later_ts = LeanEvent {
665 event_id: "$3".into(),
666 power_level: 100,
667 origin_server_ts: 20,
668 ..Default::default()
669 };
670 let p_later_ts = SortPriority {
671 event: &e_later_ts,
672 version: StateResVersion::V2,
673 };
674 assert_eq!(p_base.cmp(&p_later_ts), Ordering::Greater);
676
677 let e_larger_id = LeanEvent {
678 event_id: "$2".into(),
679 power_level: 100,
680 origin_server_ts: 10,
681 ..Default::default()
682 };
683 let p_larger_id = SortPriority {
684 event: &e_larger_id,
685 version: StateResVersion::V2,
686 };
687 assert_eq!(p_base.cmp(&p_larger_id), Ordering::Less);
689 }
690
691 #[test]
692 fn test_v1_resolution_happy_path() {
693 let mut events = HashMap::new();
694 events.insert(
695 "A".into(),
696 LeanEvent {
697 event_id: "A".into(),
698 event_type: "m.room.member".into(),
699 state_key: "@alice:example.com".into(),
700 power_level: 0,
701 origin_server_ts: 100,
702 prev_events: vec![],
703 auth_events: vec![],
704 depth: 1,
705 ..Default::default()
706 },
707 );
708 events.insert(
709 "B".into(),
710 LeanEvent {
711 event_id: "B".into(),
712 event_type: "m.room.member".into(),
713 state_key: "@alice:example.com".into(),
714 power_level: 0,
715 origin_server_ts: 50,
716 prev_events: vec![],
717 auth_events: vec!["A".into()],
718 depth: 2,
719 ..Default::default()
720 },
721 );
722 let sorted = lean_kahn_sort(&events, StateResVersion::V1);
723 assert_eq!(sorted, vec!["A", "B"]);
724 }
725
726 #[test]
727 fn test_v2_1_strict_resolution() {
728 let mut unconflicted = BTreeMap::new();
729 unconflicted.insert(
730 ("m.room.member".into(), "@alice:example.com".into()),
731 "A".into(),
732 );
733
734 let mut conflicted = HashMap::new();
735 conflicted.insert(
736 "A".into(),
737 LeanEvent {
738 event_id: "A".into(),
739 event_type: "m.room.member".into(),
740 state_key: "@alice:example.com".into(),
741 power_level: 50,
742 origin_server_ts: 100,
743 prev_events: vec![],
744 auth_events: vec![],
745 depth: 1,
746 ..Default::default()
747 },
748 );
749 conflicted.insert(
750 "B".into(),
751 LeanEvent {
752 event_id: "B".into(),
753 event_type: "m.room.member".into(),
754 state_key: "@alice:example.com".into(),
755 power_level: 100,
756 origin_server_ts: 50,
757 prev_events: vec![],
758 auth_events: vec![],
759 depth: 1,
760 ..Default::default()
761 },
762 );
763
764 let resolved = resolve_lean(unconflicted, conflicted, StateResVersion::V2_1);
767 assert_eq!(
768 resolved.get(&("m.room.member".into(), "@alice:example.com".into())),
769 Some(&"B".into())
770 );
771 }
772
773 #[test]
774 fn test_v1_tie_break_by_id() {
775 let mut events = HashMap::new();
776 events.insert(
777 "B".into(),
778 LeanEvent {
779 event_id: "B".into(),
780 event_type: "m.room.member".into(),
781 state_key: "@alice:example.com".into(),
782 power_level: 0,
783 origin_server_ts: 100,
784 prev_events: vec![],
785 auth_events: vec![],
786 depth: 1,
787 ..Default::default()
788 },
789 );
790 events.insert(
791 "A".into(),
792 LeanEvent {
793 event_id: "A".into(),
794 event_type: "m.room.member".into(),
795 state_key: "@alice:example.com".into(),
796 power_level: 0,
797 origin_server_ts: 100,
798 prev_events: vec![],
799 auth_events: vec![],
800 depth: 1,
801 ..Default::default()
802 },
803 );
804 let sorted = lean_kahn_sort(&events, StateResVersion::V1);
805 assert_eq!(sorted, vec!["A", "B"]);
806 }
807
808 #[test]
809 fn test_v2_resolution_happy_path() {
810 let mut events = HashMap::new();
811 events.insert(
812 "A".into(),
813 LeanEvent {
814 event_id: "A".into(),
815 event_type: "m.room.member".into(),
816 state_key: "@alice:example.com".into(),
817 power_level: 100,
818 origin_server_ts: 100,
819 prev_events: vec![],
820 auth_events: vec![],
821 depth: 10,
822 ..Default::default()
823 },
824 );
825 events.insert(
826 "B".into(),
827 LeanEvent {
828 event_id: "B".into(),
829 event_type: "m.room.member".into(),
830 state_key: "@alice:example.com".into(),
831 power_level: 50,
832 origin_server_ts: 10,
833 prev_events: vec![],
834 auth_events: vec![],
835 depth: 1,
836 ..Default::default()
837 },
838 );
839 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
840 assert_eq!(sorted, vec!["B", "A"]);
842 }
843
844 #[test]
845 fn test_v2_deep_tie_break() {
846 let mut events = HashMap::new();
847 events.insert(
848 "B".into(),
849 LeanEvent {
850 event_id: "B".into(),
851 event_type: "m.room.member".into(),
852 state_key: "@alice:example.com".into(),
853 power_level: 100,
854 origin_server_ts: 10,
855 prev_events: vec![],
856 auth_events: vec![],
857 depth: 1,
858 ..Default::default()
859 },
860 );
861 events.insert(
862 "A".into(),
863 LeanEvent {
864 event_id: "A".into(),
865 event_type: "m.room.member".into(),
866 state_key: "@alice:example.com".into(),
867 power_level: 100,
868 origin_server_ts: 10,
869 prev_events: vec![],
870 auth_events: vec![],
871 depth: 1,
872 ..Default::default()
873 },
874 );
875 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
876 assert_eq!(sorted, vec!["B", "A"]);
878 }
879
880 #[test]
881 fn test_v1_v2_v2_1_comparison_determinism() {
882 let mut events = HashMap::new();
883 events.insert(
884 "A".into(),
885 LeanEvent {
886 event_id: "A".into(),
887 event_type: "m.room.member".into(),
888 state_key: "@alice:example.com".into(),
889 power_level: 10,
890 origin_server_ts: 10,
891 prev_events: vec![],
892 auth_events: vec![],
893 depth: 1,
894 ..Default::default()
895 },
896 );
897 events.insert(
898 "B".into(),
899 LeanEvent {
900 event_id: "B".into(),
901 event_type: "m.room.member".into(),
902 state_key: "@alice:example.com".into(),
903 power_level: 100,
904 origin_server_ts: 100,
905 prev_events: vec![],
906 auth_events: vec![],
907 depth: 10,
908 ..Default::default()
909 },
910 );
911 let sorted_v1 = lean_kahn_sort(&events, StateResVersion::V1);
912 let sorted_v2 = lean_kahn_sort(&events, StateResVersion::V2);
913 let sorted_v2_1 = lean_kahn_sort(&events, StateResVersion::V2_1);
914 assert_eq!(sorted_v1, vec!["A", "B"]);
915 assert_eq!(sorted_v2, vec!["A", "B"]);
917 assert_eq!(sorted_v2_1, vec!["A", "B"]);
918 }
919
920 #[test]
921 fn test_unhappy_path_cycle_detection() {
922 let mut events = HashMap::new();
923 events.insert(
924 "A".into(),
925 LeanEvent {
926 event_id: "A".into(),
927 event_type: "m.room.member".into(),
928 state_key: "@alice:example.com".into(),
929 power_level: 100,
930 origin_server_ts: 100,
931 prev_events: vec!["B".into()],
932 auth_events: vec!["B".into()],
933 depth: 1,
934 ..Default::default()
935 },
936 );
937 events.insert(
938 "B".into(),
939 LeanEvent {
940 event_id: "B".into(),
941 event_type: "m.room.member".into(),
942 state_key: "@alice:example.com".into(),
943 power_level: 100,
944 origin_server_ts: 100,
945 prev_events: vec!["A".into()],
946 auth_events: vec!["A".into()],
947 depth: 1,
948 ..Default::default()
949 },
950 );
951 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
952 assert!(sorted.is_empty());
953 }
954
955 #[test]
956 fn test_serialization_roundtrip() {
957 let event = LeanEvent {
958 event_id: "$abc".into(),
959 event_type: "m.room.member".into(),
960 state_key: "@alice:example.com".into(),
961 power_level: 100,
962 origin_server_ts: 12345,
963 prev_events: vec![],
964 auth_events: vec![],
965 depth: 5,
966 ..Default::default()
967 };
968 let serialized = serde_json::to_string(&event).unwrap();
969 let deserialized: LeanEvent = serde_json::from_str(&serialized).unwrap();
970 assert_eq!(event, deserialized);
971 }
972
973 #[test]
974 fn test_partial_ord_implementations() {
975 let e1 = LeanEvent {
976 event_id: "a".into(),
977 event_type: "m.room.member".into(),
978 state_key: "@alice:example.com".into(),
979 power_level: 100,
980 origin_server_ts: 10,
981 prev_events: vec![],
982 auth_events: vec![],
983 depth: 1,
984 ..Default::default()
985 };
986 let e2 = LeanEvent {
987 event_id: "b".into(),
988 event_type: "m.room.member".into(),
989 state_key: "@alice:example.com".into(),
990 power_level: 100,
991 origin_server_ts: 10,
992 prev_events: vec![],
993 auth_events: vec![],
994 depth: 1,
995 ..Default::default()
996 };
997 assert!(e1.partial_cmp(&e2).is_some());
998
999 let p1 = SortPriority {
1000 event: &e1,
1001 version: StateResVersion::V2,
1002 };
1003 let p2 = SortPriority {
1004 event: &e2,
1005 version: StateResVersion::V2,
1006 };
1007 assert!(p1.partial_cmp(&p2).is_some());
1008 }
1009
1010 #[test]
1011 fn test_trait_coverage() {
1012 let v = StateResVersion::V2;
1013 assert_eq!(v, StateResVersion::V2);
1014 let _ = alloc::format!("{:?}", v);
1015
1016 let e = LeanEvent {
1017 event_id: "a".into(),
1018 event_type: "m.room.member".into(),
1019 state_key: "@alice:example.com".into(),
1020 power_level: 100,
1021 origin_server_ts: 10,
1022 prev_events: vec![],
1023 auth_events: vec![],
1024 depth: 1,
1025 ..Default::default()
1026 };
1027 let _ = e.clone();
1028 let _ = alloc::format!("{:?}", e);
1029 }
1030
1031 #[test]
1032 fn test_complex_dag_sort() {
1033 let mut events = HashMap::new();
1034 events.insert(
1035 "1".into(),
1036 LeanEvent {
1037 event_id: "1".into(),
1038 event_type: "m.room.member".into(),
1039 state_key: "@alice:example.com".into(),
1040 power_level: 100,
1041 origin_server_ts: 10,
1042 prev_events: vec![],
1043 auth_events: vec![],
1044 depth: 1,
1045 ..Default::default()
1046 },
1047 );
1048 events.insert(
1049 "2".into(),
1050 LeanEvent {
1051 event_id: "2".into(),
1052 event_type: "m.room.member".into(),
1053 state_key: "@alice:example.com".into(),
1054 power_level: 50,
1055 origin_server_ts: 20,
1056 prev_events: vec!["1".into()],
1057 auth_events: vec!["1".into()],
1058 depth: 2,
1059 ..Default::default()
1060 },
1061 );
1062 events.insert(
1063 "3".into(),
1064 LeanEvent {
1065 event_id: "3".into(),
1066 event_type: "m.room.member".into(),
1067 state_key: "@alice:example.com".into(),
1068 power_level: 50,
1069 origin_server_ts: 15,
1070 prev_events: vec!["1".into()],
1071 auth_events: vec!["1".into()],
1072 depth: 2,
1073 ..Default::default()
1074 },
1075 );
1076 events.insert(
1077 "4".into(),
1078 LeanEvent {
1079 event_id: "4".into(),
1080 event_type: "m.room.member".into(),
1081 state_key: "@alice:example.com".into(),
1082 power_level: 10,
1083 origin_server_ts: 30,
1084 prev_events: vec!["2".into(), "3".into()],
1085 auth_events: vec!["2".into(), "3".into()],
1086 depth: 3,
1087 ..Default::default()
1088 },
1089 );
1090 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1091 assert_eq!(sorted, vec!["1", "3", "2", "4"]);
1096 }
1097
1098 #[test]
1099 fn test_kahn_missing_parents() {
1100 let mut events = HashMap::new();
1101 events.insert(
1102 "A".into(),
1103 LeanEvent {
1104 event_id: "A".into(),
1105 event_type: "m.room.member".into(),
1106 state_key: "@alice:example.com".into(),
1107 power_level: 100,
1108 origin_server_ts: 10,
1109 prev_events: vec!["MISSING".into()],
1110 auth_events: vec!["MISSING".into()],
1111 depth: 1,
1112 ..Default::default()
1113 },
1114 );
1115 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1116 assert_eq!(sorted, vec!["A"]);
1117 }
1118
1119 #[test]
1120 fn test_resolve_lean_functionality() {
1121 let mut unconflicted = BTreeMap::new();
1122 unconflicted.insert(("type".into(), "key".into()), "id".into());
1123 let conflicted = HashMap::new();
1124 let resolved = resolve_lean(unconflicted.clone(), conflicted, StateResVersion::V2);
1125 assert_eq!(resolved, unconflicted);
1126 }
1127
1128 #[test]
1129 fn test_resolve_lean_v2_1_overlay() {
1130 use serde_json::json;
1131
1132 let mut unconflicted = BTreeMap::new();
1133 unconflicted.insert(
1134 ("m.room.member".into(), "@alice:example.com".into()),
1135 "id1".into(),
1136 );
1137 unconflicted.insert(
1138 ("m.room.member".into(), "@bob:example.com".into()),
1139 "id2".into(),
1140 );
1141
1142 let mut conflicted = HashMap::new();
1143 conflicted.insert(
1145 "create".into(),
1146 LeanEvent {
1147 event_id: "create".into(),
1148 event_type: "m.room.create".into(),
1149 state_key: String::new(),
1150 sender: "@alice:example.com".into(),
1151 power_level: 100,
1152 origin_server_ts: 1,
1153 content: json!({}),
1154 ..Default::default()
1155 },
1156 );
1157 conflicted.insert(
1159 "id1".into(),
1160 LeanEvent {
1161 event_id: "id1".into(),
1162 event_type: "m.room.member".into(),
1163 state_key: "@alice:example.com".into(),
1164 sender: "@alice:example.com".into(),
1165 power_level: 50,
1166 origin_server_ts: 500,
1167 content: json!({"membership": "join"}),
1168 auth_events: vec!["create".into()],
1169 ..Default::default()
1170 },
1171 );
1172 conflicted.insert(
1173 "id2".into(),
1174 LeanEvent {
1175 event_id: "id2".into(),
1176 event_type: "m.room.member".into(),
1177 state_key: "@bob:example.com".into(),
1178 sender: "@bob:example.com".into(),
1179 power_level: 50,
1180 origin_server_ts: 500,
1181 content: json!({"membership": "join"}),
1182 auth_events: vec!["create".into()],
1183 ..Default::default()
1184 },
1185 );
1186 conflicted.insert(
1187 "id2_new".into(),
1188 LeanEvent {
1189 event_id: "id2_new".into(),
1190 event_type: "m.room.member".into(),
1191 state_key: "@bob:example.com".into(),
1192 sender: "@bob:example.com".into(),
1193 power_level: 100,
1194 origin_server_ts: 1000,
1195 content: json!({"membership": "join"}),
1196 auth_events: vec!["create".into()],
1197 ..Default::default()
1198 },
1199 );
1200
1201 let resolved = resolve_lean(unconflicted.clone(), conflicted, StateResVersion::V2_1);
1202
1203 assert_eq!(
1204 resolved.get(&("m.room.member".into(), "@alice:example.com".into())),
1205 Some(&"id1".into())
1206 );
1207 assert_eq!(
1208 resolved.get(&("m.room.member".into(), "@bob:example.com".into())),
1209 Some(&"id2_new".into())
1210 );
1211 }
1212
1213 fn run_batch_test(
1214 version: StateResVersion,
1215 rows: &[(&str, i64, u64, u64, &[&str])],
1216 expected: &[&str],
1217 ) {
1218 let mut events = HashMap::new();
1219 for r in rows {
1220 events.insert(
1221 r.0.to_string(),
1222 LeanEvent {
1223 event_id: r.0.to_string(),
1224 event_type: "m.room.member".into(),
1225 state_key: "@alice:example.com".into(),
1226 power_level: r.1,
1227 origin_server_ts: r.2,
1228 depth: r.3,
1229 prev_events: r.4.iter().map(|s| s.to_string()).collect(),
1230 auth_events: r.4.iter().map(|s| s.to_string()).collect(),
1231 ..Default::default()
1232 },
1233 );
1234 }
1235 let result = lean_kahn_sort(&events, version);
1236 assert_eq!(
1237 result,
1238 expected.iter().map(|s| s.to_string()).collect::<Vec<_>>()
1239 );
1240 }
1241
1242 #[test]
1243 fn test_resolution_batch() {
1244 run_batch_test(
1245 StateResVersion::V2,
1246 &[("Alice", 100, 500, 1, &[]), ("Bob", 50, 100, 1, &[])],
1247 &["Bob", "Alice"], );
1249 run_batch_test(
1250 StateResVersion::V1,
1251 &[("Deep", 100, 100, 10, &[]), ("Shallow", 10, 100, 1, &[])],
1252 &["Shallow", "Deep"],
1253 );
1254 }
1255
1256 #[test]
1257 fn test_native_resolution_bootstrap_parity() {
1258 let mut events = HashMap::new();
1259 events.insert(
1260 "1".into(),
1261 LeanEvent {
1262 event_id: "1".into(),
1263 event_type: "m.room.member".into(),
1264 state_key: "@user:example.com".into(),
1265 power_level: 100,
1266 origin_server_ts: 10,
1267 prev_events: vec![],
1268 auth_events: vec![],
1269 depth: 1,
1270 ..Default::default()
1271 },
1272 );
1273 events.insert(
1274 "2".into(),
1275 LeanEvent {
1276 event_id: "2".into(),
1277 event_type: "m.room.member".into(),
1278 state_key: "@user:example.com".into(),
1279 power_level: 0,
1280 origin_server_ts: 20,
1281 prev_events: vec!["1".into()],
1282 auth_events: vec!["1".into()],
1283 depth: 2,
1284 ..Default::default()
1285 },
1286 );
1287 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1288 let mut resolved_state = BTreeMap::new();
1289 for id in sorted {
1290 let ev = events.get(&id).unwrap();
1291 let key = (ev.event_type.clone(), ev.state_key.clone());
1292 resolved_state.insert(key, ev.event_id.clone());
1293 }
1294 assert_eq!(
1295 resolved_state.get(&("m.room.member".to_string(), "@user:example.com".to_string())),
1296 Some(&"2".to_string())
1297 );
1298 }
1299
1300 #[test]
1301 fn test_enum_coverage() {
1302 let v = StateResVersion::V2;
1303 let v2 = v;
1304 assert_eq!(v, v2);
1305 let debug_str = alloc::format!("{:?}", v);
1306 assert!(debug_str.contains("V2"));
1307 }
1308
1309 #[test]
1310 fn test_event_traits_coverage() {
1311 let e = LeanEvent {
1312 event_id: "a".into(),
1313 event_type: "m.room.member".into(),
1314 state_key: "@alice:example.com".into(),
1315 power_level: 100,
1316 origin_server_ts: 10,
1317 prev_events: vec![],
1318 auth_events: vec![],
1319 depth: 1,
1320 ..Default::default()
1321 };
1322 let e2 = e.clone();
1323 assert_eq!(e, e2);
1324 let debug_str = alloc::format!("{:?}", e);
1325 assert!(debug_str.contains("event_id"));
1326 }
1327
1328 #[test]
1329 fn test_sort_priority_traits() {
1330 let e = LeanEvent {
1331 event_id: "a".into(),
1332 event_type: "m.room.member".into(),
1333 state_key: "@alice:example.com".into(),
1334 power_level: 100,
1335 origin_server_ts: 10,
1336 prev_events: vec![],
1337 auth_events: vec![],
1338 depth: 1,
1339 ..Default::default()
1340 };
1341 let p = SortPriority {
1342 event: &e,
1343 version: StateResVersion::V2,
1344 };
1345 let p2 = p;
1346 assert_eq!(p, p2);
1347 let debug_str = alloc::format!("{:?}", p);
1348 assert!(debug_str.contains("version"));
1349 }
1350
1351 #[test]
1352 fn test_v1_equal_depth_tie_break() {
1353 let mut events = HashMap::new();
1354 events.insert(
1355 "B".into(),
1356 LeanEvent {
1357 event_id: "B".into(),
1358 event_type: "m.room.member".into(),
1359 state_key: "@alice:example.com".into(),
1360 power_level: 0,
1361 origin_server_ts: 10,
1362 prev_events: vec![],
1363 auth_events: vec![],
1364 depth: 1,
1365 ..Default::default()
1366 },
1367 );
1368 events.insert(
1369 "A".into(),
1370 LeanEvent {
1371 event_id: "A".into(),
1372 event_type: "m.room.member".into(),
1373 state_key: "@alice:example.com".into(),
1374 power_level: 0,
1375 origin_server_ts: 10,
1376 prev_events: vec![],
1377 auth_events: vec![],
1378 depth: 1,
1379 ..Default::default()
1380 },
1381 );
1382 let sorted = lean_kahn_sort(&events, StateResVersion::V1);
1383 assert_eq!(sorted, vec!["A", "B"]);
1384 }
1385
1386 #[test]
1387 fn test_kahn_no_neighbors() {
1388 let mut events = HashMap::new();
1389 events.insert(
1390 "1".into(),
1391 LeanEvent {
1392 event_id: "1".into(),
1393 event_type: "m.room.member".into(),
1394 state_key: "@alice:example.com".into(),
1395 power_level: 100,
1396 origin_server_ts: 10,
1397 prev_events: vec![],
1398 auth_events: vec![],
1399 depth: 1,
1400 ..Default::default()
1401 },
1402 );
1403 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1404 assert_eq!(sorted, vec!["1"]);
1405 }
1406
1407 #[test]
1408 fn test_v2_1_full_coverage() {
1409 let mut events = HashMap::new();
1410 events.insert(
1411 "A".into(),
1412 LeanEvent {
1413 event_id: "A".into(),
1414 event_type: "m.room.member".into(),
1415 state_key: "@alice:example.com".into(),
1416 power_level: 100,
1417 origin_server_ts: 10,
1418 prev_events: vec![],
1419 auth_events: vec![],
1420 depth: 1,
1421 ..Default::default()
1422 },
1423 );
1424 let sorted = lean_kahn_sort(&events, StateResVersion::V2_1);
1425 assert_eq!(sorted, vec!["A"]);
1426 }
1427
1428 #[test]
1432 fn test_v2_1_later_timestamp_wins() {
1433 let mut events = HashMap::new();
1434 events.insert(
1435 "$early".into(),
1436 LeanEvent {
1437 event_id: "$early".into(),
1438 event_type: "m.room.member".into(),
1439 state_key: "@user:example.com".into(),
1440 power_level: 100,
1441 origin_server_ts: 1000,
1442 auth_events: vec![],
1443 ..Default::default()
1444 },
1445 );
1446 events.insert(
1447 "$late".into(),
1448 LeanEvent {
1449 event_id: "$late".into(),
1450 event_type: "m.room.member".into(),
1451 state_key: "@user:example.com".into(),
1452 power_level: 100,
1453 origin_server_ts: 2000,
1454 auth_events: vec![],
1455 ..Default::default()
1456 },
1457 );
1458 let sorted = lean_kahn_sort(&events, StateResVersion::V2_1);
1460 assert_eq!(sorted, vec!["$early", "$late"]);
1461
1462 let sorted_v2 = lean_kahn_sort(&events, StateResVersion::V2);
1464 assert_eq!(sorted_v2, vec!["$early", "$late"]);
1465 }
1466
1467 #[test]
1470 fn test_v2_1_millisecond_race_tiebreak() {
1471 let mut events = HashMap::new();
1472 events.insert(
1473 "$ban_a".into(),
1474 LeanEvent {
1475 event_id: "$ban_a".into(),
1476 event_type: "m.room.member".into(),
1477 state_key: "@spammer:evil.com".into(),
1478 power_level: 50,
1479 origin_server_ts: 1772724243891,
1480 auth_events: vec![],
1481 ..Default::default()
1482 },
1483 );
1484 events.insert(
1485 "$ban_b".into(),
1486 LeanEvent {
1487 event_id: "$ban_b".into(),
1488 event_type: "m.room.member".into(),
1489 state_key: "@spammer:evil.com".into(),
1490 power_level: 50,
1491 origin_server_ts: 1772724243893, auth_events: vec![],
1493 ..Default::default()
1494 },
1495 );
1496 let sorted_v2 = lean_kahn_sort(&events, StateResVersion::V2);
1498 assert_eq!(sorted_v2, vec!["$ban_a", "$ban_b"]);
1499
1500 let sorted_v2_1 = lean_kahn_sort(&events, StateResVersion::V2_1);
1501 assert_eq!(sorted_v2_1, vec!["$ban_a", "$ban_b"]);
1502 }
1503
1504 #[test]
1505 fn test_total_order_properties() {
1506 let e1 = LeanEvent {
1507 event_id: "a".into(),
1508 event_type: "m.room.member".into(),
1509 state_key: "@alice:example.com".into(),
1510 power_level: 100,
1511 origin_server_ts: 10,
1512 prev_events: vec![],
1513 auth_events: vec![],
1514 depth: 1,
1515 ..Default::default()
1516 };
1517 let e2 = LeanEvent {
1518 event_id: "b".into(),
1519 event_type: "m.room.member".into(),
1520 state_key: "@alice:example.com".into(),
1521 power_level: 100,
1522 origin_server_ts: 10,
1523 prev_events: vec![],
1524 auth_events: vec![],
1525 depth: 1,
1526 ..Default::default()
1527 };
1528 let e3 = LeanEvent {
1529 event_id: "c".into(),
1530 event_type: "m.room.member".into(),
1531 state_key: "@alice:example.com".into(),
1532 power_level: 50,
1533 origin_server_ts: 10,
1534 prev_events: vec![],
1535 auth_events: vec![],
1536 depth: 1,
1537 ..Default::default()
1538 };
1539 assert_eq!(e1.cmp(&e1), Ordering::Equal);
1540 assert!(e1 <= e1);
1541 assert!(e1 <= e2 || e2 <= e1);
1542 if e1 <= e2 && e2 <= e3 {
1543 assert!(e1 <= e3);
1544 }
1545 let e1_copy = e1.clone();
1546 if e1 <= e1_copy && e1_copy <= e1 {
1547 assert_eq!(e1, e1_copy);
1548 }
1549 }
1550
1551 #[test]
1552 fn test_coverage_booster_all_branches() {
1553 let e_base = LeanEvent {
1554 event_id: "m".into(),
1555 event_type: "m.room.member".into(),
1556 state_key: "@alice:example.com".into(),
1557 power_level: 50,
1558 origin_server_ts: 50,
1559 prev_events: vec![],
1560 auth_events: vec![],
1561 depth: 50,
1562 ..Default::default()
1563 };
1564 let p_base = SortPriority {
1565 event: &e_base,
1566 version: StateResVersion::V2,
1567 };
1568 let e_high_power = LeanEvent {
1569 power_level: 100,
1570 ..e_base.clone()
1571 };
1572 let p_high_power = SortPriority {
1573 event: &e_high_power,
1574 version: StateResVersion::V2,
1575 };
1576 assert_eq!(p_base.cmp(&p_high_power), Ordering::Greater);
1578 let e_early_ts = LeanEvent {
1579 origin_server_ts: 10,
1580 ..e_base.clone()
1581 };
1582 let p_early_ts = SortPriority {
1583 event: &e_early_ts,
1584 version: StateResVersion::V2,
1585 };
1586 assert_eq!(p_base.cmp(&p_early_ts), Ordering::Less);
1588 let e_early_id = LeanEvent {
1589 event_id: "a".into(),
1590 ..e_base.clone()
1591 };
1592 let p_early_id = SortPriority {
1593 event: &e_early_id,
1594 version: StateResVersion::V2,
1595 };
1596 assert_eq!(p_base.cmp(&p_early_id), Ordering::Greater);
1598 let p_v1_base = SortPriority {
1599 event: &e_base,
1600 version: StateResVersion::V1,
1601 };
1602 let e_shallow = LeanEvent {
1603 depth: 1,
1604 ..e_base.clone()
1605 };
1606 let p_shallow = SortPriority {
1607 event: &e_shallow,
1608 version: StateResVersion::V1,
1609 };
1610 assert_eq!(p_v1_base.cmp(&p_shallow), Ordering::Less);
1611 let p_v1_early_id = SortPriority {
1612 event: &e_early_id,
1613 version: StateResVersion::V1,
1614 };
1615 assert_eq!(p_v1_base.cmp(&p_v1_early_id), Ordering::Less);
1616 assert_eq!(p_v1_base.cmp(&p_v1_base), Ordering::Equal);
1617 }
1618
1619 #[test]
1624 fn test_cycle_detection_detailed() {
1625 let mut events = HashMap::new();
1626 events.insert(
1627 "A".into(),
1628 LeanEvent {
1629 event_id: "A".into(),
1630 event_type: "m.room.member".into(),
1631 state_key: "@alice:example.com".into(),
1632 auth_events: vec!["B".into()],
1633 ..Default::default()
1634 },
1635 );
1636 events.insert(
1637 "B".into(),
1638 LeanEvent {
1639 event_id: "B".into(),
1640 event_type: "m.room.member".into(),
1641 state_key: "@alice:example.com".into(),
1642 auth_events: vec!["A".into()],
1643 ..Default::default()
1644 },
1645 );
1646 let result = lean_kahn_sort_detailed(&events, StateResVersion::V2);
1647 match result {
1648 KahnSortResult::CycleDetected { sorted, stuck } => {
1649 assert!(sorted.is_empty());
1650 assert_eq!(stuck.len(), 2);
1651 let mut stuck_sorted = stuck.clone();
1652 stuck_sorted.sort();
1653 assert_eq!(stuck_sorted, vec!["A", "B"]);
1654 }
1655 KahnSortResult::Ok(_) => panic!("Expected cycle detection"),
1656 }
1657 }
1658
1659 #[test]
1660 fn test_cycle_detection_partial_sort() {
1661 let mut events = HashMap::new();
1663 events.insert(
1664 "C".into(),
1665 LeanEvent {
1666 event_id: "C".into(),
1667 event_type: "m.room.member".into(),
1668 state_key: "@alice:example.com".into(),
1669 auth_events: vec![],
1670 ..Default::default()
1671 },
1672 );
1673 events.insert(
1674 "A".into(),
1675 LeanEvent {
1676 event_id: "A".into(),
1677 event_type: "m.room.member".into(),
1678 state_key: "@alice:example.com".into(),
1679 auth_events: vec!["B".into(), "C".into()],
1680 ..Default::default()
1681 },
1682 );
1683 events.insert(
1684 "B".into(),
1685 LeanEvent {
1686 event_id: "B".into(),
1687 event_type: "m.room.member".into(),
1688 state_key: "@alice:example.com".into(),
1689 auth_events: vec!["A".into()],
1690 ..Default::default()
1691 },
1692 );
1693 let result = lean_kahn_sort_detailed(&events, StateResVersion::V2);
1694 match result {
1695 KahnSortResult::CycleDetected { sorted, stuck } => {
1696 assert_eq!(sorted, vec!["C"]);
1697 assert_eq!(stuck.len(), 2);
1698 }
1699 KahnSortResult::Ok(_) => panic!("Expected cycle detection"),
1700 }
1701 }
1702
1703 #[test]
1704 fn test_kahn_sort_result_api() {
1705 let ok = KahnSortResult::Ok(vec!["A".into()]);
1706 assert!(ok.is_ok());
1707 assert_eq!(ok.into_sorted(), vec!["A"]);
1708
1709 let cycle = KahnSortResult::CycleDetected {
1710 sorted: vec!["C".into()],
1711 stuck: vec!["A".into(), "B".into()],
1712 };
1713 assert!(!cycle.is_ok());
1714 assert!(cycle.into_sorted().is_empty());
1715 }
1716
1717 #[test]
1718 fn test_power_level_coercion_integer() {
1719 let json = r#"{"event_id": "$1", "type": "m.room.member", "origin_server_ts": 1, "power_level": 100}"#;
1720 let ev: LeanEvent = serde_json::from_str(json).unwrap();
1721 assert_eq!(ev.power_level, 100);
1722 }
1723
1724 #[test]
1725 fn test_power_level_coercion_string() {
1726 let json = r#"{"event_id": "$1", "type": "m.room.member", "origin_server_ts": 1, "power_level": "100"}"#;
1727 let ev: LeanEvent = serde_json::from_str(json).unwrap();
1728 assert_eq!(ev.power_level, 100);
1729 }
1730
1731 #[test]
1732 fn test_power_level_coercion_float() {
1733 let json = r#"{"event_id": "$1", "type": "m.room.member", "origin_server_ts": 1, "power_level": 100.0}"#;
1734 let ev: LeanEvent = serde_json::from_str(json).unwrap();
1735 assert_eq!(ev.power_level, 100);
1736 }
1737
1738 #[test]
1739 fn test_power_level_coercion_invalid_string() {
1740 let json = r#"{"event_id": "$1", "type": "m.room.member", "origin_server_ts": 1, "power_level": "abc"}"#;
1741 let ev: LeanEvent = serde_json::from_str(json).unwrap();
1742 assert_eq!(ev.power_level, 0);
1743 }
1744
1745 #[test]
1746 fn test_deep_chain_stack_safety() {
1747 let mut events = HashMap::new();
1749 for i in 0..1000u32 {
1750 let id = alloc::format!("ev_{}", i);
1751 let auth = if i > 0 {
1752 vec![alloc::format!("ev_{}", i - 1)]
1753 } else {
1754 vec![]
1755 };
1756 events.insert(
1757 id.clone(),
1758 LeanEvent {
1759 event_id: id,
1760 event_type: "m.room.member".into(),
1761 state_key: "@alice:example.com".into(),
1762 power_level: 100,
1763 origin_server_ts: i as u64,
1764 auth_events: auth,
1765 depth: i as u64,
1766 ..Default::default()
1767 },
1768 );
1769 }
1770 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1771 assert_eq!(sorted.len(), 1000);
1772 assert_eq!(sorted[0], "ev_0");
1774 assert_eq!(sorted[999], "ev_999");
1776 }
1777
1778 #[test]
1779 fn test_subgraph_bounded_depth() {
1780 let mut graph = HashMap::new();
1782 for (id, auths) in [
1783 ("A", vec![]),
1784 ("B", vec!["A"]),
1785 ("C", vec!["B"]),
1786 ("D", vec!["C"]),
1787 ] {
1788 graph.insert(
1789 id.to_string(),
1790 LeanEvent {
1791 event_id: id.into(),
1792 event_type: "m.room.member".into(),
1793 state_key: "@alice:example.com".into(),
1794 auth_events: auths.iter().map(|s| s.to_string()).collect(),
1795 ..Default::default()
1796 },
1797 );
1798 }
1799 let full = compute_v2_1_conflicted_subgraph_bounded(
1801 &graph,
1802 &["A".to_string(), "D".to_string()],
1803 None,
1804 );
1805 assert!(full.subgraph.contains_key("A"));
1806 assert!(full.subgraph.contains_key("D"));
1807
1808 let bounded = compute_v2_1_conflicted_subgraph_bounded(
1812 &graph,
1813 &["A".to_string(), "D".to_string()],
1814 Some(1),
1815 );
1816 assert!(bounded.subgraph.contains_key("D"));
1818 assert!(bounded.subgraph.contains_key("A"));
1819 assert!(!bounded.subgraph.contains_key("B"));
1821 }
1822
1823 #[test]
1824 fn test_subgraph_missing_auth_detection() {
1825 let mut graph = HashMap::new();
1826 graph.insert(
1827 "X".to_string(),
1828 LeanEvent {
1829 event_id: "X".into(),
1830 event_type: "m.room.member".into(),
1831 state_key: "@alice:example.com".into(),
1832 auth_events: vec!["MISSING_1".into(), "MISSING_2".into()],
1833 ..Default::default()
1834 },
1835 );
1836 let result = compute_v2_1_conflicted_subgraph_bounded(&graph, &["X".to_string()], None);
1837 let mut missing = result.missing_auth_events.clone();
1838 missing.sort();
1839 assert_eq!(missing, vec!["MISSING_1", "MISSING_2"]);
1840 }
1841}