Skip to main content

qtcloud_devops_cli/commands/
stage.rs

1use std::path::Path;
2
3use crate::model::release::{FileStorage, ReleaseRecord, ReleaseStatus, Storage};
4
5pub fn run(version: &str, repo_path: &Path) -> Result<String, Box<dyn std::error::Error>> {
6    if !crate::commands::release::validate_version(version) {
7        return Err(format!("版本号格式错误: {}", version).into());
8    }
9
10    let mut storage = FileStorage::new(repo_path);
11
12    if let Some(existing) = storage.load(version) {
13        match existing.status {
14            ReleaseStatus::Published => {
15                return Err(format!("版本 {} 已发布,不可重复 stage", version).into());
16            }
17            ReleaseStatus::Staged => {
18                let now = std::time::SystemTime::now()
19                    .duration_since(std::time::UNIX_EPOCH)
20                    .unwrap_or_default()
21                    .as_secs()
22                    .to_string();
23                let mut updated = existing.clone();
24                updated.updated_at = now;
25                storage.save(&updated)?;
26                return Ok(updated.id);
27            }
28            ReleaseStatus::Cancelled => {}
29            ReleaseStatus::Retired => {
30                return Err(format!("版本 {} 已退役,不可重复 stage", version).into());
31            }
32        }
33    }
34
35    let record = ReleaseRecord::new_staged(version);
36    storage.save(&record)?;
37    println!("✓ 版本 {} 已进入 Staged 状态 (发布尝试 ID: {})", version, record.id);
38    Ok(record.id)
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use crate::model::release::Storage;
45
46    fn make_record(version: &str, status: ReleaseStatus) -> ReleaseRecord {
47        let now = std::time::SystemTime::now()
48            .duration_since(std::time::UNIX_EPOCH)
49            .unwrap_or_default()
50            .as_secs()
51            .to_string();
52        ReleaseRecord {
53            id: uuid::Uuid::new_v4().to_string(),
54            version: version.to_string(),
55            status,
56            created_at: now.clone(),
57            updated_at: now,
58        }
59    }
60
61    #[test]
62    fn test_stage_new_version() {
63        let dir = tempfile::tempdir().unwrap();
64        let id = run("v1.0.0", dir.path()).unwrap();
65        assert!(!id.is_empty());
66
67        let s = FileStorage::new(dir.path());
68        let r = s.load("v1.0.0").unwrap();
69        assert_eq!(r.status, ReleaseStatus::Staged);
70    }
71
72    #[test]
73    fn test_stage_invalid_version() {
74        let dir = tempfile::tempdir().unwrap();
75        assert!(run("bad", dir.path()).is_err());
76    }
77
78    #[test]
79    fn test_stage_published_rejected() {
80        let dir = tempfile::tempdir().unwrap();
81        let mut s = FileStorage::new(dir.path());
82        s.save(&make_record("v1.0.0", ReleaseStatus::Published))
83            .unwrap();
84        let err = run("v1.0.0", dir.path()).unwrap_err().to_string();
85        assert!(err.contains("已发布"));
86    }
87
88    #[test]
89    fn test_stage_cancelled_restage() {
90        let dir = tempfile::tempdir().unwrap();
91        let old_id;
92        {
93            let mut s = FileStorage::new(dir.path());
94            let r = make_record("v1.0.0", ReleaseStatus::Cancelled);
95            old_id = r.id.clone();
96            s.save(&r).unwrap();
97        }
98        let id = run("v1.0.0", dir.path()).unwrap();
99        assert!(!id.is_empty());
100
101        let s = FileStorage::new(dir.path());
102        let loaded = s.load("v1.0.0").unwrap();
103        assert_eq!(loaded.status, ReleaseStatus::Staged);
104        assert_ne!(loaded.id, old_id);
105    }
106
107    #[test]
108    fn test_stage_retired_rejected() {
109        let dir = tempfile::tempdir().unwrap();
110        let mut s = FileStorage::new(dir.path());
111        s.save(&make_record("v1.0.0", ReleaseStatus::Retired))
112            .unwrap();
113        assert!(run("v1.0.0", dir.path()).unwrap_err().to_string().contains("退役"));
114    }
115
116    #[test]
117    fn test_stage_idempotent() {
118        let dir = tempfile::tempdir().unwrap();
119        let id1 = run("v1.0.0", dir.path()).unwrap();
120        let id2 = run("v1.0.0", dir.path()).unwrap();
121        assert_eq!(id1, id2);
122        assert_eq!(FileStorage::new(dir.path()).list().len(), 1);
123    }
124}