1use 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#[async_trait]
17pub trait Factory<T: Send>: Send + Sync {
18 async fn create(&self) -> TestResult<T>;
20
21 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 fn build(&self) -> TestResult<T>;
32
33 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#[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 pub fn new() -> Self {
54 Self {
55 attributes: HashMap::new(),
56 database: None,
57 _phantom: std::marker::PhantomData,
58 }
59 }
60
61 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 pub fn with_attributes(mut self, attributes: HashMap<String, JsonValue>) -> Self {
71 self.attributes.extend(attributes);
72 self
73 }
74
75 pub fn with_database(mut self, database: Arc<TestDatabase>) -> Self {
77 self.database = Some(database);
78 self
79 }
80
81 pub fn with_relationship_data(mut self, name: &str, data: JsonValue) -> Self {
83 self.attributes.insert(format!("{}_data", name), data);
85 self
86 }
87
88
89 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
102pub trait HasId {
104 fn id(&self) -> JsonValue;
105}
106
107#[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 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 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 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 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 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 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#[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 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 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 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 pub fn with_user(self) -> Self {
291 let mut new_self = self;
292 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 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
376pub 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 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}