qtcloud_devops_cli/commands/
cancel.rs1use 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}