use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct TestFixture {
pub name: String,
pub content: Vec<u8>,
pub expected: Option<Vec<u8>>,
pub metadata: HashMap<String, String>,
}
impl TestFixture {
#[must_use]
pub fn new(name: impl Into<String>, content: impl Into<Vec<u8>>) -> Self {
Self {
name: name.into(),
content: content.into(),
expected: None,
metadata: HashMap::new(),
}
}
#[must_use]
pub fn from_str(name: impl Into<String>, content: &str) -> Self {
Self::new(name, content.as_bytes().to_vec())
}
#[must_use]
pub fn with_expected(mut self, expected: impl Into<Vec<u8>>) -> Self {
self.expected = Some(expected.into());
self
}
#[must_use]
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
#[must_use]
pub fn content_str(&self) -> &str {
std::str::from_utf8(&self.content).unwrap_or("<invalid utf8>")
}
#[must_use]
pub fn expected_str(&self) -> Option<&str> {
self.expected
.as_ref()
.and_then(|e| std::str::from_utf8(e).ok())
}
}
#[derive(Debug, Default)]
pub struct Fixtures {
fixtures: HashMap<String, TestFixture>,
base_path: Option<PathBuf>,
}
impl Fixtures {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_base_path(mut self, path: impl Into<PathBuf>) -> Self {
self.base_path = Some(path.into());
self
}
pub fn add(&mut self, fixture: TestFixture) {
self.fixtures.insert(fixture.name.clone(), fixture);
}
pub fn add_inline(&mut self, name: impl Into<String>, content: &str) {
self.add(TestFixture::from_str(name, content));
}
pub fn load(&mut self, name: impl Into<String>, filename: &str) -> std::io::Result<()> {
let name = name.into();
let path = if let Some(base) = &self.base_path {
base.join(filename)
} else {
PathBuf::from(filename)
};
let content = std::fs::read(&path)?;
self.add(TestFixture::new(name, content));
Ok(())
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&TestFixture> {
self.fixtures.get(name)
}
#[must_use]
pub fn content(&self, name: &str) -> Option<&str> {
self.get(name).map(TestFixture::content_str)
}
#[must_use]
pub fn bytes(&self, name: &str) -> Option<&[u8]> {
self.get(name).map(|f| f.content.as_slice())
}
#[must_use]
pub fn names(&self) -> Vec<&str> {
self.fixtures.keys().map(String::as_str).collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.fixtures.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.fixtures.is_empty()
}
#[must_use]
pub fn terminal_fixtures() -> Self {
let mut fixtures = Self::new();
fixtures.add_inline("login_prompt", "Login: ");
fixtures.add_inline("password_prompt", "Password: ");
fixtures.add_inline("shell_prompt", "$ ");
fixtures.add_inline("bash_prompt", "[user@host ~]$ ");
fixtures.add_inline("root_prompt", "# ");
fixtures.add_inline("confirm_prompt", "Are you sure? [y/N] ");
fixtures.add_inline(
"progress_output",
"Processing... 10%\nProcessing... 50%\nProcessing... 100%\nDone.",
);
fixtures.add_inline(
"error_output",
"Error: Something went wrong\nPlease try again.",
);
fixtures.add(TestFixture::new(
"ansi_output",
b"\x1b[31mRed\x1b[0m \x1b[32mGreen\x1b[0m \x1b[34mBlue\x1b[0m".to_vec(),
));
fixtures.add_inline("multiline", "Line 1\nLine 2\nLine 3\nLine 4\nLine 5");
fixtures
}
}
#[must_use]
pub fn find_fixtures_dir() -> Option<PathBuf> {
let mut path = std::env::current_dir().ok()?;
for _ in 0..5 {
let fixtures = path.join("fixtures");
if fixtures.is_dir() {
return Some(fixtures);
}
let test_fixtures = path.join("tests").join("fixtures");
if test_fixtures.is_dir() {
return Some(test_fixtures);
}
path = path.parent()?.to_path_buf();
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fixture_creation() {
let fixture = TestFixture::from_str("test", "Hello, World!")
.with_expected(b"Expected".to_vec())
.with_metadata("type", "greeting");
assert_eq!(fixture.name, "test");
assert_eq!(fixture.content_str(), "Hello, World!");
assert_eq!(fixture.expected_str(), Some("Expected"));
assert_eq!(fixture.metadata.get("type"), Some(&"greeting".to_string()));
}
#[test]
fn test_fixtures_collection() {
let mut fixtures = Fixtures::new();
fixtures.add_inline("prompt", "$ ");
fixtures.add_inline("greeting", "Hello!");
assert_eq!(fixtures.len(), 2);
assert_eq!(fixtures.content("prompt"), Some("$ "));
assert!(fixtures.names().contains(&"greeting"));
}
#[test]
fn test_terminal_fixtures() {
let fixtures = Fixtures::terminal_fixtures();
assert!(fixtures.get("login_prompt").is_some());
assert!(fixtures.get("shell_prompt").is_some());
assert!(fixtures.get("ansi_output").is_some());
}
#[test]
fn test_find_fixtures_dir() {
let result = find_fixtures_dir();
if let Some(path) = result {
assert!(path.is_dir(), "found path should be a directory");
}
}
}