pagbank-cli 0.2.0

CLI para as APIs do PagBank
use anyhow::Result;
use pagbank_sdk::PagBankError;

use crate::config::PbConfig;
use crate::output;

pub async fn run(connect: bool) -> Result<()> {
    let mut ok = true;

    let path = PbConfig::config_path()?;
    if path.exists() {
        println!("✓ Arquivo de configuração: {}", path.display());
    } else {
        println!(
            "✗ Arquivo de configuração não encontrado: {}",
            path.display()
        );
        println!("  Rode 'pb config init' para criar a configuração.");
        ok = false;
    }

    let config = match PbConfig::load() {
        Ok(c) => c,
        Err(e) => {
            println!("✗ Falha ao ler a configuração: {e}");
            print_verdict(ok);
            return Ok(());
        }
    };

    match config
        .default
        .environment
        .parse::<pagbank_sdk::Environment>()
    {
        Ok(env) => println!("✓ Ambiente: {env}"),
        Err(_) => {
            println!("✗ Ambiente inválido: '{}'", config.default.environment);
            ok = false;
        }
    }

    if config.default.token.is_empty() {
        println!("✗ Token de autenticação não configurado");
        println!("  Rode 'pb auth login --token <TOKEN>'");
        ok = false;
    } else {
        println!("✓ Token de autenticação configurado");
    }

    if config.default.recurring_token.is_none() {
        println!("⚠ Token de recorrência não configurado (necessário apenas para recorrência)");
    } else {
        println!("✓ Token de recorrência configurado");
    }

    if config.default.transfer_token.is_none() {
        println!(
            "⚠ Token de transferência não configurado (necessário apenas para transferências)"
        );
    } else {
        println!("✓ Token de transferência configurado");
    }

    if config.default.edi_user.is_some() && config.default.edi_token.is_some() {
        println!("✓ Credenciais EDI configuradas");
    } else {
        println!("⚠ Credenciais EDI não configuradas (necessário apenas para extratos EDI)");
    }

    if connect {
        match check_connectivity(&config).await {
            Ok(status) => println!("✓ API acessível (HTTP {status})"),
            Err(msg) => {
                println!("✗ {msg}");
                ok = false;
            }
        }
    }

    print_verdict(ok);
    if !ok {
        std::process::exit(1);
    }
    Ok(())
}

async fn check_connectivity(config: &PbConfig) -> Result<u16, String> {
    let client = crate::client::make_client(config, None).map_err(|e| e.to_string())?;
    match client.get(pagbank_sdk::Service::Main, "/public-keys").await {
        Ok(resp) => Ok(resp.status().as_u16()),
        Err(e) => Err(map_connectivity_error(&e)),
    }
}

fn map_connectivity_error(e: &PagBankError) -> String {
    match e {
        PagBankError::Api { status, .. } if *status == 401 => {
            "API acessível, mas o token foi rejeitado (401)".to_string()
        }
        PagBankError::Api { status, .. } => format!("API respondeu com status {status}"),
        PagBankError::NoToken => "token não configurado para testar a API".to_string(),
        other => format!("sem conexão com a API: {other}"),
    }
}

fn print_verdict(ok: bool) {
    if ok {
        output::print_success("pb doctor: nenhum problema encontrado");
    } else {
        eprintln!("\nProblemas encontrados. Corrija e rode 'pb doctor' novamente.");
    }
}

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

    #[test]
    fn map_connectivity_401() {
        let err = PagBankError::Api {
            status: 401,
            code: "UNAUTHORIZED".to_string(),
            message: "invalid".to_string(),
        };
        assert!(map_connectivity_error(&err).contains("401"));
        assert!(map_connectivity_error(&err).contains("rejeitado"));
    }

    #[test]
    fn map_connectivity_500() {
        let err = PagBankError::Api {
            status: 500,
            code: "INTERNAL".to_string(),
            message: "boom".to_string(),
        };
        assert!(map_connectivity_error(&err).contains("500"));
    }

    #[test]
    fn map_connectivity_no_token() {
        assert!(map_connectivity_error(&PagBankError::NoToken).contains("token"));
    }
}