use super::*; use crate::pull_requests::{Direction, ReviewFilter, SortField, SortRule}; struct Fixture(PathBuf); impl Fixture { fn new() -> Self { let path = std::env::temp_dir().join(format!( "vamp-pr-preferences-{}-{}", std::process::id(), NEXT_TEMP.fetch_add(1, Ordering::Relaxed) )); fs::create_dir(&path).unwrap(); Self(path) } fn path(&self) -> PathBuf { self.0.join("state/vamp/pull_requests.json") } } impl Drop for Fixture { fn drop(&mut self) { let _ = fs::remove_dir_all(&self.0); } } #[test] fn paths_use_absolute_xdg_state_or_home_without_reading_environment() { assert_eq!( path_from(Some(OsStr::new("/state")), Some(OsStr::new("/home"))).unwrap(), PathBuf::from("/state/vamp/pull_requests.json") ); for xdg in [None, Some(OsStr::new("")), Some(OsStr::new("relative"))] { assert_eq!( path_from(xdg, Some(OsStr::new("/home"))).unwrap(), PathBuf::from("/home/.local/state/vamp/pull_requests.json") ); } assert!(path_from(None, None).is_err()); assert!(path_from(None, Some(OsStr::new("relative"))).is_err()); } #[test] fn loading_is_lazy_and_applied_preferences_round_trip_atomically() { let fixture = Fixture::new(); let path = fixture.path(); let (store, mut preferences, notice) = Store::load(Ok(path.clone())); assert_eq!(preferences, Preferences::default()); assert!(notice.is_none()); assert!(!path.parent().unwrap().exists()); preferences.filters = vec![ReviewFilter::Approved]; preferences.sort.insert( SortField::UpdatedAt, SortRule { precedence: Some(1), direction: Direction::Asc, }, ); store.save(&preferences).unwrap(); let (_, loaded, notice) = Store::load(Ok(path.clone())); assert_eq!(loaded.sort, preferences.sort); assert_eq!(loaded.filters, Preferences::default().filters); assert!(!fs::read_to_string(&path).unwrap().contains("filters")); assert!(notice.is_none()); preferences.filters.clear(); preferences.sort.clear(); store.save(&preferences).unwrap(); assert_eq!(Store::load(Ok(path)).1, Preferences::default()); assert_eq!( fs::read_dir(fixture.0.join("state/vamp")).unwrap().count(), 1 ); } #[test] fn previously_saved_filters_are_ignored_without_changing_sort_or_file() { let fixture = Fixture::new(); let path = fixture.path(); fs::create_dir_all(path.parent().unwrap()).unwrap(); for filters in [ r#"[]"#, r#"["approved"]"#, r#"["unknown"]"#, r#"{"invalid":true}"#, ] { let bytes = format!( r#"{{"version":1,"preferences":{{"sort":{{"created_at":{{"precedence":1,"direction":"asc"}}}},"filters":{filters}}}}}"# ); fs::write(&path, &bytes).unwrap(); let (_, loaded, notice) = Store::load(Ok(path.clone())); assert!(notice.is_none()); assert_eq!(loaded.filters, Preferences::default().filters); assert_eq!(loaded.sort[&SortField::CreatedAt].direction, Direction::Asc); assert_eq!(fs::read_to_string(&path).unwrap(), bytes); } } #[test] fn corrupt_incompatible_and_invalid_data_is_preserved_even_after_apply() { let fixture = Fixture::new(); let path = fixture.path(); fs::create_dir_all(path.parent().unwrap()).unwrap(); for bytes in [ "not json", r#"{"version":2,"preferences":{}}"#, r#"{"version":1,"preferences":{"sort":{"unknown":{}}}}"#, r#"{"version":1,"preferences":{"sort":{"updated_at":{"direction":"up"}}}}"#, r#"{"version":1,"preferences":{"sort":{"updated_at":{"precedence":0}}}}"#, r#"{"version":1,"preferences":{"sort":{"updated_at":{"precedence":1},"created_at":{"precedence":1}}}}"#, ] { fs::write(&path, bytes).unwrap(); let (store, loaded, notice) = Store::load(Ok(path.clone())); assert_eq!(loaded, Preferences::default()); assert!(notice.unwrap().contains("session-only")); assert!(store.save(&loaded).unwrap_err().contains("preserved")); assert_eq!(fs::read_to_string(&path).unwrap(), bytes); } } #[test] fn failed_writes_and_newly_incompatible_data_are_reported_without_replacement() { let fixture = Fixture::new(); let path = fixture.path(); let (store, preferences, _) = Store::load(Ok(path.clone())); fs::write(fixture.0.join("state"), "keep this file").unwrap(); assert!( store .save(&preferences) .unwrap_err() .contains("session-only") ); assert_eq!( fs::read_to_string(fixture.0.join("state")).unwrap(), "keep this file" ); fs::remove_file(fixture.0.join("state")).unwrap(); fs::create_dir_all(path.parent().unwrap()).unwrap(); let incompatible = r#"{"version":9,"preferences":{}}"#; fs::write(&path, incompatible).unwrap(); assert!( store .save(&preferences) .unwrap_err() .contains("unsupported") ); assert_eq!(fs::read_to_string(path).unwrap(), incompatible); }