mod common;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use apibrasil::core::{HttpClient, ResponseData};
use apibrasil::{ApiBrasil, Config, FnHooks, Method, RequestOptions, ResponseType, RetryConfig};
use common::{
build_api, build_api_anonymous, build_api_with, ok, raw_bytes, FakeTransport, BASE_URL,
};
use serde_json::json;
#[tokio::test]
async fn url_nao_duplica_barras() {
let (api, transport) = build_api_with(Config {
base_url: Some(format!("{BASE_URL}/")),
..Default::default()
});
api.request(Method::Get, "/balance", None).await.unwrap();
assert_eq!(transport.last().url, format!("{BASE_URL}/balance"));
}
#[tokio::test]
async fn query_sai_ordenada_e_ignora_nulos() {
let (api, transport) = build_api();
api.with_options(
RequestOptions::new().query(json!({ "z": 1, "a": "x y", "n": null, "b": true })),
)
.request(Method::Get, "balance", None)
.await
.unwrap();
assert_eq!(
transport.last().url,
format!("{BASE_URL}/balance?a=x+y&b=true&z=1")
);
}
#[tokio::test]
async fn headers_do_cliente_e_da_requisicao_sao_aplicados() {
let (api, transport) = build_api_with(Config::new().header("X-Cliente", "sdk"));
api.with_options(RequestOptions::new().header("X-Chamada", "1"))
.request(Method::Get, "balance", None)
.await
.unwrap();
assert_eq!(transport.last_header("X-Cliente").as_deref(), Some("sdk"));
assert_eq!(transport.last_header("X-Chamada").as_deref(), Some("1"));
}
#[tokio::test]
async fn tokens_da_requisicao_sobrescrevem_os_do_cliente() {
let (api, transport) = build_api();
api.with_options(
RequestOptions::new()
.bearer_token("outro-jwt")
.device_token("outro-device"),
)
.request(Method::Get, "balance", None)
.await
.unwrap();
assert_eq!(
transport.last_header("Authorization").as_deref(),
Some("Bearer outro-jwt")
);
assert_eq!(
transport.last_header("DeviceToken").as_deref(),
Some("outro-device")
);
}
#[tokio::test]
async fn sem_credenciais_nao_envia_headers_de_autenticacao() {
let (api, transport) = build_api_anonymous();
api.catalog().status().await.unwrap();
assert_eq!(transport.last_header("Authorization"), None);
assert_eq!(transport.last_header("DeviceToken"), None);
}
#[tokio::test]
async fn tokens_definidos_depois_valem_para_todos_os_servicos() {
let (api, transport) = build_api_anonymous();
api.set_bearer_token("jwt-novo")
.set_device_token("dev-novo");
api.whatsapp()
.send_text(json!({ "text": "oi" }))
.await
.unwrap();
assert_eq!(
transport.last_header("Authorization").as_deref(),
Some("Bearer jwt-novo")
);
assert_eq!(
transport.last_header("DeviceToken").as_deref(),
Some("dev-novo")
);
api.clear_bearer_token().clear_device_token();
api.whatsapp()
.send_text(json!({ "text": "oi" }))
.await
.unwrap();
assert_eq!(transport.last_header("Authorization"), None);
assert_eq!(transport.last_header("DeviceToken"), None);
}
#[tokio::test]
async fn with_device_isola_o_device_e_mantem_as_credenciais() {
let transport = FakeTransport::new();
let api = ApiBrasil::new(Config {
transport: Some(Arc::new(transport.clone())),
retry: Some(RetryConfig::none()),
base_url: Some(BASE_URL.to_string()),
bearer_token: Some("jwt".to_string()),
device_token: Some("dev-1".to_string()),
..Default::default()
});
let outro = api.with_device("dev-2");
outro.whatsapp().send_text(None).await.unwrap();
assert_eq!(
transport.last_header("DeviceToken").as_deref(),
Some("dev-2")
);
assert_eq!(
transport.last_header("Authorization").as_deref(),
Some("Bearer jwt")
);
api.whatsapp().send_text(None).await.unwrap();
assert_eq!(
transport.last_header("DeviceToken").as_deref(),
Some("dev-1")
);
}
#[tokio::test]
async fn resposta_vazia_vira_objeto_vazio() {
let (api, transport) = build_api();
transport.respond_with(apibrasil::TransportResponse {
status: 200,
headers: Default::default(),
data: ResponseData::Value(serde_json::Value::Null),
body: Vec::new(),
});
let response = api.request(Method::Get, "balance", None).await.unwrap();
assert_eq!(response, json!({}));
}
#[tokio::test]
async fn resposta_que_nao_e_objeto_e_embrulhada_em_data() {
let (api, transport) = build_api();
transport.respond_with(ok(json!([1, 2, 3])));
let response = api.request(Method::Get, "servers", None).await.unwrap();
assert_eq!(response, json!({ "data": [1, 2, 3] }));
}
#[tokio::test]
async fn execute_devolve_a_resposta_sem_normalizar() {
let (api, transport) = build_api();
transport.respond_with(ok(json!([1, 2, 3])));
let data = api.execute(Method::Get, "servers", None).await.unwrap();
assert_eq!(data.into_value(), json!([1, 2, 3]));
}
#[tokio::test]
async fn download_pede_bytes_e_devolve_o_corpo_cru() {
let (api, transport) = build_api();
transport.respond_with(raw_bytes(200, &[0x25, 0x50, 0x44, 0x46]));
let bytes = api.download("inter/boleto/1/pdf").await.unwrap();
assert_eq!(bytes, vec![0x25, 0x50, 0x44, 0x46]);
assert_eq!(transport.last().response_type, ResponseType::Bytes);
}
#[tokio::test]
async fn timeout_da_requisicao_chega_ao_transporte() {
let (api, transport) = build_api_with(Config::new().timeout(Duration::from_secs(7)));
api.request(Method::Get, "balance", None).await.unwrap();
assert_eq!(transport.last().timeout, Some(Duration::from_secs(7)));
api.with_options(RequestOptions::new().timeout(Duration::from_secs(1)))
.request(Method::Get, "balance", None)
.await
.unwrap();
assert_eq!(transport.last().timeout, Some(Duration::from_secs(1)));
}
#[tokio::test]
async fn hooks_recebem_requisicao_e_resposta() {
let requisicoes = Arc::new(AtomicUsize::new(0));
let respostas = Arc::new(AtomicUsize::new(0));
let hooks = {
let requisicoes = requisicoes.clone();
let respostas = respostas.clone();
FnHooks::new()
.on_request(move |info| {
assert_eq!(info.method, Method::Post);
assert!(info.url.ends_with("/whatsapp/sendText"));
assert_eq!(info.attempt, 0);
assert!(info.headers.contains_key("Authorization"));
requisicoes.fetch_add(1, Ordering::SeqCst);
})
.on_response(move |info| {
assert_eq!(info.status, 200);
respostas.fetch_add(1, Ordering::SeqCst);
})
};
let (api, _transport) = build_api_with(Config::new().hooks(hooks));
api.whatsapp()
.send_text(json!({ "text": "oi" }))
.await
.unwrap();
assert_eq!(requisicoes.load(Ordering::SeqCst), 1);
assert_eq!(respostas.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn config_reflete_o_estado_atual_do_cliente() {
let (api, _transport) = build_api();
api.set_bearer_token("outro");
let config = api.config();
assert_eq!(config.bearer_token.as_deref(), Some("outro"));
assert_eq!(config.device_token.as_deref(), Some("dev"));
assert_eq!(config.base_url.as_deref(), Some(BASE_URL));
}
#[test]
fn config_do_ambiente_tem_prioridade_menor() {
let doambiente = Config::from_env();
let explicita = doambiente.merge(Config::new().bearer_token("explicito"));
assert_eq!(explicita.bearer_token.as_deref(), Some("explicito"));
}
#[test]
fn cliente_http_usa_os_padroes_da_plataforma() {
let client = HttpClient::new(Config {
base_url: Some(BASE_URL.to_string()),
..Default::default()
});
assert_eq!(client.base_url(), BASE_URL);
assert_eq!(client.timeout(), apibrasil::DEFAULT_TIMEOUT);
assert_eq!(client.url("/balance"), format!("{BASE_URL}/balance"));
}