Skip to main content

fastapi_core/
fixtures.rs

1//! Test fixtures and factory helpers for reducing test boilerplate.
2//!
3//! This module provides factory patterns for creating common test data
4//! with minimal setup code. All factories use the builder pattern for
5//! customization.
6//!
7//! # Factory Types
8//!
9//! - [`RequestFactory`]: Create test HTTP requests
10//! - [`ResponseFactory`]: Create test HTTP responses
11//! - [`AuthFactory`]: Create authentication tokens and credentials
12//! - [`JsonFactory`]: Create JSON test payloads
13//! - [`UserFactory`]: Create user test data
14//!
15//! # Example
16//!
17//! ```ignore
18//! use fastapi_core::fixtures::*;
19//!
20//! // Create a GET request quickly
21//! let req = RequestFactory::get("/users").build();
22//!
23//! // Create an authenticated POST request
24//! let req = RequestFactory::post("/items")
25//!     .json(&item)
26//!     .bearer_token("abc123")
27//!     .build();
28//!
29//! // Create test users
30//! let user = UserFactory::new().email("test@example.com").build();
31//!
32//! // Create valid/invalid JSON
33//! let valid = JsonFactory::valid_object().build();
34//! let invalid = JsonFactory::malformed().build();
35//! ```
36
37use std::collections::HashMap;
38
39use crate::request::{Body, HttpVersion, Method, Request};
40use crate::response::{Response, ResponseBody, StatusCode};
41
42// =============================================================================
43// Request Factory
44// =============================================================================
45
46/// Factory for creating test HTTP requests with minimal boilerplate.
47///
48/// # Example
49///
50/// ```ignore
51/// use fastapi_core::fixtures::RequestFactory;
52///
53/// // Simple GET
54/// let req = RequestFactory::get("/users").build();
55///
56/// // POST with JSON body and auth
57/// let req = RequestFactory::post("/items")
58///     .json(&Item { name: "Widget" })
59///     .bearer_token("token123")
60///     .header("X-Request-Id", "abc")
61///     .build();
62/// ```
63#[derive(Debug, Clone)]
64pub struct RequestFactory {
65    method: Method,
66    path: String,
67    query: Option<String>,
68    version: HttpVersion,
69    headers: Vec<(String, Vec<u8>)>,
70    body: Option<Vec<u8>>,
71}
72
73impl RequestFactory {
74    /// Create a new request factory with the given method and path.
75    #[must_use]
76    pub fn new(method: Method, path: impl Into<String>) -> Self {
77        Self {
78            method,
79            path: path.into(),
80            query: None,
81            version: HttpVersion::Http11,
82            headers: Vec::new(),
83            body: None,
84        }
85    }
86
87    /// Create a GET request factory.
88    #[must_use]
89    pub fn get(path: impl Into<String>) -> Self {
90        Self::new(Method::Get, path)
91    }
92
93    /// Create a POST request factory.
94    #[must_use]
95    pub fn post(path: impl Into<String>) -> Self {
96        Self::new(Method::Post, path)
97    }
98
99    /// Create a PUT request factory.
100    #[must_use]
101    pub fn put(path: impl Into<String>) -> Self {
102        Self::new(Method::Put, path)
103    }
104
105    /// Create a DELETE request factory.
106    #[must_use]
107    pub fn delete(path: impl Into<String>) -> Self {
108        Self::new(Method::Delete, path)
109    }
110
111    /// Create a PATCH request factory.
112    #[must_use]
113    pub fn patch(path: impl Into<String>) -> Self {
114        Self::new(Method::Patch, path)
115    }
116
117    /// Create an OPTIONS request factory.
118    #[must_use]
119    pub fn options(path: impl Into<String>) -> Self {
120        Self::new(Method::Options, path)
121    }
122
123    /// Create a HEAD request factory.
124    #[must_use]
125    pub fn head(path: impl Into<String>) -> Self {
126        Self::new(Method::Head, path)
127    }
128
129    /// Set the query string.
130    #[must_use]
131    pub fn query(mut self, query: impl Into<String>) -> Self {
132        self.query = Some(query.into());
133        self
134    }
135
136    /// Add query parameters from an iterator.
137    #[must_use]
138    pub fn query_params<I, K, V>(mut self, params: I) -> Self
139    where
140        I: IntoIterator<Item = (K, V)>,
141        K: AsRef<str>,
142        V: AsRef<str>,
143    {
144        let query: String = params
145            .into_iter()
146            .map(|(k, v)| format!("{}={}", k.as_ref(), v.as_ref()))
147            .collect::<Vec<_>>()
148            .join("&");
149        self.query = Some(query);
150        self
151    }
152
153    /// Set the HTTP version.
154    #[must_use]
155    pub fn version(mut self, version: HttpVersion) -> Self {
156        self.version = version;
157        self
158    }
159
160    /// Add a header.
161    #[must_use]
162    pub fn header(mut self, name: impl Into<String>, value: impl Into<Vec<u8>>) -> Self {
163        self.headers.push((name.into(), value.into()));
164        self
165    }
166
167    /// Set the Content-Type header.
168    #[must_use]
169    pub fn content_type(self, content_type: impl AsRef<str>) -> Self {
170        self.header("Content-Type", content_type.as_ref().as_bytes().to_vec())
171    }
172
173    /// Set the Accept header.
174    #[must_use]
175    pub fn accept(self, accept: impl AsRef<str>) -> Self {
176        self.header("Accept", accept.as_ref().as_bytes().to_vec())
177    }
178
179    /// Set a Bearer token for Authorization.
180    #[must_use]
181    pub fn bearer_token(self, token: impl AsRef<str>) -> Self {
182        self.header(
183            "Authorization",
184            format!("Bearer {}", token.as_ref()).into_bytes(),
185        )
186    }
187
188    /// Set Basic auth credentials.
189    #[must_use]
190    pub fn basic_auth(self, username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
191        use std::io::Write;
192        let mut encoded = Vec::new();
193        let _ = write!(
194            &mut encoded,
195            "Basic {}",
196            base64_encode(&format!("{}:{}", username.as_ref(), password.as_ref()))
197        );
198        self.header("Authorization", encoded)
199    }
200
201    /// Set an API key header.
202    #[must_use]
203    pub fn api_key(self, key: impl AsRef<str>) -> Self {
204        self.header("X-API-Key", key.as_ref().as_bytes().to_vec())
205    }
206
207    /// Set the raw body bytes.
208    #[must_use]
209    pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
210        self.body = Some(body.into());
211        self
212    }
213
214    /// Set a JSON body (serializes the value).
215    #[must_use]
216    pub fn json<T: serde::Serialize>(self, value: &T) -> Self {
217        let json = serde_json::to_vec(value).unwrap_or_default();
218        self.content_type("application/json").body(json)
219    }
220
221    /// Set a form-encoded body.
222    #[must_use]
223    pub fn form<I, K, V>(self, fields: I) -> Self
224    where
225        I: IntoIterator<Item = (K, V)>,
226        K: AsRef<str>,
227        V: AsRef<str>,
228    {
229        let body: String = fields
230            .into_iter()
231            .map(|(k, v)| {
232                format!(
233                    "{}={}",
234                    urlencoding_simple(k.as_ref()),
235                    urlencoding_simple(v.as_ref())
236                )
237            })
238            .collect::<Vec<_>>()
239            .join("&");
240        self.content_type("application/x-www-form-urlencoded")
241            .body(body.into_bytes())
242    }
243
244    /// Set a plain text body.
245    #[must_use]
246    pub fn text(self, text: impl AsRef<str>) -> Self {
247        self.content_type("text/plain")
248            .body(text.as_ref().as_bytes().to_vec())
249    }
250
251    /// Build the request.
252    #[must_use]
253    pub fn build(self) -> Request {
254        let mut req = Request::with_version(self.method, self.path, self.version);
255        req.set_query(self.query);
256
257        for (name, value) in self.headers {
258            req.headers_mut().insert(name, value);
259        }
260
261        if let Some(body) = self.body {
262            if !body.is_empty() {
263                req.headers_mut()
264                    .insert("Content-Length", body.len().to_string().into_bytes());
265            }
266            req.set_body(Body::Bytes(body));
267        }
268
269        req
270    }
271}
272
273// =============================================================================
274// Response Factory
275// =============================================================================
276
277/// Factory for creating test HTTP responses.
278///
279/// # Example
280///
281/// ```ignore
282/// use fastapi_core::fixtures::ResponseFactory;
283///
284/// // Simple 200 OK
285/// let resp = ResponseFactory::ok().build();
286///
287/// // JSON response
288/// let resp = ResponseFactory::ok()
289///     .json(&user)
290///     .build();
291///
292/// // Error response
293/// let resp = ResponseFactory::not_found()
294///     .json(&ErrorBody { message: "User not found" })
295///     .build();
296/// ```
297#[derive(Debug, Clone)]
298pub struct ResponseFactory {
299    status: StatusCode,
300    headers: Vec<(String, Vec<u8>)>,
301    body: Option<Vec<u8>>,
302}
303
304impl ResponseFactory {
305    /// Create a new response factory with the given status code.
306    #[must_use]
307    pub fn new(status: StatusCode) -> Self {
308        Self {
309            status,
310            headers: Vec::new(),
311            body: None,
312        }
313    }
314
315    /// Create a 200 OK response factory.
316    #[must_use]
317    pub fn ok() -> Self {
318        Self::new(StatusCode::OK)
319    }
320
321    /// Create a 201 Created response factory.
322    #[must_use]
323    pub fn created() -> Self {
324        Self::new(StatusCode::CREATED)
325    }
326
327    /// Create a 204 No Content response factory.
328    #[must_use]
329    pub fn no_content() -> Self {
330        Self::new(StatusCode::NO_CONTENT)
331    }
332
333    /// Create a 301 Moved Permanently response factory.
334    #[must_use]
335    pub fn moved_permanently(location: impl AsRef<str>) -> Self {
336        Self::new(StatusCode::MOVED_PERMANENTLY)
337            .header("Location", location.as_ref().as_bytes().to_vec())
338    }
339
340    /// Create a 302 Found response factory.
341    #[must_use]
342    pub fn found(location: impl AsRef<str>) -> Self {
343        Self::new(StatusCode::FOUND).header("Location", location.as_ref().as_bytes().to_vec())
344    }
345
346    /// Create a 400 Bad Request response factory.
347    #[must_use]
348    pub fn bad_request() -> Self {
349        Self::new(StatusCode::BAD_REQUEST)
350    }
351
352    /// Create a 401 Unauthorized response factory.
353    #[must_use]
354    pub fn unauthorized() -> Self {
355        Self::new(StatusCode::UNAUTHORIZED)
356    }
357
358    /// Create a 403 Forbidden response factory.
359    #[must_use]
360    pub fn forbidden() -> Self {
361        Self::new(StatusCode::FORBIDDEN)
362    }
363
364    /// Create a 404 Not Found response factory.
365    #[must_use]
366    pub fn not_found() -> Self {
367        Self::new(StatusCode::NOT_FOUND)
368    }
369
370    /// Create a 422 Unprocessable Entity response factory.
371    #[must_use]
372    pub fn unprocessable_entity() -> Self {
373        Self::new(StatusCode::UNPROCESSABLE_ENTITY)
374    }
375
376    /// Create a 500 Internal Server Error response factory.
377    #[must_use]
378    pub fn internal_server_error() -> Self {
379        Self::new(StatusCode::INTERNAL_SERVER_ERROR)
380    }
381
382    /// Set the status code.
383    #[must_use]
384    pub fn status(mut self, status: StatusCode) -> Self {
385        self.status = status;
386        self
387    }
388
389    /// Add a header.
390    #[must_use]
391    pub fn header(mut self, name: impl Into<String>, value: impl Into<Vec<u8>>) -> Self {
392        self.headers.push((name.into(), value.into()));
393        self
394    }
395
396    /// Set the Content-Type header.
397    #[must_use]
398    pub fn content_type(self, content_type: impl AsRef<str>) -> Self {
399        self.header("Content-Type", content_type.as_ref().as_bytes().to_vec())
400    }
401
402    /// Set the raw body bytes.
403    #[must_use]
404    pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
405        self.body = Some(body.into());
406        self
407    }
408
409    /// Set a JSON body.
410    #[must_use]
411    pub fn json<T: serde::Serialize>(self, value: &T) -> Self {
412        let json = serde_json::to_vec(value).unwrap_or_default();
413        self.content_type("application/json").body(json)
414    }
415
416    /// Set a plain text body.
417    #[must_use]
418    pub fn text(self, text: impl AsRef<str>) -> Self {
419        self.content_type("text/plain")
420            .body(text.as_ref().as_bytes().to_vec())
421    }
422
423    /// Set an HTML body.
424    #[must_use]
425    pub fn html(self, html: impl AsRef<str>) -> Self {
426        self.content_type("text/html")
427            .body(html.as_ref().as_bytes().to_vec())
428    }
429
430    /// Build the response.
431    #[must_use]
432    pub fn build(self) -> Response {
433        let mut resp = Response::with_status(self.status);
434
435        for (name, value) in self.headers {
436            resp = resp.header(name, value);
437        }
438
439        if let Some(body) = self.body {
440            resp = resp.body(ResponseBody::Bytes(body));
441        }
442
443        resp
444    }
445}
446
447// =============================================================================
448// Auth Factory
449// =============================================================================
450
451/// Factory for creating authentication test data.
452///
453/// # Example
454///
455/// ```ignore
456/// use fastapi_core::fixtures::AuthFactory;
457///
458/// // Bearer token
459/// let token = AuthFactory::bearer_token();
460///
461/// // API key
462/// let key = AuthFactory::api_key();
463///
464/// // JWT-like token
465/// let jwt = AuthFactory::jwt_token()
466///     .sub("user123")
467///     .build();
468/// ```
469/// Factory for creating authentication test data - provides static methods.
470pub struct AuthFactory;
471
472impl AuthFactory {
473    /// Generate a random-looking bearer token.
474    #[must_use]
475    pub fn bearer_token() -> String {
476        // Generate a deterministic but realistic-looking token
477        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U".to_string()
478    }
479
480    /// Generate a random-looking API key.
481    #[must_use]
482    pub fn api_key() -> String {
483        "sk_test_abcdefghijklmnopqrstuvwxyz123456".to_string()
484    }
485
486    /// Generate a random-looking session ID.
487    #[must_use]
488    pub fn session_id() -> String {
489        "sess_abcdef123456789012345678901234567890".to_string()
490    }
491
492    /// Generate a refresh token.
493    #[must_use]
494    pub fn refresh_token() -> String {
495        "rt_abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGH".to_string()
496    }
497
498    /// Create a JWT-like token factory.
499    #[must_use]
500    pub fn jwt_token() -> JwtFactory {
501        JwtFactory::new()
502    }
503
504    /// Generate basic auth credentials.
505    #[must_use]
506    pub fn basic_credentials() -> (String, String) {
507        ("testuser".to_string(), "testpass123".to_string())
508    }
509
510    /// Generate an OAuth2 authorization code.
511    #[must_use]
512    pub fn oauth_code() -> String {
513        "authcode_abcdefghijklmnopqrstuvwxyz123456".to_string()
514    }
515}
516
517/// Factory for creating JWT-like tokens.
518#[derive(Debug, Clone, Default)]
519pub struct JwtFactory {
520    sub: Option<String>,
521    iat: Option<u64>,
522    exp: Option<u64>,
523    iss: Option<String>,
524    aud: Option<String>,
525    custom: HashMap<String, String>,
526}
527
528impl JwtFactory {
529    /// Create a new JWT factory.
530    #[must_use]
531    pub fn new() -> Self {
532        Self::default()
533    }
534
535    /// Set the subject claim.
536    #[must_use]
537    #[allow(clippy::should_implement_trait)]
538    pub fn sub(mut self, sub: impl Into<String>) -> Self {
539        self.sub = Some(sub.into());
540        self
541    }
542
543    /// Set the issued-at timestamp.
544    #[must_use]
545    pub fn iat(mut self, iat: u64) -> Self {
546        self.iat = Some(iat);
547        self
548    }
549
550    /// Set the expiration timestamp.
551    #[must_use]
552    pub fn exp(mut self, exp: u64) -> Self {
553        self.exp = Some(exp);
554        self
555    }
556
557    /// Set the issuer claim.
558    #[must_use]
559    pub fn iss(mut self, iss: impl Into<String>) -> Self {
560        self.iss = Some(iss.into());
561        self
562    }
563
564    /// Set the audience claim.
565    #[must_use]
566    pub fn aud(mut self, aud: impl Into<String>) -> Self {
567        self.aud = Some(aud.into());
568        self
569    }
570
571    /// Add a custom claim.
572    #[must_use]
573    pub fn claim(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
574        self.custom.insert(key.into(), value.into());
575        self
576    }
577
578    /// Build a JWT-like token string.
579    ///
580    /// Note: This is NOT cryptographically valid. It's for testing only.
581    #[must_use]
582    pub fn build(self) -> String {
583        // Build a fake JWT for testing
584        let mut claims = serde_json::Map::new();
585
586        if let Some(sub) = self.sub {
587            claims.insert("sub".into(), serde_json::Value::String(sub));
588        }
589        if let Some(iat) = self.iat {
590            claims.insert("iat".into(), serde_json::Value::Number(iat.into()));
591        }
592        if let Some(exp) = self.exp {
593            claims.insert("exp".into(), serde_json::Value::Number(exp.into()));
594        }
595        if let Some(iss) = self.iss {
596            claims.insert("iss".into(), serde_json::Value::String(iss));
597        }
598        if let Some(aud) = self.aud {
599            claims.insert("aud".into(), serde_json::Value::String(aud));
600        }
601        for (k, v) in self.custom {
602            claims.insert(k, serde_json::Value::String(v));
603        }
604
605        let header = base64_encode(r#"{"alg":"HS256","typ":"JWT"}"#);
606        let payload = base64_encode(&serde_json::to_string(&claims).unwrap_or_default());
607        let signature = "test_signature_not_valid";
608
609        format!("{header}.{payload}.{signature}")
610    }
611}
612
613// =============================================================================
614// JSON Factory
615// =============================================================================
616
617/// Factory for creating JSON test payloads.
618///
619/// # Example
620///
621/// ```ignore
622/// use fastapi_core::fixtures::JsonFactory;
623///
624/// // Valid JSON object
625/// let obj = JsonFactory::object()
626///     .field("name", "Alice")
627///     .field("age", 30)
628///     .build();
629///
630/// // Valid JSON array
631/// let arr = JsonFactory::array()
632///     .push(1)
633///     .push(2)
634///     .build();
635///
636/// // Invalid JSON
637/// let invalid = JsonFactory::malformed();
638/// ```
639pub struct JsonFactory;
640
641impl JsonFactory {
642    /// Create an object factory.
643    #[must_use]
644    pub fn object() -> JsonObjectFactory {
645        JsonObjectFactory::new()
646    }
647
648    /// Create an array factory.
649    #[must_use]
650    pub fn array() -> JsonArrayFactory {
651        JsonArrayFactory::new()
652    }
653
654    /// Generate malformed JSON (unclosed brace).
655    #[must_use]
656    pub fn malformed() -> Vec<u8> {
657        b"{\"key\": \"value\"".to_vec()
658    }
659
660    /// Generate JSON with trailing comma.
661    #[must_use]
662    pub fn trailing_comma() -> Vec<u8> {
663        b"{\"key\": \"value\",}".to_vec()
664    }
665
666    /// Generate JSON with single quotes (invalid).
667    #[must_use]
668    pub fn single_quotes() -> Vec<u8> {
669        b"{'key': 'value'}".to_vec()
670    }
671
672    /// Generate JSON with unquoted keys (invalid).
673    #[must_use]
674    pub fn unquoted_keys() -> Vec<u8> {
675        b"{key: \"value\"}".to_vec()
676    }
677
678    /// Generate empty object.
679    #[must_use]
680    pub fn empty_object() -> Vec<u8> {
681        b"{}".to_vec()
682    }
683
684    /// Generate empty array.
685    #[must_use]
686    pub fn empty_array() -> Vec<u8> {
687        b"[]".to_vec()
688    }
689
690    /// Generate null.
691    #[must_use]
692    pub fn null() -> Vec<u8> {
693        b"null".to_vec()
694    }
695}
696
697/// Factory for building JSON objects.
698#[derive(Debug, Clone, Default)]
699pub struct JsonObjectFactory {
700    fields: Vec<(String, serde_json::Value)>,
701}
702
703impl JsonObjectFactory {
704    /// Create a new object factory.
705    #[must_use]
706    pub fn new() -> Self {
707        Self::default()
708    }
709
710    /// Add a string field.
711    #[must_use]
712    pub fn string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
713        self.fields
714            .push((key.into(), serde_json::Value::String(value.into())));
715        self
716    }
717
718    /// Add a numeric field.
719    #[must_use]
720    pub fn number(mut self, key: impl Into<String>, value: i64) -> Self {
721        self.fields
722            .push((key.into(), serde_json::Value::Number(value.into())));
723        self
724    }
725
726    /// Add a float field.
727    #[must_use]
728    pub fn float(mut self, key: impl Into<String>, value: f64) -> Self {
729        if let Some(n) = serde_json::Number::from_f64(value) {
730            self.fields.push((key.into(), serde_json::Value::Number(n)));
731        }
732        self
733    }
734
735    /// Add a boolean field.
736    #[must_use]
737    pub fn bool(mut self, key: impl Into<String>, value: bool) -> Self {
738        self.fields
739            .push((key.into(), serde_json::Value::Bool(value)));
740        self
741    }
742
743    /// Add a null field.
744    #[must_use]
745    pub fn null(mut self, key: impl Into<String>) -> Self {
746        self.fields.push((key.into(), serde_json::Value::Null));
747        self
748    }
749
750    /// Add a nested object field.
751    #[must_use]
752    pub fn object(mut self, key: impl Into<String>, factory: JsonObjectFactory) -> Self {
753        let map: serde_json::Map<String, serde_json::Value> = factory.fields.into_iter().collect();
754        self.fields
755            .push((key.into(), serde_json::Value::Object(map)));
756        self
757    }
758
759    /// Add an array field.
760    #[must_use]
761    pub fn array(mut self, key: impl Into<String>, factory: JsonArrayFactory) -> Self {
762        self.fields
763            .push((key.into(), serde_json::Value::Array(factory.items)));
764        self
765    }
766
767    /// Build the JSON bytes.
768    #[must_use]
769    pub fn build(self) -> Vec<u8> {
770        let map: serde_json::Map<String, serde_json::Value> = self.fields.into_iter().collect();
771        serde_json::to_vec(&map).unwrap_or_default()
772    }
773
774    /// Build as a serde_json::Value.
775    #[must_use]
776    pub fn build_value(self) -> serde_json::Value {
777        let map: serde_json::Map<String, serde_json::Value> = self.fields.into_iter().collect();
778        serde_json::Value::Object(map)
779    }
780}
781
782/// Factory for building JSON arrays.
783#[derive(Debug, Clone, Default)]
784pub struct JsonArrayFactory {
785    items: Vec<serde_json::Value>,
786}
787
788impl JsonArrayFactory {
789    /// Create a new array factory.
790    #[must_use]
791    pub fn new() -> Self {
792        Self::default()
793    }
794
795    /// Push a string item.
796    #[must_use]
797    pub fn push_string(mut self, value: impl Into<String>) -> Self {
798        self.items.push(serde_json::Value::String(value.into()));
799        self
800    }
801
802    /// Push a numeric item.
803    #[must_use]
804    pub fn push_number(mut self, value: i64) -> Self {
805        self.items.push(serde_json::Value::Number(value.into()));
806        self
807    }
808
809    /// Push a boolean item.
810    #[must_use]
811    pub fn push_bool(mut self, value: bool) -> Self {
812        self.items.push(serde_json::Value::Bool(value));
813        self
814    }
815
816    /// Push a null item.
817    #[must_use]
818    pub fn push_null(mut self) -> Self {
819        self.items.push(serde_json::Value::Null);
820        self
821    }
822
823    /// Push an object item.
824    #[must_use]
825    pub fn push_object(mut self, factory: JsonObjectFactory) -> Self {
826        self.items.push(factory.build_value());
827        self
828    }
829
830    /// Build the JSON bytes.
831    #[must_use]
832    pub fn build(self) -> Vec<u8> {
833        serde_json::to_vec(&self.items).unwrap_or_default()
834    }
835}
836
837// =============================================================================
838// User Factory
839// =============================================================================
840
841/// Factory for creating user test data.
842///
843/// # Example
844///
845/// ```ignore
846/// use fastapi_core::fixtures::UserFactory;
847///
848/// let user = UserFactory::new()
849///     .email("test@example.com")
850///     .name("Alice")
851///     .build();
852/// ```
853#[derive(Debug, Clone, Default)]
854pub struct UserFactory {
855    id: Option<i64>,
856    email: Option<String>,
857    name: Option<String>,
858    role: Option<String>,
859    active: Option<bool>,
860}
861
862impl UserFactory {
863    /// Create a new user factory with default values.
864    #[must_use]
865    pub fn new() -> Self {
866        Self::default()
867    }
868
869    /// Set the user ID.
870    #[must_use]
871    pub fn id(mut self, id: i64) -> Self {
872        self.id = Some(id);
873        self
874    }
875
876    /// Set the email.
877    #[must_use]
878    pub fn email(mut self, email: impl Into<String>) -> Self {
879        self.email = Some(email.into());
880        self
881    }
882
883    /// Set the name.
884    #[must_use]
885    pub fn name(mut self, name: impl Into<String>) -> Self {
886        self.name = Some(name.into());
887        self
888    }
889
890    /// Set the role.
891    #[must_use]
892    pub fn role(mut self, role: impl Into<String>) -> Self {
893        self.role = Some(role.into());
894        self
895    }
896
897    /// Set whether the user is active.
898    #[must_use]
899    pub fn active(mut self, active: bool) -> Self {
900        self.active = Some(active);
901        self
902    }
903
904    /// Create an admin user.
905    #[must_use]
906    pub fn admin() -> Self {
907        Self::new()
908            .id(1)
909            .email("admin@example.com")
910            .name("Admin User")
911            .role("admin")
912            .active(true)
913    }
914
915    /// Create a regular user.
916    #[must_use]
917    pub fn regular() -> Self {
918        Self::new()
919            .id(2)
920            .email("user@example.com")
921            .name("Regular User")
922            .role("user")
923            .active(true)
924    }
925
926    /// Create an inactive user.
927    #[must_use]
928    pub fn inactive() -> Self {
929        Self::new()
930            .id(3)
931            .email("inactive@example.com")
932            .name("Inactive User")
933            .role("user")
934            .active(false)
935    }
936
937    /// Build as JSON bytes.
938    #[must_use]
939    pub fn build(self) -> Vec<u8> {
940        let mut map = serde_json::Map::new();
941
942        if let Some(id) = self.id {
943            map.insert("id".into(), serde_json::Value::Number(id.into()));
944        }
945        if let Some(email) = self.email {
946            map.insert("email".into(), serde_json::Value::String(email));
947        }
948        if let Some(name) = self.name {
949            map.insert("name".into(), serde_json::Value::String(name));
950        }
951        if let Some(role) = self.role {
952            map.insert("role".into(), serde_json::Value::String(role));
953        }
954        if let Some(active) = self.active {
955            map.insert("active".into(), serde_json::Value::Bool(active));
956        }
957
958        serde_json::to_vec(&map).unwrap_or_default()
959    }
960
961    /// Build as a serde_json::Value.
962    #[must_use]
963    pub fn build_value(self) -> serde_json::Value {
964        serde_json::from_slice(&self.build()).unwrap_or(serde_json::Value::Null)
965    }
966}
967
968// =============================================================================
969// Common Test Data
970// =============================================================================
971
972/// Pre-built common test data for quick access.
973pub struct CommonFixtures;
974
975impl CommonFixtures {
976    /// A valid email address for testing.
977    pub const TEST_EMAIL: &'static str = "test@example.com";
978
979    /// A valid phone number for testing.
980    pub const TEST_PHONE: &'static str = "+1234567890";
981
982    /// A valid URL for testing.
983    pub const TEST_URL: &'static str = "https://example.com";
984
985    /// A valid UUID for testing.
986    pub const TEST_UUID: &'static str = "550e8400-e29b-41d4-a716-446655440000";
987
988    /// A valid ISO date for testing.
989    pub const TEST_DATE: &'static str = "2025-01-15";
990
991    /// A valid ISO datetime for testing.
992    pub const TEST_DATETIME: &'static str = "2025-01-15T10:30:00Z";
993
994    /// An IPv4 address for testing.
995    pub const TEST_IPV4: &'static str = "192.168.1.1";
996
997    /// An IPv6 address for testing.
998    pub const TEST_IPV6: &'static str = "::1";
999}
1000
1001// =============================================================================
1002// Helper Functions
1003// =============================================================================
1004
1005/// Simple base64 encoding.
1006fn base64_encode(input: &str) -> String {
1007    const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1008
1009    let bytes = input.as_bytes();
1010    let mut result = String::new();
1011
1012    for chunk in bytes.chunks(3) {
1013        let b = match chunk.len() {
1014            1 => [chunk[0], 0, 0],
1015            2 => [chunk[0], chunk[1], 0],
1016            _ => [chunk[0], chunk[1], chunk[2]],
1017        };
1018
1019        let n = (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]);
1020
1021        result.push(ALPHABET[((n >> 18) & 63) as usize] as char);
1022        result.push(ALPHABET[((n >> 12) & 63) as usize] as char);
1023
1024        if chunk.len() > 1 {
1025            result.push(ALPHABET[((n >> 6) & 63) as usize] as char);
1026        } else {
1027            result.push('=');
1028        }
1029
1030        if chunk.len() > 2 {
1031            result.push(ALPHABET[(n & 63) as usize] as char);
1032        } else {
1033            result.push('=');
1034        }
1035    }
1036
1037    result
1038}
1039
1040/// Simple URL encoding.
1041fn urlencoding_simple(s: &str) -> String {
1042    let mut result = String::with_capacity(s.len() * 3);
1043    for c in s.chars() {
1044        match c {
1045            'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => result.push(c),
1046            ' ' => result.push('+'),
1047            _ => {
1048                for byte in c.to_string().as_bytes() {
1049                    result.push_str(&format!("%{byte:02X}"));
1050                }
1051            }
1052        }
1053    }
1054    result
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059    use super::*;
1060
1061    #[test]
1062    fn test_request_factory_get() {
1063        let req = RequestFactory::get("/users").build();
1064        assert_eq!(req.method(), Method::Get);
1065        assert_eq!(req.path(), "/users");
1066    }
1067
1068    #[test]
1069    fn test_request_factory_post_json() {
1070        let data = serde_json::json!({"name": "Alice"});
1071        let req = RequestFactory::post("/users").json(&data).build();
1072
1073        assert_eq!(req.method(), Method::Post);
1074        assert!(req.headers().get("content-type").is_some());
1075    }
1076
1077    #[test]
1078    fn test_request_factory_with_auth() {
1079        let req = RequestFactory::get("/protected")
1080            .bearer_token("token123")
1081            .build();
1082
1083        let auth = req.headers().get("authorization").unwrap();
1084        assert!(std::str::from_utf8(auth).unwrap().starts_with("Bearer "));
1085    }
1086
1087    #[test]
1088    fn test_request_factory_query_params() {
1089        let req = RequestFactory::get("/search")
1090            .query_params([("q", "rust"), ("limit", "10")])
1091            .build();
1092
1093        assert_eq!(req.query(), Some("q=rust&limit=10"));
1094    }
1095
1096    #[test]
1097    fn test_response_factory_ok() {
1098        let resp = ResponseFactory::ok().build();
1099        assert_eq!(resp.status(), StatusCode::OK);
1100    }
1101
1102    #[test]
1103    fn test_response_factory_not_found_json() {
1104        let body = serde_json::json!({"error": "Not found"});
1105        let resp = ResponseFactory::not_found().json(&body).build();
1106
1107        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1108    }
1109
1110    #[test]
1111    fn test_auth_factory_tokens() {
1112        let bearer = AuthFactory::bearer_token();
1113        assert!(!bearer.is_empty());
1114
1115        let api_key = AuthFactory::api_key();
1116        assert!(api_key.starts_with("sk_test_"));
1117
1118        let session = AuthFactory::session_id();
1119        assert!(session.starts_with("sess_"));
1120    }
1121
1122    #[test]
1123    fn test_jwt_factory() {
1124        let token = JwtFactory::new().sub("user123").iss("test-issuer").build();
1125
1126        let parts: Vec<_> = token.split('.').collect();
1127        assert_eq!(parts.len(), 3);
1128    }
1129
1130    #[test]
1131    fn test_json_factory_object() {
1132        let json = JsonFactory::object()
1133            .string("name", "Alice")
1134            .number("age", 30)
1135            .bool("active", true)
1136            .build();
1137
1138        let parsed: serde_json::Value = serde_json::from_slice(&json).unwrap();
1139        assert_eq!(parsed["name"], "Alice");
1140        assert_eq!(parsed["age"], 30);
1141        assert_eq!(parsed["active"], true);
1142    }
1143
1144    #[test]
1145    fn test_json_factory_array() {
1146        let json = JsonArrayFactory::new()
1147            .push_number(1)
1148            .push_number(2)
1149            .push_number(3)
1150            .build();
1151
1152        let parsed: Vec<i64> = serde_json::from_slice(&json).unwrap();
1153        assert_eq!(parsed, vec![1, 2, 3]);
1154    }
1155
1156    #[test]
1157    fn test_json_factory_malformed() {
1158        let malformed = JsonFactory::malformed();
1159        assert!(serde_json::from_slice::<serde_json::Value>(&malformed).is_err());
1160    }
1161
1162    #[test]
1163    fn test_user_factory() {
1164        let user = UserFactory::admin().build();
1165        let parsed: serde_json::Value = serde_json::from_slice(&user).unwrap();
1166
1167        assert_eq!(parsed["role"], "admin");
1168        assert_eq!(parsed["active"], true);
1169    }
1170
1171    #[test]
1172    fn test_common_fixtures() {
1173        assert!(CommonFixtures::TEST_EMAIL.contains('@'));
1174        assert!(CommonFixtures::TEST_UUID.contains('-'));
1175    }
1176
1177    #[test]
1178    fn test_base64_encode() {
1179        assert_eq!(base64_encode("hello"), "aGVsbG8=");
1180        assert_eq!(base64_encode("a"), "YQ==");
1181    }
1182}