xerv_core/testing/providers/
uuid.rs

1//! UUID provider for deterministic UUID generation.
2//!
3//! Allows tests to use predictable UUIDs for reproducible behavior.
4
5use parking_lot::Mutex;
6use std::sync::atomic::{AtomicU64, Ordering};
7use uuid::Uuid;
8
9/// Provider trait for UUID generation.
10pub trait UuidProvider: Send + Sync {
11    /// Generate a new UUID.
12    fn new_v4(&self) -> Uuid;
13
14    /// Check if this is a mock provider.
15    fn is_mock(&self) -> bool;
16}
17
18/// Real UUID provider that generates random UUIDs.
19pub struct RealUuid;
20
21impl RealUuid {
22    /// Create a new real UUID provider.
23    pub fn new() -> Self {
24        Self
25    }
26}
27
28impl Default for RealUuid {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl UuidProvider for RealUuid {
35    fn new_v4(&self) -> Uuid {
36        Uuid::new_v4()
37    }
38
39    fn is_mock(&self) -> bool {
40        false
41    }
42}
43
44/// Mock UUID provider for testing.
45///
46/// Can generate sequential UUIDs or return predetermined values.
47pub struct MockUuid {
48    mode: MockUuidMode,
49}
50
51enum MockUuidMode {
52    /// Generate sequential UUIDs (00000000-0000-0000-0000-000000000001, etc.)
53    Sequential(AtomicU64),
54    /// Return predetermined UUIDs in order.
55    Predetermined(Mutex<Vec<Uuid>>),
56}
57
58impl MockUuid {
59    /// Create a mock UUID provider that generates sequential UUIDs.
60    ///
61    /// The first UUID will be 00000000-0000-0000-0000-000000000001.
62    ///
63    /// # Example
64    ///
65    /// ```
66    /// use xerv_core::testing::MockUuid;
67    /// use xerv_core::testing::UuidProvider;
68    ///
69    /// let provider = MockUuid::sequential();
70    /// let id1 = provider.new_v4();
71    /// let id2 = provider.new_v4();
72    ///
73    /// assert_eq!(id1.to_string(), "00000000-0000-0000-0000-000000000001");
74    /// assert_eq!(id2.to_string(), "00000000-0000-0000-0000-000000000002");
75    /// ```
76    pub fn sequential() -> Self {
77        Self {
78            mode: MockUuidMode::Sequential(AtomicU64::new(1)),
79        }
80    }
81
82    /// Create a mock UUID provider that generates sequential UUIDs starting at the given value.
83    pub fn sequential_from(start: u64) -> Self {
84        Self {
85            mode: MockUuidMode::Sequential(AtomicU64::new(start)),
86        }
87    }
88
89    /// Create a mock UUID provider that returns predetermined UUIDs.
90    ///
91    /// UUIDs are returned in the order provided. If more UUIDs are requested
92    /// than provided, sequential UUIDs are generated starting from 1.
93    ///
94    /// # Example
95    ///
96    /// ```
97    /// use xerv_core::testing::MockUuid;
98    /// use xerv_core::testing::UuidProvider;
99    /// use uuid::Uuid;
100    ///
101    /// let id = Uuid::parse_str("12345678-1234-1234-1234-123456789abc").unwrap();
102    /// let provider = MockUuid::predetermined(vec![id]);
103    ///
104    /// assert_eq!(provider.new_v4(), id);
105    /// // Next call returns sequential UUID (starting from 1)
106    /// assert_eq!(provider.new_v4().to_string(), "00000000-0000-0000-0000-000000000001");
107    /// ```
108    pub fn predetermined(uuids: Vec<Uuid>) -> Self {
109        // Store in reverse order for efficient pop
110        let mut reversed = uuids;
111        reversed.reverse();
112        Self {
113            mode: MockUuidMode::Predetermined(Mutex::new(reversed)),
114        }
115    }
116
117    /// Create a mock UUID provider from string representations.
118    ///
119    /// # Panics
120    ///
121    /// Panics if any string is not a valid UUID.
122    pub fn from_strings(uuids: &[&str]) -> Self {
123        let parsed: Vec<Uuid> = uuids
124            .iter()
125            .map(|s| Uuid::parse_str(s).expect("Invalid UUID string"))
126            .collect();
127        Self::predetermined(parsed)
128    }
129
130    /// Get the current sequential counter value (for debugging).
131    pub fn current_counter(&self) -> Option<u64> {
132        match &self.mode {
133            MockUuidMode::Sequential(counter) => Some(counter.load(Ordering::SeqCst)),
134            MockUuidMode::Predetermined(_) => None,
135        }
136    }
137}
138
139impl UuidProvider for MockUuid {
140    fn new_v4(&self) -> Uuid {
141        match &self.mode {
142            MockUuidMode::Sequential(counter) => {
143                let n = counter.fetch_add(1, Ordering::SeqCst);
144                // Create a UUID where the last 8 bytes are the counter
145                let bytes: [u8; 16] = [
146                    0,
147                    0,
148                    0,
149                    0,
150                    0,
151                    0,
152                    0,
153                    0,
154                    (n >> 56) as u8,
155                    (n >> 48) as u8,
156                    (n >> 40) as u8,
157                    (n >> 32) as u8,
158                    (n >> 24) as u8,
159                    (n >> 16) as u8,
160                    (n >> 8) as u8,
161                    n as u8,
162                ];
163                Uuid::from_bytes(bytes)
164            }
165            MockUuidMode::Predetermined(uuids) => {
166                let mut guard = uuids.lock();
167                if let Some(uuid) = guard.pop() {
168                    uuid
169                } else {
170                    // Fallback to sequential starting from current position
171                    let n = guard.len() as u64 + 1;
172                    drop(guard);
173                    let bytes: [u8; 16] = [
174                        0,
175                        0,
176                        0,
177                        0,
178                        0,
179                        0,
180                        0,
181                        0,
182                        (n >> 56) as u8,
183                        (n >> 48) as u8,
184                        (n >> 40) as u8,
185                        (n >> 32) as u8,
186                        (n >> 24) as u8,
187                        (n >> 16) as u8,
188                        (n >> 8) as u8,
189                        n as u8,
190                    ];
191                    Uuid::from_bytes(bytes)
192                }
193            }
194        }
195    }
196
197    fn is_mock(&self) -> bool {
198        true
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn sequential_uuids() {
208        let provider = MockUuid::sequential();
209
210        let id1 = provider.new_v4();
211        let id2 = provider.new_v4();
212        let id3 = provider.new_v4();
213
214        assert_eq!(id1.to_string(), "00000000-0000-0000-0000-000000000001");
215        assert_eq!(id2.to_string(), "00000000-0000-0000-0000-000000000002");
216        assert_eq!(id3.to_string(), "00000000-0000-0000-0000-000000000003");
217    }
218
219    #[test]
220    fn sequential_from() {
221        let provider = MockUuid::sequential_from(100);
222
223        let id = provider.new_v4();
224        assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000064"); // 100 in hex = 64
225    }
226
227    #[test]
228    fn predetermined_uuids() {
229        let id1 = Uuid::parse_str("12345678-1234-1234-1234-123456789abc").unwrap();
230        let id2 = Uuid::parse_str("abcdefab-cdef-abcd-efab-cdefabcdefab").unwrap();
231
232        let provider = MockUuid::predetermined(vec![id1, id2]);
233
234        assert_eq!(provider.new_v4(), id1);
235        assert_eq!(provider.new_v4(), id2);
236    }
237
238    #[test]
239    fn from_strings() {
240        let provider = MockUuid::from_strings(&[
241            "11111111-1111-1111-1111-111111111111",
242            "22222222-2222-2222-2222-222222222222",
243        ]);
244
245        assert_eq!(
246            provider.new_v4().to_string(),
247            "11111111-1111-1111-1111-111111111111"
248        );
249        assert_eq!(
250            provider.new_v4().to_string(),
251            "22222222-2222-2222-2222-222222222222"
252        );
253    }
254
255    #[test]
256    fn real_uuid_is_random() {
257        let provider = RealUuid::new();
258        let id1 = provider.new_v4();
259        let id2 = provider.new_v4();
260
261        assert_ne!(id1, id2);
262    }
263}