use crate::config::Config;
use crate::error::{Error, Result};
use crate::file::SUPPORTED_FILE_TYPES;
use crate::http::api_paths::SOCIAL_MEDIA;
use crate::models::{BaseResponse, UploadSocialMediaOptions};
use crate::utils::{determine_content_type, is_valid_url};
use crate::UploadResult;
use reqwest::{Client as ReqwestClient, ClientBuilder, Response, StatusCode};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::path::Path;
use std::time::Duration;
pub mod api_paths {
pub const SIGNED_URL: &str = "/api/files/aws-presigned";
pub const MEDIA_RESULT: &str = "/api/media/users";
pub const ALL_MEDIA_RESULTS: &str = "/api/v2/media/users/pages";
pub const SOCIAL_MEDIA: &str = "/api/files/social";
}
pub struct HttpClient {
client: ReqwestClient,
config: Config,
}
impl HttpClient {
pub fn new(config: Config) -> Result<Self> {
config.validate()?;
let client = ClientBuilder::new()
.user_agent("realitydefender-go-sdk/1.0")
.timeout(Duration::from_secs(config.get_timeout_seconds()))
.build()?;
Ok(Self { client, config })
}
pub async fn get<T: DeserializeOwned>(&self, endpoint: &str) -> Result<T> {
let url = format!("{}{}", self.config.get_base_url(), endpoint);
let request = self
.client
.get(&url)
.header("X-API-KEY", &self.config.api_key)
.header("Accept", "application/json")
.header("Accept-Encoding", "gzip")
.build()?;
let response = self.client.execute(request).await?;
self.handle_response(response).await
}
pub async fn get_with_params<T: DeserializeOwned>(
&self,
endpoint: &str,
params: &[(&str, &str)],
) -> Result<T> {
let url = format!("{}{}", self.config.get_base_url(), endpoint);
let request = self
.client
.get(&url)
.query(params)
.header("X-API-KEY", &self.config.api_key)
.header("Accept", "application/json")
.header("Accept-Encoding", "gzip")
.build()?;
let response = self.client.execute(request).await?;
self.handle_response(response).await
}
pub async fn post<T: DeserializeOwned, D: Serialize>(
&self,
endpoint: &str,
data: &D,
) -> Result<T> {
let url = format!("{}{}", self.config.get_base_url(), endpoint);
let request = self
.client
.post(&url)
.header("X-API-KEY", &self.config.api_key)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Accept-Encoding", "gzip")
.json(data)
.build()?;
let response = self.client.execute(request).await?;
self.handle_response(response).await
}
pub async fn put(&self, url: &str, data: Vec<u8>, content_type: &str) -> Result<()> {
let request = self
.client
.put(url)
.header("Content-Type", content_type)
.header("Content-Length", data.len().to_string())
.body(data.clone())
.build()?;
let response = self.client.execute(request).await?;
let status = response.status();
if !status.is_success() {
let body = response.text().await?;
return Err(Error::UploadFailed(format!(
"Failed to upload to presigned URL. Status: {status} Body: {body}"
)));
}
Ok(())
}
pub async fn upload_file<T: DeserializeOwned>(&self, file_path: &str) -> Result<T> {
let path = Path::new(file_path);
if !path.exists() {
return Err(Error::InvalidFile(format!("File not found: {file_path}")));
}
let file_extension = path
.extension()
.ok_or_else(|| Error::InvalidFile("Invalid file name".to_string()))?;
let supported_file_type = SUPPORTED_FILE_TYPES
.iter()
.find(|x| x.extensions.contains(&file_extension.to_str().unwrap()));
if supported_file_type.is_none() {
return Err(Error::InvalidFile(format!(
"Unsupported file type: {file_path}"
)));
}
let file_size = path.metadata()?.len();
if file_size > supported_file_type.unwrap().size_limit {
return Err(Error::InvalidFile(format!("File too large: {file_path}")));
}
let file_name = path
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| Error::InvalidFile("Invalid file name".to_string()))?;
let payload = serde_json::json!({ "fileName": file_name });
let signed_url_response = self
.post::<crate::models::SignedUrlResponse, _>(api_paths::SIGNED_URL, &payload)
.await?;
let file_content = tokio::fs::read(path).await?;
if file_content.is_empty() {
let std_file_content = std::fs::read(path)?;
if std_file_content.is_empty() {
return Err(Error::InvalidFile(format!("File is empty: {file_path}")));
}
return self
.upload_file_with_content(signed_url_response, std_file_content, path)
.await;
}
let content_type = determine_content_type(path);
self.put(
&signed_url_response.response.signed_url,
file_content,
content_type,
)
.await?;
let upload_result = UploadResult {
request_id: signed_url_response.request_id,
media_id: Option::from(signed_url_response.media_id),
result_url: None,
};
Ok(serde_json::from_value(serde_json::to_value(
upload_result,
)?)?)
}
async fn upload_file_with_content<T: DeserializeOwned>(
&self,
signed_url_response: crate::models::SignedUrlResponse,
file_content: Vec<u8>,
path: &Path,
) -> Result<T> {
let content_type = determine_content_type(path);
self.put(
&signed_url_response.response.signed_url,
file_content,
content_type,
)
.await?;
let upload_result = UploadResult {
request_id: signed_url_response.request_id,
media_id: Option::from(signed_url_response.media_id),
result_url: None,
};
Ok(serde_json::from_value(serde_json::to_value(
upload_result,
)?)?)
}
pub async fn upload_social_media_link(&self, social_media_link: &str) -> Result<UploadResult> {
is_valid_url(social_media_link)?;
let response: BaseResponse = self
.post(
SOCIAL_MEDIA,
&UploadSocialMediaOptions {
social_link: social_media_link.to_string(),
},
)
.await?;
Ok(UploadResult {
request_id: response.request_id.unwrap(),
media_id: None,
result_url: None,
})
}
async fn handle_response<T: DeserializeOwned>(&self, response: Response) -> Result<T> {
let status = response.status();
let body = response.bytes().await?;
if status == StatusCode::OK || status == StatusCode::CREATED {
return Ok(serde_json::from_slice(&body)?);
}
let response: BaseResponse =
if let Ok(base_response) = serde_json::from_slice::<BaseResponse>(&body) {
base_response
} else {
BaseResponse {
code: "UNKNOWN".to_string(),
response: format!("Unknown error (HTTP {status})"),
errno: -1,
..Default::default()
}
};
match status {
StatusCode::BAD_REQUEST => {
if response.code == "free-tier-not-allowed"
|| response.code == "upload-limit-reached"
{
Err(Error::Unauthorized(response.response))
} else {
Err(Error::InvalidRequest(response.response))
}
}
StatusCode::UNAUTHORIZED => Err(Error::Unauthorized("Invalid API key".to_string())),
StatusCode::NOT_FOUND => Err(Error::NotFound),
_ => Err(Error::ServerError(response.response)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Client, UploadOptions};
use mockito::Matcher;
use serde_json::json;
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;
#[tokio::test]
async fn test_client_new() {
let config = Config {
api_key: "test_api_key".to_string(),
..Default::default()
};
let client = Client::new(config);
assert!(client.is_ok());
let invalid_config = Config {
api_key: "".to_string(),
..Default::default()
};
let client = Client::new(invalid_config);
assert!(client.is_err());
}
#[tokio::test]
async fn test_client_with_custom_url() {
let server = mockito::Server::new_async().await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config);
assert!(client.is_ok());
}
#[tokio::test]
async fn test_api_error_handling() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("GET", "/api/media/users/test-request")
.with_status(401)
.with_header("content-type", "application/json")
.with_body(r#"{"response": "Unauthorized access"}"#)
.create_async()
.await;
let config = Config {
api_key: "invalid_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client.get_result("test-request", None).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::Unauthorized(..) => {} err => panic!("Unexpected error: {:?}", err),
}
let _m = server
.mock("GET", "/api/media/users/not-found")
.with_status(404)
.with_header("content-type", "application/json")
.with_body(r#"{"response": "Resource not found"}"#)
.create_async()
.await;
let result = client.get_result("not-found", None).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::NotFound => {} err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_upload_file_flow() {
let mut server = mockito::Server::new_async().await;
println!("Server URL: {}", server.url());
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.jpg");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"test image data").unwrap();
println!("Created test file at: {:?}", file_path);
let _m1 = server
.mock("POST", "/api/files/aws-presigned")
.with_status(200)
.with_header("content-type", "application/json")
.match_header("X-API-KEY", "test_api_key")
.match_body(Matcher::Json(json!({"fileName": "test.jpg"})))
.with_body(
json!({
"code": "success",
"errno": 0,
"requestId": "test-request-id",
"mediaId": "test-media-id",
"response": {
"signedUrl": format!("{}/upload", server.url())
}
})
.to_string(),
)
.create_async()
.await;
println!("Mocked presigned URL endpoint");
let _m2 = server
.mock("PUT", "/upload")
.with_status(200)
.match_header("content-type", "image/jpeg")
.create_async()
.await;
println!("Mocked upload endpoint");
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
println!("Created client");
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
if let Err(ref e) = result {
println!("Upload failed with error: {:?}", e);
}
assert!(result.is_ok(), "Upload failed: {:?}", result.err());
let upload_result = result.unwrap();
assert_eq!(upload_result.request_id, "test-request-id");
assert_eq!(upload_result.media_id.unwrap(), "test-media-id");
assert!(upload_result.result_url.is_none());
}
#[tokio::test]
async fn test_http_post_request() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("POST", "/api/test-endpoint")
.with_status(200)
.with_header("content-type", "application/json")
.match_header("X-API-KEY", "test_api_key")
.match_header("Content-Type", "application/json")
.match_body(Matcher::Json(json!({"key": "value"})))
.with_body(r#"{"status": "success", "message": "Test completed"}"#)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let _test_data = json!({"key": "value"});
let mock_endpoint = server
.mock("POST", "/api/files/aws-presigned")
.with_status(200)
.with_header("content-type", "application/json")
.match_body(Matcher::Json(json!({"fileName": "test.jpg"})))
.with_body(
json!({
"code": "success",
"errno": 0,
"requestId": "test-request-id",
"mediaId": "test-media-id",
"response": {
"signedUrl": format!("{}/upload", server.url())
}
})
.to_string(),
)
.create_async()
.await;
let mock_upload = server
.mock("PUT", "/upload")
.with_status(200)
.create_async()
.await;
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.jpg");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"test image data").unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(result.is_ok());
mock_endpoint.assert_async().await;
mock_upload.assert_async().await;
}
#[tokio::test]
async fn test_server_error_handling() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("GET", "/api/media/users/test-server-error")
.with_status(500)
.with_header("content-type", "application/json")
.with_body(r#"{"error": "Internal server error"}"#)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client.get_result("test-server-error", None).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::ServerError(_) => {} err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_forbidden_error_handling() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("GET", "/api/media/users/test-forbidden")
.with_status(403)
.with_header("content-type", "application/json")
.with_body(r#"{"response": "Forbidden access"}"#)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client.get_result("test-forbidden", None).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::ServerError(..) => {} err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_unknown_error_handling() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("GET", "/api/media/users/test-unknown-error")
.with_status(400)
.with_header("content-type", "application/json")
.with_body(r#"{"code": "custom-code", "errno": 0, "response": "Custom error message", "some": "other-data"}"#)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client.get_result("test-unknown-error", None).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidRequest(msg) => {
assert_eq!(msg, "Custom error message");
}
err => panic!("Unexpected error: {:?}", err),
}
let _m2 = server
.mock("GET", "/api/media/users/test-unknown-error-2")
.with_status(422)
.with_header("content-type", "text/plain")
.with_body("Unparseable error")
.create_async()
.await;
let result = client.get_result("test-unknown-error-2", None).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::ServerError(msg) => {
assert_eq!(msg, "Unknown error (HTTP 422 Unprocessable Entity)");
}
err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_upload_with_empty_file() {
let mut server = mockito::Server::new_async().await;
let dir = tempdir().unwrap();
let file_path = dir.path().join("empty.jpg");
File::create(&file_path).unwrap();
let _m = server
.mock("POST", "/api/files/aws-presigned")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
json!({
"code": "success",
"errno": 0,
"requestId": "test-request-id",
"mediaId": "test-media-id",
"response": {
"signedUrl": format!("{}/upload", server.url())
}
})
.to_string(),
)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidFile(_) => {} err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_upload_failure() {
let mut server = mockito::Server::new_async().await;
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.jpg");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"test image data").unwrap();
let _m1 = server
.mock("POST", "/api/files/aws-presigned")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
json!({
"code": "success",
"errno": 0,
"requestId": "test-request-id",
"mediaId": "test-media-id",
"response": {
"signedUrl": format!("{}/upload-fail", server.url())
}
})
.to_string(),
)
.create_async()
.await;
let _m2 = server
.mock("PUT", "/upload-fail")
.with_status(400)
.with_body("Upload failed")
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(result.is_err());
match result.unwrap_err() {
Error::UploadFailed(_) => {} err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_file_content_type_detection() {
let mut server = mockito::Server::new_async().await;
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.jpg");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"test image data").unwrap();
let _m1 = server
.mock("POST", "/api/files/aws-presigned")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
json!({
"code": "success",
"errno": 0,
"requestId": "test-request-id",
"mediaId": "test-media-id",
"response": {
"signedUrl": format!("{}/upload-test", server.url())
}
})
.to_string(),
)
.create_async()
.await;
let _m2 = server
.mock("PUT", "/upload-test")
.match_header("Content-Type", "image/jpeg")
.with_status(200)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(
result.is_ok(),
"Failed to upload JPEG file: {:?}",
result.err()
);
let png_path = dir.path().join("test.png");
let mut png_file = File::create(&png_path).unwrap();
png_file.write_all(b"test png data").unwrap();
let _m3 = server
.mock("POST", "/api/files/aws-presigned")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
json!({
"code": "success",
"errno": 0,
"requestId": "test-png-id",
"mediaId": "test-png-media",
"response": {
"signedUrl": format!("{}/upload-png", server.url())
}
})
.to_string(),
)
.create_async()
.await;
let _m4 = server
.mock("PUT", "/upload-png")
.match_header("Content-Type", "image/png")
.with_status(200)
.create_async()
.await;
let result = client
.upload(UploadOptions {
file_path: png_path.to_str().unwrap().to_string(),
})
.await;
assert!(
result.is_ok(),
"Failed to upload PNG file: {:?}",
result.err()
);
}
#[tokio::test]
async fn test_unsupported_file_extension() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.xyz");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"test data").unwrap();
let config = Config {
api_key: "test_api_key".to_string(),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidFile(msg) => assert!(msg.contains("Unsupported file type")),
err => panic!("Expected InvalidFile error, got: {:?}", err),
}
}
#[tokio::test]
async fn test_file_size_exceeds_limit() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("large.jpg");
let mut file = File::create(&file_path).unwrap();
let large_data = vec![0u8; 52428801]; file.write_all(&large_data).unwrap();
let config = Config {
api_key: "test_api_key".to_string(),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidFile(msg) => assert!(msg.contains("File too large")),
err => panic!("Expected InvalidFile error, got: {:?}", err),
}
}
#[tokio::test]
async fn test_supported_file_extension_within_limit() {
let mut server = mockito::Server::new_async().await;
let dir = tempdir().unwrap();
let file_path = dir.path().join("small.jpg");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"small image").unwrap();
let _m1 = server
.mock("POST", "/api/files/aws-presigned")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
json!({
"code": "success",
"errno": 0,
"requestId": "test-id",
"mediaId": "test-media",
"response": {"signedUrl": format!("{}/upload", server.url())}
})
.to_string(),
)
.create_async()
.await;
let _m2 = server
.mock("PUT", "/upload")
.with_status(200)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_file_without_extension() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("no_extension");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"test data").unwrap();
let config = Config {
api_key: "test_api_key".to_string(),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidFile(msg) => assert!(msg.contains("Invalid file name")),
err => panic!("Expected InvalidFile error, got: {:?}", err),
}
}
#[tokio::test]
async fn test_upload_social_media_link_success() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("POST", "/api/files/social")
.with_status(200)
.with_header("content-type", "application/json")
.match_header("X-API-KEY", "test_api_key")
.match_body(Matcher::Json(json!({
"socialLink": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
})))
.with_body(
json!({
"code": "success",
"errno": 0,
"response": "",
"requestId": "social-request-id",
})
.to_string(),
)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload_social_media("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
.await;
println!("{:?}", result);
assert!(result.is_ok());
let upload_result = result.unwrap();
assert_eq!(upload_result.request_id, "social-request-id");
}
#[tokio::test]
async fn test_upload_social_media_link_invalid_url() {
let config = Config {
api_key: "test_api_key".to_string(),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client.upload_social_media("www.example.com").await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidRequest(msg) => {
assert!(msg.contains("Invalid URL"));
}
err => panic!("Expected InvalidRequest error, got: {:?}", err),
}
}
#[tokio::test]
async fn test_upload_social_media_link_invalid_scheme() {
let config = Config {
api_key: "test_api_key".to_string(),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client.upload_social_media("ftp://example.com/video").await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidRequest(msg) => {
assert!(msg.contains("http or https scheme"));
}
err => panic!("Expected InvalidRequest error, got: {:?}", err),
}
}
#[tokio::test]
async fn test_upload_social_media_link_ip_address() {
let config = Config {
api_key: "test_api_key".to_string(),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload_social_media("https://192.168.1.1/video")
.await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidRequest(msg) => {
assert!(msg.contains("valid domain"));
}
err => panic!("Expected InvalidRequest error, got: {:?}", err),
}
}
#[tokio::test]
async fn test_upload_social_media_link_server_error() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("POST", "/api/files/social")
.with_status(500)
.with_header("content-type", "application/json")
.with_body(r#"{"response": "Internal server error"}"#)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload_social_media("https://www.instagram.com/p/ABC123/")
.await;
assert!(result.is_err());
match result.unwrap_err() {
Error::ServerError(_) => {} err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_bad_request_error_handling() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("GET", "/api/media/users/test-bad-request")
.with_status(400)
.with_header("content-type", "application/json")
.with_body(
r#"{"code": "error", "errno": 400, "response": "Invalid request parameters"}"#,
)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client.get_result("test-bad-request", None).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidRequest(_) => {} err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_upload_bad_request_error() {
let mut server = mockito::Server::new_async().await;
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.jpg");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"test data").unwrap();
let _m = server
.mock("POST", "/api/files/aws-presigned")
.with_status(400)
.with_header("content-type", "application/json")
.with_body(r#"{"code": "error", "errno": 400, "response": "Invalid file format"}"#)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client
.upload(UploadOptions {
file_path: file_path.to_str().unwrap().to_string(),
})
.await;
assert!(result.is_err());
match result.unwrap_err() {
Error::InvalidRequest(_) | Error::UploadFailed(_) => {} err => panic!("Unexpected error: {:?}", err),
}
}
#[tokio::test]
async fn test_free_tier_not_allowed_error() {
let mut server = mockito::Server::new_async().await;
let _m = server
.mock("GET", "/api/media/users/test-free-tier")
.with_status(400)
.with_header("content-type", "application/json")
.with_body(r#"{"code": "free-tier-not-allowed", "errno": 400, "response": "This feature is not available in the free tier"}"#)
.create_async()
.await;
let config = Config {
api_key: "test_api_key".to_string(),
base_url: Some(server.url()),
..Default::default()
};
let client = Client::new(config).unwrap();
let result = client.get_result("test-free-tier", None).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::Unauthorized(_) => {} err => panic!("Unexpected error: {:?}", err),
}
}
}