use diffo::*;
use serde::Serialize;
use std::collections::HashMap;
#[derive(Serialize)]
struct User {
id: u64,
name: String,
email: String,
active: bool,
}
#[derive(Serialize)]
struct Config {
version: String,
database: DatabaseConfig,
api: ApiConfig,
features: Vec<String>,
}
#[derive(Serialize)]
struct DatabaseConfig {
host: String,
port: u16,
max_connections: u32,
}
#[derive(Serialize)]
struct ApiConfig {
timeout: u64,
retries: u32,
endpoints: HashMap<String, String>,
}
#[test]
fn test_simple_struct_diff() {
let old = User {
id: 1,
name: "Alice".into(),
email: "alice@old.com".into(),
active: true,
};
let new = User {
id: 1,
name: "Alice Smith".into(),
email: "alice@new.com".into(),
active: true,
};
let diff = diff(&old, &new).unwrap();
assert!(!diff.is_empty());
assert!(diff.get("name").is_some());
assert!(diff.get("email").is_some());
assert!(diff.get("id").is_none());
assert!(diff.get("active").is_none());
}
#[test]
fn test_nested_struct_diff() {
let old = Config {
version: "1.0.0".into(),
database: DatabaseConfig {
host: "localhost".into(),
port: 5432,
max_connections: 100,
},
api: ApiConfig {
timeout: 30,
retries: 3,
endpoints: HashMap::from([("users".into(), "/api/users".into())]),
},
features: vec!["auth".into(), "logging".into()],
};
let new = Config {
version: "1.1.0".into(),
database: DatabaseConfig {
host: "db.prod.com".into(),
port: 5432,
max_connections: 200,
},
api: ApiConfig {
timeout: 60,
retries: 5,
endpoints: HashMap::from([
("users".into(), "/api/v2/users".into()),
("posts".into(), "/api/posts".into()),
]),
},
features: vec!["auth".into(), "logging".into(), "metrics".into()],
};
let diff = diff(&old, &new).unwrap();
assert!(diff.get("version").is_some());
assert!(diff.get("database.host").is_some());
assert!(diff.get("database.port").is_none());
assert!(diff.get("database.max_connections").is_some());
assert!(diff.get("api.timeout").is_some());
assert!(diff.get("api.retries").is_some());
assert!(diff.get("api.endpoints.users").is_some());
assert!(diff.get("api.endpoints.posts").is_some());
assert!(diff.get("features[2]").is_some());
}
#[test]
fn test_identical_values() {
let v1 = User {
id: 1,
name: "Alice".into(),
email: "alice@example.com".into(),
active: true,
};
let v2 = User {
id: 1,
name: "Alice".into(),
email: "alice@example.com".into(),
active: true,
};
let diff = diff(&v1, &v2).unwrap();
assert!(diff.is_empty());
}
#[test]
fn test_vector_changes() {
let old = vec![1, 2, 3, 4];
let new = vec![1, 2, 5, 4, 6];
let diff = diff(&old, &new).unwrap();
assert!(diff.get("[2]").is_some());
assert!(diff.get("[4]").is_some());
assert!(diff.get("[0]").is_none());
assert!(diff.get("[1]").is_none());
assert!(diff.get("[3]").is_none());
}
#[test]
fn test_hashmap_changes() {
let mut old = HashMap::new();
old.insert("a", 1);
old.insert("b", 2);
let mut new = HashMap::new();
new.insert("a", 1);
new.insert("b", 3);
new.insert("c", 4);
let diff = diff(&old, &new).unwrap();
assert!(diff.get("a").is_none());
assert!(diff.get("b").is_some());
assert!(diff.get("c").is_some());
}
#[test]
fn test_with_masking() {
#[derive(Serialize)]
struct Credentials {
username: String,
password: String,
api_key: String,
}
let old = Credentials {
username: "admin".into(),
password: "old_secret".into(),
api_key: "key123".into(),
};
let new = Credentials {
username: "admin".into(),
password: "new_secret".into(),
api_key: "key456".into(),
};
let config = DiffConfig::new().mask("password").mask("api_key");
let diff = diff_with(&old, &new, &config).unwrap();
assert!(diff.get("password").is_some());
assert!(diff.get("api_key").is_some());
assert!(diff.get("username").is_none());
}
#[test]
fn test_float_tolerance() {
#[derive(Serialize)]
struct Metrics {
value: f64,
threshold: f64,
}
let old = Metrics {
value: 1.0,
threshold: 0.5,
};
let new = Metrics {
value: 1.0000001,
threshold: 0.6,
};
let config = DiffConfig::new().default_float_tolerance(0.001);
let diff = diff_with(&old, &new, &config).unwrap();
assert!(diff.get("value").is_none());
assert!(diff.get("threshold").is_some());
}
#[test]
fn test_collection_limit() {
let old: Vec<i32> = (0..2000).collect();
let new: Vec<i32> = (0..2000).collect();
let config = DiffConfig::new().collection_limit(100);
let diff = diff_with(&old, &new, &config).unwrap();
assert!(!diff.is_empty());
assert_eq!(diff.len(), 1);
let change = diff.changes().values().next().unwrap();
assert!(change.is_elided());
}
#[test]
fn test_formatters() {
let old = vec![1, 2, 3];
let new = vec![1, 2, 4];
let diff = diff(&old, &new).unwrap();
let pretty = diff.to_pretty();
assert!(!pretty.is_empty());
let json = diff.to_json().unwrap();
assert!(json.contains("\"type\""));
let patch = diff.to_json_patch().unwrap();
assert!(patch.contains("\"op\""));
let markdown = diff.to_markdown().unwrap();
assert!(markdown.contains("| Path |"));
}
#[test]
fn test_deep_nesting() {
#[derive(Serialize)]
struct Level3 {
value: i32,
}
#[derive(Serialize)]
struct Level2 {
inner: Level3,
}
#[derive(Serialize)]
struct Level1 {
inner: Level2,
}
let old = Level1 {
inner: Level2 {
inner: Level3 { value: 1 },
},
};
let new = Level1 {
inner: Level2 {
inner: Level3 { value: 2 },
},
};
let diff = diff(&old, &new).unwrap();
assert!(diff.get("inner.inner.value").is_some());
}