use httpmock::prelude::*;
use serde_json::json;
use slack_rs::api::{execute_api_call, ApiCallArgs, ApiCallContext, ApiClient, ApiClientConfig};
#[tokio::test]
async fn test_api_call_with_form_data() {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method(POST)
.path("/chat.postMessage")
.header("Authorization", "Bearer test-token")
.header("Content-Type", "application/x-www-form-urlencoded");
then.status(200)
.header("content-type", "application/json")
.json_body(json!({
"ok": true,
"channel": "C123456",
"ts": "1234567890.123456"
}));
});
let config = ApiClientConfig {
base_url: server.base_url(),
max_retries: 3,
initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let client = ApiClient::with_config(config);
let args_vec = vec![
"chat.postMessage".to_string(),
"channel=C123456".to_string(),
"text=Hello".to_string(),
];
let args = ApiCallArgs::parse(&args_vec).unwrap();
let context = ApiCallContext {
profile_name: Some("test".to_string()),
team_id: "T123ABC".to_string(),
user_id: "U456DEF".to_string(),
};
let response = execute_api_call(&client, &args, "test-token", &context, "bot", "api call")
.await
.unwrap();
assert_eq!(response.response["ok"], true);
assert_eq!(response.response["channel"], "C123456");
assert_eq!(response.meta.profile_name, Some("test".to_string()));
assert_eq!(response.meta.team_id, "T123ABC");
assert_eq!(response.meta.user_id, "U456DEF");
assert_eq!(response.meta.method, "chat.postMessage");
assert_eq!(response.meta.command, "api call");
assert_eq!(response.meta.token_type, "bot");
mock.assert();
}
#[tokio::test]
async fn test_api_call_with_json_data() {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method(POST)
.path("/chat.postMessage")
.header("Authorization", "Bearer test-token")
.header("Content-Type", "application/json");
then.status(200)
.header("content-type", "application/json")
.json_body(json!({
"ok": true,
"channel": "C123456",
"ts": "1234567890.123456"
}));
});
let config = ApiClientConfig {
base_url: server.base_url(),
max_retries: 3,
initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let client = ApiClient::with_config(config);
let args_vec = vec![
"chat.postMessage".to_string(),
"--json".to_string(),
"channel=C123456".to_string(),
"text=Hello".to_string(),
];
let args = ApiCallArgs::parse(&args_vec).unwrap();
let context = ApiCallContext {
profile_name: None,
team_id: "T123ABC".to_string(),
user_id: "U456DEF".to_string(),
};
let response = execute_api_call(&client, &args, "test-token", &context, "bot", "api call")
.await
.unwrap();
assert_eq!(response.response["ok"], true);
assert_eq!(response.meta.profile_name, None);
assert_eq!(response.meta.team_id, "T123ABC");
assert_eq!(response.meta.command, "api call");
assert_eq!(response.meta.token_type, "bot");
mock.assert();
}
#[tokio::test]
async fn test_api_call_with_get_method() {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method(GET)
.path("/users.info")
.header("Authorization", "Bearer test-token")
.query_param("user", "U123456");
then.status(200)
.header("content-type", "application/json")
.json_body(json!({
"ok": true,
"user": {
"id": "U123456",
"name": "testuser"
}
}));
});
let config = ApiClientConfig {
base_url: server.base_url(),
max_retries: 3,
initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let client = ApiClient::with_config(config);
let args_vec = vec![
"users.info".to_string(),
"--get".to_string(),
"user=U123456".to_string(),
];
let args = ApiCallArgs::parse(&args_vec).unwrap();
let context = ApiCallContext {
profile_name: Some("default".to_string()),
team_id: "T123ABC".to_string(),
user_id: "U456DEF".to_string(),
};
let response = execute_api_call(&client, &args, "test-token", &context, "user", "api call")
.await
.unwrap();
assert_eq!(response.response["ok"], true);
assert_eq!(response.response["user"]["id"], "U123456");
assert_eq!(response.meta.token_type, "user");
mock.assert();
}
#[tokio::test]
async fn test_api_call_retry_on_429() {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method(POST).path("/chat.postMessage");
then.status(429)
.header("Retry-After", "1")
.header("content-type", "application/json")
.json_body(json!({
"ok": false,
"error": "rate_limited"
}));
});
let config = ApiClientConfig {
base_url: server.base_url(),
max_retries: 2, initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let client = ApiClient::with_config(config);
let args_vec = vec![
"chat.postMessage".to_string(),
"channel=C123456".to_string(),
];
let args = ApiCallArgs::parse(&args_vec).unwrap();
let context = ApiCallContext {
profile_name: Some("test".to_string()),
team_id: "T123ABC".to_string(),
user_id: "U456DEF".to_string(),
};
let result = execute_api_call(&client, &args, "test-token", &context, "bot", "api call").await;
assert!(result.is_err());
assert!(
mock.calls() >= 3,
"Expected at least 3 calls (1 initial + 2 retries), got {}",
mock.calls()
);
}
#[tokio::test]
async fn test_output_json_with_meta() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(POST).path("/test.method");
then.status(200)
.header("content-type", "application/json")
.json_body(json!({
"ok": true,
"data": "test"
}));
});
let config = ApiClientConfig {
base_url: server.base_url(),
max_retries: 3,
initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let client = ApiClient::with_config(config);
let args_vec = vec!["test.method".to_string()];
let args = ApiCallArgs::parse(&args_vec).unwrap();
let context = ApiCallContext {
profile_name: Some("production".to_string()),
team_id: "T999XYZ".to_string(),
user_id: "U888ABC".to_string(),
};
let response = execute_api_call(&client, &args, "test-token", &context, "bot", "api call")
.await
.unwrap();
assert_eq!(response.meta.profile_name, Some("production".to_string()));
assert_eq!(response.meta.team_id, "T999XYZ");
assert_eq!(response.meta.user_id, "U888ABC");
assert_eq!(response.meta.method, "test.method");
assert_eq!(response.meta.token_type, "bot");
let json = serde_json::to_value(&response).unwrap();
assert!(json["response"].is_object());
assert!(json["meta"].is_object());
assert_eq!(json["meta"]["profile_name"], "production");
assert_eq!(json["meta"]["team_id"], "T999XYZ");
assert_eq!(json["meta"]["user_id"], "U888ABC");
assert_eq!(json["meta"]["method"], "test.method");
}
#[tokio::test]
async fn test_api_call_conversations_replies_with_get() {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method(GET)
.path("/conversations.replies")
.header("Authorization", "Bearer test-token")
.query_param("channel", "C1234567890")
.query_param("ts", "1234567890.123456");
then.status(200)
.header("content-type", "application/json")
.json_body(json!({
"ok": true,
"messages": [
{
"type": "message",
"user": "U061F7AUR",
"text": "island",
"thread_ts": "1234567890.123456",
"reply_count": 3,
"replies": [
{
"user": "U061F7AUR",
"ts": "1234567890.123456"
}
],
"ts": "1234567890.123456"
}
],
"has_more": false
}));
});
let config = ApiClientConfig {
base_url: server.base_url(),
max_retries: 3,
initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let client = ApiClient::with_config(config);
let args_vec = vec![
"conversations.replies".to_string(),
"--get".to_string(),
"channel=C1234567890".to_string(),
"ts=1234567890.123456".to_string(),
];
let args = ApiCallArgs::parse(&args_vec).unwrap();
let context = ApiCallContext {
profile_name: Some("default".to_string()),
team_id: "T123ABC".to_string(),
user_id: "U456DEF".to_string(),
};
let response = execute_api_call(&client, &args, "test-token", &context, "bot", "api call")
.await
.unwrap();
assert_eq!(response.response["ok"], true);
assert!(response.response["messages"].is_array());
assert_eq!(response.meta.method, "conversations.replies");
mock.assert();
}