elif_testing/
factories.rs

1//! Factory system for test data generation
2//!
3//! Provides a powerful and type-safe factory system for generating
4//! test data with support for relationships, custom attributes,
5//! and database persistence.
6
7use std::collections::HashMap;
8use std::sync::Arc;
9use async_trait::async_trait;
10use serde_json::{Value as JsonValue, json};
11use uuid::Uuid;
12use chrono::{DateTime, Utc};
13use crate::{TestResult, database::TestDatabase};
14
15/// Factory trait for creating test data
16#[async_trait]
17pub trait Factory<T: Send>: Send + Sync {
18    /// Create a single instance
19    async fn create(&self) -> TestResult<T>;
20    
21    /// Create multiple instances
22    async fn create_many(&self, count: usize) -> TestResult<Vec<T>> {
23        let mut results = Vec::with_capacity(count);
24        for _ in 0..count {
25            results.push(self.create().await?);
26        }
27        Ok(results)
28    }
29    
30    /// Build the data without persisting to database
31    fn build(&self) -> TestResult<T>;
32    
33    /// Build multiple instances without persisting
34    fn build_many(&self, count: usize) -> TestResult<Vec<T>> {
35        let mut results = Vec::with_capacity(count);
36        for _ in 0..count {
37            results.push(self.build()?);
38        }
39        Ok(results)
40    }
41}
42
43/// Factory builder for fluent API
44#[derive(Clone)]
45pub struct FactoryBuilder<T> {
46    attributes: HashMap<String, JsonValue>,
47    database: Option<Arc<TestDatabase>>,
48    _phantom: std::marker::PhantomData<T>,
49}
50
51impl<T> FactoryBuilder<T> {
52    /// Create a new factory builder
53    pub fn new() -> Self {
54        Self {
55            attributes: HashMap::new(),
56            database: None,
57            _phantom: std::marker::PhantomData,
58        }
59    }
60    
61    /// Set an attribute value
62    pub fn with<V: serde::Serialize>(mut self, key: &str, value: V) -> Self {
63        if let Ok(json_value) = serde_json::to_value(value) {
64            self.attributes.insert(key.to_string(), json_value);
65        }
66        self
67    }
68    
69    /// Set multiple attributes
70    pub fn with_attributes(mut self, attributes: HashMap<String, JsonValue>) -> Self {
71        self.attributes.extend(attributes);
72        self
73    }
74    
75    /// Set database connection for persistence
76    pub fn with_database(mut self, database: Arc<TestDatabase>) -> Self {
77        self.database = Some(database);
78        self
79    }
80    
81    /// Add a relationship (simplified version)
82    pub fn with_relationship_data(mut self, name: &str, data: JsonValue) -> Self {
83        // For now, just store as attributes - relationships would be handled differently in real implementation
84        self.attributes.insert(format!("{}_data", name), data);
85        self
86    }
87    
88    
89    /// Get the current attributes
90    pub fn attributes(&self) -> &HashMap<String, JsonValue> {
91        &self.attributes
92    }
93}
94
95impl<T> Default for FactoryBuilder<T> {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101
102/// Trait for models that have an ID
103pub trait HasId {
104    fn id(&self) -> JsonValue;
105}
106
107/// Common factory implementations
108
109/// User factory
110#[derive(Clone)]
111pub struct UserFactory {
112    builder: FactoryBuilder<User>,
113}
114
115#[derive(Debug, Clone)]
116pub struct User {
117    pub id: Uuid,
118    pub name: String,
119    pub email: String,
120    pub created_at: DateTime<Utc>,
121    pub updated_at: Option<DateTime<Utc>>,
122}
123
124impl HasId for User {
125    fn id(&self) -> JsonValue {
126        json!(self.id)
127    }
128}
129
130impl UserFactory {
131    pub fn new() -> Self {
132        let builder = FactoryBuilder::new();
133            
134        Self { builder }
135    }
136    
137    /// Create an admin user
138    pub fn admin(self) -> Self {
139        let mut new_self = self;
140        new_self.builder = new_self.builder.with("role", "admin");
141        new_self
142    }
143    
144    /// Set custom name
145    pub fn named(self, name: &str) -> Self {
146        let mut new_self = self;
147        new_self.builder = new_self.builder.with("name", name);
148        new_self
149    }
150    
151    /// Set custom email
152    pub fn with_email(self, email: &str) -> Self {
153        let mut new_self = self;
154        new_self.builder = new_self.builder.with("email", email);
155        new_self
156    }
157    
158    /// Add posts relationship (simplified)
159    pub fn with_posts(self, count: usize) -> Self {
160        let mut new_self = self;
161        new_self.builder = new_self.builder.with("posts_count", count);
162        new_self
163    }
164}
165
166#[async_trait]
167impl Factory<User> for UserFactory {
168    async fn create(&self) -> TestResult<User> {
169        let user = self.build()?;
170        
171        // If database is available, persist the user
172        if let Some(db) = &self.builder.database {
173            let insert_sql = r#"
174                INSERT INTO users (id, name, email, created_at, updated_at)
175                VALUES ($1, $2, $3, $4, $5)
176            "#;
177            
178            sqlx::query(insert_sql)
179                .bind(&user.id)
180                .bind(&user.name)
181                .bind(&user.email)
182                .bind(&user.created_at)
183                .bind(&user.updated_at)
184                .execute(db.pool())
185                .await?;
186        }
187        
188        Ok(user)
189    }
190    
191    fn build(&self) -> TestResult<User> {
192        let attrs = &self.builder.attributes;
193        
194        // Generate fresh values for each build
195        let id = attrs.get("id")
196            .and_then(|v| v.as_str())
197            .and_then(|s| Uuid::parse_str(s).ok())
198            .unwrap_or_else(Uuid::new_v4);
199        
200        let name = attrs.get("name")
201            .and_then(|v| v.as_str())
202            .map(|s| s.to_string())
203            .unwrap_or_else(|| format!("Test User {}", crate::utils::random_string(None)));
204        
205        let email = attrs.get("email")
206            .and_then(|v| v.as_str())
207            .map(|s| s.to_string())
208            .unwrap_or_else(|| crate::utils::random_email());
209        
210        Ok(User {
211            id,
212            name,
213            email,
214            created_at: attrs.get("created_at")
215                .and_then(|v| v.as_str())
216                .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
217                .map(|dt| dt.with_timezone(&Utc))
218                .unwrap_or_else(Utc::now),
219            updated_at: attrs.get("updated_at")
220                .and_then(|v| {
221                    if v.is_null() {
222                        None
223                    } else {
224                        v.as_str()
225                            .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
226                            .map(|dt| dt.with_timezone(&Utc))
227                    }
228                }),
229        })
230    }
231}
232
233impl Default for UserFactory {
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239/// Post factory
240#[derive(Clone)]
241pub struct PostFactory {
242    builder: FactoryBuilder<Post>,
243}
244
245#[derive(Debug, Clone)]
246pub struct Post {
247    pub id: Uuid,
248    pub title: String,
249    pub content: String,
250    pub user_id: Uuid,
251    pub created_at: DateTime<Utc>,
252    pub updated_at: Option<DateTime<Utc>>,
253}
254
255impl HasId for Post {
256    fn id(&self) -> JsonValue {
257        json!(self.id)
258    }
259}
260
261impl PostFactory {
262    pub fn new() -> Self {
263        let builder = FactoryBuilder::new();
264            
265        Self { builder }
266    }
267    
268    /// Set custom title
269    pub fn with_title(self, title: &str) -> Self {
270        let mut new_self = self;
271        new_self.builder = new_self.builder.with("title", title);
272        new_self
273    }
274    
275    /// Set custom content
276    pub fn with_content(self, content: &str) -> Self {
277        let mut new_self = self;
278        new_self.builder = new_self.builder.with("content", content);
279        new_self
280    }
281    
282    /// Set user relationship
283    pub fn for_user(self, user_id: Uuid) -> Self {
284        let mut new_self = self;
285        new_self.builder = new_self.builder.with("user_id", user_id);
286        new_self
287    }
288    
289    /// Set user relationship using factory (simplified)
290    pub fn with_user(self) -> Self {
291        let mut new_self = self;
292        // In real implementation, this would create a user and set user_id
293        let user_id = Uuid::new_v4();
294        new_self.builder = new_self.builder.with("user_id", user_id);
295        new_self
296    }
297}
298
299#[async_trait]
300impl Factory<Post> for PostFactory {
301    async fn create(&self) -> TestResult<Post> {
302        let post = self.build()?;
303        
304        if let Some(db) = &self.builder.database {
305            let insert_sql = r#"
306                INSERT INTO posts (id, title, content, user_id, created_at, updated_at)
307                VALUES ($1, $2, $3, $4, $5, $6)
308            "#;
309            
310            sqlx::query(insert_sql)
311                .bind(&post.id)
312                .bind(&post.title)
313                .bind(&post.content)
314                .bind(&post.user_id)
315                .bind(&post.created_at)
316                .bind(&post.updated_at)
317                .execute(db.pool())
318                .await?;
319        }
320        
321        Ok(post)
322    }
323    
324    fn build(&self) -> TestResult<Post> {
325        let attrs = &self.builder.attributes;
326        
327        // Generate fresh values for each build
328        let id = attrs.get("id")
329            .and_then(|v| v.as_str())
330            .and_then(|s| Uuid::parse_str(s).ok())
331            .unwrap_or_else(Uuid::new_v4);
332        
333        let title = attrs.get("title")
334            .and_then(|v| v.as_str())
335            .map(|s| s.to_string())
336            .unwrap_or_else(|| format!("Test Post {}", crate::utils::random_string(None)));
337        
338        let user_id = attrs.get("user_id")
339            .and_then(|v| v.as_str())
340            .and_then(|s| Uuid::parse_str(s).ok())
341            .unwrap_or_else(Uuid::new_v4);
342        
343        Ok(Post {
344            id,
345            title,
346            content: attrs.get("content")
347                .and_then(|v| v.as_str())
348                .unwrap_or("This is test content for the post.")
349                .to_string(),
350            user_id,
351            created_at: attrs.get("created_at")
352                .and_then(|v| v.as_str())
353                .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
354                .map(|dt| dt.with_timezone(&Utc))
355                .unwrap_or_else(Utc::now),
356            updated_at: attrs.get("updated_at")
357                .and_then(|v| {
358                    if v.is_null() {
359                        None
360                    } else {
361                        v.as_str()
362                            .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
363                            .map(|dt| dt.with_timezone(&Utc))
364                    }
365                }),
366        })
367    }
368}
369
370impl Default for PostFactory {
371    fn default() -> Self {
372        Self::new()
373    }
374}
375
376/// Sequence generator for unique values
377pub struct Sequence {
378    current: std::sync::atomic::AtomicUsize,
379}
380
381impl Sequence {
382    pub fn new() -> Self {
383        Self {
384            current: std::sync::atomic::AtomicUsize::new(0),
385        }
386    }
387    
388    pub fn next(&self) -> usize {
389        self.current.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
390    }
391    
392    pub fn next_string(&self, prefix: &str) -> String {
393        format!("{}{}", prefix, self.next())
394    }
395}
396
397impl Default for Sequence {
398    fn default() -> Self {
399        Self::new()
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406    
407    #[test]
408    fn test_user_factory_build() -> TestResult<()> {
409        let factory = UserFactory::new();
410        let user = factory.build()?;
411        
412        assert!(!user.name.is_empty());
413        assert!(user.email.contains("@"));
414        assert!(user.created_at <= Utc::now());
415        
416        Ok(())
417    }
418    
419    #[test]
420    fn test_user_factory_with_custom_attributes() -> TestResult<()> {
421        let factory = UserFactory::new()
422            .named("John Doe")
423            .with_email("john@example.com");
424            
425        let user = factory.build()?;
426        
427        assert_eq!(user.name, "John Doe");
428        assert_eq!(user.email, "john@example.com");
429        
430        Ok(())
431    }
432    
433    #[test]
434    fn test_post_factory_build() -> TestResult<()> {
435        let factory = PostFactory::new();
436        let post = factory.build()?;
437        
438        assert!(!post.title.is_empty());
439        assert!(!post.content.is_empty());
440        assert!(post.created_at <= Utc::now());
441        
442        Ok(())
443    }
444    
445    #[test]
446    fn test_sequence() {
447        let seq = Sequence::new();
448        
449        assert_eq!(seq.next(), 0);
450        assert_eq!(seq.next(), 1);
451        assert_eq!(seq.next_string("user"), "user2");
452    }
453    
454    #[test]
455    fn test_factory_builder() {
456        let builder = FactoryBuilder::<User>::new()
457            .with("name", "Test User")
458            .with("email", "test@example.com");
459            
460        assert_eq!(builder.attributes().get("name"), Some(&json!("Test User")));
461        assert_eq!(builder.attributes().get("email"), Some(&json!("test@example.com")));
462    }
463    
464    #[tokio::test]
465    async fn test_factory_create_many() -> TestResult<()> {
466        let factory = UserFactory::new();
467        let users = factory.build_many(3)?;
468        
469        assert_eq!(users.len(), 3);
470        
471        // Ensure all users are unique
472        for i in 0..users.len() {
473            for j in (i + 1)..users.len() {
474                assert_ne!(users[i].id, users[j].id);
475            }
476        }
477        
478        Ok(())
479    }
480}