ferrotype 0.1.3

An opinionated wrapper for insta.rs
Documentation
//! Real-world testing example using ferrotype
//!
//! This example demonstrates how to use ferrotype in actual tests,
//! simulating a JSON API client that we want to test comprehensively.
//!
//! Run with: cargo test --example testing

use {ferrotype::Ferrotype, std::collections::HashMap};

// Simulated API types
#[derive(Debug, Clone)]
#[allow(unused)]
struct User {
  id: String,
  username: String,
  email: String,
  created_at: String,
  metadata: HashMap<String, String>,
}

#[derive(Debug)]
#[allow(unused)]
struct ApiResponse<T> {
  status: String,
  data: Option<T>,
  error: Option<String>,
  request_id: String,
  timestamp: String,
}

// Simulated API client
struct ApiClient {
  base_url: String,
  session_id: String,
}

impl ApiClient {
  fn new(base_url: &str) -> Self {
    Self {
      base_url: base_url.to_string(),
      session_id: uuid::Uuid::new_v4().to_string(),
    }
  }

  fn get_user(&self, user_id: &str) -> ApiResponse<User> {
    // Simulate different responses based on user_id
    match user_id {
      | "valid_user" => ApiResponse {
        status: "success".to_string(),
        data: Some(User {
          id: user_id.to_string(),
          username: "john_doe".to_string(),
          email: "john@example.com".to_string(),
          created_at: "2024-01-15T10:30:00Z".to_string(),
          metadata: {
            let mut map = HashMap::new();
            map.insert("role".to_string(), "admin".to_string());
            map.insert("verified".to_string(), "true".to_string());
            map
          },
        }),
        error: None,
        request_id: uuid::Uuid::new_v4().to_string(),
        timestamp: chrono::Utc::now().to_rfc3339(),
      },
      | "not_found" => ApiResponse {
        status: "error".to_string(),
        data: None,
        error: Some("User not found".to_string()),
        request_id: uuid::Uuid::new_v4().to_string(),
        timestamp: chrono::Utc::now().to_rfc3339(),
      },
      | _ => ApiResponse {
        status: "error".to_string(),
        data: None,
        error: Some("Invalid user ID format".to_string()),
        request_id: uuid::Uuid::new_v4().to_string(),
        timestamp: chrono::Utc::now().to_rfc3339(),
      },
    }
  }

  fn create_user(&self, username: &str, email: &str) -> ApiResponse<User> {
    if username.is_empty() || email.is_empty() {
      return ApiResponse {
        status: "error".to_string(),
        data: None,
        error: Some("Username and email are required".to_string()),
        request_id: uuid::Uuid::new_v4().to_string(),
        timestamp: chrono::Utc::now().to_rfc3339(),
      };
    }

    ApiResponse {
      status: "success".to_string(),
      data: Some(User {
        id: uuid::Uuid::new_v4().to_string(),
        username: username.to_string(),
        email: email.to_string(),
        created_at: chrono::Utc::now().to_rfc3339(),
        metadata: HashMap::new(),
      }),
      error: None,
      request_id: uuid::Uuid::new_v4().to_string(),
      timestamp: chrono::Utc::now().to_rfc3339(),
    }
  }
}

// Example test scenarios
fn test_get_user_success() {
  let mut snapshot = Ferrotype::new();

  // Set up test context
  let client = ApiClient::new("https://api.example.com");
  snapshot.add("Test Case", "Get existing user".to_string());
  snapshot.add("Client Session", client.session_id.clone());

  // Execute the API call
  let response = client.get_user("valid_user");

  // Add the response to snapshot
  snapshot.add("Response Status", response.status.clone());
  snapshot.add_debug("Response Data", &response.data);

  // Verify the response
  assert_eq!(response.status, "success");
  assert!(response.data.is_some());
  assert!(response.error.is_none());

  let user = response.data.unwrap();
  assert_eq!(user.username, "john_doe");
  assert_eq!(user.email, "john@example.com");

  // Add verification results
  snapshot.add("Verification", "All assertions passed".to_string());

  // In a real test:
  // ferrotype::assert!(snapshot);
  snapshot.print();
}

fn test_get_user_not_found() {
  let mut snapshot = Ferrotype::new();

  let client = ApiClient::new("https://api.example.com");
  snapshot.add("Test Case", "Get non-existent user".to_string());

  let response = client.get_user("not_found");

  snapshot.add("Response Status", response.status.clone());
  snapshot.add("Error Message", response.error.clone().unwrap_or_default());
  snapshot.add_debug("Full Response", &response);

  assert_eq!(response.status, "error");
  assert!(response.data.is_none());
  assert_eq!(response.error.as_deref(), Some("User not found"));

  // ferrotype::assert!(snapshot);
  snapshot.print();
}

