← back to kornia__bubbaloop

Function bodies 699 total

All specs Real LLM only Function bodies
run function · rust · L398-L440 (43 LOC)
crates/bubbaloop/src/cli/node.rs
    pub async fn run(self) -> Result<()> {
        match self.action {
            None => {
                Self::print_help();
                Ok(())
            }
            Some(NodeAction::Init(args)) => init_node(args),
            Some(NodeAction::Validate(args)) => validate_node(args),
            Some(NodeAction::List(args)) => list_nodes(args).await,
            Some(NodeAction::Add(args)) => {
                log::warn!("Note: 'bubbaloop node add' is deprecated. Use MCP tool 'install_node' instead.");
                add_node(args).await
            }
            Some(NodeAction::Remove(args)) => {
                log::warn!("Note: 'bubbaloop node remove' is deprecated. Use MCP tool 'remove_node' instead.");
                remove_node(args).await
            }
            Some(NodeAction::Instance(args)) => create_instance(args).await,
            Some(NodeAction::Install(args)) => handle_install(args).await,
            Some(NodeAction::Uninstall(args)) => send_command(&a
print_help function · rust · L441-L469 (29 LOC)
crates/bubbaloop/src/cli/node.rs
    fn print_help() {
        eprintln!("Node management commands\n");
        eprintln!("Usage: bubbaloop node <command>\n");
        eprintln!("Commands:");
        eprintln!("  init        Initialize a new node from template");
        eprintln!("  validate    Validate a node manifest and directory structure");
        eprintln!("  list        List all registered nodes");
        eprintln!("  add         Add a node from local path or GitHub URL");
        eprintln!("  remove      Remove a node from the registry");
        eprintln!("  instance    Create an instance of a multi-instance node");
        eprintln!(
            "              Example: bubbaloop node instance rtsp-camera terrace -c config.yaml"
        );
        eprintln!("  search      Search the node marketplace");
        eprintln!("  discover    Discover available nodes with status");
        eprintln!("  install     Install a node (or from marketplace by name)");
        eprintln!("  uninstall   Uninstall a node's 
get_zenoh_session function · rust · L471-L499 (29 LOC)
crates/bubbaloop/src/cli/node.rs
pub(crate) async fn get_zenoh_session() -> Result<zenoh::Session> {
    // Connect to local zenoh router, or custom endpoint via env var
    let mut config = zenoh::Config::default();

    // Run as client mode - only connect to router, don't listen
    config
        .insert_json5("mode", "\"client\"")
        .map_err(|e| NodeError::Zenoh(e.to_string()))?;

    let endpoint = std::env::var("BUBBALOOP_ZENOH_ENDPOINT")
        .unwrap_or_else(|_| "tcp/127.0.0.1:7447".to_string());
    config
        .insert_json5("connect/endpoints", &format!("[\"{}\"]", endpoint))
        .map_err(|e| NodeError::Zenoh(e.to_string()))?;

    // Disable all scouting to avoid connecting to remote peers via Tailscale
    config
        .insert_json5("scouting/multicast/enabled", "false")
        .map_err(|e| NodeError::Zenoh(e.to_string()))?;
    config
        .insert_json5("scouting/gossip/enabled", "false")
        .map_err(|e| NodeError::Zenoh(e.to_string()))?;

    let session = zenoh::open(config)
init_node function · rust · L500-L561 (62 LOC)
crates/bubbaloop/src/cli/node.rs
fn init_node(args: InitArgs) -> Result<()> {
    // Validate node name before creating any files
    if args.name.is_empty() || args.name.len() > 64 {
        return Err(NodeError::CommandFailed(format!(
            "Node name must be 1-64 characters, got '{}'",
            args.name
        )));
    }
    if !args
        .name
        .chars()
        .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
    {
        return Err(NodeError::CommandFailed(format!(
            "Node name '{}' contains invalid characters (only alphanumeric, hyphens, and underscores allowed)",
            args.name
        )));
    }

    // Determine output directory (default: ./<name> in current directory)
    let output_dir = args
        .output
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from(".").join(&args.name));

    // Use shared template module
    let output_dir = templates::create_node_at(
        &args.name,
        &args.node_type,
        &args.author,
        &args.de
validate_node function · rust · L562-L615 (54 LOC)
crates/bubbaloop/src/cli/node.rs
fn validate_node(args: ValidateArgs) -> Result<()> {
    let path = PathBuf::from(&args.path);

    // 1. Check node.yaml exists
    let manifest_path = path.join("node.yaml");
    if !manifest_path.exists() {
        println!("FAIL: node.yaml not found at {}", manifest_path.display());
        return Err(NodeError::NotFound("node.yaml".into()));
    }
    println!("OK: node.yaml found");

    // 2. Parse manifest
    let content = std::fs::read_to_string(&manifest_path)?;
    let manifest: serde_yaml::Value = serde_yaml::from_str(&content)
        .map_err(|e| NodeError::CommandFailed(format!("Invalid YAML: {}", e)))?;
    println!("OK: node.yaml parses correctly");

    // 3. Check required fields
    let required = ["name", "version", "type"];
    for field in required {
        if manifest.get(field).is_none() {
            println!("FAIL: Missing required field: {}", field);
            return Err(NodeError::CommandFailed(format!(
                "Missing field: {}",
            
list_nodes function · rust · L616-L727 (112 LOC)
crates/bubbaloop/src/cli/node.rs
async fn list_nodes(args: ListArgs) -> Result<()> {
    if args.base && args.instances {
        return Err(NodeError::InvalidArgs(
            "Cannot use --base and --instances together".into(),
        ));
    }

    let session = get_zenoh_session().await?;

    // Retry up to 3 times with 1 second delay between retries
    let mut best_data: Option<NodeListResponse> = None;

    for attempt in 1..=3 {
        let replies_result = session
            .get("bubbaloop/daemon/api/nodes")
            .target(QueryTarget::BestMatching)
            .timeout(std::time::Duration::from_secs(30))
            .await;

        if let Ok(replies) = replies_result {
            for reply in replies {
                if let Ok(sample) = reply.into_result() {
                    if let Ok(data) =
                        serde_json::from_slice::<NodeListResponse>(&sample.payload().to_bytes())
                    {
                        if !data.nodes.is_empty() || best_data.is_none() {
         
discover_nodes_in_subdirs function · rust · L731-L767 (37 LOC)
crates/bubbaloop/src/cli/node.rs
fn discover_nodes_in_subdirs(base_path: &Path) -> Vec<(String, String, String)> {
    let manifest_field = |manifest: &serde_yaml::Value, key: &str| -> String {
        manifest
            .get(key)
            .and_then(|v| v.as_str())
            .unwrap_or("unknown")
            .to_string()
    };

    let mut nodes: Vec<_> = std::fs::read_dir(base_path)
        .into_iter()
        .flatten()
        .flatten()
        .filter(|e| e.path().is_dir())
        .filter_map(|entry| {
            let yaml_path = entry.path().join("node.yaml");
            let content = std::fs::read_to_string(&yaml_path).ok()?;
            match serde_yaml::from_str::<serde_yaml::Value>(&content) {
                Ok(manifest) => {
                    let subdir = entry.file_name().to_string_lossy().to_string();
                    Some((
                        manifest_field(&manifest, "name"),
                        subdir,
                        manifest_field(&manifest, "type"),
                
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
resolve_node_path function · rust · L770-L824 (55 LOC)
crates/bubbaloop/src/cli/node.rs
fn resolve_node_path(base_path: &str, subdir: Option<&str>) -> Result<String> {
    let base = Path::new(base_path);

    if let Some(sub) = subdir {
        // Validate subdir to prevent path traversal
        if sub.is_empty()
            || sub.contains("..")
            || sub.contains('/')
            || sub.contains('\\')
            || sub.starts_with('.')
        {
            return Err(NodeError::InvalidArgs(
                "subdir must be a simple directory name (no paths, no '..')".into(),
            ));
        }
        let node_path = base.join(sub);
        let manifest = node_path.join("node.yaml");
        if !manifest.exists() {
            return Err(NodeError::NotFound(format!(
                "No node.yaml found at {}/{}",
                base_path, sub
            )));
        }
        return Ok(node_path.to_string_lossy().to_string());
    }

    // Check for node.yaml at root
    let manifest = base.join("node.yaml");
    if manifest.exists() {
        retur
add_node function · rust · L825-L914 (90 LOC)
crates/bubbaloop/src/cli/node.rs
async fn add_node(args: AddArgs) -> Result<()> {
    // Normalize source URL
    let source = normalize_git_url(&args.source);

    let base_path = if is_git_url(&source) {
        // Clone from GitHub
        clone_from_github(&source, args.output.as_deref(), &args.branch)?
    } else {
        // Local path
        let path = Path::new(&args.source);
        if !path.exists() {
            return Err(NodeError::NotFound(args.source));
        }
        path.canonicalize()?.to_string_lossy().to_string()
    };

    // Resolve the actual node path (handles --subdir and multi-node discovery)
    let node_path = resolve_node_path(&base_path, args.subdir.as_deref())?;

    // Add to daemon via Zenoh
    let session = get_zenoh_session().await?;

    let payload = serde_json::to_string(&serde_json::json!({
        "command": "add",
        "node_path": node_path,
        "name": args.name,
        "config": args.config,
    }))?;

    let mut node_name: Option<String> = None;

    for att
create_instance function · rust · L933-L1103 (171 LOC)
crates/bubbaloop/src/cli/node.rs
async fn create_instance(args: InstanceArgs) -> Result<()> {
    // Validate suffix (same rules as node names: alphanumeric, hyphens, underscores)
    if args.suffix.is_empty() || args.suffix.len() > 64 {
        return Err(NodeError::InvalidArgs(
            "Instance suffix must be 1-64 characters".into(),
        ));
    }
    if !args
        .suffix
        .chars()
        .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
    {
        return Err(NodeError::InvalidArgs(
            "Instance suffix can only contain alphanumeric characters, hyphens, and underscores"
                .into(),
        ));
    }
    if args.suffix.starts_with('-') || args.suffix.starts_with('_') {
        return Err(NodeError::InvalidArgs(
            "Instance suffix cannot start with hyphen or underscore".into(),
        ));
    }

    // Build the full instance name: base-suffix
    let instance_name = format!("{}-{}", args.base_node, args.suffix);

    // First, find the base node to get its p
find_example_config function · rust · L1106-L1126 (21 LOC)
crates/bubbaloop/src/cli/node.rs
fn find_example_config(configs_dir: &Path) -> Result<PathBuf> {
    let entries: Vec<_> = std::fs::read_dir(configs_dir)?
        .filter_map(|e| e.ok())
        .filter(|e| {
            e.path()
                .extension()
                .map(|ext| ext == "yaml" || ext == "yml")
                .unwrap_or(false)
        })
        .collect();

    if entries.is_empty() {
        return Err(NodeError::NotFound(format!(
            "No .yaml config files found in {}",
            configs_dir.display()
        )));
    }

    // Return the first one found
    Ok(entries[0].path())
}
normalize_git_url function · rust · L1127-L1137 (11 LOC)
crates/bubbaloop/src/cli/node.rs
fn normalize_git_url(source: &str) -> String {
    // If it's an existing local path, return it unchanged
    if std::path::Path::new(source).exists() {
        return source.to_string();
    }
    if source.starts_with("https://") || source.starts_with("git@") {
        source.to_string()
    } else if source.starts_with("github.com/") {
        format!("https://{}", source)
    } else if source.contains('/')
extract_node_name function · rust · L1154-L1170 (17 LOC)
crates/bubbaloop/src/cli/node.rs
fn extract_node_name(path: &str) -> Result<String> {
    let node_yaml = Path::new(path).join("node.yaml");
    if node_yaml.exists() {
        let content = std::fs::read_to_string(&node_yaml)?;
        let manifest: serde_yaml::Value =
            serde_yaml::from_str(&content).map_err(|e| NodeError::CommandFailed(e.to_string()))?;
        if let Some(name) = manifest.get("name").and_then(|v| v.as_str()) {
            return Ok(name.to_string());
        }
    }
    // Fallback to directory name
    Path::new(path)
        .file_name()
        .map(|s| s.to_string_lossy().to_string())
        .ok_or_else(|| NodeError::CommandFailed("Cannot extract node name".into()))
}
clone_from_github function · rust · L1171-L1248 (78 LOC)
crates/bubbaloop/src/cli/node.rs
fn clone_from_github(url: &str, output: Option<&str>, branch: &str) -> Result<String> {
    // Prevent argument injection via branch or URL starting with '-'
    if branch.starts_with('-') {
        return Err(NodeError::InvalidUrl(format!(
            "Invalid branch name: {}",
            branch
        )));
    }
    if url.starts_with('-') {
        return Err(NodeError::InvalidUrl(format!("Invalid URL: {}", url)));
    }

    // Extract repo name from URL
    let repo_name = url
        .trim_end_matches('/')
        .trim_end_matches(".git")
        .rsplit('/')
        .next()
        .ok_or_else(|| NodeError::InvalidUrl(url.to_string()))?;

    // Determine target directory
    let target_dir = if let Some(out) = output {
        PathBuf::from(out)
    } else {
        let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
        PathBuf::from(home)
            .join(".bubbaloop")
            .join("nodes")
            .join(repo_name)
    };

    if target_
remove_node function · rust · L1249-L1289 (41 LOC)
crates/bubbaloop/src/cli/node.rs
async fn remove_node(args: RemoveArgs) -> Result<()> {
    let session = get_zenoh_session().await?;

    let payload = serde_json::to_string(&serde_json::json!({
        "command": "remove"
    }))?;

    let key = format!("bubbaloop/daemon/api/nodes/{}/command", args.name);
    let replies: Vec<_> = session
        .get(&key)
        .payload(payload)
        .target(QueryTarget::BestMatching)
        .timeout(std::time::Duration::from_secs(30))
        .await
        .map_err(|e| NodeError::Zenoh(e.to_string()))?
        .into_iter()
        .collect();

    for reply in replies {
        if let Ok(sample) = reply.into_result() {
            let data: CommandResponse = serde_json::from_slice(&sample.payload().to_bytes())?;
            if data.success {
                println!("Removed node: {}", args.name);
            } else {
                return Err(NodeError::CommandFailed(data.message));
            }
        }
    }

    if args.delete_files {
        // Get node path firs
If a scraper extracted this row, it came from Repobility (https://repobility.com)
send_command function · rust · L1290-L1336 (47 LOC)
crates/bubbaloop/src/cli/node.rs
pub(crate) async fn send_command(name: &str, command: &str) -> Result<()> {
    let session = get_zenoh_session().await?;
    let payload = serde_json::to_string(&serde_json::json!({"command": command}))?;
    let key = format!("bubbaloop/daemon/api/nodes/{}/command", name);

    let mut last_error = None;

    for attempt in 1..=3 {
        if let Ok(replies) = session
            .get(&key)
            .payload(payload.clone())
            .target(QueryTarget::BestMatching)
            .timeout(std::time::Duration::from_secs(30))
            .await
        {
            for reply in replies {
                if let Ok(sample) = reply.into_result() {
                    let data: CommandResponse =
                        serde_json::from_slice(&sample.payload().to_bytes())?;
                    if data.success {
                        println!("{}", data.message);
                        if !data.output.is_empty() {
                            println!("{}", data.output);
          
search_nodes function · rust · L1612-L1668 (57 LOC)
crates/bubbaloop/src/cli/node.rs
fn search_nodes(args: SearchArgs) -> Result<()> {
    log::info!(
        "node search: query={:?} category={:?} tag={:?}",
        args.query,
        args.category,
        args.tag
    );
    println!("Refreshing marketplace registry...");
    if let Err(e) = registry::refresh_cache() {
        log::warn!("registry refresh failed: {}", e);
        eprintln!("Warning: could not refresh registry (using cache): {}", e);
    }
    let all_nodes = registry::load_cached_registry();

    if all_nodes.is_empty() {
        println!("No nodes found in marketplace registry.");
        println!("The registry cache may not have been fetched yet.");
        return Ok(());
    }

    let results = registry::search_registry(
        &all_nodes,
        &args.query,
        args.category.as_deref(),
        args.tag.as_deref(),
    );

    if results.is_empty() {
        println!("No nodes matching your search.");
        if !args.query.is_empty() || args.category.is_some() || args.tag.is_some() {
discover_nodes function · rust · L1669-L1782 (114 LOC)
crates/bubbaloop/src/cli/node.rs
async fn discover_nodes(args: DiscoverArgs) -> Result<()> {
    // Refresh marketplace cache
    if let Err(e) = registry::refresh_cache() {
        log::warn!("registry refresh failed: {}", e);
        eprintln!("Warning: could not refresh registry (using cache): {}", e);
    }
    let all_marketplace = registry::load_cached_registry();

    // Query daemon for registered nodes
    let registered: Vec<NodeState> = match get_zenoh_session().await {
        Ok(session) => {
            let result = session
                .get("bubbaloop/daemon/api/nodes")
                .target(QueryTarget::BestMatching)
                .timeout(std::time::Duration::from_secs(5))
                .await;
            let mut nodes = Vec::new();
            if let Ok(replies) = result {
                for reply in replies {
                    if let Ok(sample) = reply.into_result() {
                        if let Ok(data) =
                            serde_json::from_slice::<NodeListResponse>(&sampl
view_logs function · rust · L1783-L1831 (49 LOC)
crates/bubbaloop/src/cli/node.rs
async fn view_logs(args: LogsArgs) -> Result<()> {
    if args.follow {
        // Use journalctl directly for follow mode
        let service = format!("bubbaloop-{}.service", args.name);
        let status = Command::new("journalctl")
            .args(["--user", "-u", &service, "-f", "--no-pager"])
            .status()?;

        if !status.success() {
            // Fallback to systemctl status
            let _ = Command::new("systemctl")
                .args(["--user", "status", "-l", "--no-pager", &service])
                .status();
        }
        return Ok(());
    }

    let session = get_zenoh_session().await?;

    let key = format!("bubbaloop/daemon/api/nodes/{}/logs", args.name);
    let replies: Vec<_> = session
        .get(&key)
        .target(QueryTarget::BestMatching)
        .timeout(std::time::Duration::from_secs(30))
        .await
        .map_err(|e| NodeError::Zenoh(e.to_string()))?
        .into_iter()
        .collect();

    for reply in replies {
  
truncate function · rust · L1832-L1848 (17 LOC)
crates/bubbaloop/src/cli/node.rs
fn truncate(s: &str, max: usize) -> String {
    if s.len() <= max {
        return s.to_string();
    }
    // Find the last char that *ends* at or before byte position max-3.
    let target = max.saturating_sub(3);
    let mut end = 0;
    for (i, c) in s.char_indices() {
        let char_end = i + c.len_utf8();
        if char_end > target {
            break;
        }
        end = char_end;
    }
    format!("{}...", &s[..end])
}
test_normalize_git_url_full_https function · rust · L1855-L1860 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_normalize_git_url_full_https() {
        assert_eq!(
            normalize_git_url("https://github.com/user/repo"),
            "https://github.com/user/repo"
        );
    }
test_normalize_git_url_ssh function · rust · L1863-L1868 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_normalize_git_url_ssh() {
        assert_eq!(
            normalize_git_url("[email protected]:user/repo.git"),
            "[email protected]:user/repo.git"
        );
    }
test_normalize_git_url_with_github_prefix function · rust · L1871-L1884 (14 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_normalize_git_url_with_github_prefix() {
        assert_eq!(
            normalize_git_url("github.com/user/repo"),
            "https://github.com/user/repo"
        );
    }

    #[test]
    fn test_normalize_git_url_shorthand() {
        assert_eq!(
            normalize_git_url("user/repo"),
            "https://github.com/user/repo"
        );
    }
Repobility (the analyzer behind this table) · https://repobility.com
test_normalize_git_url_shorthand function · rust · L1879-L1902 (24 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_normalize_git_url_shorthand() {
        assert_eq!(
            normalize_git_url("user/repo"),
            "https://github.com/user/repo"
        );
    }

    #[test]
    fn test_normalize_git_url_local_path() {
        assert_eq!(normalize_git_url("/path/to/node"), "/path/to/node");
    }

    #[test]
    fn test_normalize_git_url_relative_path() {
        // Relative paths starting with . should be preserved as local paths
        assert_eq!(normalize_git_url("./node"), "./node");
        assert_eq!(normalize_git_url("../my-node"), "../my-node");
        assert_eq!(normalize_git_url("./path/to/node"), "./path/to/node");
    }

    #[test]
    fn test_is_git_url_https() {
        assert!(is_git_url("https://github.com/user/repo"));
    }
test_normalize_git_url_relative_path function · rust · L1892-L1897 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_normalize_git_url_relative_path() {
        // Relative paths starting with . should be preserved as local paths
        assert_eq!(normalize_git_url("./node"), "./node");
        assert_eq!(normalize_git_url("../my-node"), "../my-node");
        assert_eq!(normalize_git_url("./path/to/node"), "./path/to/node");
    }
test_is_git_url_https function · rust · L1900-L1975 (76 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_is_git_url_https() {
        assert!(is_git_url("https://github.com/user/repo"));
    }

    #[test]
    fn test_is_git_url_ssh() {
        assert!(is_git_url("[email protected]:user/repo.git"));
    }

    #[test]
    fn test_is_git_url_with_prefix() {
        assert!(is_git_url("github.com/user/repo"));
    }

    #[test]
    fn test_is_git_url_local_path() {
        assert!(!is_git_url("/path/to/node"));
    }

    #[test]
    fn test_is_git_url_relative_path() {
        assert!(!is_git_url("./node"));
    }

    #[test]
    fn test_truncate_short_string() {
        assert_eq!(truncate("hello", 10), "hello");
    }

    #[test]
    fn test_truncate_exact_length() {
        assert_eq!(truncate("hello", 5), "hello");
    }

    #[test]
    fn test_truncate_long_string() {
        assert_eq!(truncate("hello world", 8), "hello...");
    }

    #[test]
    fn test_truncate_very_long_string() {
        let long = "This is a very long description that exceeds the maximum length";
test_truncate_very_long_string function · rust · L1940-L1945 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_truncate_very_long_string() {
        let long = "This is a very long description that exceeds the maximum length";
        // Function takes first (max - 3) chars and adds "..."
        assert_eq!(truncate(long, 30), "This is a very long descrip...");
        assert_eq!(truncate(long, 30).len(), 30);
    }
test_truncate_multibyte_utf8 function · rust · L1948-L1962 (15 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_truncate_multibyte_utf8() {
        // Should not panic when truncation would fall inside a multi-byte char
        let s = "cafe\u{0301} is great"; // "café is great" with combining accent
        let result = truncate(s, 8);
        assert!(result.ends_with("..."));
        // Result must be valid UTF-8 and not exceed max bytes
        assert!(result.len() <= 8);

        // Pure multi-byte: each snowman is 3 bytes, 5 snowmen = 15 bytes
        let s = "\u{2603}\u{2603}\u{2603}\u{2603}\u{2603}";
        let result = truncate(s, 10);
        assert!(result.ends_with("..."));
        // 10 - 3 = 7 target bytes, fits 2 snowmen (6 bytes) + "..." = 9
        assert_eq!(result, "\u{2603}\u{2603}...");
    }
test_command_request_serialization function · rust · L1965-L1975 (11 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_command_request_serialization() {
        let req = serde_json::json!({
            "command": "start",
            "node_path": "/path/to/node"
        });

        let json = serde_json::to_string(&req).unwrap();
        assert!(json.contains("\"command\""));
        assert!(json.contains("\"start\""));
        assert!(json.contains("\"node_path\""));
    }
test_command_response_deserialization function · rust · L1978-L1983 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_command_response_deserialization() {
        let json = r#"{"success": true, "message": "Node started", "output": ""}"#;
        let response: CommandResponse = serde_json::from_str(json).unwrap();
        assert!(response.success);
        assert_eq!(response.message, "Node started");
    }
test_command_response_with_output function · rust · L1986-L1991 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_command_response_with_output() {
        let json = r#"{"success": true, "message": "Built", "output": "Compiling..."}"#;
        let response: CommandResponse = serde_json::from_str(json).unwrap();
        assert!(response.success);
        assert_eq!(response.output, "Compiling...");
    }
Want this analysis on your repo? https://repobility.com/scan/
test_node_state_serialization function · rust · L1994-L2011 (18 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_node_state_serialization() {
        let node = NodeState {
            name: "test-node".to_string(),
            path: "/path/to/node".to_string(),
            status: "running".to_string(),
            installed: true,
            autostart_enabled: false,
            version: "1.0.0".to_string(),
            description: "Test node".to_string(),
            node_type: "rust".to_string(),
            is_built: true,
            base_node: String::new(),
        };

        let json = serde_json::to_string(&node).unwrap();
        assert!(json.contains("test-node"));
        assert!(json.contains("running"));
    }
test_node_list_response_deserialization function · rust · L2014-L2021 (8 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_node_list_response_deserialization() {
        let json = r#"{"nodes": [{"name": "node1", "path": "/path", "status": "running",
                     "installed": true, "autostart_enabled": false, "version": "1.0.0",
                     "description": "Test", "node_type": "rust", "is_built": true}]}"#;
        let response: NodeListResponse = serde_json::from_str(json).unwrap();
        assert_eq!(response.nodes.len(), 1);
        assert_eq!(response.nodes[0].name, "node1");
    }
test_logs_response_deserialization function · rust · L2024-L2030 (7 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_logs_response_deserialization() {
        let json = r#"{"lines": ["line1", "line2"], "success": true}"#;
        let response: LogsResponse = serde_json::from_str(json).unwrap();
        assert!(response.success);
        assert_eq!(response.lines.len(), 2);
        assert_eq!(response.lines[0], "line1");
    }
test_logs_response_with_error function · rust · L2033-L2038 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_logs_response_with_error() {
        let json = r#"{"lines": [], "success": false, "error": "Node not found"}"#;
        let response: LogsResponse = serde_json::from_str(json).unwrap();
        assert!(!response.success);
        assert_eq!(response.error, Some("Node not found".to_string()));
    }
test_clone_rejects_branch_argument_injection function · rust · L2041-L2073 (33 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_clone_rejects_branch_argument_injection() {
        // Branch starting with '-' could be interpreted as a git flag
        let result = clone_from_github("https://github.com/user/repo", None, "--upload-pack=evil");
        assert!(result.is_err());
        let err = result.unwrap_err().to_string();
        assert!(err.contains("Invalid branch name"));
    }

    #[test]
    fn test_clone_rejects_url_argument_injection() {
        let result = clone_from_github("--upload-pack=evil", None, "main");
        assert!(result.is_err());
        let err = result.unwrap_err().to_string();
        assert!(err.contains("Invalid URL"));
    }

    #[test]
    fn test_clone_accepts_valid_branch() {
        // This will fail at the git clone step (no network), but should not
        // fail at the argument validation step. We check by verifying the error
        // is NOT about an invalid branch/URL.
        let result = clone_from_github(
            "https://github.com/user/repo",
    
test_clone_rejects_url_argument_injection function · rust · L2050-L2055 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_clone_rejects_url_argument_injection() {
        let result = clone_from_github("--upload-pack=evil", None, "main");
        assert!(result.is_err());
        let err = result.unwrap_err().to_string();
        assert!(err.contains("Invalid URL"));
    }
test_clone_accepts_valid_branch function · rust · L2058-L2129 (72 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_clone_accepts_valid_branch() {
        // This will fail at the git clone step (no network), but should not
        // fail at the argument validation step. We check by verifying the error
        // is NOT about an invalid branch/URL.
        let result = clone_from_github(
            "https://github.com/user/repo",
            Some("/tmp/bubbaloop-test-nonexistent"),
            "main",
        );
        // Either succeeds or fails for a reason other than argument injection
        if let Err(e) = result {
            let msg = e.to_string();
            assert!(!msg.contains("Invalid branch name"));
            assert!(!msg.contains("Invalid URL"));
        }
    }

    /// Validate node name checking logic (mirrors submit_create_node_form validation)
    fn is_valid_node_name(name: &str) -> bool {
        !name.is_empty()
            && name
                .chars()
                .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
            && !name.starts_with(
is_valid_node_name function · rust · L2076-L2083 (8 LOC)
crates/bubbaloop/src/cli/node.rs
    fn is_valid_node_name(name: &str) -> bool {
        !name.is_empty()
            && name
                .chars()
                .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
            && !name.starts_with('-')
            && !name.starts_with('.')
    }
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
test_valid_node_names function · rust · L2086-L2091 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_valid_node_names() {
        assert!(is_valid_node_name("my-node"));
        assert!(is_valid_node_name("my_node"));
        assert!(is_valid_node_name("sensor1"));
        assert!(is_valid_node_name("MyNode"));
    }
test_invalid_node_names_special_chars function · rust · L2101-L2106 (6 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_invalid_node_names_special_chars() {
        assert!(!is_valid_node_name("node;evil"));
        assert!(!is_valid_node_name("node name"));
        assert!(!is_valid_node_name("node&evil"));
        assert!(!is_valid_node_name("$HOME"));
    }
test_resolve_node_path_single_node_at_root function · rust · L2118-L2129 (12 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_resolve_node_path_single_node_at_root() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("node.yaml"),
            "name: test-node\nversion: \"0.1.0\"\ntype: rust",
        )
        .unwrap();

        let result = resolve_node_path(dir.path().to_str().unwrap(), None);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), dir.path().to_str().unwrap());
    }
test_resolve_node_path_with_subdir function · rust · L2132-L2145 (14 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_resolve_node_path_with_subdir() {
        let dir = tempfile::tempdir().unwrap();
        let subdir = dir.path().join("my-node");
        std::fs::create_dir(&subdir).unwrap();
        std::fs::write(
            subdir.join("node.yaml"),
            "name: my-node\nversion: \"0.1.0\"\ntype: rust",
        )
        .unwrap();

        let result = resolve_node_path(dir.path().to_str().unwrap(), Some("my-node"));
        assert!(result.is_ok());
        assert!(result.unwrap().ends_with("my-node"));
    }
test_resolve_node_path_subdir_missing_manifest function · rust · L2148-L2157 (10 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_resolve_node_path_subdir_missing_manifest() {
        let dir = tempfile::tempdir().unwrap();
        let subdir = dir.path().join("empty-dir");
        std::fs::create_dir(&subdir).unwrap();

        let result = resolve_node_path(dir.path().to_str().unwrap(), Some("empty-dir"));
        assert!(result.is_err());
        let err = result.unwrap_err().to_string();
        assert!(err.contains("No node.yaml found"));
    }
test_resolve_node_path_multi_node_discovery function · rust · L2160-L2182 (23 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_resolve_node_path_multi_node_discovery() {
        let dir = tempfile::tempdir().unwrap();

        // Create two node subdirectories
        for name in &["camera", "weather"] {
            let subdir = dir.path().join(name);
            std::fs::create_dir(&subdir).unwrap();
            std::fs::write(
                subdir.join("node.yaml"),
                format!("name: {}\nversion: \"0.1.0\"\ntype: rust", name),
            )
            .unwrap();
        }

        // No node.yaml at root, no --subdir -> should discover and error
        let result = resolve_node_path(dir.path().to_str().unwrap(), None);
        assert!(result.is_err());
        let err = result.unwrap_err().to_string();
        assert!(err.contains("Found 2 node(s)"));
        assert!(err.contains("camera"));
        assert!(err.contains("weather"));
        assert!(err.contains("--subdir"));
    }
test_resolve_node_path_no_nodes_found function · rust · L2185-L2192 (8 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_resolve_node_path_no_nodes_found() {
        let dir = tempfile::tempdir().unwrap();
        // Empty directory, no node.yaml anywhere
        let result = resolve_node_path(dir.path().to_str().unwrap(), None);
        assert!(result.is_err());
        let err = result.unwrap_err().to_string();
        assert!(err.contains("No node.yaml found"));
    }
test_resolve_node_path_rejects_path_traversal function · rust · L2195-L2216 (22 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_resolve_node_path_rejects_path_traversal() {
        let dir = tempfile::tempdir().unwrap();
        // ".." traversal
        let result = resolve_node_path(dir.path().to_str().unwrap(), Some("../etc"));
        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("simple directory name"));

        // Slash in subdir
        let result = resolve_node_path(dir.path().to_str().unwrap(), Some("foo/bar"));
        assert!(result.is_err());

        // Hidden directory
        let result = resolve_node_path(dir.path().to_str().unwrap(), Some(".hidden"));
        assert!(result.is_err());

        // Empty string
        let result = resolve_node_path(dir.path().to_str().unwrap(), Some(""));
        assert!(result.is_err());
    }
If a scraper extracted this row, it came from Repobility (https://repobility.com)
test_discover_nodes_in_subdirs function · rust · L2219-L2239 (21 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_discover_nodes_in_subdirs() {
        let dir = tempfile::tempdir().unwrap();

        for (name, node_type) in &[("sensor", "rust"), ("bridge", "python")] {
            let subdir = dir.path().join(name);
            std::fs::create_dir(&subdir).unwrap();
            std::fs::write(
                subdir.join("node.yaml"),
                format!("name: {}\nversion: \"0.1.0\"\ntype: {}", name, node_type),
            )
            .unwrap();
        }

        let nodes = discover_nodes_in_subdirs(dir.path());
        assert_eq!(nodes.len(), 2);
        // Sorted by name
        assert_eq!(nodes[0].0, "bridge");
        assert_eq!(nodes[0].2, "python");
        assert_eq!(nodes[1].0, "sensor");
        assert_eq!(nodes[1].2, "rust");
    }
test_node_state_base_node_deserialization function · rust · L2242-L2249 (8 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_node_state_base_node_deserialization() {
        let json = r#"{"name": "rtsp-camera-terrace", "path": "/path", "status": "running",
                     "installed": true, "autostart_enabled": false, "version": "1.0.0",
                     "description": "Test", "node_type": "rust", "is_built": true,
                     "base_node": "rtsp-camera"}"#;
        let node: NodeState = serde_json::from_str(json).unwrap();
        assert_eq!(node.base_node, "rtsp-camera");
    }
test_node_state_base_node_defaults_empty function · rust · L2252-L2258 (7 LOC)
crates/bubbaloop/src/cli/node.rs
    fn test_node_state_base_node_defaults_empty() {
        let json = r#"{"name": "openmeteo", "path": "/path", "status": "stopped",
                     "installed": true, "autostart_enabled": false, "version": "1.0.0",
                     "description": "Weather", "node_type": "rust", "is_built": true}"#;
        let node: NodeState = serde_json::from_str(json).unwrap();
        assert_eq!(node.base_node, "");
    }
‹ prevpage 3 / 14next ›