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
161#[derive(Debug, Clone, Copy)]
163struct SortPriority<'a> {
164 event: &'a LeanEvent,
165 version: StateResVersion,
166}
167
168impl<'a> PartialEq for SortPriority<'a> {
169 fn eq(&self, other: &Self) -> bool {
170 self.cmp(other) == Ordering::Equal
171 }
172}
173
174impl<'a> Eq for SortPriority<'a> {}
175
176impl<'a> Ord for SortPriority<'a> {
177 fn cmp(&self, other: &Self) -> Ordering {
178 match self.version {
179 StateResVersion::V1 => {
180 match other.event.depth.cmp(&self.event.depth) {
183 Ordering::Equal => other.event.event_id.cmp(&self.event.event_id),
184 ord => ord,
185 }
186 }
187 StateResVersion::V2 | StateResVersion::V2_1 => {
188 match other.event.power_level.cmp(&self.event.power_level) {
196 Ordering::Equal => {
197 match other
200 .event
201 .origin_server_ts
202 .cmp(&self.event.origin_server_ts)
203 {
204 Ordering::Equal => {
205 self.event.event_id.cmp(&other.event.event_id)
208 }
209 ord => ord,
210 }
211 }
212 ord => ord,
213 }
214 }
215 }
216 }
217}
218
219impl<'a> PartialOrd for SortPriority<'a> {
220 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
221 Some(self.cmp(other))
222 }
223}
224
225pub fn lean_kahn_sort_detailed(
229 events: &HashMap<String, LeanEvent>,
230 version: StateResVersion,
231) -> KahnSortResult {
232 let mut in_degree: HashMap<String, usize> = HashMap::new();
233 let mut adjacency: HashMap<String, Vec<String>> = HashMap::new();
234
235 for (id, event) in events {
236 in_degree.entry(id.clone()).or_insert(0);
237 for auth in &event.auth_events {
238 if events.contains_key(auth) {
239 adjacency.entry(auth.clone()).or_default().push(id.clone());
240 *in_degree.entry(id.clone()).or_insert(0) += 1;
241 }
242 }
243 }
244
245 let mut queue: BinaryHeap<SortPriority> = BinaryHeap::new();
246 for (id, °ree) in &in_degree {
247 if degree == 0 {
248 if let Some(event) = events.get(id) {
249 queue.push(SortPriority { event, version });
250 }
251 }
252 }
253
254 let mut result = Vec::new();
255 while let Some(priority) = queue.pop() {
256 let event = priority.event;
257 result.push(event.event_id.clone());
258 if let Some(neighbors) = adjacency.get(&event.event_id) {
259 for next_id in neighbors {
260 let degree = in_degree.get_mut(next_id).unwrap();
261 *degree -= 1;
262 if *degree == 0 {
263 queue.push(SortPriority {
264 event: events.get(next_id).unwrap(),
265 version,
266 });
267 }
268 }
269 }
270 }
271
272 if result.len() != events.len() {
274 let sorted_set: alloc::collections::BTreeSet<&String> = result.iter().collect();
275 let stuck: Vec<String> = events
276 .keys()
277 .filter(|id| !sorted_set.contains(id))
278 .cloned()
279 .collect();
280 return KahnSortResult::CycleDetected {
281 sorted: result,
282 stuck,
283 };
284 }
285
286 KahnSortResult::Ok(result)
287}
288
289pub fn lean_kahn_sort(
292 events: &HashMap<String, LeanEvent>,
293 version: StateResVersion,
294) -> Vec<String> {
295 lean_kahn_sort_detailed(events, version).into_sorted()
296}
297
298pub fn resolve_lean(
299 unconflicted_state: BTreeMap<(String, String), String>,
300 conflicted_events: HashMap<String, LeanEvent>,
301 version: StateResVersion,
302) -> BTreeMap<(String, String), String> {
303 let (mut resolved, sort_set) = match version {
305 StateResVersion::V2_1 => (BTreeMap::new(), conflicted_events.clone()),
306 _ => (unconflicted_state, conflicted_events),
307 };
308
309 let mut power_events = HashMap::new();
316 let mut non_power_events = HashMap::new();
317
318 for (id, ev) in &sort_set {
319 if ev.event_type == "m.room.member"
320 || ev.event_type == "m.room.create"
321 || ev.event_type == "m.room.power_levels"
322 || ev.event_type == "m.room.join_rules"
323 {
324 power_events.insert(id.clone(), ev.clone());
325 } else {
326 non_power_events.insert(id.clone(), ev.clone());
327 }
328 }
329
330 let sorted_power_ids = lean_kahn_sort(&power_events, version);
333 for id in &sorted_power_ids {
334 if let Some(event) = sort_set.get(id) {
335 if iterative_auth_ok(event, &resolved, &sort_set) {
336 resolved.insert(
337 (event.event_type.clone(), event.state_key.clone()),
338 event.event_id.clone(),
339 );
340 }
341 }
342 }
343
344 let mainline = build_mainline(&resolved, &sort_set);
346
347 let mut non_power_list: Vec<&LeanEvent> = non_power_events.values().collect();
349 mainline_sort(&mut non_power_list, &mainline, &sort_set);
350
351 for ev in non_power_list {
352 if iterative_auth_ok(ev, &resolved, &sort_set) {
353 resolved.insert(
354 (ev.event_type.clone(), ev.state_key.clone()),
355 ev.event_id.clone(),
356 );
357 }
358 }
359
360 resolved
361}
362
363fn iterative_auth_ok(
368 event: &LeanEvent,
369 resolved: &BTreeMap<(String, String), String>,
370 all_events: &HashMap<String, LeanEvent>,
371) -> bool {
372 if event.event_type == "m.room.member" {
374 let new_membership = event
375 .content
376 .get("membership")
377 .and_then(|v| v.as_str())
378 .unwrap_or("");
379
380 if new_membership == "join" || new_membership == "invite" {
381 let target_key = (
382 alloc::string::String::from("m.room.member"),
383 event.state_key.clone(),
384 );
385 if let Some(resolved_eid) = resolved.get(&target_key) {
386 if let Some(resolved_ev) = all_events.get(resolved_eid) {
387 let resolved_membership = resolved_ev
388 .content
389 .get("membership")
390 .and_then(|v| v.as_str())
391 .unwrap_or("");
392 if resolved_membership == "ban" && resolved_ev.sender != resolved_ev.state_key {
394 return false;
395 }
396 }
397 }
398 }
399 }
400
401 true
402}
403
404fn build_mainline(
407 resolved: &BTreeMap<(String, String), String>,
408 all_events: &HashMap<String, LeanEvent>,
409) -> Vec<String> {
410 let mut mainline = Vec::new();
411 let pl_key = (
412 alloc::string::String::from("m.room.power_levels"),
413 alloc::string::String::new(),
414 );
415 let mut current = resolved.get(&pl_key).cloned();
416
417 while let Some(eid) = current {
418 mainline.push(eid.clone());
419 current = None;
420 if let Some(ev) = all_events.get(&eid) {
421 for auth_id in &ev.auth_events {
422 if let Some(auth_ev) = all_events.get(auth_id) {
423 if auth_ev.event_type == "m.room.power_levels" {
424 current = Some(auth_id.clone());
425 break;
426 }
427 }
428 }
429 }
430 }
431
432 mainline
433}
434
435fn closest_mainline_position(
438 event: &LeanEvent,
439 mainline: &[String],
440 all_events: &HashMap<String, LeanEvent>,
441) -> usize {
442 if let Some(pos) = mainline.iter().position(|id| id == &event.event_id) {
444 return pos;
445 }
446
447 let mut visited = alloc::collections::BTreeSet::new();
449 let mut stack: Vec<String> = event.auth_events.clone();
450
451 while let Some(auth_id) = stack.pop() {
452 if !visited.insert(auth_id.clone()) {
453 continue;
454 }
455 if let Some(pos) = mainline.iter().position(|id| id == &auth_id) {
456 return pos;
457 }
458 if let Some(auth_ev) = all_events.get(&auth_id) {
459 for parent_auth in &auth_ev.auth_events {
460 stack.push(parent_auth.clone());
461 }
462 }
463 }
464
465 mainline.len()
467}
468
469fn mainline_sort(
474 events: &mut Vec<&LeanEvent>,
475 mainline: &[String],
476 all_events: &HashMap<String, LeanEvent>,
477) {
478 let positions: HashMap<String, usize> = events
480 .iter()
481 .map(|ev| {
482 (
483 ev.event_id.clone(),
484 closest_mainline_position(ev, mainline, all_events),
485 )
486 })
487 .collect();
488
489 events.sort_by(|a, b| {
490 let pos_a = positions.get(&a.event_id).copied().unwrap_or(usize::MAX);
491 let pos_b = positions.get(&b.event_id).copied().unwrap_or(usize::MAX);
492
493 match pos_b.cmp(&pos_a) {
496 Ordering::Equal => {
497 match a.origin_server_ts.cmp(&b.origin_server_ts) {
499 Ordering::Equal => a.event_id.cmp(&b.event_id),
500 ord => ord,
501 }
502 }
503 ord => ord,
504 }
505 });
506}
507
508#[derive(Debug, Clone)]
510pub struct SubgraphResult {
511 pub subgraph: HashMap<String, LeanEvent>,
513 pub missing_auth_events: Vec<String>,
515}
516
517pub fn compute_v2_1_conflicted_subgraph(
518 auth_graph: &HashMap<String, LeanEvent>,
519 conflicted_set: &[String],
520) -> HashMap<String, LeanEvent> {
521 compute_v2_1_conflicted_subgraph_bounded(auth_graph, conflicted_set, None).subgraph
522}
523
524pub fn compute_v2_1_conflicted_subgraph_bounded(
529 auth_graph: &HashMap<String, LeanEvent>,
530 conflicted_set: &[String],
531 max_auth_depth: Option<usize>,
532) -> SubgraphResult {
533 let mut backwards_reachable = BTreeSet::new();
534 let mut forwards_reachable = BTreeSet::new();
535 let mut missing_auth_events = BTreeSet::new();
536
537 let mut b_stack: Vec<(String, usize)> = conflicted_set.iter().map(|s| (s.clone(), 0)).collect();
540 while let Some((node, depth)) = b_stack.pop() {
541 if let Some(max_depth) = max_auth_depth {
543 if depth > max_depth {
544 continue;
545 }
546 }
547 if backwards_reachable.insert(node.clone()) {
548 if let Some(event) = auth_graph.get(&node) {
549 for auth_id in &event.auth_events {
550 if !auth_graph.contains_key(auth_id) {
551 missing_auth_events.insert(auth_id.clone());
552 }
553 b_stack.push((auth_id.clone(), depth + 1));
554 }
555 }
556 }
557 }
558
559 let mut children_map: HashMap<String, Vec<String>> = HashMap::new();
561 for (id, event) in auth_graph {
562 for prev in &event.auth_events {
563 children_map
564 .entry(prev.clone())
565 .or_default()
566 .push(id.clone());
567 }
568 }
569
570 let mut f_stack: Vec<String> = conflicted_set.to_vec();
572 while let Some(node) = f_stack.pop() {
573 if forwards_reachable.insert(node.clone()) {
574 if let Some(children) = children_map.get(&node) {
575 f_stack.extend(children.clone());
576 }
577 }
578 }
579
580 let mut subgraph = HashMap::new();
582 let backwards_ids: BTreeSet<String> = backwards_reachable.iter().cloned().collect();
583 let forwards_ids: BTreeSet<String> = forwards_reachable.iter().cloned().collect();
584
585 for id in backwards_ids.intersection(&forwards_ids) {
586 if let Some(event) = auth_graph.get(id) {
587 subgraph.insert(id.clone(), event.clone());
588 }
589 }
590
591 SubgraphResult {
592 subgraph,
593 missing_auth_events: missing_auth_events.into_iter().collect(),
594 }
595}
596
597#[cfg(feature = "zkvm")]
598pub fn verify_signature(_public_key: &[u8; 32], _message: &[u8], _signature: &[u8; 64]) {
599 }
601
602#[cfg(all(feature = "std", not(feature = "zkvm")))]
603pub fn verify_signature(public_key: &[u8; 32], message: &[u8], signature: &[u8; 64]) {
604 use ed25519_consensus::{Signature, VerificationKey};
605 let vk = VerificationKey::try_from(*public_key).expect("Invalid public key");
606 let sig = Signature::from(*signature);
607 vk.verify(&sig, message)
608 .expect("Signature verification failed");
609}
610
611#[cfg(all(not(feature = "std"), not(feature = "zkvm")))]
612pub fn verify_signature(_public_key: &[u8; 32], _message: &[u8], _signature: &[u8; 64]) {
613 }
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619 use alloc::string::ToString;
620 use alloc::vec;
621
622 #[cfg(not(feature = "std"))]
623 use hashbrown::HashMap;
624 #[cfg(feature = "std")]
625 use std::collections::HashMap;
626
627 #[test]
628 fn test_leanevent_deserialization_defaults() {
629 let json = r#"{
630 "event_id": "$test",
631 "type": "m.room.message",
632 "origin_server_ts": 12345
633 }"#;
634 let ev: LeanEvent = serde_json::from_str(json).unwrap();
635 assert_eq!(ev.event_id, "$test");
636 assert_eq!(ev.event_type, "m.room.message");
637 assert_eq!(ev.origin_server_ts, 12345);
638 assert_eq!(ev.state_key, "");
639 assert_eq!(ev.power_level, 0);
640 assert_eq!(ev.sender, "");
641 assert_eq!(ev.prev_events.len(), 0);
642 assert_eq!(ev.auth_events.len(), 0);
643 assert_eq!(ev.depth, 0);
644 }
645
646 #[test]
647 fn test_sort_priority_v2_tie_break() {
648 let e_base = LeanEvent {
649 event_id: "$1".into(),
650 power_level: 100,
651 origin_server_ts: 10,
652 ..Default::default()
653 };
654 let e_worst_pl = LeanEvent {
655 event_id: "$2".into(),
656 power_level: 50,
657 origin_server_ts: 10,
658 ..Default::default()
659 };
660 let p_base = SortPriority {
661 event: &e_base,
662 version: StateResVersion::V2,
663 };
664 let p_worst_pl = SortPriority {
665 event: &e_worst_pl,
666 version: StateResVersion::V2,
667 };
668
669 assert_eq!(p_base.cmp(&p_worst_pl), Ordering::Less); let e_later_ts = LeanEvent {
673 event_id: "$3".into(),
674 power_level: 100,
675 origin_server_ts: 20,
676 ..Default::default()
677 };
678 let p_later_ts = SortPriority {
679 event: &e_later_ts,
680 version: StateResVersion::V2,
681 };
682 assert_eq!(p_base.cmp(&p_later_ts), Ordering::Greater);
684
685 let e_larger_id = LeanEvent {
686 event_id: "$2".into(),
687 power_level: 100,
688 origin_server_ts: 10,
689 ..Default::default()
690 };
691 let p_larger_id = SortPriority {
692 event: &e_larger_id,
693 version: StateResVersion::V2,
694 };
695 assert_eq!(p_base.cmp(&p_larger_id), Ordering::Less);
697 }
698
699 #[test]
700 fn test_v1_resolution_happy_path() {
701 let mut events = HashMap::new();
702 events.insert(
703 "A".into(),
704 LeanEvent {
705 event_id: "A".into(),
706 event_type: "m.room.member".into(),
707 state_key: "@alice:example.com".into(),
708 power_level: 0,
709 origin_server_ts: 100,
710 prev_events: vec![],
711 auth_events: vec![],
712 depth: 1,
713 ..Default::default()
714 },
715 );
716 events.insert(
717 "B".into(),
718 LeanEvent {
719 event_id: "B".into(),
720 event_type: "m.room.member".into(),
721 state_key: "@alice:example.com".into(),
722 power_level: 0,
723 origin_server_ts: 50,
724 prev_events: vec![],
725 auth_events: vec!["A".into()],
726 depth: 2,
727 ..Default::default()
728 },
729 );
730 let sorted = lean_kahn_sort(&events, StateResVersion::V1);
731 assert_eq!(sorted, vec!["A", "B"]);
732 }
733
734 #[test]
735 fn test_v2_1_strict_resolution() {
736 let mut unconflicted = BTreeMap::new();
737 unconflicted.insert(
738 ("m.room.member".into(), "@alice:example.com".into()),
739 "A".into(),
740 );
741
742 let mut conflicted = HashMap::new();
743 conflicted.insert(
744 "A".into(),
745 LeanEvent {
746 event_id: "A".into(),
747 event_type: "m.room.member".into(),
748 state_key: "@alice:example.com".into(),
749 power_level: 50,
750 origin_server_ts: 100,
751 prev_events: vec![],
752 auth_events: vec![],
753 depth: 1,
754 ..Default::default()
755 },
756 );
757 conflicted.insert(
758 "B".into(),
759 LeanEvent {
760 event_id: "B".into(),
761 event_type: "m.room.member".into(),
762 state_key: "@alice:example.com".into(),
763 power_level: 100,
764 origin_server_ts: 50,
765 prev_events: vec![],
766 auth_events: vec![],
767 depth: 1,
768 ..Default::default()
769 },
770 );
771
772 let resolved = resolve_lean(unconflicted, conflicted, StateResVersion::V2_1);
775 assert_eq!(
776 resolved.get(&("m.room.member".into(), "@alice:example.com".into())),
777 Some(&"B".into())
778 );
779 }
780
781 #[test]
782 fn test_v1_tie_break_by_id() {
783 let mut events = HashMap::new();
784 events.insert(
785 "B".into(),
786 LeanEvent {
787 event_id: "B".into(),
788 event_type: "m.room.member".into(),
789 state_key: "@alice:example.com".into(),
790 power_level: 0,
791 origin_server_ts: 100,
792 prev_events: vec![],
793 auth_events: vec![],
794 depth: 1,
795 ..Default::default()
796 },
797 );
798 events.insert(
799 "A".into(),
800 LeanEvent {
801 event_id: "A".into(),
802 event_type: "m.room.member".into(),
803 state_key: "@alice:example.com".into(),
804 power_level: 0,
805 origin_server_ts: 100,
806 prev_events: vec![],
807 auth_events: vec![],
808 depth: 1,
809 ..Default::default()
810 },
811 );
812 let sorted = lean_kahn_sort(&events, StateResVersion::V1);
813 assert_eq!(sorted, vec!["A", "B"]);
814 }
815
816 #[test]
817 fn test_v2_resolution_happy_path() {
818 let mut events = HashMap::new();
819 events.insert(
820 "A".into(),
821 LeanEvent {
822 event_id: "A".into(),
823 event_type: "m.room.member".into(),
824 state_key: "@alice:example.com".into(),
825 power_level: 100,
826 origin_server_ts: 100,
827 prev_events: vec![],
828 auth_events: vec![],
829 depth: 10,
830 ..Default::default()
831 },
832 );
833 events.insert(
834 "B".into(),
835 LeanEvent {
836 event_id: "B".into(),
837 event_type: "m.room.member".into(),
838 state_key: "@alice:example.com".into(),
839 power_level: 50,
840 origin_server_ts: 10,
841 prev_events: vec![],
842 auth_events: vec![],
843 depth: 1,
844 ..Default::default()
845 },
846 );
847 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
848 assert_eq!(sorted, vec!["B", "A"]);
850 }
851
852 #[test]
853 fn test_v2_deep_tie_break() {
854 let mut events = HashMap::new();
855 events.insert(
856 "B".into(),
857 LeanEvent {
858 event_id: "B".into(),
859 event_type: "m.room.member".into(),
860 state_key: "@alice:example.com".into(),
861 power_level: 100,
862 origin_server_ts: 10,
863 prev_events: vec![],
864 auth_events: vec![],
865 depth: 1,
866 ..Default::default()
867 },
868 );
869 events.insert(
870 "A".into(),
871 LeanEvent {
872 event_id: "A".into(),
873 event_type: "m.room.member".into(),
874 state_key: "@alice:example.com".into(),
875 power_level: 100,
876 origin_server_ts: 10,
877 prev_events: vec![],
878 auth_events: vec![],
879 depth: 1,
880 ..Default::default()
881 },
882 );
883 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
884 assert_eq!(sorted, vec!["B", "A"]);
886 }
887
888 #[test]
889 fn test_v1_v2_v2_1_comparison_determinism() {
890 let mut events = HashMap::new();
891 events.insert(
892 "A".into(),
893 LeanEvent {
894 event_id: "A".into(),
895 event_type: "m.room.member".into(),
896 state_key: "@alice:example.com".into(),
897 power_level: 10,
898 origin_server_ts: 10,
899 prev_events: vec![],
900 auth_events: vec![],
901 depth: 1,
902 ..Default::default()
903 },
904 );
905 events.insert(
906 "B".into(),
907 LeanEvent {
908 event_id: "B".into(),
909 event_type: "m.room.member".into(),
910 state_key: "@alice:example.com".into(),
911 power_level: 100,
912 origin_server_ts: 100,
913 prev_events: vec![],
914 auth_events: vec![],
915 depth: 10,
916 ..Default::default()
917 },
918 );
919 let sorted_v1 = lean_kahn_sort(&events, StateResVersion::V1);
920 let sorted_v2 = lean_kahn_sort(&events, StateResVersion::V2);
921 let sorted_v2_1 = lean_kahn_sort(&events, StateResVersion::V2_1);
922 assert_eq!(sorted_v1, vec!["A", "B"]);
923 assert_eq!(sorted_v2, vec!["A", "B"]);
925 assert_eq!(sorted_v2_1, vec!["A", "B"]);
926 }
927
928 #[test]
929 fn test_unhappy_path_cycle_detection() {
930 let mut events = HashMap::new();
931 events.insert(
932 "A".into(),
933 LeanEvent {
934 event_id: "A".into(),
935 event_type: "m.room.member".into(),
936 state_key: "@alice:example.com".into(),
937 power_level: 100,
938 origin_server_ts: 100,
939 prev_events: vec!["B".into()],
940 auth_events: vec!["B".into()],
941 depth: 1,
942 ..Default::default()
943 },
944 );
945 events.insert(
946 "B".into(),
947 LeanEvent {
948 event_id: "B".into(),
949 event_type: "m.room.member".into(),
950 state_key: "@alice:example.com".into(),
951 power_level: 100,
952 origin_server_ts: 100,
953 prev_events: vec!["A".into()],
954 auth_events: vec!["A".into()],
955 depth: 1,
956 ..Default::default()
957 },
958 );
959 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
960 assert!(sorted.is_empty());
961 }
962
963 #[test]
964 #[cfg(all(feature = "std", not(feature = "zkvm")))]
965 #[should_panic(expected = "Signature verification failed")]
966 fn test_signature_verification_failure() {
967 let pk = [
968 215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114,
969 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26,
970 ];
971 let sig = [0u8; 64];
972 let msg = b"test";
973 verify_signature(&pk, msg, &sig);
974 }
975
976 #[test]
977 fn test_serialization_roundtrip() {
978 let event = LeanEvent {
979 event_id: "$abc".into(),
980 event_type: "m.room.member".into(),
981 state_key: "@alice:example.com".into(),
982 power_level: 100,
983 origin_server_ts: 12345,
984 prev_events: vec![],
985 auth_events: vec![],
986 depth: 5,
987 ..Default::default()
988 };
989 let serialized = serde_json::to_string(&event).unwrap();
990 let deserialized: LeanEvent = serde_json::from_str(&serialized).unwrap();
991 assert_eq!(event, deserialized);
992 }
993
994 #[test]
995 fn test_partial_ord_implementations() {
996 let e1 = LeanEvent {
997 event_id: "a".into(),
998 event_type: "m.room.member".into(),
999 state_key: "@alice:example.com".into(),
1000 power_level: 100,
1001 origin_server_ts: 10,
1002 prev_events: vec![],
1003 auth_events: vec![],
1004 depth: 1,
1005 ..Default::default()
1006 };
1007 let e2 = LeanEvent {
1008 event_id: "b".into(),
1009 event_type: "m.room.member".into(),
1010 state_key: "@alice:example.com".into(),
1011 power_level: 100,
1012 origin_server_ts: 10,
1013 prev_events: vec![],
1014 auth_events: vec![],
1015 depth: 1,
1016 ..Default::default()
1017 };
1018 assert!(e1.partial_cmp(&e2).is_some());
1019
1020 let p1 = SortPriority {
1021 event: &e1,
1022 version: StateResVersion::V2,
1023 };
1024 let p2 = SortPriority {
1025 event: &e2,
1026 version: StateResVersion::V2,
1027 };
1028 assert!(p1.partial_cmp(&p2).is_some());
1029 }
1030
1031 #[test]
1032 fn test_trait_coverage() {
1033 let v = StateResVersion::V2;
1034 assert_eq!(v, StateResVersion::V2);
1035 let _ = alloc::format!("{:?}", v);
1036
1037 let e = LeanEvent {
1038 event_id: "a".into(),
1039 event_type: "m.room.member".into(),
1040 state_key: "@alice:example.com".into(),
1041 power_level: 100,
1042 origin_server_ts: 10,
1043 prev_events: vec![],
1044 auth_events: vec![],
1045 depth: 1,
1046 ..Default::default()
1047 };
1048 let _ = e.clone();
1049 let _ = alloc::format!("{:?}", e);
1050 }
1051
1052 #[test]
1053 fn test_complex_dag_sort() {
1054 let mut events = HashMap::new();
1055 events.insert(
1056 "1".into(),
1057 LeanEvent {
1058 event_id: "1".into(),
1059 event_type: "m.room.member".into(),
1060 state_key: "@alice:example.com".into(),
1061 power_level: 100,
1062 origin_server_ts: 10,
1063 prev_events: vec![],
1064 auth_events: vec![],
1065 depth: 1,
1066 ..Default::default()
1067 },
1068 );
1069 events.insert(
1070 "2".into(),
1071 LeanEvent {
1072 event_id: "2".into(),
1073 event_type: "m.room.member".into(),
1074 state_key: "@alice:example.com".into(),
1075 power_level: 50,
1076 origin_server_ts: 20,
1077 prev_events: vec!["1".into()],
1078 auth_events: vec!["1".into()],
1079 depth: 2,
1080 ..Default::default()
1081 },
1082 );
1083 events.insert(
1084 "3".into(),
1085 LeanEvent {
1086 event_id: "3".into(),
1087 event_type: "m.room.member".into(),
1088 state_key: "@alice:example.com".into(),
1089 power_level: 50,
1090 origin_server_ts: 15,
1091 prev_events: vec!["1".into()],
1092 auth_events: vec!["1".into()],
1093 depth: 2,
1094 ..Default::default()
1095 },
1096 );
1097 events.insert(
1098 "4".into(),
1099 LeanEvent {
1100 event_id: "4".into(),
1101 event_type: "m.room.member".into(),
1102 state_key: "@alice:example.com".into(),
1103 power_level: 10,
1104 origin_server_ts: 30,
1105 prev_events: vec!["2".into(), "3".into()],
1106 auth_events: vec!["2".into(), "3".into()],
1107 depth: 3,
1108 ..Default::default()
1109 },
1110 );
1111 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1112 assert_eq!(sorted, vec!["1", "3", "2", "4"]);
1117 }
1118
1119 #[test]
1120 fn test_kahn_missing_parents() {
1121 let mut events = HashMap::new();
1122 events.insert(
1123 "A".into(),
1124 LeanEvent {
1125 event_id: "A".into(),
1126 event_type: "m.room.member".into(),
1127 state_key: "@alice:example.com".into(),
1128 power_level: 100,
1129 origin_server_ts: 10,
1130 prev_events: vec!["MISSING".into()],
1131 auth_events: vec!["MISSING".into()],
1132 depth: 1,
1133 ..Default::default()
1134 },
1135 );
1136 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1137 assert_eq!(sorted, vec!["A"]);
1138 }
1139
1140 #[test]
1141 fn test_resolve_lean_functionality() {
1142 let mut unconflicted = BTreeMap::new();
1143 unconflicted.insert(("type".into(), "key".into()), "id".into());
1144 let conflicted = HashMap::new();
1145 let resolved = resolve_lean(unconflicted.clone(), conflicted, StateResVersion::V2);
1146 assert_eq!(resolved, unconflicted);
1147 }
1148
1149 #[test]
1150 fn test_resolve_lean_v2_1_overlay() {
1151 use serde_json::json;
1152
1153 let mut unconflicted = BTreeMap::new();
1154 unconflicted.insert(
1155 ("m.room.member".into(), "@alice:example.com".into()),
1156 "id1".into(),
1157 );
1158 unconflicted.insert(
1159 ("m.room.member".into(), "@bob:example.com".into()),
1160 "id2".into(),
1161 );
1162
1163 let mut conflicted = HashMap::new();
1164 conflicted.insert(
1166 "create".into(),
1167 LeanEvent {
1168 event_id: "create".into(),
1169 event_type: "m.room.create".into(),
1170 state_key: String::new(),
1171 sender: "@alice:example.com".into(),
1172 power_level: 100,
1173 origin_server_ts: 1,
1174 content: json!({}),
1175 ..Default::default()
1176 },
1177 );
1178 conflicted.insert(
1180 "id1".into(),
1181 LeanEvent {
1182 event_id: "id1".into(),
1183 event_type: "m.room.member".into(),
1184 state_key: "@alice:example.com".into(),
1185 sender: "@alice:example.com".into(),
1186 power_level: 50,
1187 origin_server_ts: 500,
1188 content: json!({"membership": "join"}),
1189 auth_events: vec!["create".into()],
1190 ..Default::default()
1191 },
1192 );
1193 conflicted.insert(
1194 "id2".into(),
1195 LeanEvent {
1196 event_id: "id2".into(),
1197 event_type: "m.room.member".into(),
1198 state_key: "@bob:example.com".into(),
1199 sender: "@bob:example.com".into(),
1200 power_level: 50,
1201 origin_server_ts: 500,
1202 content: json!({"membership": "join"}),
1203 auth_events: vec!["create".into()],
1204 ..Default::default()
1205 },
1206 );
1207 conflicted.insert(
1208 "id2_new".into(),
1209 LeanEvent {
1210 event_id: "id2_new".into(),
1211 event_type: "m.room.member".into(),
1212 state_key: "@bob:example.com".into(),
1213 sender: "@bob:example.com".into(),
1214 power_level: 100,
1215 origin_server_ts: 1000,
1216 content: json!({"membership": "join"}),
1217 auth_events: vec!["create".into()],
1218 ..Default::default()
1219 },
1220 );
1221
1222 let resolved = resolve_lean(unconflicted.clone(), conflicted, StateResVersion::V2_1);
1223
1224 assert_eq!(
1225 resolved.get(&("m.room.member".into(), "@alice:example.com".into())),
1226 Some(&"id1".into())
1227 );
1228 assert_eq!(
1229 resolved.get(&("m.room.member".into(), "@bob:example.com".into())),
1230 Some(&"id2_new".into())
1231 );
1232 }
1233
1234 fn run_batch_test(
1235 version: StateResVersion,
1236 rows: &[(&str, i64, u64, u64, &[&str])],
1237 expected: &[&str],
1238 ) {
1239 let mut events = HashMap::new();
1240 for r in rows {
1241 events.insert(
1242 r.0.to_string(),
1243 LeanEvent {
1244 event_id: r.0.to_string(),
1245 event_type: "m.room.member".into(),
1246 state_key: "@alice:example.com".into(),
1247 power_level: r.1,
1248 origin_server_ts: r.2,
1249 depth: r.3,
1250 prev_events: r.4.iter().map(|s| s.to_string()).collect(),
1251 auth_events: r.4.iter().map(|s| s.to_string()).collect(),
1252 ..Default::default()
1253 },
1254 );
1255 }
1256 let result = lean_kahn_sort(&events, version);
1257 assert_eq!(
1258 result,
1259 expected.iter().map(|s| s.to_string()).collect::<Vec<_>>()
1260 );
1261 }
1262
1263 #[test]
1264 fn test_resolution_batch() {
1265 run_batch_test(
1266 StateResVersion::V2,
1267 &[("Alice", 100, 500, 1, &[]), ("Bob", 50, 100, 1, &[])],
1268 &["Bob", "Alice"], );
1270 run_batch_test(
1271 StateResVersion::V1,
1272 &[("Deep", 100, 100, 10, &[]), ("Shallow", 10, 100, 1, &[])],
1273 &["Shallow", "Deep"],
1274 );
1275 }
1276
1277 #[test]
1278 fn test_native_resolution_bootstrap_parity() {
1279 let mut events = HashMap::new();
1280 events.insert(
1281 "1".into(),
1282 LeanEvent {
1283 event_id: "1".into(),
1284 event_type: "m.room.member".into(),
1285 state_key: "@user:example.com".into(),
1286 power_level: 100,
1287 origin_server_ts: 10,
1288 prev_events: vec![],
1289 auth_events: vec![],
1290 depth: 1,
1291 ..Default::default()
1292 },
1293 );
1294 events.insert(
1295 "2".into(),
1296 LeanEvent {
1297 event_id: "2".into(),
1298 event_type: "m.room.member".into(),
1299 state_key: "@user:example.com".into(),
1300 power_level: 0,
1301 origin_server_ts: 20,
1302 prev_events: vec!["1".into()],
1303 auth_events: vec!["1".into()],
1304 depth: 2,
1305 ..Default::default()
1306 },
1307 );
1308 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1309 let mut resolved_state = BTreeMap::new();
1310 for id in sorted {
1311 let ev = events.get(&id).unwrap();
1312 let key = (ev.event_type.clone(), ev.state_key.clone());
1313 resolved_state.insert(key, ev.event_id.clone());
1314 }
1315 assert_eq!(
1316 resolved_state.get(&("m.room.member".to_string(), "@user:example.com".to_string())),
1317 Some(&"2".to_string())
1318 );
1319 }
1320
1321 #[test]
1322 fn test_enum_coverage() {
1323 let v = StateResVersion::V2;
1324 let v2 = v;
1325 assert_eq!(v, v2);
1326 let debug_str = alloc::format!("{:?}", v);
1327 assert!(debug_str.contains("V2"));
1328 }
1329
1330 #[test]
1331 fn test_event_traits_coverage() {
1332 let e = LeanEvent {
1333 event_id: "a".into(),
1334 event_type: "m.room.member".into(),
1335 state_key: "@alice:example.com".into(),
1336 power_level: 100,
1337 origin_server_ts: 10,
1338 prev_events: vec![],
1339 auth_events: vec![],
1340 depth: 1,
1341 ..Default::default()
1342 };
1343 let e2 = e.clone();
1344 assert_eq!(e, e2);
1345 let debug_str = alloc::format!("{:?}", e);
1346 assert!(debug_str.contains("event_id"));
1347 }
1348
1349 #[test]
1350 fn test_sort_priority_traits() {
1351 let e = LeanEvent {
1352 event_id: "a".into(),
1353 event_type: "m.room.member".into(),
1354 state_key: "@alice:example.com".into(),
1355 power_level: 100,
1356 origin_server_ts: 10,
1357 prev_events: vec![],
1358 auth_events: vec![],
1359 depth: 1,
1360 ..Default::default()
1361 };
1362 let p = SortPriority {
1363 event: &e,
1364 version: StateResVersion::V2,
1365 };
1366 let p2 = p;
1367 assert_eq!(p, p2);
1368 let debug_str = alloc::format!("{:?}", p);
1369 assert!(debug_str.contains("version"));
1370 }
1371
1372 #[test]
1373 fn test_v1_equal_depth_tie_break() {
1374 let mut events = HashMap::new();
1375 events.insert(
1376 "B".into(),
1377 LeanEvent {
1378 event_id: "B".into(),
1379 event_type: "m.room.member".into(),
1380 state_key: "@alice:example.com".into(),
1381 power_level: 0,
1382 origin_server_ts: 10,
1383 prev_events: vec![],
1384 auth_events: vec![],
1385 depth: 1,
1386 ..Default::default()
1387 },
1388 );
1389 events.insert(
1390 "A".into(),
1391 LeanEvent {
1392 event_id: "A".into(),
1393 event_type: "m.room.member".into(),
1394 state_key: "@alice:example.com".into(),
1395 power_level: 0,
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::V1);
1404 assert_eq!(sorted, vec!["A", "B"]);
1405 }
1406
1407 #[test]
1408 fn test_kahn_no_neighbors() {
1409 let mut events = HashMap::new();
1410 events.insert(
1411 "1".into(),
1412 LeanEvent {
1413 event_id: "1".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);
1425 assert_eq!(sorted, vec!["1"]);
1426 }
1427
1428 #[test]
1429 fn test_v2_1_full_coverage() {
1430 let mut events = HashMap::new();
1431 events.insert(
1432 "A".into(),
1433 LeanEvent {
1434 event_id: "A".into(),
1435 event_type: "m.room.member".into(),
1436 state_key: "@alice:example.com".into(),
1437 power_level: 100,
1438 origin_server_ts: 10,
1439 prev_events: vec![],
1440 auth_events: vec![],
1441 depth: 1,
1442 ..Default::default()
1443 },
1444 );
1445 let sorted = lean_kahn_sort(&events, StateResVersion::V2_1);
1446 assert_eq!(sorted, vec!["A"]);
1447 }
1448
1449 #[test]
1450 fn test_total_order_properties() {
1451 let e1 = LeanEvent {
1452 event_id: "a".into(),
1453 event_type: "m.room.member".into(),
1454 state_key: "@alice:example.com".into(),
1455 power_level: 100,
1456 origin_server_ts: 10,
1457 prev_events: vec![],
1458 auth_events: vec![],
1459 depth: 1,
1460 ..Default::default()
1461 };
1462 let e2 = LeanEvent {
1463 event_id: "b".into(),
1464 event_type: "m.room.member".into(),
1465 state_key: "@alice:example.com".into(),
1466 power_level: 100,
1467 origin_server_ts: 10,
1468 prev_events: vec![],
1469 auth_events: vec![],
1470 depth: 1,
1471 ..Default::default()
1472 };
1473 let e3 = LeanEvent {
1474 event_id: "c".into(),
1475 event_type: "m.room.member".into(),
1476 state_key: "@alice:example.com".into(),
1477 power_level: 50,
1478 origin_server_ts: 10,
1479 prev_events: vec![],
1480 auth_events: vec![],
1481 depth: 1,
1482 ..Default::default()
1483 };
1484 assert_eq!(e1.cmp(&e1), Ordering::Equal);
1485 assert!(e1 <= e1);
1486 assert!(e1 <= e2 || e2 <= e1);
1487 if e1 <= e2 && e2 <= e3 {
1488 assert!(e1 <= e3);
1489 }
1490 let e1_copy = e1.clone();
1491 if e1 <= e1_copy && e1_copy <= e1 {
1492 assert_eq!(e1, e1_copy);
1493 }
1494 }
1495
1496 #[test]
1497 fn test_coverage_booster_all_branches() {
1498 let e_base = LeanEvent {
1499 event_id: "m".into(),
1500 event_type: "m.room.member".into(),
1501 state_key: "@alice:example.com".into(),
1502 power_level: 50,
1503 origin_server_ts: 50,
1504 prev_events: vec![],
1505 auth_events: vec![],
1506 depth: 50,
1507 ..Default::default()
1508 };
1509 let p_base = SortPriority {
1510 event: &e_base,
1511 version: StateResVersion::V2,
1512 };
1513 let e_high_power = LeanEvent {
1514 power_level: 100,
1515 ..e_base.clone()
1516 };
1517 let p_high_power = SortPriority {
1518 event: &e_high_power,
1519 version: StateResVersion::V2,
1520 };
1521 assert_eq!(p_base.cmp(&p_high_power), Ordering::Greater);
1523 let e_early_ts = LeanEvent {
1524 origin_server_ts: 10,
1525 ..e_base.clone()
1526 };
1527 let p_early_ts = SortPriority {
1528 event: &e_early_ts,
1529 version: StateResVersion::V2,
1530 };
1531 assert_eq!(p_base.cmp(&p_early_ts), Ordering::Less);
1533 let e_early_id = LeanEvent {
1534 event_id: "a".into(),
1535 ..e_base.clone()
1536 };
1537 let p_early_id = SortPriority {
1538 event: &e_early_id,
1539 version: StateResVersion::V2,
1540 };
1541 assert_eq!(p_base.cmp(&p_early_id), Ordering::Greater);
1543 let p_v1_base = SortPriority {
1544 event: &e_base,
1545 version: StateResVersion::V1,
1546 };
1547 let e_shallow = LeanEvent {
1548 depth: 1,
1549 ..e_base.clone()
1550 };
1551 let p_shallow = SortPriority {
1552 event: &e_shallow,
1553 version: StateResVersion::V1,
1554 };
1555 assert_eq!(p_v1_base.cmp(&p_shallow), Ordering::Less);
1556 let p_v1_early_id = SortPriority {
1557 event: &e_early_id,
1558 version: StateResVersion::V1,
1559 };
1560 assert_eq!(p_v1_base.cmp(&p_v1_early_id), Ordering::Less);
1561 assert_eq!(p_v1_base.cmp(&p_v1_base), Ordering::Equal);
1562 }
1563
1564 #[test]
1569 fn test_cycle_detection_detailed() {
1570 let mut events = HashMap::new();
1571 events.insert(
1572 "A".into(),
1573 LeanEvent {
1574 event_id: "A".into(),
1575 event_type: "m.room.member".into(),
1576 state_key: "@alice:example.com".into(),
1577 auth_events: vec!["B".into()],
1578 ..Default::default()
1579 },
1580 );
1581 events.insert(
1582 "B".into(),
1583 LeanEvent {
1584 event_id: "B".into(),
1585 event_type: "m.room.member".into(),
1586 state_key: "@alice:example.com".into(),
1587 auth_events: vec!["A".into()],
1588 ..Default::default()
1589 },
1590 );
1591 let result = lean_kahn_sort_detailed(&events, StateResVersion::V2);
1592 match result {
1593 KahnSortResult::CycleDetected { sorted, stuck } => {
1594 assert!(sorted.is_empty());
1595 assert_eq!(stuck.len(), 2);
1596 let mut stuck_sorted = stuck.clone();
1597 stuck_sorted.sort();
1598 assert_eq!(stuck_sorted, vec!["A", "B"]);
1599 }
1600 KahnSortResult::Ok(_) => panic!("Expected cycle detection"),
1601 }
1602 }
1603
1604 #[test]
1605 fn test_cycle_detection_partial_sort() {
1606 let mut events = HashMap::new();
1608 events.insert(
1609 "C".into(),
1610 LeanEvent {
1611 event_id: "C".into(),
1612 event_type: "m.room.member".into(),
1613 state_key: "@alice:example.com".into(),
1614 auth_events: vec![],
1615 ..Default::default()
1616 },
1617 );
1618 events.insert(
1619 "A".into(),
1620 LeanEvent {
1621 event_id: "A".into(),
1622 event_type: "m.room.member".into(),
1623 state_key: "@alice:example.com".into(),
1624 auth_events: vec!["B".into(), "C".into()],
1625 ..Default::default()
1626 },
1627 );
1628 events.insert(
1629 "B".into(),
1630 LeanEvent {
1631 event_id: "B".into(),
1632 event_type: "m.room.member".into(),
1633 state_key: "@alice:example.com".into(),
1634 auth_events: vec!["A".into()],
1635 ..Default::default()
1636 },
1637 );
1638 let result = lean_kahn_sort_detailed(&events, StateResVersion::V2);
1639 match result {
1640 KahnSortResult::CycleDetected { sorted, stuck } => {
1641 assert_eq!(sorted, vec!["C"]);
1642 assert_eq!(stuck.len(), 2);
1643 }
1644 KahnSortResult::Ok(_) => panic!("Expected cycle detection"),
1645 }
1646 }
1647
1648 #[test]
1649 fn test_kahn_sort_result_api() {
1650 let ok = KahnSortResult::Ok(vec!["A".into()]);
1651 assert!(ok.is_ok());
1652 assert_eq!(ok.into_sorted(), vec!["A"]);
1653
1654 let cycle = KahnSortResult::CycleDetected {
1655 sorted: vec!["C".into()],
1656 stuck: vec!["A".into(), "B".into()],
1657 };
1658 assert!(!cycle.is_ok());
1659 assert!(cycle.into_sorted().is_empty());
1660 }
1661
1662 #[test]
1663 fn test_power_level_coercion_integer() {
1664 let json = r#"{"event_id": "$1", "type": "m.room.member", "origin_server_ts": 1, "power_level": 100}"#;
1665 let ev: LeanEvent = serde_json::from_str(json).unwrap();
1666 assert_eq!(ev.power_level, 100);
1667 }
1668
1669 #[test]
1670 fn test_power_level_coercion_string() {
1671 let json = r#"{"event_id": "$1", "type": "m.room.member", "origin_server_ts": 1, "power_level": "100"}"#;
1672 let ev: LeanEvent = serde_json::from_str(json).unwrap();
1673 assert_eq!(ev.power_level, 100);
1674 }
1675
1676 #[test]
1677 fn test_power_level_coercion_float() {
1678 let json = r#"{"event_id": "$1", "type": "m.room.member", "origin_server_ts": 1, "power_level": 100.0}"#;
1679 let ev: LeanEvent = serde_json::from_str(json).unwrap();
1680 assert_eq!(ev.power_level, 100);
1681 }
1682
1683 #[test]
1684 fn test_power_level_coercion_invalid_string() {
1685 let json = r#"{"event_id": "$1", "type": "m.room.member", "origin_server_ts": 1, "power_level": "abc"}"#;
1686 let ev: LeanEvent = serde_json::from_str(json).unwrap();
1687 assert_eq!(ev.power_level, 0);
1688 }
1689
1690 #[test]
1691 fn test_deep_chain_stack_safety() {
1692 let mut events = HashMap::new();
1694 for i in 0..1000u32 {
1695 let id = alloc::format!("ev_{}", i);
1696 let auth = if i > 0 {
1697 vec![alloc::format!("ev_{}", i - 1)]
1698 } else {
1699 vec![]
1700 };
1701 events.insert(
1702 id.clone(),
1703 LeanEvent {
1704 event_id: id,
1705 event_type: "m.room.member".into(),
1706 state_key: "@alice:example.com".into(),
1707 power_level: 100,
1708 origin_server_ts: i as u64,
1709 auth_events: auth,
1710 depth: i as u64,
1711 ..Default::default()
1712 },
1713 );
1714 }
1715 let sorted = lean_kahn_sort(&events, StateResVersion::V2);
1716 assert_eq!(sorted.len(), 1000);
1717 assert_eq!(sorted[0], "ev_0");
1719 assert_eq!(sorted[999], "ev_999");
1721 }
1722
1723 #[test]
1724 fn test_subgraph_bounded_depth() {
1725 let mut graph = HashMap::new();
1727 for (id, auths) in [
1728 ("A", vec![]),
1729 ("B", vec!["A"]),
1730 ("C", vec!["B"]),
1731 ("D", vec!["C"]),
1732 ] {
1733 graph.insert(
1734 id.to_string(),
1735 LeanEvent {
1736 event_id: id.into(),
1737 event_type: "m.room.member".into(),
1738 state_key: "@alice:example.com".into(),
1739 auth_events: auths.iter().map(|s| s.to_string()).collect(),
1740 ..Default::default()
1741 },
1742 );
1743 }
1744 let full = compute_v2_1_conflicted_subgraph_bounded(
1746 &graph,
1747 &["A".to_string(), "D".to_string()],
1748 None,
1749 );
1750 assert!(full.subgraph.contains_key("A"));
1751 assert!(full.subgraph.contains_key("D"));
1752
1753 let bounded = compute_v2_1_conflicted_subgraph_bounded(
1757 &graph,
1758 &["A".to_string(), "D".to_string()],
1759 Some(1),
1760 );
1761 assert!(bounded.subgraph.contains_key("D"));
1763 assert!(bounded.subgraph.contains_key("A"));
1764 assert!(!bounded.subgraph.contains_key("B"));
1766 }
1767
1768 #[test]
1769 fn test_subgraph_missing_auth_detection() {
1770 let mut graph = HashMap::new();
1771 graph.insert(
1772 "X".to_string(),
1773 LeanEvent {
1774 event_id: "X".into(),
1775 event_type: "m.room.member".into(),
1776 state_key: "@alice:example.com".into(),
1777 auth_events: vec!["MISSING_1".into(), "MISSING_2".into()],
1778 ..Default::default()
1779 },
1780 );
1781 let result = compute_v2_1_conflicted_subgraph_bounded(&graph, &["X".to_string()], None);
1782 let mut missing = result.missing_auth_events.clone();
1783 missing.sort();
1784 assert_eq!(missing, vec!["MISSING_1", "MISSING_2"]);
1785 }
1786}