fn test_create_user_with_filtering() {
  let mut snapshot = Ferrotype::new();

  // For creation tests, we want to filter out random IDs
  // so our snapshots are reproducible
  snapshot.set_filter_random_ids(true);

  let client = ApiClient::new("https://api.example.com");
  snapshot.add("Test Case", "Create new user".to_string());
  snapshot.add(
    "Client Info",
    format!("Session: {}, URL: {}", client.session_id, client.base_url),
  );

  // Test input
  let username = "new_user";
  let email = "new@example.com";
  snapshot.add_debug("Input", (username, email));

  // Execute
  let response = client.create_user(username, email);

  // Add full response (IDs will be filtered)
  snapshot.add_debug("Response", &response);

  // Verify
  assert_eq!(response.status, "success");
  assert!(response.data.is_some());

  let user = response.data.unwrap();
  assert_eq!(user.username, username);
  assert_eq!(user.email, email);
  assert!(!user.id.is_empty());

  // ferrotype::assert!(snapshot);
  snapshot.print();
}

fn test_error_conditions() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_filter_uuids(true); // Only filter UUIDs, keep other info

  let client = ApiClient::new("https://api.example.com");
  snapshot.add("Test Case", "Error condition handling".to_string());

  // Test multiple error conditions
  let test_cases = vec![
    ("", "test@example.com", "Empty username"),
    ("test_user", "", "Empty email"),
    ("", "", "Both fields empty"),
  ];

  for (username, email, description) in test_cases {
    snapshot.add("Scenario", description.to_string());

    let response = client.create_user(username, email);

    snapshot.add_debug("Input", (username, email));
    snapshot.add("Status", response.status.clone());
    snapshot.add("Error", response.error.clone().unwrap_or_default());

    assert_eq!(response.status, "error");
    assert!(response.error.is_some());
  }

  // ferrotype::assert!(snapshot);
  snapshot.print();
}

fn test_complex_response_with_metadata() {
  let mut snapshot = Ferrotype::new();

  // For complex data, we might want selective filtering
  snapshot.set_filter_memory_addresses(true);
  snapshot.set_filter_uuids(true);
  snapshot.set_filter_type_ids(false); // Keep type info for debugging
  snapshot.set_filter_hashes(false); // Keep hash info

  let client = ApiClient::new("https://api.example.com");
  let response = client.get_user("valid_user");

  snapshot.add("Test", "Complex response with metadata".to_string());

  // Add type information
  snapshot.add(
    "Response Type",
    format!("{:?}", std::any::type_name::<ApiResponse<User>>()),
  );

  // Add structured view
  if let Some(user) = &response.data {
    snapshot.add("User ID", user.id.clone());
    snapshot.add("Username", user.username.clone());
    snapshot.add_debug("Metadata", &user.metadata);

    // Add computed properties
    let metadata_count = user.metadata.len();
    let is_verified = user
      .metadata
      .get("verified")
      .map(|v| v == "true")
      .unwrap_or(false);

    snapshot.add_debug(
      "Computed Properties",
      (
        ("metadata_count", metadata_count),
        ("is_verified", is_verified),
        ("has_role", user.metadata.contains_key("role")),
      ),
    );
  }

  // Add memory layout info (will be filtered)
  snapshot.add(
    "Memory Info",
    format!(
      "Response at {:p}, size: {}",
      &response,
      std::mem::size_of_val(&response)
    ),
  );

  // ferrotype::assert!(snapshot);
  snapshot.print();
}

// Run the tests as examples
fn main() {
  println!("Running test examples...\n");

  println!("=== Test 1: Get User Success ===");
  test_get_user_success();
  println!("\n");

  println!("=== Test 2: Get User Not Found ===");
  test_get_user_not_found();
  println!("\n");

  println!("=== Test 3: Create User with Filtering ===");
  test_create_user_with_filtering();
  println!("\n");

  println!("=== Test 4: Error Conditions ===");
  test_error_conditions();
  println!("\n");

  println!("=== Test 5: Complex Response ===");
  test_complex_response_with_metadata();
  println!("\n");

  println!("In real tests, these would use ferrotype::assert!(snapshot)");
  println!("to compare against saved snapshots.");
}

// Mock uuid module for the example
mod uuid {
  pub struct Uuid;
  impl Uuid {
    pub fn new_v4() -> String {
      "550e8400-e29b-41d4-a716-446655440000".to_string()
    }
  }
}

// Mock chrono module for the example
mod chrono {
  pub struct Utc;
  impl Utc {
    pub fn now() -> UtcDateTime {
      UtcDateTime
    }
  }

  pub struct UtcDateTime;
  impl UtcDateTime {
    pub fn to_rfc3339(&self) -> String {
      "2024-01-15T10:30:00Z".to_string()
    }
  }
}