buster/api/tests/common/assertions/response.rs

179 lines
5.1 KiB
Rust
Raw Normal View History

use anyhow::Result;
use reqwest::Response;
use serde::de::DeserializeOwned;
use async_trait::async_trait;
/// Extension trait for reqwest::Response that adds common assertions
#[async_trait]
pub trait ResponseAssertions {
/// Assert response has expected status code
fn assert_status(&self, expected: reqwest::StatusCode) -> &Self;
/// Parse response body as JSON and perform assertions on it
async fn assert_json<F, T>(&self, assertions: F) -> Result<T>
where
F: FnOnce(&T) -> bool + Send,
T: DeserializeOwned;
/// Assert response contains expected header
fn assert_header(&self, name: &str, expected_value: &str) -> &Self;
/// Assert response body contains a string
async fn assert_body_contains(&self, expected_content: &str) -> Result<&Self>;
/// Assert response body matches exactly
async fn assert_body_text(&self, expected_text: &str) -> Result<&Self>;
}
/// Helper for cloning responses so we can extract JSON and still keep the response
fn try_clone(response: &Response) -> Result<Response> {
response.try_clone()
.ok_or_else(|| anyhow::anyhow!("Failed to clone response"))
}
#[async_trait]
impl ResponseAssertions for Response {
fn assert_status(&self, expected: reqwest::StatusCode) -> &Self {
assert_eq!(
self.status(),
expected,
"Expected status code {}, got {}",
expected,
self.status()
);
self
}
async fn assert_json<F, T>(&self, assertions: F) -> Result<T>
where
F: FnOnce(&T) -> bool + Send,
T: DeserializeOwned,
{
// Clone the response so we can return the parsed data
let mut response_copy = try_clone(self)?;
// Parse JSON
let json_data: T = response_copy.json().await?;
// Run assertions
assert!(
assertions(&json_data),
"JSON assertions failed for response"
);
Ok(json_data)
}
fn assert_header(&self, name: &str, expected_value: &str) -> &Self {
let header_value = self.headers().get(name)
.unwrap_or_else(|| panic!("Header {} not found in response", name))
.to_str()
.unwrap_or_else(|_| panic!("Header {} contains non-string value", name));
assert_eq!(
header_value,
expected_value,
"Expected header {} to have value {}, got {}",
name,
expected_value,
header_value
);
self
}
async fn assert_body_contains(&self, expected_content: &str) -> Result<&Self> {
let response_copy = try_clone(self)?;
let body_text = response_copy.text().await?;
assert!(
body_text.contains(expected_content),
"Expected response body to contain '{}', got '{}'",
expected_content,
body_text
);
Ok(self)
}
async fn assert_body_text(&self, expected_text: &str) -> Result<&Self> {
let response_copy = try_clone(self)?;
let body_text = response_copy.text().await?;
assert_eq!(
body_text,
expected_text,
"Expected response body to be '{}', got '{}'",
expected_text,
body_text
);
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use reqwest::{Client, StatusCode, Response};
use serde::{Deserialize, Serialize};
use mockito::Server;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct TestUser {
id: i32,
name: String,
}
async fn get_mock_response() -> Result<Response> {
let server = Server::new_async().await;
let _mock = server.mock("GET", "/test")
.with_status(200)
.with_header("X-Test", "test-value")
.with_body(r#"{"id": 123, "name": "Test User"}"#)
.create();
let client = Client::new();
let response = client.get(&format!("{}/test", server.url()))
.send()
.await?;
Ok(response)
}
#[tokio::test]
async fn test_assert_status() -> Result<()> {
let response = get_mock_response().await?;
response.assert_status(StatusCode::OK);
Ok(())
}
#[tokio::test]
async fn test_assert_json() -> Result<()> {
let response = get_mock_response().await?;
let user = response.assert_json::<_, TestUser>(|user| {
user.id == 123 && user.name == "Test User"
}).await?;
assert_eq!(user.id, 123);
assert_eq!(user.name, "Test User");
Ok(())
}
#[tokio::test]
async fn test_assert_header() -> Result<()> {
let response = get_mock_response().await?;
response.assert_header("X-Test", "test-value");
Ok(())
}
#[tokio::test]
async fn test_assert_body_contains() -> Result<()> {
let response = get_mock_response().await?;
response.assert_body_contains("Test User").await?;
Ok(())
}
}