mockforge_http/
replay_listing.rs

1//! Record/replay listing for HTTP/gRPC/WS fixtures.
2use globwalk::GlobWalkerBuilder;
3use serde::Serialize;
4
5/// A single replay fixture item
6#[derive(Debug, Serialize)]
7pub struct ReplayItem {
8    /// Protocol name (http, grpc, ws)
9    pub protocol: String,
10    /// OpenAPI operation ID
11    pub operation_id: String,
12    /// Timestamp when the fixture was saved
13    pub saved_at: String,
14    /// File system path to the fixture file
15    pub path: String,
16}
17
18/// List all replay fixtures from the fixtures root directory
19///
20/// # Arguments
21/// * `fixtures_root` - Root directory containing protocol subdirectories (http, grpc, ws)
22///
23/// # Returns
24/// Vector of replay items sorted by timestamp (newest first)
25pub fn list_all(fixtures_root: &str) -> anyhow::Result<Vec<ReplayItem>> {
26    use std::path::Path;
27
28    let fixtures_path = Path::new(fixtures_root);
29    let mut out = Vec::new();
30
31    for proto in ["http", "grpc", "ws"] {
32        // Use the fixtures_root as the base directory for globbing
33        let proto_pattern = format!("{proto}/**/*.json");
34        for entry in GlobWalkerBuilder::from_patterns(fixtures_path, &[&proto_pattern]).build()? {
35            let p = entry?.path().to_path_buf();
36            if p.extension().map(|e| e == "json").unwrap_or(false) {
37                // Get relative path from fixtures_root for consistent path handling
38                let relative_path = p.strip_prefix(fixtures_path).unwrap_or(&p).to_path_buf();
39
40                let comps: Vec<_> = relative_path
41                    .components()
42                    .map(|c| c.as_os_str().to_string_lossy().to_string())
43                    .collect();
44                let len = comps.len();
45                let (op_id, ts) = if len >= 2 {
46                    (comps[len - 2].clone(), comps[len - 1].replace(".json", ""))
47                } else {
48                    ("unknown".into(), "unknown".into())
49                };
50                out.push(ReplayItem {
51                    protocol: proto.to_string(),
52                    operation_id: op_id,
53                    saved_at: ts,
54                    path: p.to_string_lossy().to_string(),
55                });
56            }
57        }
58    }
59    out.sort_by(|a, b| b.saved_at.cmp(&a.saved_at));
60    Ok(out)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use std::fs;
67    use std::path::Path;
68    use tempfile::TempDir;
69
70    fn create_fixture_file(
71        dir: &Path,
72        protocol: &str,
73        op_id: &str,
74        timestamp: &str,
75    ) -> std::io::Result<()> {
76        let path = dir.join(protocol).join(op_id);
77        fs::create_dir_all(&path)?;
78        let file_path = path.join(format!("{}.json", timestamp));
79        fs::write(file_path, "{}")?;
80        Ok(())
81    }
82
83    #[test]
84    fn test_replay_item_structure() {
85        let item = ReplayItem {
86            protocol: "http".to_string(),
87            operation_id: "getUser".to_string(),
88            saved_at: "2024-01-01T12:00:00".to_string(),
89            path: "/fixtures/http/getUser/2024-01-01T12:00:00.json".to_string(),
90        };
91
92        assert_eq!(item.protocol, "http");
93        assert_eq!(item.operation_id, "getUser");
94        assert_eq!(item.saved_at, "2024-01-01T12:00:00");
95    }
96
97    #[test]
98    fn test_list_all_empty_directory() {
99        let temp_dir = TempDir::new().unwrap();
100        let fixtures_root = temp_dir.path().to_str().unwrap();
101
102        let result = list_all(fixtures_root);
103        assert!(result.is_ok());
104        let items = result.unwrap();
105        assert_eq!(items.len(), 0);
106    }
107
108    #[test]
109    fn test_list_all_with_http_fixtures() {
110        let temp_dir = TempDir::new().unwrap();
111
112        // Create some HTTP fixtures
113        let temp_path = temp_dir.path().to_path_buf();
114        create_fixture_file(&temp_path, "http", "getUser", "2024-01-01T12:00:00").unwrap();
115        create_fixture_file(&temp_path, "http", "getUser", "2024-01-02T12:00:00").unwrap();
116        create_fixture_file(&temp_path, "http", "createUser", "2024-01-03T12:00:00").unwrap();
117
118        let fixtures_root = temp_path.to_str().unwrap();
119        let result = list_all(fixtures_root);
120
121        assert!(result.is_ok());
122        let items = result.unwrap();
123
124        assert_eq!(items.len(), 3);
125        assert!(items.iter().all(|item| item.protocol == "http"));
126    }
127
128    #[test]
129    fn test_list_all_with_multiple_protocols() {
130        let temp_dir = TempDir::new().unwrap();
131
132        // Create fixtures for different protocols
133        let temp_path = temp_dir.path().to_path_buf();
134        create_fixture_file(&temp_path, "http", "getUser", "2024-01-01T12:00:00").unwrap();
135        create_fixture_file(&temp_path, "grpc", "GetUser", "2024-01-02T12:00:00").unwrap();
136        create_fixture_file(&temp_path, "ws", "subscribe", "2024-01-03T12:00:00").unwrap();
137
138        let fixtures_root = temp_path.to_str().unwrap();
139        let result = list_all(fixtures_root);
140
141        assert!(result.is_ok());
142        let items = result.unwrap();
143
144        assert_eq!(items.len(), 3);
145
146        let protocols: Vec<&str> = items.iter().map(|i| i.protocol.as_str()).collect();
147        assert!(protocols.contains(&"http"));
148        assert!(protocols.contains(&"grpc"));
149        assert!(protocols.contains(&"ws"));
150    }
151
152    #[test]
153    fn test_list_all_sorted_by_timestamp() {
154        let temp_dir = TempDir::new().unwrap();
155
156        // Create fixtures with different timestamps
157        let temp_path = temp_dir.path().to_path_buf();
158        create_fixture_file(&temp_path, "http", "op1", "2024-01-01T12:00:00").unwrap();
159        create_fixture_file(&temp_path, "http", "op2", "2024-01-03T12:00:00").unwrap();
160        create_fixture_file(&temp_path, "http", "op3", "2024-01-02T12:00:00").unwrap();
161
162        let fixtures_root = temp_path.to_str().unwrap();
163        let result = list_all(fixtures_root);
164
165        assert!(result.is_ok());
166        let items = result.unwrap();
167
168        assert_eq!(items.len(), 3);
169        // Should be sorted by timestamp descending
170        assert!(items[0].saved_at >= items[1].saved_at);
171        assert!(items[1].saved_at >= items[2].saved_at);
172    }
173
174    #[test]
175    fn test_list_all_ignores_non_json_files() {
176        let temp_dir = TempDir::new().unwrap();
177
178        // Create JSON and non-JSON files
179        let temp_path = temp_dir.path().to_path_buf();
180        create_fixture_file(&temp_path, "http", "getUser", "2024-01-01T12:00:00").unwrap();
181
182        let txt_path = temp_path.join("http").join("getUser");
183        fs::create_dir_all(&txt_path).unwrap();
184        fs::write(txt_path.join("data.txt"), "not json").unwrap();
185
186        let fixtures_root = temp_path.to_str().unwrap();
187        let result = list_all(fixtures_root);
188
189        assert!(result.is_ok());
190        let items = result.unwrap();
191
192        // Should only find the .json file
193        assert_eq!(items.len(), 1);
194        assert!(items[0].path.ends_with(".json"));
195    }
196
197    #[test]
198    fn test_list_all_extracts_operation_id() {
199        let temp_dir = TempDir::new().unwrap();
200
201        let temp_path = temp_dir.path().to_path_buf();
202        create_fixture_file(&temp_path, "http", "getUserById", "2024-01-01T12:00:00").unwrap();
203
204        let fixtures_root = temp_path.to_str().unwrap();
205        let result = list_all(fixtures_root);
206
207        assert!(result.is_ok());
208        let items = result.unwrap();
209
210        assert_eq!(items.len(), 1);
211        assert_eq!(items[0].operation_id, "getUserById");
212    }
213
214    #[test]
215    fn test_list_all_extracts_timestamp_without_extension() {
216        let temp_dir = TempDir::new().unwrap();
217
218        let temp_path = temp_dir.path().to_path_buf();
219        create_fixture_file(&temp_path, "http", "getUser", "2024-01-01T12:00:00").unwrap();
220
221        let fixtures_root = temp_path.to_str().unwrap();
222        let result = list_all(fixtures_root);
223
224        assert!(result.is_ok());
225        let items = result.unwrap();
226
227        assert_eq!(items.len(), 1);
228        assert_eq!(items[0].saved_at, "2024-01-01T12:00:00");
229        assert!(!items[0].saved_at.contains(".json"));
230    }
231}