#![cfg(feature = "cli")]
use idb::innodb::compliance::{encryption_audit, scan_residue, verify_deleted, Pattern, Region};
use idb::innodb::tablespace::Tablespace;
const FIXTURE: &str = "tests/fixtures/mysql9/mysql90_standard.ibd";
fn open() -> Tablespace {
Tablespace::open(FIXTURE).expect("open fixture")
}
#[test]
fn scan_residue_finds_known_record_string() {
let mut ts = open();
let pat = Pattern::parse("test_row_1").unwrap();
let matches = scan_residue(&mut ts, &pat, 1000).unwrap();
assert_eq!(matches.len(), 1, "expected exactly one live record match");
let m = &matches[0];
assert_eq!(m.page_number, 4, "leaf page holding the clustered records");
assert_eq!(m.region, Region::RecordHeap);
assert!(m.context_hex.starts_with("746573745f726f775f31"));
}
#[test]
fn scan_residue_absent_pattern_finds_nothing() {
let mut ts = open();
let pat = Pattern::parse("zzz_definitely_absent_value").unwrap();
let matches = scan_residue(&mut ts, &pat, 1000).unwrap();
assert!(matches.is_empty());
}
#[test]
fn scan_residue_hex_pattern() {
let mut ts = open();
let pat = Pattern::parse("hex:746573745f726f775f31").unwrap();
let matches = scan_residue(&mut ts, &pat, 1000).unwrap();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].page_number, 4);
}
#[test]
fn scan_residue_respects_max_hits() {
let mut ts = open();
let pat = Pattern::parse("x").unwrap();
let matches = scan_residue(&mut ts, &pat, 5).unwrap();
assert_eq!(matches.len(), 5);
}
#[test]
fn verify_present_string_is_not_purged() {
let mut ts = open();
let report = verify_deleted(&mut ts, "name", "test_row_1", false).unwrap();
assert!(!report.fully_purged);
assert!(report
.residue_sites
.iter()
.any(|s| s.region == "live_record"));
assert_eq!(report.table_name.as_deref(), Some("standard"));
assert_eq!(report.column, "name");
}
#[test]
fn verify_absent_string_is_purged() {
let mut ts = open();
let report = verify_deleted(&mut ts, "name", "absent_zzz_value", false).unwrap();
assert!(report.fully_purged);
assert!(report.residue_sites.is_empty());
}
#[test]
fn verify_present_pk_is_not_purged() {
let mut ts = open();
let report = verify_deleted(&mut ts, "id", "1", false).unwrap();
assert!(!report.fully_purged);
}
#[test]
fn verify_absent_pk_is_purged() {
let mut ts = open();
let report = verify_deleted(&mut ts, "id", "999999", false).unwrap();
assert!(report.fully_purged);
}
#[test]
fn verify_pk_column_scans_undo_region_but_non_pk_does_not() {
let mut ts = open();
let pk_report = verify_deleted(&mut ts, "id", "1", false).unwrap();
assert!(pk_report
.regions_scanned
.iter()
.any(|r| r == "undo_del_mark"));
let mut ts2 = open();
let non_pk_report = verify_deleted(&mut ts2, "name", "test_row_1", false).unwrap();
assert!(!non_pk_report
.regions_scanned
.iter()
.any(|r| r == "undo_del_mark"));
}
#[test]
fn verify_thorough_adds_raw_pass() {
let mut ts = open();
let report = verify_deleted(&mut ts, "name", "test_row_1", true).unwrap();
assert!(report.thorough);
assert!(report.regions_scanned.iter().any(|r| r == "raw_page_bytes"));
assert!(report.residue_sites.len() >= 2);
assert!(report
.residue_sites
.iter()
.any(|s| s.region.starts_with("raw_")));
}
#[test]
fn verify_unknown_column_is_argument_error() {
let mut ts = open();
let err = verify_deleted(&mut ts, "no_such_column", "x", false).unwrap_err();
match err {
idb::IdbError::Argument(_) => {}
other => panic!("expected Argument error, got {:?}", other),
}
}
#[test]
fn encryption_audit_plaintext_fixture() {
let mut ts = open();
let report = encryption_audit(&mut ts).unwrap();
assert!(!report.tablespace_encrypted);
assert_eq!(report.encrypted_page_count, 0);
assert_eq!(report.total_pages, 7);
assert!(report.algorithm.is_none());
}
#[test]
fn cli_scan_mode_reports_match() {
let opts = idb::cli::comply::ComplyOptions {
file: FIXTURE.to_string(),
verify_deleted: false,
scan_residue: true,
encryption_audit: false,
table: None,
where_clause: None,
pattern: Some("test_row_1".to_string()),
thorough: false,
max_hits: 1000,
page_size: None,
keyring: None,
mmap: false,
json: false,
csv: false,
verbose: false,
};
let mut out = Vec::new();
idb::cli::comply::execute(&opts, &mut out).unwrap();
let text = String::from_utf8(out).unwrap();
assert!(text.contains("1 match"));
assert!(text.contains("record_heap"));
}
#[test]
fn cli_verify_mode_rejects_missing_where() {
let opts = idb::cli::comply::ComplyOptions {
file: FIXTURE.to_string(),
verify_deleted: true,
scan_residue: false,
encryption_audit: false,
table: None,
where_clause: None,
pattern: None,
thorough: false,
max_hits: 1000,
page_size: None,
keyring: None,
mmap: false,
json: false,
csv: false,
verbose: false,
};
let mut out = Vec::new();
let err = idb::cli::comply::execute(&opts, &mut out).unwrap_err();
assert!(matches!(err, idb::IdbError::Argument(_)));
}
#[test]
fn cli_no_mode_selected_errors() {
let opts = idb::cli::comply::ComplyOptions {
file: FIXTURE.to_string(),
verify_deleted: false,
scan_residue: false,
encryption_audit: false,
table: None,
where_clause: None,
pattern: None,
thorough: false,
max_hits: 1000,
page_size: None,
keyring: None,
mmap: false,
json: false,
csv: false,
verbose: false,
};
let mut out = Vec::new();
assert!(idb::cli::comply::execute(&opts, &mut out).is_err());
}
#[test]
fn audit_compliance_scans_directory() {
let opts = idb::cli::audit::AuditOptions {
datadir: "tests/fixtures/mysql9".to_string(),
health: false,
checksum_mismatch: false,
verbose: false,
json: false,
csv: false,
prometheus: false,
page_size: None,
keyring: None,
mmap: false,
min_fill_factor: None,
max_fragmentation: None,
bloat: false,
max_bloat_grade: None,
depth: None,
compliance: true,
pattern: Some("test_row_1".to_string()),
};
let mut out = Vec::new();
idb::cli::audit::execute(&opts, &mut out).unwrap();
let text = String::from_utf8(out).unwrap();
assert!(text.contains("mysql90_standard.ibd"));
assert!(text.contains("match"));
}
#[test]
fn audit_compliance_requires_pattern() {
let opts = idb::cli::audit::AuditOptions {
datadir: "tests/fixtures/mysql9".to_string(),
health: false,
checksum_mismatch: false,
verbose: false,
json: false,
csv: false,
prometheus: false,
page_size: None,
keyring: None,
mmap: false,
min_fill_factor: None,
max_fragmentation: None,
bloat: false,
max_bloat_grade: None,
depth: None,
compliance: true,
pattern: None,
};
let mut out = Vec::new();
assert!(idb::cli::audit::execute(&opts, &mut out).is_err());
}