Skip to main content

mockforge_test/
fixtures.rs

1//! Shared test fixture builders for MockForge crates.
2//!
3//! Provides reusable utilities for constructing test data that would
4//! otherwise be duplicated across every crate's test modules.
5
6use serde_json::{json, Value};
7use std::path::{Path, PathBuf};
8
9/// Create a minimal valid OpenAPI 3.0 spec as a JSON value.
10///
11/// Useful when you need a valid spec but don't care about specific
12/// paths or operations.
13pub fn minimal_openapi_spec() -> Value {
14    json!({
15        "openapi": "3.0.0",
16        "info": {
17            "title": "Test API",
18            "version": "1.0.0"
19        },
20        "paths": {}
21    })
22}
23
24/// Create an OpenAPI spec with a single GET endpoint that returns JSON.
25///
26/// # Arguments
27/// * `path` — The API path (e.g., `"/users"`)
28/// * `status` — The HTTP status code for the response (e.g., `200`)
29pub fn openapi_spec_with_get(path: &str, status: u16) -> Value {
30    json!({
31        "openapi": "3.0.0",
32        "info": {
33            "title": "Test API",
34            "version": "1.0.0"
35        },
36        "paths": {
37            path: {
38                "get": {
39                    "operationId": format!("get_{}", path.trim_start_matches('/')),
40                    "responses": {
41                        status.to_string(): {
42                            "description": "Success",
43                            "content": {
44                                "application/json": {
45                                    "schema": {
46                                        "type": "object"
47                                    }
48                                }
49                            }
50                        }
51                    }
52                }
53            }
54        }
55    })
56}
57
58/// Create an OpenAPI spec with CRUD operations (GET, POST, PUT, DELETE) for a resource.
59///
60/// # Arguments
61/// * `resource` — The resource name (e.g., `"users"`)
62pub fn openapi_spec_crud(resource: &str) -> Value {
63    let collection_path = format!("/{resource}");
64    let item_path = format!("/{resource}/{{id}}");
65
66    json!({
67        "openapi": "3.0.0",
68        "info": {
69            "title": format!("{} API", capitalize(resource)),
70            "version": "1.0.0"
71        },
72        "paths": {
73            collection_path: {
74                "get": {
75                    "operationId": format!("list_{resource}"),
76                    "responses": {
77                        "200": {
78                            "description": "List all",
79                            "content": {
80                                "application/json": {
81                                    "schema": {
82                                        "type": "array",
83                                        "items": { "type": "object" }
84                                    }
85                                }
86                            }
87                        }
88                    }
89                },
90                "post": {
91                    "operationId": format!("create_{resource}"),
92                    "requestBody": {
93                        "content": {
94                            "application/json": {
95                                "schema": { "type": "object" }
96                            }
97                        }
98                    },
99                    "responses": {
100                        "201": {
101                            "description": "Created",
102                            "content": {
103                                "application/json": {
104                                    "schema": { "type": "object" }
105                                }
106                            }
107                        }
108                    }
109                }
110            },
111            item_path: {
112                "get": {
113                    "operationId": format!("get_{resource}"),
114                    "parameters": [{
115                        "name": "id",
116                        "in": "path",
117                        "required": true,
118                        "schema": { "type": "string" }
119                    }],
120                    "responses": {
121                        "200": {
122                            "description": "Found",
123                            "content": {
124                                "application/json": {
125                                    "schema": { "type": "object" }
126                                }
127                            }
128                        }
129                    }
130                },
131                "put": {
132                    "operationId": format!("update_{resource}"),
133                    "parameters": [{
134                        "name": "id",
135                        "in": "path",
136                        "required": true,
137                        "schema": { "type": "string" }
138                    }],
139                    "requestBody": {
140                        "content": {
141                            "application/json": {
142                                "schema": { "type": "object" }
143                            }
144                        }
145                    },
146                    "responses": {
147                        "200": {
148                            "description": "Updated",
149                            "content": {
150                                "application/json": {
151                                    "schema": { "type": "object" }
152                                }
153                            }
154                        }
155                    }
156                },
157                "delete": {
158                    "operationId": format!("delete_{resource}"),
159                    "parameters": [{
160                        "name": "id",
161                        "in": "path",
162                        "required": true,
163                        "schema": { "type": "string" }
164                    }],
165                    "responses": {
166                        "204": {
167                            "description": "Deleted"
168                        }
169                    }
170                }
171            }
172        }
173    })
174}
175
176/// Write a JSON value as an OpenAPI spec file to a temporary directory.
177///
178/// Returns `(TempDir, PathBuf)` — keep `TempDir` alive for the test duration.
179///
180/// # Panics
181/// Panics if the temporary directory or file cannot be created.
182pub fn write_temp_spec(spec: &Value) -> (tempfile::TempDir, PathBuf) {
183    let temp_dir = tempfile::TempDir::new().expect("create temp dir for test spec");
184    let spec_path = temp_dir.path().join("spec.json");
185    std::fs::write(&spec_path, serde_json::to_string_pretty(spec).expect("serialize spec"))
186        .expect("write spec file");
187    (temp_dir, spec_path)
188}
189
190/// Write a JSON value as an OpenAPI spec file to a specific path.
191///
192/// # Panics
193/// Panics if the file cannot be written.
194pub fn write_spec_to(spec: &Value, path: &Path) {
195    std::fs::write(path, serde_json::to_string_pretty(spec).expect("serialize spec"))
196        .expect("write spec file");
197}
198
199/// Resolve a fixture path relative to the calling crate's `CARGO_MANIFEST_DIR`.
200///
201/// Typically used as: `fixture_path(env!("CARGO_MANIFEST_DIR"), "tests/fixtures/my_spec.json")`
202pub fn fixture_path(manifest_dir: &str, relative: &str) -> PathBuf {
203    PathBuf::from(manifest_dir).join(relative)
204}
205
206fn capitalize(s: &str) -> String {
207    let mut chars = s.chars();
208    match chars.next() {
209        None => String::new(),
210        Some(c) => c.to_uppercase().chain(chars).collect(),
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_minimal_spec_is_valid() {
220        let spec = minimal_openapi_spec();
221        assert_eq!(spec["openapi"], "3.0.0");
222        assert!(spec["paths"].is_object());
223    }
224
225    #[test]
226    fn test_spec_with_get() {
227        let spec = openapi_spec_with_get("/users", 200);
228        assert!(spec["paths"]["/users"]["get"].is_object());
229        assert!(spec["paths"]["/users"]["get"]["responses"]["200"].is_object());
230    }
231
232    #[test]
233    fn test_crud_spec() {
234        let spec = openapi_spec_crud("users");
235        assert!(spec["paths"]["/users"]["get"].is_object());
236        assert!(spec["paths"]["/users"]["post"].is_object());
237        assert!(spec["paths"]["/users/{id}"]["get"].is_object());
238        assert!(spec["paths"]["/users/{id}"]["put"].is_object());
239        assert!(spec["paths"]["/users/{id}"]["delete"].is_object());
240    }
241
242    #[test]
243    fn test_write_temp_spec() {
244        let spec = minimal_openapi_spec();
245        let (_dir, path) = write_temp_spec(&spec);
246        assert!(path.exists());
247        let content = std::fs::read_to_string(&path).unwrap();
248        let parsed: Value = serde_json::from_str(&content).unwrap();
249        assert_eq!(parsed["openapi"], "3.0.0");
250    }
251}