mecha10-cli 0.8.0

Mecha10 CLI tool
Documentation
//! Latest published build-version lookup.
//!
//! Calls `GET {control_plane_url}/api/catalog/binaries/by-robot/{robot_id}/latest?target={target}`,
//! used by `mecha10 init` when linking a new project to an already-registered robot.

use anyhow::{Context, Result};
use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
struct LatestBinaryVersion {
    version: String,
}

/// Pure URL-construction logic behind [`fetch_latest_build_version`], split out so it's unit
/// testable without a real HTTP client/server (mirrors `publish_status.rs`'s `build_versions_url`).
fn build_latest_url(control_plane_url: &str, robot_id: &str, target: &str) -> String {
    format!(
        "{}/api/catalog/binaries/by-robot/{}/latest?target={}",
        control_plane_url.trim_end_matches('/'),
        robot_id,
        target
    )
}

/// Look up the latest published build version for `robot_id` and `target` ("robot"/"remote").
///
/// Returns `Ok(None)` when nothing has been built/uploaded for this robot yet.
pub async fn fetch_latest_build_version(
    control_plane_url: &str,
    robot_id: &str,
    target: &str,
    api_key: &str,
    user_id: &str,
) -> Result<Option<String>> {
    let url = build_latest_url(control_plane_url, robot_id, target);

    let client = reqwest::Client::new();
    let response = client
        .get(&url)
        .header("Authorization", format!("Bearer {}", api_key))
        .header("X-User-ID", user_id)
        .send()
        .await
        .context("Failed to reach registry")?;

    if response.status().as_u16() == 404 {
        return Ok(None);
    }

    let status = response.status();
    if !status.is_success() {
        let body = response.text().await.unwrap_or_default();
        anyhow::bail!("Latest build version check failed with status {}: {}", status, body);
    }

    let info: LatestBinaryVersion = response
        .json()
        .await
        .context("Failed to parse latest build version response")?;

    Ok(Some(info.version))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn build_latest_url_includes_target_query_param() {
        let url = build_latest_url("https://api.example.com", "robot-123", "remote");
        assert_eq!(
            url,
            "https://api.example.com/api/catalog/binaries/by-robot/robot-123/latest?target=remote"
        );
    }

    #[test]
    fn build_latest_url_trims_trailing_slash_on_control_plane_url() {
        let url = build_latest_url("https://api.example.com/", "robot-123", "robot");
        assert_eq!(
            url,
            "https://api.example.com/api/catalog/binaries/by-robot/robot-123/latest?target=robot"
        );
    }
}