use rstest::*;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, thiserror::Error)]
pub enum FixtureError {
#[error("Fixture not found: {0}")]
NotFound(String),
#[error("Load error: {0}")]
Load(String),
#[error("Parse error: {0}")]
Parse(String),
}
pub type FixtureResult<T> = Result<T, FixtureError>;
pub struct FixtureLoader {
fixtures: Arc<RwLock<HashMap<String, serde_json::Value>>>,
}
impl FixtureLoader {
pub fn new() -> Self {
Self {
fixtures: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn load_from_json(&self, name: String, json: &str) -> FixtureResult<()> {
let value: serde_json::Value =
serde_json::from_str(json).map_err(|e| FixtureError::Parse(e.to_string()))?;
self.fixtures.write().await.insert(name, value);
Ok(())
}
pub async fn load<T: for<'de> Deserialize<'de>>(&self, name: &str) -> FixtureResult<T> {
let fixtures = self.fixtures.read().await;
let value = fixtures
.get(name)
.ok_or_else(|| FixtureError::NotFound(name.to_string()))?;
serde_json::from_value(value.clone()).map_err(|e| FixtureError::Parse(e.to_string()))
}
pub async fn get(&self, name: &str) -> FixtureResult<serde_json::Value> {
let fixtures = self.fixtures.read().await;
fixtures
.get(name)
.cloned()
.ok_or_else(|| FixtureError::NotFound(name.to_string()))
}
pub async fn exists(&self, name: &str) -> bool {
self.fixtures.read().await.contains_key(name)
}
pub async fn clear(&self) {
self.fixtures.write().await.clear();
}
pub async fn list(&self) -> Vec<String> {
self.fixtures.read().await.keys().cloned().collect()
}
}
impl Default for FixtureLoader {
fn default() -> Self {
Self::new()
}
}
pub trait Factory<T>: Send + Sync {
fn build(&self) -> T;
fn build_batch(&self, count: usize) -> Vec<T> {
(0..count).map(|_| self.build()).collect()
}
}
pub struct FactoryBuilder<T, F>
where
F: Fn() -> T + Send + Sync,
{
builder: F,
_phantom: std::marker::PhantomData<T>,
}
impl<T, F> FactoryBuilder<T, F>
where
F: Fn() -> T + Send + Sync,
{
pub fn new(builder: F) -> Self {
Self {
builder,
_phantom: std::marker::PhantomData,
}
}
}
impl<T, F> Factory<T> for FactoryBuilder<T, F>
where
F: Fn() -> T + Send + Sync,
T: Send + Sync,
{
fn build(&self) -> T {
(self.builder)()
}
}
pub fn random_test_key() -> String {
use uuid::Uuid;
format!("test_key_{}", Uuid::now_v7().simple())
}
pub fn test_config_value(value: &str) -> serde_json::Value {
serde_json::json!({
"value": value,
"timestamp": chrono::Utc::now().to_rfc3339(),
})
}
#[fixture]
pub fn fixture_loader() -> FixtureLoader {
FixtureLoader::new()
}
#[fixture]
pub fn api_client() -> crate::client::APIClient {
crate::client::APIClient::new()
}
#[fixture]
pub fn temp_dir() -> tempfile::TempDir {
tempfile::tempdir().expect("Failed to create temporary directory")
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Serialize;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct TestData {
id: i32,
name: String,
}
#[tokio::test]
async fn test_fixture_loader() {
let loader = FixtureLoader::new();
let json = r#"{"id": 1, "name": "Test"}"#;
loader
.load_from_json("test".to_string(), json)
.await
.unwrap();
let data: TestData = loader.load("test").await.unwrap();
assert_eq!(data.id, 1);
assert_eq!(data.name, "Test");
}
#[tokio::test]
async fn test_fixture_not_found() {
let loader = FixtureLoader::new();
let result: FixtureResult<TestData> = loader.load("missing").await;
assert!(result.is_err());
}
#[test]
fn test_factory_builder() {
let factory = FactoryBuilder::new(|| TestData {
id: 1,
name: "Test".to_string(),
});
let data = factory.build();
assert_eq!(data.id, 1);
let batch = factory.build_batch(3);
assert_eq!(batch.len(), 3);
}
}