apibrasil 1.0.0

SDK oficial Rust da plataforma APIBrasil: WhatsApp, SMS, consultas de CPF/CNPJ, veiculos, CEP, correios, pagamentos PIX/boleto e mais.
Documentation
//! Rotas dos serviços da plataforma.

mod common;

use apibrasil::{Config, Method, RequestOptions};
use common::{
    assert_request, build_api, build_api_anonymous, build_api_with, ok, raw_bytes, BASE_URL,
};
use serde_json::json;

#[tokio::test]
async fn auth_guarda_o_token_do_login() {
    let (api, transport) = build_api_anonymous();
    transport.respond_with(ok(json!({ "authorization": { "token": "novo-jwt" } })));

    api.auth()
        .login(json!({ "email": "a@b.c", "password": "x" }))
        .await
        .unwrap();

    assert_request(&transport, Method::Post, "/auth/login", None);
    assert_eq!(api.http().bearer_token().as_deref(), Some("novo-jwt"));
}

#[tokio::test]
async fn auth_guarda_o_token_no_formato_plano() {
    let (api, transport) = build_api();
    transport.respond_with(ok(json!({ "token": "jwt-2fa" })));

    api.auth()
        .verify_2fa(json!({ "challenge": "x", "code": "000000" }))
        .await
        .unwrap();

    assert_request(&transport, Method::Post, "/auth/login/verify-2fa", None);
    assert_eq!(api.http().bearer_token().as_deref(), Some("jwt-2fa"));
}

#[tokio::test]
async fn auth_login_sinaliza_2fa() {
    let (api, transport) = build_api();
    transport.respond_with(ok(json!({ "requires_2fa": true, "challenge": "abc" })));

    let sessao = api.auth().login(json!({ "email": "a@b.c" })).await.unwrap();

    assert!(apibrasil::requires_2fa(&sessao));
    assert_eq!(apibrasil::extract_token(&sessao), None);
}

#[tokio::test]
async fn auth_logout_limpa_o_token() {
    let (api, _transport) = build_api();

    api.auth().logout().await.unwrap();

    assert_eq!(api.http().bearer_token(), None);
}

#[tokio::test]
async fn auth_cobre_perfil_e_senha() {
    let (api, transport) = build_api();

    api.auth().profile().await.unwrap();
    assert_request(&transport, Method::Post, "/profile", None);

    api.auth().me().await.unwrap();
    assert_request(&transport, Method::Get, "/profile/me", None);

    api.auth().update_me(json!({ "name": "x" })).await.unwrap();
    assert_request(&transport, Method::Put, "/profile/me", None);

    api.auth()
        .change_password(json!({ "password": "x" }))
        .await
        .unwrap();
    assert_request(&transport, Method::Post, "/password/change", None);

    api.auth().two_factor_methods().await.unwrap();
    assert_request(&transport, Method::Get, "/auth/2fa/methods", None);
}

#[tokio::test]
async fn devices_store_envia_a_secret_key_no_header() {
    let (api, transport) = build_api_with(Config {
        secret_key: Some("segredo".to_string()),
        ..Default::default()
    });

    api.devices()
        .store(json!({ "device_name": "bot", "type": "server" }))
        .await
        .unwrap();

    assert_request(&transport, Method::Post, "/devices/store", None);
    assert_eq!(
        transport.last_header("SecretKey").as_deref(),
        Some("segredo")
    );
}

#[tokio::test]
async fn devices_store_aceita_secret_key_por_requisicao() {
    let (api, transport) = build_api();

    api.with_options(RequestOptions::new().secret_key("da-chamada"))
        .devices()
        .store(json!({ "device_name": "bot" }))
        .await
        .unwrap();

    assert_eq!(
        transport.last_header("SecretKey").as_deref(),
        Some("da-chamada")
    );
}

#[tokio::test]
async fn devices_show_e_destroy_usam_o_device_do_cliente() {
    let (api, transport) = build_api();

    api.devices().show(None).await.unwrap();
    assert_eq!(transport.last().method, Method::Get);
    assert_eq!(
        transport.last().url,
        format!("{BASE_URL}/devices/show?search=dev")
    );

    api.devices().show(Some("outro")).await.unwrap();
    assert_eq!(
        transport.last().url,
        format!("{BASE_URL}/devices/show?search=outro")
    );

    api.devices().destroy(None).await.unwrap();
    assert_request(
        &transport,
        Method::Delete,
        "/devices/destroy",
        Some(json!({ "search": "dev" })),
    );
}

#[tokio::test]
async fn devices_list_monta_a_query() {
    let (api, transport) = build_api();

    api.devices()
        .list(json!({ "page": 2, "search": "bot" }))
        .await
        .unwrap();

    assert_eq!(
        transport.last().url,
        format!("{BASE_URL}/devices?page=2&search=bot")
    );
}

#[tokio::test]
async fn catalog_cobre_apis_planos_e_documentacoes() {
    let (api, transport) = build_api();

    api.catalog().apis(None).await.unwrap();
    assert_request(&transport, Method::Get, "/apis", None);

    api.catalog().apis(Some("whats app")).await.unwrap();
    assert_eq!(
        transport.last().url,
        format!("{BASE_URL}/apis?search=whats+app")
    );

    api.catalog().api("123").await.unwrap();
    assert_request(&transport, Method::Get, "/apis/123", None);

    api.catalog().api_by_name("API de CNPJ").await.unwrap();
    assert_request(&transport, Method::Get, "/apis/name/API%20de%20CNPJ", None);

    api.catalog().plans().await.unwrap();
    assert_request(&transport, Method::Get, "/plans", None);

    api.catalog().documentations_by_server("srv").await.unwrap();
    assert_request(&transport, Method::Get, "/documentations/server/srv", None);

    api.catalog()
        .endpoint_url(json!({ "action": "sendText" }))
        .await
        .unwrap();
    assert_request(&transport, Method::Post, "/endpoint/url", None);
}

