1mod fetch;
6
7use std::{
8 borrow::Borrow,
9 collections::{BTreeSet, HashMap},
10 slice,
11 sync::atomic::{AtomicU64, Ordering::SeqCst},
12};
13
14use ruma::{
15 EventId, OwnedEventId, RoomId, UserId,
16 events::{
17 TimelineEventType,
18 room::{
19 join_rules::{JoinRule, RoomJoinRulesEventContent},
20 member::{MembershipState, RoomMemberEventContent},
21 },
22 },
23 room_id, uint, user_id,
24};
25#[cfg(test)]
26use ruma::{
27 MilliSecondsSinceUnixEpoch, event_id,
28 events::StateEventType,
29 int,
30 room_version_rules::{AuthorizationRules, RoomVersionRules},
31};
32use serde_json::{
33 json,
34 value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
35};
36use tuwunel_core::{
37 Error, Result, err,
38 matrix::{Event, EventHash, EventTypeExt, PduEvent},
39};
40#[cfg(test)]
41use tuwunel_core::{info, matrix::StateKey, utils::stream::IterStream};
42
43use super::{AuthSet, StateMap};
44#[cfg(test)]
45use super::{auth_types_for_event, events::RoomCreateEvent};
46#[cfg(test)]
47use crate::rooms::state_res::topological_sort::ReferencedIds;
48
49#[cfg(test)]
50type StateEvents = HashMap<StateEventType, HashMap<String, PduEvent>>;
51
52static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
53
54#[cfg(test)]
55pub(super) fn not_found() -> Error { err!(Request(NotFound("Test event not found"))) }
56
57pub(super) fn event_not_found(event_id: &EventId) -> Error {
58 err!(Request(NotFound("Test event not found: {event_id:?}")))
59}
60
61#[cfg(test)]
62pub(super) fn state_not_found(ty: &StateEventType, sk: &str) -> Error {
63 err!(Request(NotFound("Test state not found: ({ty:?},{sk:?})")))
64}
65
66#[cfg(test)]
67pub(super) async fn do_check(
68 events: &[PduEvent],
69 edges: Vec<Vec<OwnedEventId>>,
70 expected_state_ids: Vec<OwnedEventId>,
71) {
72 let init_events = INITIAL_EVENTS();
75 let mut store = build_test_store(&init_events, events);
76 let (graph, fake_event_map) = build_event_graph(&init_events, events, &edges);
77
78 let mut event_map: HashMap<OwnedEventId, PduEvent> = HashMap::new();
79 let mut state_at_event: HashMap<OwnedEventId, StateMap<OwnedEventId>> = HashMap::new();
80
81 let sorted = super::topological_sort(graph.clone(), &async |_id| {
82 Ok((int!(0).into(), MilliSecondsSinceUnixEpoch(uint!(0))))
83 })
84 .await
85 .unwrap();
86
87 for node in sorted {
88 let fake_event = &fake_event_map[&node];
89 let event_id = fake_event.event_id().to_owned();
90 let prev_events = &graph[&node];
91
92 let state_before =
93 resolve_state_before(&store, &event_map, &state_at_event, &node, prev_events).await;
94
95 let auth_events = collect_auth_events(fake_event, &state_before);
96 let state_after = state_after_with(&state_before, fake_event, &event_id);
97 let event = rebuild_with_auth(fake_event, &auth_events, prev_events);
98
99 store.0.insert(event_id.clone(), event);
100 event_map.insert(event_id.clone(), store.0[&event_id].clone());
101 state_at_event.insert(node, state_after);
102 }
103
104 let expected_state = build_expected_state(&expected_state_ids, &event_map);
105 let end_state = compute_end_state(&state_at_event, &expected_state);
106
107 assert_eq!(expected_state, end_state);
108}
109
110#[cfg(test)]
111fn build_test_store(
112 init_events: &HashMap<OwnedEventId, PduEvent>,
113 events: &[PduEvent],
114) -> TestStore {
115 TestStore(
116 init_events
117 .values()
118 .chain(events)
119 .map(|ev| (ev.event_id().to_owned(), ev.clone()))
120 .collect(),
121 )
122}
123
124#[cfg(test)]
125fn build_event_graph(
126 init_events: &HashMap<OwnedEventId, PduEvent>,
127 events: &[PduEvent],
128 edges: &[Vec<OwnedEventId>],
129) -> (HashMap<OwnedEventId, ReferencedIds>, HashMap<OwnedEventId, PduEvent>) {
130 let mut graph = HashMap::<OwnedEventId, ReferencedIds>::new();
131 let mut fake_event_map = HashMap::new();
132
133 for ev in init_events.values().chain(events) {
134 graph.insert(ev.event_id().to_owned(), Default::default());
135 fake_event_map.insert(ev.event_id().to_owned(), ev.clone());
136 }
137
138 add_chain_edges(&mut graph, &INITIAL_EDGES());
139 for edge_list in edges {
140 add_chain_edges(&mut graph, edge_list);
141 }
142
143 (graph, fake_event_map)
144}
145
146#[cfg(test)]
147fn add_chain_edges(graph: &mut HashMap<OwnedEventId, ReferencedIds>, chain: &[OwnedEventId]) {
148 for pair in chain.windows(2) {
149 if let [a, b] = pair {
150 graph
151 .entry(a.to_owned())
152 .or_default()
153 .push(b.clone());
154 }
155 }
156}
157
158#[cfg(test)]
159async fn resolve_state_before(
160 store: &TestStore,
161 event_map: &HashMap<OwnedEventId, PduEvent>,
162 state_at_event: &HashMap<OwnedEventId, StateMap<OwnedEventId>>,
163 node: &EventId,
164 prev_events: &[OwnedEventId],
165) -> StateMap<OwnedEventId> {
166 match prev_events {
167 | [] => StateMap::new(),
168 | [single] => state_at_event[single].clone(),
169 | _ => merge_prev_states(store, event_map, state_at_event, node, prev_events).await,
170 }
171}
172
173#[cfg(test)]
174async fn merge_prev_states(
175 store: &TestStore,
176 event_map: &HashMap<OwnedEventId, PduEvent>,
177 state_at_event: &HashMap<OwnedEventId, StateMap<OwnedEventId>>,
178 node: &EventId,
179 prev_events: &[OwnedEventId],
180) -> StateMap<OwnedEventId> {
181 let state_sets = prev_events
182 .iter()
183 .filter_map(|k| state_at_event.get(k).cloned())
184 .collect::<Vec<_>>();
185
186 info!(
187 "{:#?}",
188 state_sets
189 .iter()
190 .map(|map| map
191 .iter()
192 .map(|((ty, key), id)| format!("(({ty}{key:?}), {id})"))
193 .collect::<Vec<_>>())
194 .collect::<Vec<_>>()
195 );
196
197 let auth_chain_sets = state_sets
198 .iter()
199 .map(|map| {
200 store
201 .auth_event_ids(room_id(), map.values().cloned().collect())
202 .unwrap()
203 })
204 .collect::<Vec<_>>();
205
206 let rules = RoomVersionRules::V6;
207 super::resolve(
208 &rules,
209 state_sets.into_iter().stream(),
210 auth_chain_sets.into_iter().stream(),
211 event_map,
212 false,
213 )
214 .await
215 .unwrap_or_else(|e| panic!("resolution for {node} failed: {e}"))
216}
217
218#[cfg(test)]
219fn state_after_with(
220 state_before: &StateMap<OwnedEventId>,
221 fake_event: &PduEvent,
222 event_id: &EventId,
223) -> StateMap<OwnedEventId> {
224 let mut state_after = state_before.clone();
225 let ty = fake_event.event_type();
226 let key = fake_event.state_key().unwrap();
227 state_after.insert(ty.with_state_key(key), event_id.to_owned());
228 state_after
229}
230
231#[cfg(test)]
232fn collect_auth_events(
233 fake_event: &PduEvent,
234 state_before: &StateMap<OwnedEventId>,
235) -> Vec<OwnedEventId> {
236 auth_types_for_event(
237 fake_event.event_type(),
238 fake_event.sender(),
239 fake_event.state_key(),
240 fake_event.content(),
241 &AuthorizationRules::V6,
242 false,
243 )
244 .unwrap()
245 .into_iter()
246 .filter_map(|key| state_before.get(&key).cloned())
247 .collect()
248}
249
250#[cfg(test)]
251fn rebuild_with_auth(
252 fake_event: &PduEvent,
253 auth_events: &[OwnedEventId],
254 prev_events: &[OwnedEventId],
255) -> PduEvent {
256 to_pdu_event(
257 fake_event.event_id().as_str(),
258 fake_event.sender(),
259 fake_event.event_type().clone(),
260 fake_event.state_key(),
261 fake_event.content().to_owned(),
262 auth_events,
263 prev_events,
264 )
265}
266
267#[cfg(test)]
268fn build_expected_state(
269 expected_state_ids: &[OwnedEventId],
270 event_map: &HashMap<OwnedEventId, PduEvent>,
271) -> StateMap<OwnedEventId> {
272 expected_state_ids
273 .iter()
274 .map(|node| {
275 let ev = event_map.get(node).unwrap_or_else(|| {
276 panic!(
277 "{node} not found in {:?}",
278 event_map
279 .keys()
280 .map(ToString::to_string)
281 .collect::<Vec<_>>()
282 )
283 });
284 let key = ev
285 .event_type()
286 .with_state_key(ev.state_key().unwrap());
287 (key, node.clone())
288 })
289 .collect()
290}
291
292#[cfg(test)]
293fn compute_end_state(
294 state_at_event: &HashMap<OwnedEventId, StateMap<OwnedEventId>>,
295 expected_state: &StateMap<OwnedEventId>,
296) -> StateMap<OwnedEventId> {
297 let start_state = state_at_event
298 .get(event_id!("$START:foo"))
299 .unwrap();
300
301 state_at_event
302 .get(event_id!("$END:foo"))
303 .unwrap()
304 .iter()
305 .filter(|(k, v)| {
306 expected_state.contains_key(k)
307 || start_state.get(k) != Some(*v)
308 && **k != ("m.room.message".into(), "dummy".into())
309 })
310 .map(|(k, v)| (k.clone(), v.clone()))
311 .collect()
312}
313
314pub struct TestStore(
318 pub HashMap<OwnedEventId, PduEvent>,
320);
321
322impl TestStore {
323 pub(super) fn get_event(&self, _: &RoomId, event_id: &EventId) -> Result<PduEvent> {
324 self.0
325 .get(event_id)
326 .cloned()
327 .ok_or_else(|| event_not_found(event_id))
328 }
329
330 pub fn auth_event_ids(
335 &self,
336 room_id: &RoomId,
337 event_ids: Vec<OwnedEventId>,
338 ) -> Result<AuthSet<OwnedEventId>> {
339 let mut result = BTreeSet::new();
340 let mut stack = event_ids;
341
342 while let Some(ev_id) = stack.pop() {
344 if result.contains(&ev_id) {
345 continue;
346 }
347
348 result.insert(ev_id.clone());
349
350 let event = self.get_event(room_id, ev_id.borrow())?;
351
352 stack.extend(event.auth_events().map(ToOwned::to_owned));
353 }
354
355 Ok(AuthSet::from_distinct(result.into_iter().collect()))
356 }
357}
358
359impl TestStore {
361 pub fn set_up(
365 &mut self,
366 ) -> (StateMap<OwnedEventId>, StateMap<OwnedEventId>, StateMap<OwnedEventId>) {
367 let create_event = to_pdu_event::<&EventId>(
368 "CREATE",
369 alice(),
370 TimelineEventType::RoomCreate,
371 Some(""),
372 to_raw_json_value(&json!({ "creator": alice() }))
373 .expect("fixture content serializes to JSON"),
374 &[],
375 &[],
376 );
377
378 let cre = create_event.event_id().to_owned();
379 self.0.insert(cre.clone(), create_event.clone());
380
381 let alice_mem = to_pdu_event(
382 "IMA",
383 alice(),
384 TimelineEventType::RoomMember,
385 Some(alice().as_str()),
386 member_content_join(),
387 slice::from_ref(&cre),
388 slice::from_ref(&cre),
389 );
390 self.0
391 .insert(alice_mem.event_id().to_owned(), alice_mem.clone());
392
393 let join_rules = to_pdu_event(
394 "IJR",
395 alice(),
396 TimelineEventType::RoomJoinRules,
397 Some(""),
398 to_raw_json_value(&RoomJoinRulesEventContent::new(JoinRule::Public))
399 .expect("fixture content serializes to JSON"),
400 &[cre.clone(), alice_mem.event_id().to_owned()],
401 &[alice_mem.event_id().to_owned()],
402 );
403
404 self.0
405 .insert(join_rules.event_id().to_owned(), join_rules.clone());
406
407 let bob_mem = to_pdu_event(
410 "IMB",
411 bob(),
412 TimelineEventType::RoomMember,
413 Some(bob().as_str()),
414 member_content_join(),
415 &[cre.clone(), join_rules.event_id().to_owned()],
416 &[join_rules.event_id().to_owned()],
417 );
418 self.0
419 .insert(bob_mem.event_id().to_owned(), bob_mem.clone());
420
421 let charlie_mem = to_pdu_event(
422 "IMC",
423 charlie(),
424 TimelineEventType::RoomMember,
425 Some(charlie().as_str()),
426 member_content_join(),
427 &[cre, join_rules.event_id().to_owned()],
428 &[join_rules.event_id().to_owned()],
429 );
430 self.0
431 .insert(charlie_mem.event_id().to_owned(), charlie_mem.clone());
432
433 let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem]
434 .iter()
435 .map(|e| {
436 (
437 e.event_type().with_state_key(
438 e.state_key()
439 .expect("fixture event has a state key"),
440 ),
441 e.event_id().to_owned(),
442 )
443 })
444 .collect::<StateMap<_>>();
445
446 let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem]
447 .iter()
448 .map(|e| {
449 (
450 e.event_type().with_state_key(
451 e.state_key()
452 .expect("fixture event has a state key"),
453 ),
454 e.event_id().to_owned(),
455 )
456 })
457 .collect::<StateMap<_>>();
458
459 let expected = [&create_event, &alice_mem, &join_rules, &bob_mem, &charlie_mem]
460 .iter()
461 .map(|e| {
462 (
463 e.event_type().with_state_key(
464 e.state_key()
465 .expect("fixture event has a state key"),
466 ),
467 e.event_id().to_owned(),
468 )
469 })
470 .collect::<StateMap<_>>();
471
472 (state_at_bob, state_at_charlie, expected)
473 }
474}
475
476#[must_use]
480pub fn event_id(id: &str) -> OwnedEventId {
481 if id.contains('$') {
482 return id
483 .try_into()
484 .expect("fixture event identifier is valid");
485 }
486
487 format!("${id}:foo")
488 .try_into()
489 .expect("fixture event identifier is valid")
490}
491
492#[must_use]
496pub fn alice() -> &'static UserId { user_id!("@alice:foo") }
497
498#[cfg(test)]
499pub(super) fn aya() -> &'static UserId { user_id!("@aya:other.server") }
500
501#[must_use]
505pub fn bob() -> &'static UserId { user_id!("@bob:foo") }
506
507#[must_use]
511pub fn charlie() -> &'static UserId { user_id!("@charlie:foo") }
512
513#[must_use]
517pub fn ella() -> &'static UserId { user_id!("@ella:foo") }
518
519#[cfg(test)]
520pub(super) fn zara() -> &'static UserId { user_id!("@zara:foo") }
521
522#[must_use]
526pub fn room_id() -> &'static RoomId { room_id!("!test:foo") }
527
528#[cfg(test)]
529pub(crate) fn hydra_room_id() -> &'static RoomId { room_id!("!CREATE") }
530
531#[must_use]
535pub fn member_content_ban() -> Box<RawJsonValue> {
536 to_raw_json_value(&RoomMemberEventContent::new(MembershipState::Ban))
537 .expect("fixture content serializes to JSON")
538}
539
540#[must_use]
544pub fn member_content_join() -> Box<RawJsonValue> {
545 to_raw_json_value(&RoomMemberEventContent::new(MembershipState::Join))
546 .expect("fixture content serializes to JSON")
547}
548
549#[cfg(test)]
550pub(super) fn to_init_pdu_event(
551 id: &str,
552 sender: &UserId,
553 ev_type: TimelineEventType,
554 state_key: Option<&str>,
555 content: Box<RawJsonValue>,
556) -> PduEvent {
557 let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
558 let state_key = state_key.map(ToOwned::to_owned);
559 let id = if id.contains('$') {
560 id.to_owned()
561 } else {
562 format!("${id}:foo")
563 };
564
565 PduEvent {
566 event_id: id.try_into().unwrap(),
567 room_id: room_id().to_owned(),
568 sender: sender.to_owned(),
569 origin: None,
570 origin_server_ts: ts.try_into().unwrap(),
571 state_key: state_key.map(Into::into),
572 kind: ev_type,
573 content: content.into(),
574 redacts: None,
575 unsigned: None,
576 auth_events: Default::default(),
577 prev_events: Default::default(),
578 depth: uint!(0),
579 hashes: EventHash::default(),
580 }
582}
583
584pub fn to_pdu_event<S>(
588 id: &str,
589 sender: &UserId,
590 ev_type: TimelineEventType,
591 state_key: Option<&str>,
592 content: Box<RawJsonValue>,
593 auth_events: &[S],
594 prev_events: &[S],
595) -> PduEvent
596where
597 S: AsRef<str>,
598{
599 let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
600 let state_key = state_key.map(ToOwned::to_owned);
601 let id = if id.contains('$') {
602 id.to_owned()
603 } else {
604 format!("${id}:foo")
605 };
606 let auth_events = auth_events
607 .iter()
608 .map(AsRef::as_ref)
609 .map(event_id)
610 .collect();
611 let prev_events = prev_events
612 .iter()
613 .map(AsRef::as_ref)
614 .map(event_id)
615 .collect();
616
617 PduEvent {
618 event_id: id
619 .try_into()
620 .expect("fixture event identifier is valid"),
621 room_id: room_id().to_owned(),
622 sender: sender.to_owned(),
623 origin: None,
624 origin_server_ts: ts
625 .try_into()
626 .expect("fixture timestamp fits the protocol integer"),
627 state_key: state_key.map(Into::into),
628 kind: ev_type,
629 content: content.into(),
630 redacts: None,
631 unsigned: None,
632 auth_events,
633 prev_events,
634 depth: uint!(0),
635 hashes: EventHash::default(),
636 }
638}
639
640#[cfg(test)]
643pub(super) fn to_hydra_pdu_event<S>(
644 id: &str,
645 sender: &UserId,
646 ev_type: TimelineEventType,
647 state_key: Option<&str>,
648 content: Box<RawJsonValue>,
649 auth_events: &[S],
650 prev_events: &[S],
651) -> PduEvent
652where
653 S: AsRef<str>,
654{
655 fn event_id(id: &str) -> OwnedEventId {
656 if id.contains('$') {
657 id.try_into().unwrap()
658 } else {
659 format!("${id}").try_into().unwrap()
660 }
661 }
662
663 let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
664 let state_key = state_key.map(ToOwned::to_owned);
665 let auth_events = auth_events
666 .iter()
667 .map(AsRef::as_ref)
668 .map(event_id)
669 .collect();
670 let prev_events = prev_events
671 .iter()
672 .map(AsRef::as_ref)
673 .map(event_id)
674 .collect();
675
676 PduEvent {
677 event_id: event_id(id),
678 room_id: hydra_room_id().to_owned(),
679 sender: sender.to_owned(),
680 origin: None,
681 origin_server_ts: ts.try_into().unwrap(),
682 state_key: state_key.map(Into::into),
683 kind: ev_type,
684 content: content.into(),
685 redacts: None,
686 unsigned: None,
687 auth_events,
688 prev_events,
689 depth: uint!(0),
690 hashes: EventHash::default(),
691 }
693}
694
695#[cfg(test)]
696pub(super) fn room_redaction_pdu_event<S>(
697 id: &str,
698 sender: &UserId,
699 redacts: OwnedEventId,
700 content: Box<RawJsonValue>,
701 auth_events: &[S],
702 prev_events: &[S],
703) -> PduEvent
704where
705 S: AsRef<str>,
706{
707 let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
708 let id = if id.contains('$') {
709 id.to_owned()
710 } else {
711 format!("${id}:foo")
712 };
713 let auth_events = auth_events
714 .iter()
715 .map(AsRef::as_ref)
716 .map(event_id)
717 .collect();
718 let prev_events = prev_events
719 .iter()
720 .map(AsRef::as_ref)
721 .map(event_id)
722 .collect();
723
724 PduEvent {
725 event_id: id.try_into().unwrap(),
726 room_id: room_id().to_owned(),
727 sender: sender.to_owned(),
728 origin: None,
729 origin_server_ts: ts.try_into().unwrap(),
730 state_key: None,
731 kind: TimelineEventType::RoomRedaction,
732 content: content.into(),
733 redacts: Some(redacts),
734 unsigned: None,
735 auth_events,
736 prev_events,
737 depth: uint!(0),
738 hashes: EventHash::default(),
739 }
741}
742
743#[cfg(test)]
744pub(super) fn room_create_hydra_pdu_event(
745 id: &str,
746 sender: &UserId,
747 content: Box<RawJsonValue>,
748) -> PduEvent {
749 let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
750 let eid = if id.contains('$') {
751 id.to_owned()
752 } else {
753 format!("${id}")
754 };
755 let rid = if id.contains('!') {
756 id.to_owned()
757 } else {
758 format!("!{id}")
759 };
760
761 PduEvent {
762 event_id: eid.try_into().unwrap(),
763 room_id: rid.try_into().unwrap(),
764 sender: sender.to_owned(),
765 origin: None,
766 origin_server_ts: ts.try_into().unwrap(),
767 state_key: Some(StateKey::new()),
768 kind: TimelineEventType::RoomCreate,
769 content: content.into(),
770 redacts: None,
771 unsigned: None,
772 auth_events: Default::default(),
773 prev_events: Default::default(),
774 depth: uint!(0),
775 hashes: EventHash::default(),
776 }
778}
779
780#[expect(non_snake_case)]
782#[must_use]
786pub fn INITIAL_EVENTS() -> HashMap<OwnedEventId, PduEvent> {
787 vec![
788 to_pdu_event::<&EventId>(
789 "CREATE",
790 alice(),
791 TimelineEventType::RoomCreate,
792 Some(""),
793 to_raw_json_value(&json!({ "creator": alice() }))
794 .expect("fixture content serializes to JSON"),
795 &[],
796 &[],
797 ),
798 to_pdu_event(
799 "IMA",
800 alice(),
801 TimelineEventType::RoomMember,
802 Some(alice().as_str()),
803 member_content_join(),
804 &["CREATE"],
805 &["CREATE"],
806 ),
807 to_pdu_event(
808 "IPOWER",
809 alice(),
810 TimelineEventType::RoomPowerLevels,
811 Some(""),
812 to_raw_json_value(&json!({ "users": { alice(): 100 } }))
813 .expect("fixture content serializes to JSON"),
814 &["CREATE", "IMA"],
815 &["IMA"],
816 ),
817 to_pdu_event(
818 "IJR",
819 alice(),
820 TimelineEventType::RoomJoinRules,
821 Some(""),
822 to_raw_json_value(&RoomJoinRulesEventContent::new(JoinRule::Public))
823 .expect("fixture content serializes to JSON"),
824 &["CREATE", "IMA", "IPOWER"],
825 &["IPOWER"],
826 ),
827 to_pdu_event(
828 "IMB",
829 bob(),
830 TimelineEventType::RoomMember,
831 Some(bob().as_str()),
832 member_content_join(),
833 &["CREATE", "IJR", "IPOWER"],
834 &["IJR"],
835 ),
836 to_pdu_event(
837 "IMC",
838 charlie(),
839 TimelineEventType::RoomMember,
840 Some(charlie().as_str()),
841 member_content_join(),
842 &["CREATE", "IJR", "IPOWER"],
843 &["IMB"],
844 ),
845 to_pdu_event::<&EventId>(
846 "START",
847 charlie(),
848 TimelineEventType::RoomMessage,
849 Some("dummy"),
850 to_raw_json_value(&json!({})).expect("fixture content serializes to JSON"),
851 &[],
852 &[],
853 ),
854 to_pdu_event::<&EventId>(
855 "END",
856 charlie(),
857 TimelineEventType::RoomMessage,
858 Some("dummy"),
859 to_raw_json_value(&json!({})).expect("fixture content serializes to JSON"),
860 &[],
861 &[],
862 ),
863 ]
864 .into_iter()
865 .map(|ev| (ev.event_id().to_owned(), ev))
866 .collect()
867}
868
869#[expect(non_snake_case)]
872#[cfg(test)]
873pub(super) fn INITIAL_HYDRA_EVENTS() -> HashMap<OwnedEventId, PduEvent> {
874 vec![
875 room_create_hydra_pdu_event(
876 "CREATE",
877 alice(),
878 to_raw_json_value(&json!({ "room_version": "org.matrix.hydra.11" })).unwrap(),
879 ),
880 to_hydra_pdu_event(
881 "IMA",
882 alice(),
883 TimelineEventType::RoomMember,
884 Some(alice().as_str()),
885 member_content_join(),
886 &["CREATE"],
887 &["CREATE"],
888 ),
889 to_hydra_pdu_event(
890 "IPOWER",
891 alice(),
892 TimelineEventType::RoomPowerLevels,
893 Some(""),
894 to_raw_json_value(&json!({})).unwrap(),
895 &["CREATE", "IMA"],
896 &["IMA"],
897 ),
898 to_hydra_pdu_event(
899 "IJR",
900 alice(),
901 TimelineEventType::RoomJoinRules,
902 Some(""),
903 to_raw_json_value(&RoomJoinRulesEventContent::new(JoinRule::Public)).unwrap(),
904 &["CREATE", "IMA", "IPOWER"],
905 &["IPOWER"],
906 ),
907 to_hydra_pdu_event(
908 "IMB",
909 bob(),
910 TimelineEventType::RoomMember,
911 Some(bob().as_str()),
912 member_content_join(),
913 &["CREATE", "IJR", "IPOWER"],
914 &["IJR"],
915 ),
916 to_hydra_pdu_event(
917 "IMC",
918 charlie(),
919 TimelineEventType::RoomMember,
920 Some(charlie().as_str()),
921 member_content_join(),
922 &["CREATE", "IJR", "IPOWER"],
923 &["IMB"],
924 ),
925 to_hydra_pdu_event::<&EventId>(
926 "START",
927 charlie(),
928 TimelineEventType::RoomMessage,
929 Some("dummy"),
930 to_raw_json_value(&json!({})).unwrap(),
931 &[],
932 &[],
933 ),
934 to_hydra_pdu_event::<&EventId>(
935 "END",
936 charlie(),
937 TimelineEventType::RoomMessage,
938 Some("dummy"),
939 to_raw_json_value(&json!({})).unwrap(),
940 &[],
941 &[],
942 ),
943 ]
944 .into_iter()
945 .map(|ev| (ev.event_id().to_owned(), ev))
946 .collect()
947}
948
949#[expect(non_snake_case)]
951#[cfg(test)]
952pub(super) fn INITIAL_EVENTS_CREATE_ROOM() -> HashMap<OwnedEventId, PduEvent> {
953 vec![to_pdu_event::<&EventId>(
954 "CREATE",
955 alice(),
956 TimelineEventType::RoomCreate,
957 Some(""),
958 to_raw_json_value(&json!({ "creator": alice() })).unwrap(),
959 &[],
960 &[],
961 )]
962 .into_iter()
963 .map(|ev| (ev.event_id().to_owned(), ev))
964 .collect()
965}
966
967#[expect(non_snake_case)]
968#[cfg(test)]
969pub(super) fn INITIAL_EVENTS_NO_FEDERATE() -> HashMap<OwnedEventId, PduEvent> {
970 let create = to_init_pdu_event(
971 "CREATE",
972 alice(),
973 TimelineEventType::RoomCreate,
974 Some(""),
975 to_raw_json_value(&json!({ "creator": alice(), "m.federate": false })).unwrap(),
976 );
977
978 let mut events = INITIAL_EVENTS();
979 events.insert(event_id("CREATE"), create);
980 events
981}
982
983#[expect(non_snake_case)]
984#[cfg(test)]
985pub(super) fn INITIAL_EDGES() -> Vec<OwnedEventId> {
986 vec!["START", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE"]
987 .into_iter()
988 .map(event_id)
989 .collect::<Vec<_>>()
990}
991
992#[cfg(test)]
993pub(super) fn init_subscriber() -> tracing::dispatcher::DefaultGuard {
994 tracing::subscriber::set_default(
995 tracing_subscriber::fmt()
996 .with_test_writer()
997 .finish(),
998 )
999}
1000
1001#[cfg(test)]
1003pub(super) struct TestStateMap(StateEvents);
1004
1005#[cfg(test)]
1006impl TestStateMap {
1007 pub(super) fn new(events: &HashMap<OwnedEventId, PduEvent>) -> Self {
1008 let state_map = events
1009 .values()
1010 .fold(StateEvents::new(), |mut state, event| {
1011 let event_type = StateEventType::from(event.event_type().to_string());
1012
1013 state
1014 .entry(event_type)
1015 .or_default()
1016 .insert(event.state_key().unwrap().to_owned(), event.clone());
1017
1018 state
1019 });
1020
1021 Self(state_map)
1022 }
1023
1024 pub(super) fn get(&self, event_type: &StateEventType, state_key: &str) -> Result<&PduEvent> {
1025 self.0
1026 .get(event_type)
1027 .ok_or_else(|| state_not_found(event_type, state_key))?
1028 .get(state_key)
1029 .ok_or_else(|| state_not_found(event_type, state_key))
1030 }
1031
1032 pub(super) fn room_create_event(&self) -> RoomCreateEvent<&PduEvent> {
1036 RoomCreateEvent::new(self.get(&StateEventType::RoomCreate, "").unwrap())
1037 }
1038}
1039
1040#[cfg(test)]
1042pub(super) fn room_third_party_invite(sender: &UserId) -> PduEvent {
1043 let content = json!({
1044 "display_name": "o...@g...",
1045 "key_validity_url": "https://identity.local/_matrix/identity/v2/pubkey/isvalid",
1046 "public_key": "Gb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE",
1047 "public_keys": [
1048 {
1049 "key_validity_url": "https://identity.local/_matrix/identity/v2/pubkey/isvalid",
1050 "public_key": "Gb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE"
1051 },
1052 {
1053 "key_validity_url": "https://identity.local/_matrix/identity/v2/pubkey/ephemeral/isvalid",
1054 "public_key": "Kxdvv7lo0O6JVI7yimFgmYPfpLGnctcpYjuypP5zx/c"
1055 }
1056 ]
1057 });
1058
1059 to_pdu_event(
1060 "THIRDPARTY",
1061 sender,
1062 TimelineEventType::RoomThirdPartyInvite,
1063 Some("somerandomtoken"),
1064 to_raw_json_value(&content).unwrap(),
1065 &["CREATE", "IJR", "IPOWER"],
1066 &["IPOWER"],
1067 )
1068}