1use crate::error::{FixtureError, Result};
7use crate::golden::GoldenFile;
8use std::fs;
9use std::path::{Path, PathBuf};
10use tempfile::TempDir;
11
12#[derive(Debug, Clone)]
14pub struct TestFixture {
15 pub name: String,
17 pub path: PathBuf,
19 pub ontology_files: Vec<PathBuf>,
21 pub template_files: Vec<PathBuf>,
23 pub ggen_toml: PathBuf,
25 pub golden_dir: PathBuf,
27}
28
29impl TestFixture {
30 pub fn load(path: impl AsRef<Path>, name: &str) -> Result<Self> {
32 let path = path.as_ref().to_path_buf();
33
34 if !path.exists() {
35 return Err(FixtureError::NotFound(path).into());
36 }
37
38 let ggen_toml = path.join("ggen.toml");
39 if !ggen_toml.exists() {
40 return Err(FixtureError::MissingFile(ggen_toml).into());
41 }
42
43 let ontology_dir = path.join("ontology");
44 let template_dir = path.join("templates");
45
46 let ontology_files = if ontology_dir.exists() {
47 discover_files(&ontology_dir, &["ttl", "rdf"])?
48 } else {
49 Vec::new()
50 };
51
52 let template_files = if template_dir.exists() {
53 discover_files(&template_dir, &["tera", "jinja2"])?
54 } else {
55 Vec::new()
56 };
57
58 let golden_dir = PathBuf::from("tests/e2e/golden").join(name);
60
61 Ok(TestFixture {
62 name: name.to_string(),
63 path,
64 ontology_files,
65 template_files,
66 ggen_toml,
67 golden_dir,
68 })
69 }
70
71 pub fn copy_to_temp(&self) -> Result<TempDir> {
73 let temp_dir = TempDir::new().map_err(|e| FixtureError::CopyFailed(e.to_string()))?;
74
75 copy_dir_recursive(&self.path, temp_dir.path())
76 .map_err(|e| FixtureError::CopyFailed(e.to_string()))?;
77
78 Ok(temp_dir)
79 }
80
81 pub fn golden_files(&self) -> Result<Vec<GoldenFile>> {
83 let mut files = Vec::new();
84
85 if !self.golden_dir.exists() {
86 return Ok(files);
87 }
88
89 discover_golden_files(&self.golden_dir, &self.golden_dir, &mut files)?;
90 Ok(files)
91 }
92
93 pub fn validate(&self) -> Result<()> {
95 if !self.ggen_toml.exists() {
96 return Err(FixtureError::MissingFile(self.ggen_toml.clone()).into());
97 }
98
99 if self.ontology_files.is_empty() {
100 return Err(FixtureError::Configuration(format!(
101 "Fixture '{}' has no ontology files",
102 self.name
103 ))
104 .into());
105 }
106
107 if self.template_files.is_empty() {
108 return Err(FixtureError::Configuration(format!(
109 "Fixture '{}' has no template files",
110 self.name
111 ))
112 .into());
113 }
114
115 Ok(())
116 }
117
118 pub fn manifest_content(&self) -> Result<String> {
120 fs::read_to_string(&self.ggen_toml).map_err(|e| FixtureError::Io(e).into())
121 }
122}
123
124pub fn discover_fixtures(fixtures_dir: &Path) -> Result<Vec<TestFixture>> {
126 let mut fixtures = Vec::new();
127
128 if !fixtures_dir.exists() {
129 return Ok(fixtures);
130 }
131
132 for entry in fs::read_dir(fixtures_dir).map_err(|e| FixtureError::Io(e))? {
133 let entry = entry.map_err(|e| FixtureError::Io(e))?;
134 let path = entry.path();
135
136 if path.is_dir() {
137 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
138 if let Ok(fixture) = TestFixture::load(&path, name) {
139 fixtures.push(fixture);
140 }
141 }
142 }
143 }
144
145 Ok(fixtures)
146}
147
148fn discover_files(dir: &Path, extensions: &[&str]) -> Result<Vec<PathBuf>> {
150 let mut files = Vec::new();
151
152 for entry in fs::read_dir(dir).map_err(|e| FixtureError::Io(e))? {
153 let entry = entry.map_err(|e| FixtureError::Io(e))?;
154 let path = entry.path();
155
156 if path.is_file() {
157 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
158 if extensions.contains(&ext) {
159 files.push(path);
160 }
161 }
162 }
163 }
164
165 Ok(files)
166}
167
168fn discover_golden_files(dir: &Path, base_dir: &Path, files: &mut Vec<GoldenFile>) -> Result<()> {
170 for entry in fs::read_dir(dir).map_err(|e| FixtureError::Io(e))? {
171 let entry = entry.map_err(|e| FixtureError::Io(e))?;
172 let path = entry.path();
173
174 if path.is_file() {
175 if let Ok(relative_path) = path.strip_prefix(base_dir) {
176 if let Ok(golden) = GoldenFile::load(base_dir, relative_path) {
177 files.push(golden);
178 }
179 }
180 } else if path.is_dir() {
181 discover_golden_files(&path, base_dir, files)?;
182 }
183 }
184
185 Ok(())
186}
187
188fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
190 fs::create_dir_all(dst)?;
191
192 for entry in fs::read_dir(src)? {
193 let entry = entry?;
194 let path = entry.path();
195 let file_name = entry.file_name();
196 let dest_path = dst.join(&file_name);
197
198 if path.is_dir() {
199 copy_dir_recursive(&path, &dest_path)?;
200 } else {
201 fs::copy(&path, &dest_path)?;
202 }
203 }
204
205 Ok(())
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_discover_files() {
214 let temp_dir = tempfile::TempDir::new().unwrap();
215 let test_dir = temp_dir.path().join("test");
216 fs::create_dir(&test_dir).unwrap();
217
218 fs::write(test_dir.join("file1.ttl"), "").unwrap();
219 fs::write(test_dir.join("file2.ttl"), "").unwrap();
220 fs::write(test_dir.join("file3.rdf"), "").unwrap();
221 fs::write(test_dir.join("file4.txt"), "").unwrap();
222
223 let ttl_files = discover_files(&test_dir, &["ttl"]).unwrap();
224 assert_eq!(ttl_files.len(), 2);
225
226 let rdf_files = discover_files(&test_dir, &["rdf"]).unwrap();
227 assert_eq!(rdf_files.len(), 1);
228
229 let all_files = discover_files(&test_dir, &["ttl", "rdf"]).unwrap();
230 assert_eq!(all_files.len(), 3);
231 }
232
233 #[test]
234 fn test_copy_dir_recursive() {
235 let temp_dir = tempfile::TempDir::new().unwrap();
236 let src = temp_dir.path().join("src");
237 let dst = temp_dir.path().join("dst");
238
239 fs::create_dir(&src).unwrap();
240 fs::create_dir(src.join("subdir")).unwrap();
241 fs::write(src.join("file1.txt"), "content1").unwrap();
242 fs::write(src.join("subdir/file2.txt"), "content2").unwrap();
243
244 copy_dir_recursive(&src, &dst).unwrap();
245
246 assert!(dst.join("file1.txt").exists());
247 assert!(dst.join("subdir/file2.txt").exists());
248 assert_eq!(
249 fs::read_to_string(dst.join("file1.txt")).unwrap(),
250 "content1"
251 );
252 }
253
254 #[test]
255 fn test_fixture_copy_to_temp() {
256 let temp_dir = tempfile::TempDir::new().unwrap();
257 let fixture_dir = temp_dir.path().join("fixture");
258
259 fs::create_dir(&fixture_dir).unwrap();
260 fs::create_dir(fixture_dir.join("ontology")).unwrap();
261 fs::create_dir(fixture_dir.join("templates")).unwrap();
262 fs::write(fixture_dir.join("ggen.toml"), "[package]").unwrap();
263 fs::write(fixture_dir.join("ontology/schema.ttl"), "").unwrap();
264 fs::write(fixture_dir.join("templates/main.tera"), "").unwrap();
265
266 let fixture = TestFixture::load(&fixture_dir, "test").unwrap();
267 let copy = fixture.copy_to_temp().unwrap();
268
269 assert!(copy.path().join("ggen.toml").exists());
270 assert!(copy.path().join("ontology/schema.ttl").exists());
271 }
272}