#[tokio::test]
async fn account_cobre_saldo_faturas_e_tickets() {
    let (api, transport) = build_api();

    api.account().balance().await.unwrap();
    assert_request(&transport, Method::Get, "/balance", None);

    api.account().pay_invoice(json!({ "id": 1 })).await.unwrap();
    assert_request(&transport, Method::Post, "/invoices/pay", None);

    api.account().mark_notification_read(7).await.unwrap();
    assert_request(&transport, Method::Patch, "/notifications/7/read", None);

    api.account().mark_all_notifications_read().await.unwrap();
    assert_request(
        &transport,
        Method::Post,
        "/notifications/mark-all-read",
        None,
    );

    api.account()
        .update_ticket(9, json!({ "status": "closed" }))
        .await
        .unwrap();
    assert_request(&transport, Method::Put, "/ticket/9", None);

    api.account().ticket_messages(9).await.unwrap();
    assert_request(&transport, Method::Get, "/ticket/9/messages", None);

    api.account()
        .add_ticket_message(9, json!({ "message": "oi" }))
        .await
        .unwrap();
    assert_request(&transport, Method::Post, "/ticket/9/messages", None);
}

#[tokio::test]
async fn payments_cobre_pix_boleto_e_cartao() {
    let (api, transport) = build_api();

    api.payments()
        .recharge(json!({ "amount": 50, "type": "pix" }))
        .await
        .unwrap();
    assert_request(&transport, Method::Post, "/recharge", None);

    api.payments().recharge_show("REC1").await.unwrap();
    assert_request(&transport, Method::Get, "/recharge/REC1", None);

    api.payments()
        .pix_generate("santander", json!({ "amount": 50 }))
        .await
        .unwrap();
    assert_request(&transport, Method::Post, "/santander/pix/generate", None);

    api.payments().pix_status("santander", "TX1").await.unwrap();
    assert_request(&transport, Method::Get, "/santander/pix/TX1", None);

    api.payments()
        .boleto_generate("inter", json!({ "amount": 50 }))
        .await
        .unwrap();
    assert_request(&transport, Method::Post, "/inter/boleto/generate", None);

    api.payments()
        .card_process(json!({ "token": "x" }))
        .await
        .unwrap();
    assert_request(&transport, Method::Post, "/mercadopago/card/process", None);

    api.payments().card_status("PAY1").await.unwrap();
    assert_request(&transport, Method::Get, "/mercadopago/card/PAY1", None);
}

#[tokio::test]
async fn boleto_pdf_devolve_os_bytes_crus() {
    let (api, transport) = build_api();
    transport.respond_with(raw_bytes(200, b"%PDF-1.4 conteudo"));

    let pdf = api.payments().boleto_pdf("inter", "ID").await.unwrap();

    assert_eq!(pdf, b"%PDF-1.4 conteudo");
    assert_request(&transport, Method::Get, "/inter/boleto/ID/pdf", None);
    assert_eq!(
        transport.last().response_type,
        apibrasil::ResponseType::Bytes
    );
}

#[tokio::test]
async fn seguranca_cobre_ip_whitelist_e_rate_limit() {
    let (api, transport) = build_api();

    api.ip_whitelist().get().await.unwrap();
    assert_request(&transport, Method::Get, "/ip-whitelist", None);

    api.ip_whitelist()
        .set(["1.1.1.1", "2.2.2.2"])
        .await
        .unwrap();
    assert_request(
        &transport,
        Method::Put,
        "/ip-whitelist",
        Some(json!({ "ip_whitelist": ["1.1.1.1", "2.2.2.2"] })),
    );

    api.ip_whitelist().add("3.3.3.3").await.unwrap();
    assert_request(
        &transport,
        Method::Post,
        "/ip-whitelist/add",
        Some(json!({ "entry": "3.3.3.3" })),
    );

    api.ip_whitelist().remove("3.3.3.3").await.unwrap();
    assert_request(&transport, Method::Delete, "/ip-whitelist/remove", None);

    api.ip_whitelist().add_current().await.unwrap();
    assert_request(&transport, Method::Post, "/ip-whitelist/add-current", None);

    api.bearer_rate_limit().get().await.unwrap();
    assert_request(&transport, Method::Get, "/bearer-rate-limit", None);

    api.bearer_rate_limit()
        .set(json!({ "limit": 60 }))
        .await
        .unwrap();
    assert_request(&transport, Method::Put, "/bearer-rate-limit", None);
}

#[tokio::test]
async fn reports_cobre_o_dashboard_de_consumo() {
    let (api, transport) = build_api();

    api.reports().dashboard_stats().await.unwrap();
    assert_request(&transport, Method::Get, "/dashboard/stats", None);

    api.reports().quick_stats().await.unwrap();
    assert_request(&transport, Method::Get, "/reports/quick-stats", None);

    api.reports()
        .generate_consumption_report(json!({ "month": "2026-07" }))
        .await
        .unwrap();
    assert_request(
        &transport,
        Method::Post,
        "/reports/generate-consumption-report",
        None,
    );
}