cqrs_proptest/
lib.rs

1//! # cqrs-proptest
2//!
3//! `cqrs-proptest` contains various utilities for building property tests with the
4//! `proptest` crate and aggregates from `cqrs` or `cqrs-core`.
5//!
6//! ## Examples
7//!
8//! ```
9//! use cqrs_core::{Aggregate, AggregateEvent, Event};
10//! use cqrs_proptest::AggregateFromEventSequence;
11//! use proptest::{prelude::*, strategy::{LazyTupleUnion, ValueTree, WA}, test_runner::TestRunner, prop_oneof};
12//!
13//! #[derive(Debug, Default)]
14//! struct MyAggregate {
15//!     active: bool
16//! }
17//!
18//! impl Aggregate for MyAggregate {
19//!     fn aggregate_type() -> &'static str {
20//!         "my_aggregate"
21//!     }
22//! }
23//!
24//! #[derive(Clone, Copy, Debug)]
25//! struct CreatedEvent{};
26//!
27//! impl Event for CreatedEvent {
28//!     fn event_type(&self) -> &'static str {
29//!         "created"
30//!     }
31//! }
32//!
33//! impl AggregateEvent<MyAggregate> for CreatedEvent {
34//!     fn apply_to(self, aggregate: &mut MyAggregate) {
35//!         aggregate.active = true;
36//!     }
37//! }
38//!
39//! #[derive(Clone, Copy, Debug)]
40//! struct DeletedEvent{};
41//!
42//! impl Event for DeletedEvent {
43//!     fn event_type(&self) -> &'static str {
44//!         "deleted"
45//!     }
46//! }
47//!
48//! impl AggregateEvent<MyAggregate> for DeletedEvent {
49//!     fn apply_to(self, aggregate: &mut MyAggregate) {
50//!         aggregate.active = false;
51//!     }
52//! }
53//!
54//! #[derive(Clone, Copy, Debug)]
55//! enum MyEvents {
56//!     Created(CreatedEvent),
57//!     Deleted(DeletedEvent),
58//! }
59//!
60//! impl Event for MyEvents {
61//!     fn event_type(&self) -> &'static str {
62//!         match *self {
63//!             MyEvents::Created(ref e) => e.event_type(),
64//!             MyEvents::Deleted(ref e) => e.event_type(),
65//!         }
66//!     }
67//! }
68//!
69//! impl AggregateEvent<MyAggregate> for MyEvents {
70//!     fn apply_to(self, aggregate: &mut MyAggregate) {
71//!         match self {
72//!             MyEvents::Created(e) => e.apply_to(aggregate),
73//!             MyEvents::Deleted(e) => e.apply_to(aggregate),
74//!         }
75//!     }
76//! }
77//!
78//! impl Arbitrary for MyEvents {
79//!     type Parameters = ();
80//!
81//!     fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
82//!         prop_oneof![
83//!             Just(MyEvents::Created(CreatedEvent{})),
84//!             Just(MyEvents::Deleted(DeletedEvent{})),
85//!         ]
86//!     }
87//!
88//!     type Strategy = LazyTupleUnion<(WA<Just<Self>>, WA<Just<Self>>)>;
89//! }
90//!
91//! any::<AggregateFromEventSequence<MyAggregate, MyEvents>>()
92//!     .new_tree(&mut TestRunner::default())
93//!     .unwrap()
94//!     .current()
95//!     .into_aggregate();
96//!
97//! let parameters = (prop::collection::SizeRange::from(1..10), ());
98//! any_with::<AggregateFromEventSequence<MyAggregate, MyEvents>>(parameters)
99//!     .new_tree(&mut TestRunner::default())
100//!     .unwrap()
101//!     .current();
102//! ```
103
104#![warn(unused_import_braces, unused_imports, unused_qualifications)]
105#![deny(
106    missing_debug_implementations,
107    missing_copy_implementations,
108    trivial_casts,
109    trivial_numeric_casts,
110    unsafe_code,
111    unused_must_use,
112    missing_docs
113)]
114
115use cqrs_core::{Aggregate, AggregateEvent, DeserializableEvent, Event, SerializableEvent};
116use proptest::prelude::*;
117use std::{fmt, marker::PhantomData};
118
119/// Produces a strategy to generate an arbitrary vector of events, given a strategy
120/// to generate an arbitrary event and a size range.
121///
122/// # Examples
123///
124/// ```
125/// use cqrs_core::Event;
126/// use cqrs_proptest::arb_events;
127/// use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner, prop_oneof};
128///
129/// #[derive(Clone, Copy, Debug)]
130/// struct CreatedEvent{};
131///
132/// impl Event for CreatedEvent {
133///     fn event_type(&self) -> &'static str {
134///         "created"
135///     }
136/// }
137///
138/// #[derive(Clone, Copy, Debug)]
139/// struct DeletedEvent{};
140///
141/// impl Event for DeletedEvent {
142///     fn event_type(&self) -> &'static str {
143///         "deleted"
144///     }
145/// }
146///
147/// #[derive(Clone, Copy, Debug)]
148/// enum MyEvents {
149///     Created(CreatedEvent),
150///     Deleted(DeletedEvent),
151/// }
152///
153/// impl Event for MyEvents {
154///     fn event_type(&self) -> &'static str {
155///         match *self {
156///             MyEvents::Created(ref e) => e.event_type(),
157///             MyEvents::Deleted(ref e) => e.event_type(),
158///         }
159///     }
160/// }
161///
162/// fn arb_my_events() -> impl Strategy<Value = MyEvents> {
163///     prop_oneof![
164///         Just(MyEvents::Created(CreatedEvent{})),
165///         Just(MyEvents::Deleted(DeletedEvent{})),
166///     ]
167/// }
168///
169/// arb_events(arb_my_events(), 0..10)
170///     .new_tree(&mut TestRunner::default())
171///     .unwrap()
172///     .current();
173/// ```
174pub fn arb_events<E: Event + fmt::Debug>(
175    event_strategy: impl Strategy<Value = E>,
176    size: impl Into<prop::collection::SizeRange>,
177) -> impl Strategy<Value = Vec<E>> {
178    prop::collection::vec(event_strategy, size)
179}
180
181/// Produces a strategy to generate an arbitrary aggregate, given a strategy to generate
182/// an arbitrary vector of events
183///
184/// # Examples
185///
186/// ```
187/// use cqrs_core::{Aggregate, AggregateEvent, Event};
188/// use cqrs_proptest::{arb_aggregate, arb_events};
189/// use proptest::{prelude::*, strategy::{LazyTupleUnion, ValueTree, WA}, test_runner::TestRunner, prop_oneof};
190///
191/// #[derive(Debug, Default)]
192/// struct MyAggregate {
193///     active: bool
194/// }
195///
196/// impl Aggregate for MyAggregate {
197///     fn aggregate_type() -> &'static str {
198///         "my_aggregate"
199///     }
200/// }
201///
202/// #[derive(Clone, Copy, Debug)]
203/// struct CreatedEvent{};
204///
205/// impl Event for CreatedEvent {
206///     fn event_type(&self) -> &'static str {
207///         "created"
208///     }
209/// }
210///
211/// impl AggregateEvent<MyAggregate> for CreatedEvent {
212///     fn apply_to(self, aggregate: &mut MyAggregate) {
213///         aggregate.active = true;
214///     }
215/// }
216///
217/// #[derive(Clone, Copy, Debug)]
218/// struct DeletedEvent{};
219///
220/// impl Event for DeletedEvent {
221///     fn event_type(&self) -> &'static str {
222///         "deleted"
223///     }
224/// }
225///
226/// impl AggregateEvent<MyAggregate> for DeletedEvent {
227///     fn apply_to(self, aggregate: &mut MyAggregate) {
228///         aggregate.active = false;
229///     }
230/// }
231///
232/// #[derive(Clone, Copy, Debug)]
233/// enum MyEvents {
234///     Created(CreatedEvent),
235///     Deleted(DeletedEvent),
236/// }
237///
238/// impl Event for MyEvents {
239///     fn event_type(&self) -> &'static str {
240///         match *self {
241///             MyEvents::Created(ref e) => e.event_type(),
242///             MyEvents::Deleted(ref e) => e.event_type(),
243///         }
244///     }
245/// }
246///
247/// impl AggregateEvent<MyAggregate> for MyEvents {
248///     fn apply_to(self, aggregate: &mut MyAggregate) {
249///         match self {
250///             MyEvents::Created(e) => e.apply_to(aggregate),
251///             MyEvents::Deleted(e) => e.apply_to(aggregate),
252///         }
253///     }
254/// }
255///
256/// impl Arbitrary for MyEvents {
257///     type Parameters = ();
258///
259///     fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
260///         prop_oneof![
261///             Just(MyEvents::Created(CreatedEvent{})),
262///             Just(MyEvents::Deleted(DeletedEvent{})),
263///         ]
264///     }
265///
266///     type Strategy = LazyTupleUnion<(WA<Just<Self>>, WA<Just<Self>>)>;
267/// }
268///
269/// arb_aggregate::<MyAggregate, MyEvents, _>(arb_events(any::<MyEvents>(), 0..10))
270///     .new_tree(&mut TestRunner::default())
271///     .unwrap()
272///     .current();
273/// ```
274pub fn arb_aggregate<A, E, S>(events_strategy: S) -> impl Strategy<Value = A>
275where
276    A: Aggregate + fmt::Debug,
277    E: AggregateEvent<A> + fmt::Debug,
278    S: Strategy<Value = Vec<E>>,
279{
280    events_strategy.prop_map(|e| {
281        let mut aggregate = A::default();
282        for event in e {
283            aggregate.apply(event);
284        }
285        aggregate
286    })
287}
288
289/// Given a serializable event, constructs a buffer, serializes the event to the buffer, and then
290/// deserializes the event, returning the deserialized value.
291///
292/// # Examples
293///
294/// ```
295/// use cqrs_core::{Event, SerializableEvent, DeserializableEvent};
296/// use cqrs_proptest::roundtrip_through_serialization;
297/// use serde::{Serialize, Deserialize};
298///
299/// #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
300/// struct CreatedEvent{};
301///
302/// impl Event for CreatedEvent {
303///     fn event_type(&self) -> &'static str {
304///         "created"
305///     }
306/// }
307///
308/// #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
309/// struct DeletedEvent{};
310///
311/// impl Event for DeletedEvent {
312///     fn event_type(&self) -> &'static str {
313///         "deleted"
314///     }
315/// }
316///
317/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
318/// enum MyEvents {
319///     Created(CreatedEvent),
320///     Deleted(DeletedEvent),
321/// }
322///
323/// impl Event for MyEvents {
324///     fn event_type(&self) -> &'static str {
325///         match *self {
326///             MyEvents::Created(ref e) => e.event_type(),
327///             MyEvents::Deleted(ref e) => e.event_type(),
328///         }
329///     }
330/// }
331///
332/// impl SerializableEvent for MyEvents {
333///     type Error = serde_json::Error;
334///
335///     fn serialize_event_to_buffer(&self, buffer: &mut Vec<u8>) -> Result<(), Self::Error> {
336///         match *self {
337///             MyEvents::Created(ref e) => serde_json::to_writer(buffer, e),
338///             MyEvents::Deleted(ref e) => serde_json::to_writer(buffer, e),
339///         }
340///     }
341/// }
342///
343/// impl DeserializableEvent for MyEvents {
344///     type Error = serde_json::Error;
345///
346///     fn deserialize_event_from_buffer(buffer: &[u8], event_type: &str) -> Result<Option<Self>, Self::Error> {
347///         match event_type {
348///             "created" => serde_json::from_reader(buffer).map(MyEvents::Created).map(Some),
349///             "deleted" => serde_json::from_reader(buffer).map(MyEvents::Deleted).map(Some),
350///             _ => Ok(None),
351///         }
352///     }
353/// }
354///
355/// let original = MyEvents::Created(CreatedEvent{});
356/// let roundtrip = roundtrip_through_serialization(&original);
357/// assert_eq!(original, roundtrip);
358/// ```
359pub fn roundtrip_through_serialization<E: SerializableEvent + DeserializableEvent>(
360    original: &E,
361) -> E {
362    let mut buffer = Vec::default();
363    original
364        .serialize_event_to_buffer(&mut buffer)
365        .expect("serialization");
366
367    let roundtrip =
368        E::deserialize_event_from_buffer(&buffer, original.event_type()).expect("deserialization");
369    roundtrip.expect("known event type")
370}
371
372/// A wrapper for an aggregate that was generated from an arbitrary sequence of events.
373///
374/// # Examples
375///
376/// ```
377/// use cqrs_core::{Aggregate, AggregateEvent, Event};
378/// use cqrs_proptest::AggregateFromEventSequence;
379/// use proptest::{prelude::*, strategy::{LazyTupleUnion, ValueTree, WA}, test_runner::TestRunner, prop_oneof};
380///
381/// #[derive(Debug, Default)]
382/// struct MyAggregate {
383///     active: bool
384/// }
385///
386/// impl Aggregate for MyAggregate {
387///     fn aggregate_type() -> &'static str {
388///         "my_aggregate"
389///     }
390/// }
391///
392/// #[derive(Clone, Copy, Debug)]
393/// struct CreatedEvent{};
394///
395/// impl Event for CreatedEvent {
396///     fn event_type(&self) -> &'static str {
397///         "created"
398///     }
399/// }
400///
401/// impl AggregateEvent<MyAggregate> for CreatedEvent {
402///     fn apply_to(self, aggregate: &mut MyAggregate) {
403///         aggregate.active = true;
404///     }
405/// }
406///
407/// #[derive(Clone, Copy, Debug)]
408/// struct DeletedEvent{};
409///
410/// impl Event for DeletedEvent {
411///     fn event_type(&self) -> &'static str {
412///         "deleted"
413///     }
414/// }
415///
416/// impl AggregateEvent<MyAggregate> for DeletedEvent {
417///     fn apply_to(self, aggregate: &mut MyAggregate) {
418///         aggregate.active = false;
419///     }
420/// }
421///
422/// #[derive(Clone, Copy, Debug)]
423/// enum MyEvents {
424///     Created(CreatedEvent),
425///     Deleted(DeletedEvent),
426/// }
427///
428/// impl Event for MyEvents {
429///     fn event_type(&self) -> &'static str {
430///         match *self {
431///             MyEvents::Created(ref e) => e.event_type(),
432///             MyEvents::Deleted(ref e) => e.event_type(),
433///         }
434///     }
435/// }
436///
437/// impl AggregateEvent<MyAggregate> for MyEvents {
438///     fn apply_to(self, aggregate: &mut MyAggregate) {
439///         match self {
440///             MyEvents::Created(e) => e.apply_to(aggregate),
441///             MyEvents::Deleted(e) => e.apply_to(aggregate),
442///         }
443///     }
444/// }
445///
446/// impl Arbitrary for MyEvents {
447///     type Parameters = ();
448///
449///     fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
450///         prop_oneof![
451///             Just(MyEvents::Created(CreatedEvent{})),
452///             Just(MyEvents::Deleted(DeletedEvent{})),
453///         ]
454///     }
455///
456///     type Strategy = LazyTupleUnion<(WA<Just<Self>>, WA<Just<Self>>)>;
457/// }
458///
459/// any::<AggregateFromEventSequence<MyAggregate, MyEvents>>()
460///     .new_tree(&mut TestRunner::default())
461///     .unwrap()
462///     .current()
463///     .into_aggregate();
464///
465/// let parameters = (prop::collection::SizeRange::from(1..10), ());
466/// any_with::<AggregateFromEventSequence<MyAggregate, MyEvents>>(parameters)
467///     .new_tree(&mut TestRunner::default())
468///     .unwrap()
469///     .current();
470/// ```
471#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
472pub struct AggregateFromEventSequence<A, E>
473where
474    A: Aggregate,
475    E: AggregateEvent<A>,
476{
477    aggregate: A,
478    _phantom: PhantomData<*const E>,
479}
480
481impl<A, E> fmt::Debug for AggregateFromEventSequence<A, E>
482where
483    A: Aggregate + fmt::Debug,
484    E: AggregateEvent<A>,
485{
486    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
487        f.debug_tuple("AggregateFromEventSequence")
488            .field(&self.aggregate)
489            .finish()
490    }
491}
492
493impl<A, E> From<A> for AggregateFromEventSequence<A, E>
494where
495    A: Aggregate,
496    E: AggregateEvent<A>,
497{
498    #[inline]
499    fn from(aggregate: A) -> Self {
500        AggregateFromEventSequence {
501            aggregate,
502            _phantom: PhantomData,
503        }
504    }
505}
506
507impl<A, E> AggregateFromEventSequence<A, E>
508where
509    A: Aggregate,
510    E: AggregateEvent<A>,
511{
512    /// Unwraps the generated aggregate.
513    #[inline]
514    pub fn into_aggregate(self) -> A {
515        self.aggregate
516    }
517}
518
519impl<A, E> Arbitrary for AggregateFromEventSequence<A, E>
520where
521    A: Aggregate + fmt::Debug,
522    E: AggregateEvent<A> + Arbitrary + 'static,
523{
524    type Parameters = (prop::collection::SizeRange, <E as Arbitrary>::Parameters);
525    type Strategy = BoxedStrategy<Self>;
526
527    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
528        any_with::<Vec<E>>(args)
529            .prop_map(|events| {
530                let mut aggregate = A::default();
531                for event in events {
532                    aggregate.apply(event);
533                }
534                AggregateFromEventSequence {
535                    aggregate,
536                    _phantom: PhantomData,
537                }
538            })
539            .boxed()
540    }
541}