use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateConfig {
pub auto_update_enabled: bool,
pub auto_update_check_interval: Duration,
}
impl Default for UpdateConfig {
fn default() -> Self {
Self {
auto_update_enabled: true,
auto_update_check_interval: Duration::from_secs(86400), }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateInfo {
pub version: String,
pub release_date: Timestamp,
pub notes: String,
pub download_url: String,
pub signature_url: String,
pub arch: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateHistory {
pub last_check: Timestamp,
pub current_version: String,
pub pending_update: Option<UpdateInfo>,
pub backup_versions: Vec<String>,
pub check_history: Vec<UpdateCheckEntry>,
}
impl Default for UpdateHistory {
fn default() -> Self {
Self {
last_check: Timestamp::now(),
current_version: String::new(),
pending_update: None,
backup_versions: Vec::new(),
check_history: Vec::new(),
}
}
}
impl UpdateHistory {
pub fn add_check_entry(&mut self, entry: UpdateCheckEntry) {
self.check_history.push(entry);
if self.check_history.len() > 10 {
self.check_history.remove(0);
}
}
pub fn add_backup_version(&mut self, version: String, max_backups: usize) {
self.backup_versions.push(version);
if self.backup_versions.len() > max_backups {
self.backup_versions.remove(0);
}
}
pub fn latest_backup(&self) -> Option<&String> {
self.backup_versions.last()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateCheckEntry {
pub timestamp: Timestamp,
pub result: UpdateCheckResult,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum UpdateCheckResult {
UpToDate,
UpdateAvailable { version: String, notified: bool },
CheckFailed { error: String },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_update_config_default() {
let config = UpdateConfig::default();
assert!(config.auto_update_enabled, "Default should be enabled");
assert_eq!(
config.auto_update_check_interval,
Duration::from_secs(86400),
"Default should be 24 hours"
);
}
#[test]
fn test_update_config_serialize() {
let config = UpdateConfig {
auto_update_enabled: false,
auto_update_check_interval: Duration::from_secs(3600), };
let serialized = serde_json::to_string(&config).unwrap();
let deserialized: UpdateConfig = serde_json::from_str(&serialized).unwrap();
assert_eq!(config, deserialized);
}
#[test]
fn test_update_info_serialization() {
let info = UpdateInfo {
version: "1.2.3".to_string(),
release_date: Timestamp::now(),
notes: "Test release".to_string(),
download_url: "https://example.com/binary".to_string(),
signature_url: "https://example.com/binary.sig".to_string(),
arch: "x86_64".to_string(),
};
let serialized = serde_json::to_string(&info).unwrap();
let deserialized: UpdateInfo = serde_json::from_str(&serialized).unwrap();
assert_eq!(info.version, deserialized.version);
assert_eq!(info.download_url, deserialized.download_url);
assert_eq!(info.signature_url, deserialized.signature_url);
assert_eq!(info.arch, deserialized.arch);
}
#[test]
fn test_update_history_default() {
let history = UpdateHistory::default();
assert_eq!(history.current_version, String::new());
assert!(history.pending_update.is_none());
assert!(history.backup_versions.is_empty());
assert!(history.check_history.is_empty());
}
#[test]
fn test_update_history_add_entry() {
let mut history = UpdateHistory::default();
let entry = UpdateCheckEntry {
timestamp: Timestamp::now(),
result: UpdateCheckResult::UpToDate,
};
history.add_check_entry(entry);
assert_eq!(history.check_history.len(), 1);
}
#[test]
fn test_update_history_limit_entries() {
let mut history = UpdateHistory::default();
for _i in 0..15 {
history.add_check_entry(UpdateCheckEntry {
timestamp: Timestamp::now(),
result: UpdateCheckResult::UpToDate,
});
}
assert_eq!(
history.check_history.len(),
10,
"Should keep only last 10 entries"
);
}
#[test]
fn test_update_history_add_backup() {
let mut history = UpdateHistory::default();
let max_backups = 3;
for i in 0..5 {
history.add_backup_version(format!("1.0.{}", i), max_backups);
}
assert_eq!(
history.backup_versions.len(),
3,
"Should keep only 3 backups"
);
assert_eq!(
history.backup_versions,
vec![
"1.0.2".to_string(),
"1.0.3".to_string(),
"1.0.4".to_string()
]
);
}
#[test]
fn test_update_history_latest_backup() {
let mut history = UpdateHistory::default();
history.add_backup_version("1.0.0".to_string(), 3);
history.add_backup_version("1.0.1".to_string(), 3);
assert_eq!(history.latest_backup(), Some(&"1.0.1".to_string()));
}
#[test]
fn test_update_check_entry_serialization() {
let entry = UpdateCheckEntry {
timestamp: Timestamp::now(),
result: UpdateCheckResult::UpdateAvailable {
version: "1.1.0".to_string(),
notified: false,
},
};
let serialized = serde_json::to_string(&entry).unwrap();
let deserialized: UpdateCheckEntry = serde_json::from_str(&serialized).unwrap();
match deserialized.result {
UpdateCheckResult::UpdateAvailable { version, notified } => {
assert_eq!(version, "1.1.0");
assert!(!notified);
}
_ => panic!("Expected UpdateAvailable variant"),
}
}
#[test]
fn test_update_check_result_variants() {
let up_to_date = UpdateCheckResult::UpToDate;
let update_available = UpdateCheckResult::UpdateAvailable {
version: "1.2.0".to_string(),
notified: true,
};
let check_failed = UpdateCheckResult::CheckFailed {
error: "Network error".to_string(),
};
assert_ne!(up_to_date, update_available);
assert_ne!(up_to_date, check_failed);
assert_ne!(update_available, check_failed);
for result in [up_to_date, update_available, check_failed] {
let serialized = serde_json::to_string(&result).unwrap();
let deserialized: UpdateCheckResult = serde_json::from_str(&serialized).unwrap();
assert_eq!(result, deserialized);
}
}
}