soroban_cli/config/
upgrade_check.rs1use crate::config::locator;
2use chrono::{DateTime, Utc};
3use semver::Version;
4use serde::Deserialize;
5use serde::Serialize;
6use serde_json;
7use std::fs;
8
9use super::data::project_dir;
10
11const FILE_NAME: &str = "upgrade_check.json";
12
13#[derive(Serialize, Deserialize, Debug, PartialEq)]
17pub struct UpgradeCheck {
18 pub latest_check_time: DateTime<Utc>,
20 pub max_stable_version: Version,
22 pub max_version: Version,
24}
25
26impl Default for UpgradeCheck {
27 fn default() -> Self {
28 Self {
29 latest_check_time: DateTime::<Utc>::UNIX_EPOCH,
30 max_stable_version: Version::new(0, 0, 0),
31 max_version: Version::new(0, 0, 0),
32 }
33 }
34}
35
36impl UpgradeCheck {
37 pub fn load() -> Result<Self, locator::Error> {
40 let path = project_dir()
41 .map_err(|_| locator::Error::ProjectDirsError())?
42 .data_dir()
43 .join(FILE_NAME);
44
45 if !path.exists() {
46 return Ok(Self::default());
47 }
48
49 let data = fs::read(&path)
50 .map_err(|error| locator::Error::UpgradeCheckReadFailed { path, error })?;
51
52 Ok(serde_json::from_slice(data.as_slice())?)
53 }
54
55 pub fn save(&self) -> Result<(), locator::Error> {
57 let path = project_dir()
58 .map_err(|_| locator::Error::ProjectDirsError())?
59 .data_dir()
60 .join(FILE_NAME);
61
62 let path = locator::ensure_directory(path)?;
63 let data = serde_json::to_string(self).map_err(|_| locator::Error::ConfigSerialization)?;
64 locator::write_hardened_file(&path, data.as_bytes())
65 .map_err(|error| locator::Error::UpgradeCheckWriteFailed { path, error })
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use serial_test::serial;
73 use std::env;
74
75 #[test]
76 #[serial]
77 fn test_upgrade_check_load_save() {
78 let temp_dir = tempfile::tempdir().unwrap();
82 env::remove_var("XDG_DATA_HOME");
83 env::set_var("STELLAR_DATA_HOME", temp_dir.path());
84 let default_check = UpgradeCheck::load().unwrap();
86 assert_eq!(default_check, UpgradeCheck::default());
87 assert_eq!(
88 default_check.latest_check_time,
89 DateTime::<Utc>::from_timestamp_millis(0).unwrap()
90 );
91 assert_eq!(default_check.max_stable_version, Version::new(0, 0, 0));
92
93 let saved_check = UpgradeCheck {
95 latest_check_time: DateTime::<Utc>::from_timestamp(1_234_567_890, 0).unwrap(),
96 max_stable_version: Version::new(1, 2, 3),
97 max_version: Version::parse("1.2.4-rc.1").unwrap(),
98 };
99 saved_check.save().unwrap();
100 let loaded_check = UpgradeCheck::load().unwrap();
101 assert_eq!(loaded_check, saved_check);
102 }
103
104 #[cfg(unix)]
105 #[test]
106 #[serial]
107 fn test_upgrade_check_save_uses_0600_permissions() {
108 use crate::test_utils::with_env_set;
109 use std::os::unix::fs::PermissionsExt;
110
111 let temp_dir = tempfile::tempdir().unwrap();
112 with_env_set("STELLAR_DATA_HOME", temp_dir.path(), || {
113 UpgradeCheck::default().save().unwrap();
114
115 let path = project_dir().unwrap().data_dir().join(FILE_NAME);
116 let mode = fs::metadata(&path).unwrap().permissions().mode() & 0o777;
117 assert_eq!(mode, 0o600, "expected 0600, got {mode:o}");
118 });
119 }
120}