eventcore_testing/
scenario.rs1use eventcore_memory::InMemoryEventStore;
4use eventcore_types::{
5 CommandLogic, Event, EventStore, StreamId, StreamVersion, StreamWrites, collect_events,
6};
7
8pub struct TestScenario {
10 store: InMemoryEventStore,
11}
12
13impl Default for TestScenario {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl TestScenario {
20 pub fn new() -> Self {
22 Self {
23 store: InMemoryEventStore::new(),
24 }
25 }
26
27 pub async fn given_events<E: Event>(self, stream_id: StreamId, events: Vec<E>) -> Self {
29 if events.is_empty() {
30 return self;
31 }
32
33 let stream = self
34 .store
35 .read_stream::<E>(stream_id.clone())
36 .await
37 .expect("reading stream for test setup should not fail");
38 let existing = collect_events(stream)
39 .await
40 .expect("collecting stream for test setup should not fail");
41 let current_version = StreamVersion::new(existing.len());
42
43 let mut writes = StreamWrites::new()
44 .register_stream(stream_id, current_version)
45 .expect("registering stream for test setup should not fail");
46
47 for event in events {
48 writes = writes
49 .append(event)
50 .expect("appending event for test setup should not fail");
51 }
52
53 let _ = self
54 .store
55 .append_events(writes)
56 .await
57 .expect("appending events for test setup should not fail");
58
59 self
60 }
61
62 pub async fn when<C>(self, command: C) -> ScenarioResult<C::Event>
64 where
65 C: CommandLogic,
66 C::Event: Clone + PartialEq + std::fmt::Debug,
67 {
68 let result = eventcore::execute(&self.store, command, eventcore::RetryPolicy::new()).await;
69
70 let storage: std::sync::Arc<std::sync::Mutex<Vec<C::Event>>> =
71 std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
72 let collector = crate::EventCollector::new(storage.clone());
73 let _ = eventcore::run_projection(
74 collector,
75 &self.store,
76 eventcore::ProjectionConfig::default(),
77 )
78 .await;
79
80 let all_events = storage.lock().unwrap().clone();
81
82 ScenarioResult {
83 result: result.map(|_| ()),
84 all_events,
85 }
86 }
87}
88
89pub struct ScenarioResult<E> {
91 result: Result<(), eventcore_types::CommandError>,
92 all_events: Vec<E>,
93}
94
95impl<E: PartialEq + std::fmt::Debug> ScenarioResult<E> {
96 pub fn succeeded(&self) -> &Self {
98 assert!(
99 self.result.is_ok(),
100 "expected command to succeed, got: {:?}",
101 self.result.as_ref().err()
102 );
103 self
104 }
105
106 pub fn failed_with<Err: Into<eventcore_types::CommandError>>(&self, expected: Err) -> &Self {
112 let expected_error = expected.into();
113 match &self.result {
114 Err(actual) => {
115 assert_eq!(
116 actual.to_string(),
117 expected_error.to_string(),
118 "command error mismatch"
119 );
120 }
121 Ok(()) => panic!(
122 "expected command to fail with {}, but it succeeded",
123 expected_error
124 ),
125 }
126 self
127 }
128
129 pub fn then_events(&self, expected: Vec<E>) -> &Self {
131 assert_eq!(
132 self.all_events, expected,
133 "events in store should match expected"
134 );
135 self
136 }
137
138 pub fn then_event_count(&self, expected: usize) -> &Self {
140 assert_eq!(
141 self.all_events.len(),
142 expected,
143 "expected {} events, found {}",
144 expected,
145 self.all_events.len()
146 );
147 self
148 }
149}