qtcloud-devops-cli 0.3.0-rc.8

量潮DevOps云命令行工具
Documentation
use std::path::Path;

use crate::model::release::{FileStorage, ReleaseRecord, ReleaseStatus, Storage};

pub fn run(version: &str, repo_path: &Path) -> Result<String, Box<dyn std::error::Error>> {
    if !crate::commands::release::validate_version(version) {
        return Err(format!("版本号格式错误: {}", version).into());
    }

    let mut storage = FileStorage::new(repo_path);

    if let Some(existing) = storage.load(version) {
        match existing.status {
            ReleaseStatus::Published => {
                return Err(format!("版本 {} 已发布,不可重复 stage", version).into());
            }
            ReleaseStatus::Staged => {
                let now = std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)
                    .unwrap_or_default()
                    .as_secs()
                    .to_string();
                let mut updated = existing.clone();
                updated.updated_at = now;
                storage.save(&updated)?;
                return Ok(updated.id);
            }
            ReleaseStatus::Cancelled => {}
            ReleaseStatus::Retired => {
                return Err(format!("版本 {} 已退役,不可重复 stage", version).into());
            }
        }
    }

    let record = ReleaseRecord::new_staged(version);
    storage.save(&record)?;
    println!("✓ 版本 {} 已进入 Staged 状态 (发布尝试 ID: {})", version, record.id);
    Ok(record.id)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::release::Storage;

    fn make_record(version: &str, status: ReleaseStatus) -> ReleaseRecord {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs()
            .to_string();
        ReleaseRecord {
            id: uuid::Uuid::new_v4().to_string(),
            version: version.to_string(),
            status,
            created_at: now.clone(),
            updated_at: now,
        }
    }

    #[test]
    fn test_stage_new_version() {
        let dir = tempfile::tempdir().unwrap();
        let id = run("v1.0.0", dir.path()).unwrap();
        assert!(!id.is_empty());

        let s = FileStorage::new(dir.path());
        let r = s.load("v1.0.0").unwrap();
        assert_eq!(r.status, ReleaseStatus::Staged);
    }

    #[test]
    fn test_stage_invalid_version() {
        let dir = tempfile::tempdir().unwrap();
        assert!(run("bad", dir.path()).is_err());
    }

    #[test]
    fn test_stage_published_rejected() {
        let dir = tempfile::tempdir().unwrap();
        let mut s = FileStorage::new(dir.path());
        s.save(&make_record("v1.0.0", ReleaseStatus::Published))
            .unwrap();
        let err = run("v1.0.0", dir.path()).unwrap_err().to_string();
        assert!(err.contains("已发布"));
    }

    #[test]
    fn test_stage_cancelled_restage() {
        let dir = tempfile::tempdir().unwrap();
        let old_id;
        {
            let mut s = FileStorage::new(dir.path());
            let r = make_record("v1.0.0", ReleaseStatus::Cancelled);
            old_id = r.id.clone();
            s.save(&r).unwrap();
        }
        let id = run("v1.0.0", dir.path()).unwrap();
        assert!(!id.is_empty());

        let s = FileStorage::new(dir.path());
        let loaded = s.load("v1.0.0").unwrap();
        assert_eq!(loaded.status, ReleaseStatus::Staged);
        assert_ne!(loaded.id, old_id);
    }

    #[test]
    fn test_stage_retired_rejected() {
        let dir = tempfile::tempdir().unwrap();
        let mut s = FileStorage::new(dir.path());
        s.save(&make_record("v1.0.0", ReleaseStatus::Retired))
            .unwrap();
        assert!(run("v1.0.0", dir.path()).unwrap_err().to_string().contains("退役"));
    }

    #[test]
    fn test_stage_idempotent() {
        let dir = tempfile::tempdir().unwrap();
        let id1 = run("v1.0.0", dir.path()).unwrap();
        let id2 = run("v1.0.0", dir.path()).unwrap();
        assert_eq!(id1, id2);
        assert_eq!(FileStorage::new(dir.path()).list().len(), 1);
    }
}