Skip to main content

spate_test/
fixtures.rs

1//! Constructors for the framework values a stage or connector test feeds in:
2//! records, raw payloads, acknowledgment handles, component configs, and
3//! per-test names.
4
5use spate_core::checkpoint::AckRef;
6use spate_core::config::ComponentConfig;
7use spate_core::record::{PartitionId, RawPayload, Record, RecordMeta};
8use std::sync::atomic::{AtomicU64, Ordering};
9
10/// An acknowledgment handle whose resolution is discarded.
11///
12/// To observe a resolution, take the handle from a batch polled against a
13/// real `Checkpointer`, as the crate-level example does.
14#[must_use]
15pub fn test_ack() -> AckRef {
16    AckRef::test_pair().0
17}
18
19/// A record carrying `payload`, with zeroed metadata and a [`test_ack`] handle.
20#[must_use]
21pub fn record<T>(payload: T) -> Record<T> {
22    Record {
23        payload,
24        meta: RecordMeta {
25            partition: PartitionId(0),
26            offset: 0,
27            event_time_ms: 0,
28            key_hash: None,
29        },
30        ack: test_ack(),
31    }
32}
33
34/// A keyless payload over `bytes` at partition 0, offset 0, timestamp 0.
35#[must_use]
36pub fn raw_payload(bytes: &[u8]) -> RawPayload<'_> {
37    RawPayload {
38        bytes,
39        key: None,
40        partition: PartitionId(0),
41        offset: 0,
42        timestamp_ms: 0,
43    }
44}
45
46/// A component config tagged `type_tag`, with `yaml` as its body.
47///
48/// `yaml` is the section's body without the tag line, and may be indented.
49///
50/// # Panics
51///
52/// Panics if `yaml` does not parse.
53#[must_use]
54pub fn component_config(type_tag: &str, yaml: &str) -> ComponentConfig {
55    let raw = serde_yaml::from_str(yaml)
56        .unwrap_or_else(|e| panic!("component config for {type_tag:?} does not parse: {e}"));
57    ComponentConfig::new(type_tag, raw)
58}
59
60/// `prefix` with a suffix no other call in this process returns.
61///
62/// Gauge ownership is process-wide (INV-10), so tests sharing a binary name
63/// each pipeline or component with this.
64#[must_use]
65pub fn unique_name(prefix: &str) -> String {
66    static NEXT: AtomicU64 = AtomicU64::new(0);
67    format!("{prefix}-{}", NEXT.fetch_add(1, Ordering::Relaxed))
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn unique_name_never_repeats() {
76        let a = unique_name("p");
77        let b = unique_name("p");
78        assert_ne!(a, b);
79        assert!(a.starts_with("p-") && b.starts_with("p-"));
80    }
81
82    /// An indented body parses as the mapping it would be under its tag, and
83    /// an empty mapping parses too.
84    #[test]
85    fn component_config_takes_an_indented_body() {
86        assert_eq!(
87            component_config("t", "  brokers: a\n  topic: b\n"),
88            component_config("t", "brokers: a\ntopic: b\n"),
89        );
90        assert_eq!(component_config("t", "  {}\n").type_tag(), "t");
91    }
92}