use std::collections::HashMap;
pub trait Fixture: Send + Sync + 'static {
type Output;
fn generate(&self) -> Self::Output;
fn generate_many(&self, count: usize) -> Vec<Self::Output> {
(0..count).map(|_| self.generate()).collect()
}
}
#[derive(Debug)]
pub struct StringFixture {
prefix: String,
counter: std::sync::atomic::AtomicUsize,
}
impl StringFixture {
pub fn new(prefix: impl Into<String>) -> Self {
Self {
prefix: prefix.into(),
counter: std::sync::atomic::AtomicUsize::new(0),
}
}
}
impl Default for StringFixture {
fn default() -> Self {
Self::new("test")
}
}
impl Fixture for StringFixture {
type Output = String;
fn generate(&self) -> Self::Output {
let count = self.counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
format!("{}_{}", self.prefix, count)
}
}
#[derive(Debug, Clone)]
pub struct UuidFixture;
impl Fixture for UuidFixture {
type Output = String;
fn generate(&self) -> Self::Output {
uuid::Uuid::new_v4().to_string()
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct IntFixture {
min: i64,
max: i64,
counter: std::sync::atomic::AtomicI64,
}
impl IntFixture {
pub fn new(min: i64, max: i64) -> Self {
Self {
min,
max,
counter: std::sync::atomic::AtomicI64::new(min),
}
}
pub fn sequential(start: i64) -> Self {
Self {
min: start,
max: i64::MAX,
counter: std::sync::atomic::AtomicI64::new(start),
}
}
}
impl Default for IntFixture {
fn default() -> Self {
Self::new(0, 1000)
}
}
impl Fixture for IntFixture {
type Output = i64;
fn generate(&self) -> Self::Output {
self.counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
}
}
#[derive(Debug)]
pub struct IdFixture {
prefix: String,
counter: std::sync::atomic::AtomicUsize,
}
impl IdFixture {
pub fn new(prefix: impl Into<String>) -> Self {
Self {
prefix: prefix.into(),
counter: std::sync::atomic::AtomicUsize::new(1),
}
}
}
impl Default for IdFixture {
fn default() -> Self {
Self::new("id")
}
}
impl Fixture for IdFixture {
type Output = String;
fn generate(&self) -> Self::Output {
let count = self.counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
format!("{}-{}", self.prefix, count)
}
}
#[derive(Debug, Clone)]
pub struct TimestampFixture;
impl Fixture for TimestampFixture {
type Output = chrono::DateTime<chrono::Utc>;
fn generate(&self) -> Self::Output {
chrono::Utc::now()
}
}
#[derive(Debug, Clone)]
pub struct HeadersFixture {
default_headers: HashMap<String, String>,
}
impl HeadersFixture {
pub fn new() -> Self {
Self {
default_headers: HashMap::new(),
}
}
pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.default_headers.insert(key.into(), value.into());
self
}
}
impl Default for HeadersFixture {
fn default() -> Self {
Self::new()
}
}
impl Fixture for HeadersFixture {
type Output = HashMap<String, String>;
fn generate(&self) -> Self::Output {
self.default_headers.clone()
}
}
#[derive(Debug)]
pub struct UserFixture {
id_fixture: IdFixture,
name_fixture: StringFixture,
email_fixture: StringFixture,
}
impl UserFixture {
pub fn new() -> Self {
Self {
id_fixture: IdFixture::new("user"),
name_fixture: StringFixture::new("User"),
email_fixture: StringFixture::new("user"),
}
}
}
impl Default for UserFixture {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TestUser {
pub id: String,
pub name: String,
pub email: String,
}
impl Fixture for UserFixture {
type Output = TestUser;
fn generate(&self) -> Self::Output {
TestUser {
id: self.id_fixture.generate(),
name: self.name_fixture.generate(),
email: format!("{}@example.com", self.email_fixture.generate()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_fixture() {
let fixture = StringFixture::new("test");
assert_eq!(fixture.generate(), "test_0");
assert_eq!(fixture.generate(), "test_1");
assert_eq!(fixture.generate(), "test_2");
}
#[test]
fn test_uuid_fixture() {
let fixture = UuidFixture;
let uuid1 = fixture.generate();
let uuid2 = fixture.generate();
assert_ne!(uuid1, uuid2);
assert!(uuid::Uuid::parse_str(&uuid1).is_ok());
}
#[test]
fn test_int_fixture_sequential() {
let fixture = IntFixture::sequential(1);
assert_eq!(fixture.generate(), 1);
assert_eq!(fixture.generate(), 2);
assert_eq!(fixture.generate(), 3);
}
#[test]
fn test_id_fixture() {
let fixture = IdFixture::new("user");
assert_eq!(fixture.generate(), "user-1");
assert_eq!(fixture.generate(), "user-2");
}
#[test]
fn test_headers_fixture() {
let fixture = HeadersFixture::new().with_header("Content-Type", "application/json");
let headers = fixture.generate();
assert_eq!(headers.get("Content-Type"), Some(&"application/json".to_string()));
}
#[test]
fn test_user_fixture() {
let fixture = UserFixture::new();
let user = fixture.generate();
assert!(user.id.starts_with("user-"));
assert!(user.name.starts_with("User"));
assert!(user.email.ends_with("@example.com"));
}
#[test]
fn test_fixture_generate_many() {
let fixture = IntFixture::sequential(1);
let values: Vec<i64> = fixture.generate_many(5);
assert_eq!(values, vec![1, 2, 3, 4, 5]);
}
}