Skip to main content

eventcore_testing/
event_collector.rs

1//! Test utility for collecting events during projection for assertions.
2//!
3//! `EventCollector` implements the `Projector` trait and accumulates events
4//! in an `Arc<Mutex<Vec<E>>>` for shared access during testing. This allows
5//! test code to verify that commands produced expected events by running
6//! a projection and inspecting the collected results.
7//!
8//! # Example
9//!
10//! ```ignore
11//! use eventcore::{execute, run_projection, ProjectionConfig, RetryPolicy};
12//! use std::sync::{Arc, Mutex};
13//!
14//! // `store` and `backend` are your `EventStore` (e.g. `InMemoryEventStore`);
15//! // `command` implements `CommandLogic`; `MyEvent` is the event type.
16//! execute(&store, command, RetryPolicy::new()).await?;
17//!
18//! let storage = Arc::new(Mutex::new(Vec::new()));
19//! let collector = EventCollector::<MyEvent>::new(storage.clone());
20//! run_projection(collector, &backend, ProjectionConfig::default()).await?;
21//!
22//! // Events accessible through the original storage handle
23//! assert_eq!(storage.lock().unwrap().len(), expected_count);
24//! ```
25
26use eventcore_types::{Projector, StreamPosition};
27use std::convert::Infallible;
28use std::sync::{Arc, Mutex};
29
30/// A projector that collects events for testing assertions.
31///
32/// `EventCollector` stores events in shared, thread-safe storage (`Arc<Mutex<Vec<E>>>`)
33/// so that events can be inspected after projection completes. This is the primary
34/// mechanism for black-box integration testing in EventCore.
35///
36/// # Type Parameters
37///
38/// - `E`: The event type to collect. Must be `Clone` so that `events()` can return
39///   owned copies without consuming the collector.
40///
41/// # Thread Safety
42///
43/// The internal storage uses `Arc<Mutex<_>>` to allow the collector to be shared
44/// across threads (e.g., between the projection runner and test assertions).
45#[derive(Debug)]
46pub struct EventCollector<E> {
47    events: Arc<Mutex<Vec<E>>>,
48}
49
50impl<E> EventCollector<E> {
51    /// Creates a new `EventCollector` with the provided shared storage.
52    ///
53    /// # Arguments
54    ///
55    /// * `storage` - An `Arc<Mutex<Vec<E>>>` that will hold collected events.
56    ///   The same storage can be cloned before passing to enable access to
57    ///   collected events after the collector is moved.
58    pub fn new(storage: Arc<Mutex<Vec<E>>>) -> Self {
59        Self { events: storage }
60    }
61
62    /// Returns a clone of all collected events.
63    ///
64    /// This method clones the internal vector, allowing inspection without
65    /// consuming the collector. The `Clone` bound on `E` enables this behavior.
66    pub fn events(&self) -> Vec<E>
67    where
68        E: Clone,
69    {
70        self.events
71            .lock()
72            .expect("EventCollector mutex poisoned - a test panicked while holding the lock")
73            .clone()
74    }
75}
76
77impl<E: Send + 'static> Projector for EventCollector<E> {
78    type Event = E;
79    type Error = Infallible;
80    type Context = ();
81
82    fn apply(
83        &mut self,
84        event: Self::Event,
85        _position: StreamPosition,
86        _ctx: &mut Self::Context,
87    ) -> Result<(), Self::Error> {
88        self.events
89            .lock()
90            .expect("EventCollector mutex poisoned - a test panicked while holding the lock")
91            .push(event);
92        Ok(())
93    }
94
95    fn name(&self) -> &str {
96        "event-collector"
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use crate::event_collector::EventCollector;
103
104    // Simple test event for unit tests
105    #[derive(Debug, Clone, PartialEq)]
106    struct TestEvent {
107        id: u32,
108    }
109
110    #[test]
111    fn new_collector_has_empty_events() {
112        use std::sync::{Arc, Mutex};
113
114        // Given: A newly created EventCollector
115        let storage: Arc<Mutex<Vec<TestEvent>>> = Arc::new(Mutex::new(Vec::new()));
116        let collector = EventCollector::new(storage);
117
118        // When: We retrieve the events
119        let events = collector.events();
120
121        // Then: The events vector is empty
122        assert!(events.is_empty());
123    }
124
125    #[test]
126    fn collects_event_via_projector_apply() {
127        use eventcore_types::{Projector, StreamPosition};
128        use std::sync::{Arc, Mutex};
129        use uuid::Uuid;
130
131        // Given: An EventCollector
132        let storage: Arc<Mutex<Vec<TestEvent>>> = Arc::new(Mutex::new(Vec::new()));
133        let mut collector = EventCollector::new(storage);
134        let event = TestEvent { id: 42 };
135        let position = StreamPosition::new(Uuid::nil());
136
137        // When: We apply an event via the Projector trait
138        let result = collector.apply(event.clone(), position, &mut ());
139
140        // Then: The apply succeeded and the event is collected
141        assert!(result.is_ok());
142        assert_eq!(collector.events(), vec![event]);
143    }
144
145    #[test]
146    fn events_accessible_after_collector_moved() {
147        use eventcore_types::{Projector, StreamPosition};
148        use std::sync::{Arc, Mutex};
149        use uuid::Uuid;
150
151        // Given: Shared storage and a collector using that storage
152        let storage: Arc<Mutex<Vec<TestEvent>>> = Arc::new(Mutex::new(Vec::new()));
153        let collector = EventCollector::new(storage.clone());
154
155        // When: Collector is moved (simulates move into run_projection) and events are applied
156        let mut moved_collector = collector;
157        let event = TestEvent { id: 99 };
158        let position = StreamPosition::new(Uuid::nil());
159        let _ = moved_collector.apply(event.clone(), position, &mut ());
160
161        // Then: Events are accessible through the original storage handle
162        let events = storage.lock().unwrap();
163        assert_eq!(*events, vec![event]);
164    }
165
166    #[test]
167    fn projector_name_is_event_collector() {
168        use eventcore_types::Projector;
169        use std::sync::{Arc, Mutex};
170
171        // Given: An EventCollector
172        let storage: Arc<Mutex<Vec<TestEvent>>> = Arc::new(Mutex::new(Vec::new()));
173        let collector = EventCollector::new(storage);
174
175        // When/Then: The projector name is "event-collector"
176        assert_eq!(collector.name(), "event-collector");
177    }
178}