use banshee_format::{
FormatConfig, check_idempotent, count_tokens, normalize, semantically_equal, validate_format,
};
use proptest::prelude::*;
proptest! {
#[test]
fn format_is_idempotent(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
if let Ok(sql) = std::str::from_utf8(&data) {
let once = banshee_format::format_sqlstyle(sql);
let twice = banshee_format::format_sqlstyle(&once);
prop_assert_eq!(once, twice);
}
}
#[test]
fn format_roundtrip_preserves_parse_success(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
if let Ok(sql) = std::str::from_utf8(&data) {
let parse = banshee_parser::parse(sql);
if parse.errors().is_empty() {
let formatted = banshee_format::format_sqlstyle(sql);
let formatted_parse = banshee_parser::parse(&formatted);
prop_assert!(formatted_parse.errors().is_empty());
}
}
}
#[test]
fn format_preserves_semantics(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
if let Ok(sql) = std::str::from_utf8(&data) {
let parse = banshee_parser::parse(sql);
if parse.errors().is_empty() {
let formatted = banshee_format::format_sqlstyle(sql);
prop_assert!(validate_format(sql, &formatted).is_ok(),
"Validation failed for sql: {}", sql);
}
}
}
#[test]
fn token_count_similar(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
if let Ok(sql) = std::str::from_utf8(&data) {
let parse = banshee_parser::parse(sql);
if parse.errors().is_empty() {
let formatted = banshee_format::format_sqlstyle(sql);
let orig_count = count_tokens(sql);
let fmt_count = count_tokens(&formatted);
let diff = (orig_count as i64 - fmt_count as i64).abs();
prop_assert!(diff < 20,
"Token count changed too much: {} -> {} for sql: {}",
orig_count, fmt_count, sql);
}
}
}
#[test]
fn normalized_forms_equal(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
if let Ok(sql) = std::str::from_utf8(&data) {
let orig_parse = banshee_parser::parse(sql);
if orig_parse.errors().is_empty() {
let formatted = banshee_format::format_sqlstyle(sql);
let fmt_parse = banshee_parser::parse(&formatted);
if fmt_parse.errors().is_empty() {
prop_assert!(semantically_equal(&orig_parse, &fmt_parse),
"Semantic mismatch:\n Original normalized: {}\n Formatted normalized: {}\n SQL: {}",
normalize(&orig_parse),
normalize(&fmt_parse),
sql);
}
}
}
}
#[test]
fn check_idempotent_passes(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
if let Ok(sql) = std::str::from_utf8(&data) {
let parse = banshee_parser::parse(sql);
if parse.errors().is_empty() {
let config = FormatConfig::sqlstyle();
prop_assert!(check_idempotent(sql, &config).is_ok(),
"Idempotency check failed for sql: {}", sql);
}
}
}
}
fn valid_sql_strategy() -> impl Strategy<Value = String> {
prop_oneof![
Just("SELECT id FROM users".to_string()),
Just("SELECT * FROM users".to_string()),
Just("SELECT id, name FROM users".to_string()),
Just("SELECT id FROM users WHERE id = 1".to_string()),
Just("SELECT * FROM users WHERE active = true".to_string()),
Just("SELECT id FROM users WHERE id > 10 AND active = true".to_string()),
Just("INSERT INTO users (id, name) VALUES (1, 'Alice')".to_string()),
Just("UPDATE users SET active = true WHERE id = 1".to_string()),
Just("DELETE FROM users WHERE id = 1".to_string()),
Just("WITH active AS (SELECT * FROM users WHERE active) SELECT * FROM active".to_string()),
]
}
proptest! {
#[test]
fn valid_sql_format_validates(sql in valid_sql_strategy()) {
let formatted = banshee_format::format_sqlstyle(&sql);
prop_assert!(validate_format(&sql, &formatted).is_ok(),
"Validation failed for known valid SQL: {}", sql);
}
#[test]
fn valid_sql_idempotent(sql in valid_sql_strategy()) {
let once = banshee_format::format_sqlstyle(&sql);
let twice = banshee_format::format_sqlstyle(&once);
prop_assert_eq!(once, twice, "Not idempotent for SQL: {}", sql);
}
}