use rust_xmpp::{archive, muc::Muc, protocol::ModuleResult, storage}; use std::{fs, path::Path, time::SystemTime}; fn send(muc: &mut Muc, xml: &str) -> ModuleResult { let doc = roxmltree::Document::parse(xml).unwrap(); muc.handle("alice@test/a", doc.root_element()) .unwrap() .unwrap() } fn room(muc: &mut Muc, name: &str) { send( muc, &format!( "" ), ); send( muc, &format!( "" ), ); } fn chat(muc: &mut Muc) -> ModuleResult { send( muc, "Fresh", ) } fn mark(path: &Path) -> SystemTime { let marker = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000); fs::File::options() .write(true) .open(path) .unwrap() .set_times(fs::FileTimes::new().set_modified(marker)) .unwrap(); fs::metadata(path).unwrap().modified().unwrap() } #[test] fn groupchat_skips_native_rewrites_and_archive_retention_scan() { let temp = tempfile::tempdir().unwrap(); let mut muc = Muc::new(temp.path().into(), "rooms.test".into()); muc.set_defaults(serde_json::json!({"persistent":true,"archiving":true})); muc.set_archive_retention(Some(60.0)); room(&mut muc, "r"); room(&mut muc, "other"); let config = storage::path(temp.path(), "rooms.test", Some("r"), "config"); let other = storage::path(temp.path(), "rooms.test", Some("other"), "config"); let index = storage::path(temp.path(), "rooms.test", None, "persistent"); let journal = storage::path(temp.path(), "rooms.test", None, "rust_muc_checkpoint"); let config_time = mark(&config); let other_time = mark(&other); let index_time = mark(&index); let journal_time = mark(&journal); // An unreadable historic record makes any accidental full archive scan fail. // Appending a fresh record needs only the existing file length, never its XML. let log = storage::path(temp.path(), "rooms.test", Some("r"), "muc_log").with_extension("list"); fs::create_dir_all(log.parent().unwrap()).unwrap(); let historic = b"item(this is intentionally invalid historic archive data);\n"; fs::write(&log, historic).unwrap(); let delivered = chat(&mut muc); assert_eq!(delivered.deliveries.len(), 1); let contents = fs::read(&log).unwrap(); assert!(contents.starts_with(historic)); assert!(contents.len() > historic.len()); assert!(archive::load_store(temp.path(), "rooms.test", Some("r"), "muc_log").is_err()); for (path, before) in [ (&config, config_time), (&other, other_time), (&index, index_time), ] { assert_eq!(fs::metadata(path).unwrap().modified().unwrap(), before); } assert_ne!( fs::metadata(&journal).unwrap().modified().unwrap(), journal_time ); assert!(fs::read_to_string(journal).unwrap().contains("Fresh")); } #[test] fn native_exports_repair_missing_files_and_export_changed_config() { let temp = tempfile::tempdir().unwrap(); let mut muc = Muc::new(temp.path().into(), "rooms.test".into()); muc.set_defaults(serde_json::json!({"persistent":true})); room(&mut muc, "r"); room(&mut muc, "other"); let config = storage::path(temp.path(), "rooms.test", Some("r"), "config"); let other = storage::path(temp.path(), "rooms.test", Some("other"), "config"); let index = storage::path(temp.path(), "rooms.test", None, "persistent"); let original = fs::read(&config).unwrap(); fs::remove_file(&config).unwrap(); fs::remove_file(&index).unwrap(); assert_eq!(chat(&mut muc).deliveries.len(), 1); assert_eq!(fs::read(&config).unwrap(), original); let recovered = storage::load(temp.path(), "rooms.test", None, "persistent") .unwrap() .unwrap(); assert_eq!( recovered.get("r@rooms.test"), Some(&storage::LuaValue::Bool(true)) ); assert_eq!( recovered.get("other@rooms.test"), Some(&storage::LuaValue::Bool(true)) ); let other_time = mark(&other); let index_time = mark(&index); let result = send( &mut muc, "Updated", ); assert!(result.replies[0].contains("result")); assert!(fs::read_to_string(config).unwrap().contains("Updated")); assert_eq!(fs::metadata(other).unwrap().modified().unwrap(), other_time); assert_eq!( fs::metadata(&index).unwrap().modified().unwrap(), index_time ); // Export failures are nonfatal after the authoritative room commit, and // exports must be retried even if no configuration changed since failure. fs::remove_file(&index).unwrap(); fs::create_dir(&index).unwrap(); assert_eq!(chat(&mut muc).deliveries.len(), 1); fs::remove_dir(&index).unwrap(); assert_eq!(chat(&mut muc).deliveries.len(), 1); let recovered = storage::load(temp.path(), "rooms.test", None, "persistent") .unwrap() .unwrap(); assert_eq!( recovered.get("r@rooms.test"), Some(&storage::LuaValue::Bool(true)) ); assert_eq!( recovered.get("other@rooms.test"), Some(&storage::LuaValue::Bool(true)) ); }