use data_modelling_core::models::enums::{DataVaultClassification, DatabaseType, SCDPattern};
use data_modelling_core::models::{Relationship, Table};
use data_modelling_core::validation::input::{
ValidationError, sanitize_description, sanitize_sql_identifier, validate_column_name,
validate_data_type, validate_table_name, validate_uuid,
};
use data_modelling_core::validation::relationships::RelationshipValidator;
use data_modelling_core::validation::tables::TableValidator;
use uuid::Uuid;
mod input_validation_tests {
use super::*;
#[test]
fn test_validate_table_name_edge_cases() {
let max_name = "a".repeat(255);
assert!(validate_table_name(&max_name).is_ok());
let too_long = "a".repeat(256);
assert!(matches!(
validate_table_name(&too_long),
Err(ValidationError::TooLong { .. })
));
assert!(validate_table_name("tëst_täblë").is_ok());
assert!(validate_table_name("_private").is_ok());
assert!(validate_table_name("my-table").is_ok());
}
#[test]
fn test_validate_column_name_edge_cases() {
let max_name = "a".repeat(255);
assert!(validate_column_name(&max_name).is_ok());
let too_long = "a".repeat(256);
assert!(matches!(
validate_column_name(&too_long),
Err(ValidationError::TooLong { .. })
));
assert!(validate_column_name("a.b.c.d.e.f").is_ok());
assert!(validate_column_name("data.select").is_ok());
assert!(matches!(
validate_column_name("123column"),
Err(ValidationError::InvalidFormat(..))
));
}
#[test]
fn test_validate_data_type_edge_cases() {
assert!(validate_data_type("STRUCT<id INT, name STRING>").is_ok());
assert!(validate_data_type("ARRAY<STRUCT<field STRING>>").is_ok());
assert!(validate_data_type("MAP<STRING, INT>").is_ok());
assert!(validate_data_type("DECIMAL(10, 2)").is_ok());
assert!(validate_data_type("NUMERIC(18, 4)").is_ok());
assert!(matches!(
validate_data_type("'; DROP TABLE users;--"),
Err(ValidationError::InvalidCharacters { .. })
));
assert!(matches!(
validate_data_type("INT); DELETE FROM users;--"),
Err(ValidationError::InvalidCharacters { .. })
));
}
#[test]
fn test_validate_uuid_edge_cases() {
assert!(validate_uuid("550e8400-e29b-41d4-a716-446655440000").is_ok());
assert!(validate_uuid("00000000-0000-0000-0000-000000000000").is_ok());
assert!(validate_uuid("ffffffff-ffff-ffff-ffff-ffffffffffff").is_ok());
assert!(validate_uuid("not-a-uuid").is_err());
assert!(validate_uuid("550e8400-e29b-41d4-a716").is_err());
assert!(validate_uuid("not-a-valid-uuid-format-at-all").is_err());
}
#[test]
fn test_sanitize_sql_identifier_edge_cases() {
assert_eq!(
sanitize_sql_identifier("user-name", "postgres"),
"\"user-name\""
);
assert_eq!(sanitize_sql_identifier("user_name", "mysql"), "`user_name`");
let quoted = sanitize_sql_identifier("users", "postgres");
assert!(quoted.starts_with('"') && quoted.ends_with('"'));
assert_eq!(sanitize_sql_identifier("", "postgres"), "\"\"");
let long = "a".repeat(100);
let sanitized = sanitize_sql_identifier(&long, "postgres");
assert!(sanitized.len() > long.len()); }
#[test]
fn test_sanitize_description_edge_cases() {
assert_eq!(sanitize_description(""), "");
let long_desc = "a".repeat(10000);
let sanitized = sanitize_description(&long_desc);
assert_eq!(sanitized.len(), 10000);
assert_eq!(
sanitize_description("Line1\nLine2\r\nLine3"),
"Line1\nLine2\r\nLine3"
);
assert_eq!(sanitize_description("Col1\tCol2\tCol3"), "Col1\tCol2\tCol3");
}
}
mod table_validation_tests {
use super::*;
#[test]
fn test_detect_naming_conflicts_with_database_type() {
let validator = TableValidator::new();
let mut existing = Table::new("users".to_string(), vec![]);
existing.database_type = Some(DatabaseType::Postgres);
let mut new_table_same_db = Table::new("users".to_string(), vec![]);
new_table_same_db.database_type = Some(DatabaseType::Postgres);
let mut new_table_different_db = Table::new("users".to_string(), vec![]);
new_table_different_db.database_type = Some(DatabaseType::Mysql);
let conflicts =
validator.detect_naming_conflicts(&[existing.clone()], &[new_table_same_db]);
assert_eq!(conflicts.len(), 1);
let conflicts = validator.detect_naming_conflicts(&[existing], &[new_table_different_db]);
assert_eq!(conflicts.len(), 0); }
#[test]
fn test_detect_naming_conflicts_with_schema() {
let validator = TableValidator::new();
let mut existing = Table::new("users".to_string(), vec![]);
existing.schema_name = Some("public".to_string());
let mut new_table_same_schema = Table::new("users".to_string(), vec![]);
new_table_same_schema.schema_name = Some("public".to_string());
let mut new_table_different_schema = Table::new("users".to_string(), vec![]);
new_table_different_schema.schema_name = Some("private".to_string());
let conflicts =
validator.detect_naming_conflicts(&[existing.clone()], &[new_table_same_schema]);
assert_eq!(conflicts.len(), 1);
let conflicts =
validator.detect_naming_conflicts(&[existing], &[new_table_different_schema]);
assert_eq!(conflicts.len(), 0); }
#[test]
fn test_detect_naming_conflicts_multiple_conflicts() {
let validator = TableValidator::new();
let existing1 = Table::new("users".to_string(), vec![]);
let existing2 = Table::new("orders".to_string(), vec![]);
let new1 = Table::new("users".to_string(), vec![]);
let new2 = Table::new("orders".to_string(), vec![]);
let new3 = Table::new("products".to_string(), vec![]);
let conflicts =
validator.detect_naming_conflicts(&[existing1, existing2], &[new1, new2, new3]);
assert_eq!(conflicts.len(), 2); }
#[test]
fn test_validate_pattern_exclusivity_valid_cases() {
let validator = TableValidator::new();
let mut table_scd = Table::new("test".to_string(), vec![]);
table_scd.scd_pattern = Some(SCDPattern::Type2);
table_scd.data_vault_classification = None;
assert!(validator.validate_pattern_exclusivity(&table_scd).is_ok());
let mut table_dv = Table::new("test".to_string(), vec![]);
table_dv.scd_pattern = None;
table_dv.data_vault_classification = Some(DataVaultClassification::Hub);
assert!(validator.validate_pattern_exclusivity(&table_dv).is_ok());
let table_none = Table::new("test".to_string(), vec![]);
assert!(validator.validate_pattern_exclusivity(&table_none).is_ok());
}
#[test]
fn test_validate_pattern_exclusivity_violation() {
let validator = TableValidator::new();
let mut table = Table::new("test".to_string(), vec![]);
table.scd_pattern = Some(SCDPattern::Type2);
table.data_vault_classification = Some(DataVaultClassification::Hub);
let result = validator.validate_pattern_exclusivity(&table);
assert!(result.is_err());
let violation = result.unwrap_err();
assert_eq!(violation.table_id, table.id);
assert_eq!(violation.table_name, "test");
assert!(violation.message.contains("mutually exclusive"));
}
}
mod relationship_validation_tests {
use super::*;
#[test]
fn test_check_circular_dependency_no_cycle() {
let validator = RelationshipValidator::new();
let table_a = Uuid::new_v4();
let table_b = Uuid::new_v4();
let table_c = Uuid::new_v4();
let rels = vec![
Relationship::new(table_a, table_b),
Relationship::new(table_b, table_c),
];
let table_d = Uuid::new_v4();
let (has_cycle, _) = validator
.check_circular_dependency(&rels, table_c, table_d)
.unwrap();
assert!(!has_cycle);
}
#[test]
fn test_check_circular_dependency_simple_cycle() {
let validator = RelationshipValidator::new();
let table_a = Uuid::new_v4();
let table_b = Uuid::new_v4();
let rels = vec![Relationship::new(table_a, table_b)];
let (has_cycle, path) = validator
.check_circular_dependency(&rels, table_b, table_a)
.unwrap();
assert!(has_cycle);
assert!(path.is_some());
}
#[test]
fn test_check_circular_dependency_complex_cycle() {
let validator = RelationshipValidator::new();
let table_a = Uuid::new_v4();
let table_b = Uuid::new_v4();
let table_c = Uuid::new_v4();
let table_d = Uuid::new_v4();
let rels = vec![
Relationship::new(table_a, table_b),
Relationship::new(table_b, table_c),
Relationship::new(table_c, table_d),
];
let (has_cycle, path) = validator
.check_circular_dependency(&rels, table_d, table_a)
.unwrap();
assert!(has_cycle);
assert!(path.is_some());
}
#[test]
fn test_check_circular_dependency_self_reference() {
let validator = RelationshipValidator::new();
let table_a = Uuid::new_v4();
let rels = vec![];
let (has_cycle, _) = validator
.check_circular_dependency(&rels, table_a, table_a)
.unwrap();
assert!(has_cycle);
}
#[test]
fn test_validate_no_self_reference_valid() {
let validator = RelationshipValidator::new();
let table_a = Uuid::new_v4();
let table_b = Uuid::new_v4();
assert!(
validator
.validate_no_self_reference(table_a, table_b)
.is_ok()
);
}
#[test]
fn test_validate_no_self_reference_violation() {
let validator = RelationshipValidator::new();
let table_id = Uuid::new_v4();
let result = validator.validate_no_self_reference(table_id, table_id);
assert!(result.is_err());
let self_ref = result.unwrap_err();
assert_eq!(self_ref.table_id, table_id);
}
#[test]
fn test_check_circular_dependency_empty_graph() {
let validator = RelationshipValidator::new();
let table_a = Uuid::new_v4();
let table_b = Uuid::new_v4();
let (has_cycle, _) = validator
.check_circular_dependency(&[], table_a, table_b)
.unwrap();
assert!(!has_cycle);
}
#[test]
fn test_check_circular_dependency_multiple_paths_no_cycle() {
let validator = RelationshipValidator::new();
let table_a = Uuid::new_v4();
let table_b = Uuid::new_v4();
let table_c = Uuid::new_v4();
let table_d = Uuid::new_v4();
let rels = vec![
Relationship::new(table_a, table_b),
Relationship::new(table_b, table_c),
Relationship::new(table_a, table_d),
Relationship::new(table_d, table_c),
];
let table_e = Uuid::new_v4();
let (has_cycle, _) = validator
.check_circular_dependency(&rels, table_c, table_e)
.unwrap();
assert!(!has_cycle);
}
}