Skip to main content

qtcloud_devops_cli/commands/
cancel.rs

1use std::path::Path;
2
3use crate::model::release::{FileStorage, ReleaseStatus, Storage, TransitionError};
4
5pub fn run(version: &str, repo_path: &Path) -> Result<String, Box<dyn std::error::Error>> {
6    let mut storage = FileStorage::new(repo_path);
7    let mut record = storage
8        .load(version)
9        .ok_or_else(|| format!("版本 {} 不存在", version))?;
10
11    if record.status != ReleaseStatus::Staged {
12        return Err(Box::new(TransitionError::NotStaged(version.to_string())));
13    }
14
15    crate::commands::release::rollback_tag(version, repo_path);
16
17    if let Some(repo) = crate::commands::release::get_remote_repo(repo_path) {
18        std::process::Command::new("gh")
19            .args(["release", "delete", version, "--repo", &repo, "--yes"])
20            .output()
21            .ok();
22        println!("✓ GitHub Release {} 已删除", version);
23    }
24
25    record.status = ReleaseStatus::Cancelled;
26    record.updated_at = std::time::SystemTime::now()
27        .duration_since(std::time::UNIX_EPOCH)
28        .unwrap_or_default()
29        .as_secs()
30        .to_string();
31    storage.save(&record)?;
32
33    let id = record.id.clone();
34    println!("✓ 版本 {} 已取消 (发布尝试 ID: {})", version, id);
35    Ok(id)
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use crate::model::release::{ReleaseRecord, ReleaseStatus, Storage};
42
43    fn make_record(version: &str, status: ReleaseStatus) -> ReleaseRecord {
44        let now = std::time::SystemTime::now()
45            .duration_since(std::time::UNIX_EPOCH)
46            .unwrap_or_default()
47            .as_secs()
48            .to_string();
49        ReleaseRecord {
50            id: uuid::Uuid::new_v4().to_string(),
51            version: version.to_string(),
52            status,
53            created_at: now.clone(),
54            updated_at: now,
55        }
56    }
57
58    #[test]
59    fn test_cancel_not_staged() {
60        let dir = tempfile::tempdir().unwrap();
61        let mut s = FileStorage::new(dir.path());
62        s.save(&make_record("v1.0.0", ReleaseStatus::Published))
63            .unwrap();
64        assert!(run("v1.0.0", dir.path()).is_err());
65    }
66
67    #[test]
68    fn test_cancel_nonexistent() {
69        let dir = tempfile::tempdir().unwrap();
70        assert!(run("v9.9.9", dir.path()).is_err());
71    }
72
73    #[test]
74    fn test_cancel_happy_path() {
75        let dir = tempfile::tempdir().unwrap();
76        let record_id;
77        {
78            let mut s = FileStorage::new(dir.path());
79            let r = ReleaseRecord::new_staged("v1.0.0");
80            record_id = r.id.clone();
81            s.save(&r).unwrap();
82        }
83        run("v1.0.0", dir.path()).unwrap();
84        let s = FileStorage::new(dir.path());
85        let loaded = s.load("v1.0.0").unwrap();
86        assert_eq!(loaded.status, ReleaseStatus::Cancelled);
87        assert_eq!(loaded.id, record_id);
88    }
89}