Skip to content

Commit

Permalink
Merge pull request #5 from kakilangit/tests
Browse files Browse the repository at this point in the history
Add tests
  • Loading branch information
kakilangit authored May 3, 2024
2 parents 0bab3a2 + 6ef42eb commit 834d685
Show file tree
Hide file tree
Showing 5 changed files with 425 additions and 23 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cetar"
version = "0.1.4"
version = "0.1.5"
edition = "2021"
description = "💥 CURL execution timing analyzer"
documentation = "https://docs.rs/cetar"
Expand All @@ -19,3 +19,6 @@ path = "src/main.rs"
anyhow = "1.0.82"
clap = { version = "4.5.4", features = ["derive"] }
curl = "0.4.46"

[dev-dependencies]
httpmock = "0.7.0-rc.1"
63 changes: 58 additions & 5 deletions src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// # Example
///
/// ```rust
/// use cetar::color::make_color;
/// use cetar::make_color;
///
/// let red = make_color!(31, "Red text");
/// let green = make_color!(32, "Green text");
Expand All @@ -23,7 +23,7 @@ macro_rules! make_color {
/// # Example
///
/// ```rust
/// use cetar::color::{print_error, make_color};
/// use cetar::{print_error, make_color};
///
/// print_error!("This is an error message");
/// ```
Expand All @@ -37,7 +37,7 @@ macro_rules! print_error {

/// Enum for ANSI color codes
///
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Color {
Black = 30,
Red = 31,
Expand All @@ -55,10 +55,10 @@ impl Default for Color {
}
}

impl TryFrom<String> for Color {
impl TryFrom<&str> for Color {
type Error = anyhow::Error;

fn try_from(value: String) -> Result<Self, Self::Error> {
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"black" => Ok(Self::Black),
"red" => Ok(Self::Red),
Expand Down Expand Up @@ -91,3 +91,56 @@ impl Color {
make_color!(*self as u8, text)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_color() {
let red = Color::Red.paint("Red text");
let green = Color::Green.paint("Green text");

assert_eq!(red, "\x1b[31mRed text\x1b[0m");
assert_eq!(green, "\x1b[32mGreen text\x1b[0m");
}

#[test]
fn test_try_from() {
let table = vec![
("black", Color::Black),
("red", Color::Red),
("green", Color::Green),
("yellow", Color::Yellow),
("blue", Color::Blue),
("magenta", Color::Magenta),
("cyan", Color::Cyan),
("white", Color::White),
];

for (color, expected) in table {
let result = Color::try_from(color).unwrap();
assert_eq!(result, expected);
}
}

#[test]
fn test_try_from_invalid() {
let color = Color::try_from("invalid");
assert!(color.is_err());
}

#[test]
fn test_make_color() {
let red = make_color!(31, "Red text");
let green = make_color!(32, "Green text");

assert_eq!(red, "\x1b[31mRed text\x1b[0m");
assert_eq!(green, "\x1b[32mGreen text\x1b[0m");
}

#[test]
fn test_print_error() {
print_error!("This is an error message");
}
}
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ impl TryFrom<Args> for cetar::network::Config<'_> {
url: cli.url.into(),
request_headers: cli.headers,
request_body: data.map(|x| x.into()),
method: cetar::network::Method::try_from(cli.method)?,
color: cetar::color::Color::try_from(cli.color)?,
method: cli.method.as_str().try_into()?,
color: cli.color.as_str().try_into()?,
output: cli.output.map(|x| x.into()),
display_response_body: cli.display_response_body,
display_response_headers: cli.display_response_headers,
Expand Down
183 changes: 174 additions & 9 deletions src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{make_color, print_error};
/// let config = Config {
/// url: Cow::Borrowed("https://example.com"),
/// method: Method::Get,
/// color: "cyan".into(),
/// color: "cyan".try_into().unwrap(),
/// request_headers: vec![],
/// request_body: None,
/// output: None,
Expand Down Expand Up @@ -131,7 +131,7 @@ impl<'a> curl::easy::Handler for Decorator<'a> {
/// assert_eq!(header.value, "application/json");
/// ```
///
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Header {
/// Header key
pub key: String,
Expand Down Expand Up @@ -200,6 +200,7 @@ impl std::str::FromStr for Header {
/// ```rust
/// use cetar::network::{Header, Stat};
/// use std::time::Duration;
/// use std::str::FromStr;
///
/// let stat = Stat {
/// ip_address: Some("127.0.0.1".to_string()),
Expand Down Expand Up @@ -273,7 +274,7 @@ impl Stat {
}

/// Get the server processing time
pub fn waiting(&self) -> Option<Duration> {
pub fn server_processing(&self) -> Option<Duration> {
if self.start_transfer > self.pre_transfer {
Some(self.start_transfer - self.pre_transfer)
} else {
Expand All @@ -282,7 +283,7 @@ impl Stat {
}

/// Get the content transfer time
pub fn data_transfer(&self) -> Option<Duration> {
pub fn content_transfer(&self) -> Option<Duration> {
if self.total > self.start_transfer {
Some(self.total - self.start_transfer)
} else {
Expand Down Expand Up @@ -359,13 +360,13 @@ impl<'a> TryFrom<&mut curl::easy::Easy2<Decorator<'a>>> for Stat {
/// use std::convert::TryFrom;
///
/// let get = Method::Get;
/// let post = Method::try_from("POST".to_string()).unwrap();
/// let post = Method::try_from("POST").unwrap();
///
/// assert_eq!(get, Method::Get);
/// assert_eq!(post, Method::Post);
/// ```
///
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum Method {
/// The GET method requests a representation of the specified resource. Requests using GET should only retrieve data.
Get,
Expand Down Expand Up @@ -409,10 +410,10 @@ impl<'a> From<&'a Method> for &'a str {
}
}

impl TryFrom<String> for Method {
impl TryFrom<&str> for Method {
type Error = anyhow::Error;

fn try_from(value: String) -> Result<Self, Self::Error> {
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_uppercase().as_str() {
"GET" => Ok(Self::Get),
"HEAD" => Ok(Self::Head),
Expand Down Expand Up @@ -451,7 +452,7 @@ impl TryFrom<String> for Method {
///
/// let stat = send_request(&conf).unwrap();
///
/// println!("Status code: {}", stat.response_status_code);
/// println!("Status code: {:?}", stat.response_status_code);
/// ```
///
pub fn send_request(conf: &Config) -> anyhow::Result<Stat> {
Expand Down Expand Up @@ -502,3 +503,167 @@ pub fn send_request(conf: &Config) -> anyhow::Result<Stat> {

Stat::try_from(&mut easy)
}

#[cfg(test)]
mod test {
use super::*;
use httpmock::prelude::*;
use std::str::FromStr;

#[test]
fn test_header_key_value() {
let header = Header::from_str("content-type: application/json").unwrap();
assert_eq!(header.key, "content-type");
assert_eq!(header.header_key(), "Content-Type");
assert_eq!(header.value, "application/json");
}

#[test]
fn test_method_try_from_str() {
let table = vec![
("GET", Method::Get),
("POST", Method::Post),
("PUT", Method::Put),
("DELETE", Method::Delete),
("CONNECT", Method::Connect),
("OPTIONS", Method::Options),
("TRACE", Method::Trace),
("PATCH", Method::Patch),
("HEAD", Method::Head),
];

for (method, expected) in table {
let result = Method::try_from(method).unwrap();
assert_eq!(result, expected);
}
}

#[test]
fn test_method_try_from_str_invalid() {
let result = Method::try_from("INVALID");
assert!(result.is_err());
}

#[test]
fn test_method_from_str() {
let table = vec![
(Method::Get, "GET"),
(Method::Post, "POST"),
(Method::Put, "PUT"),
(Method::Delete, "DELETE"),
(Method::Connect, "CONNECT"),
(Method::Options, "OPTIONS"),
(Method::Trace, "TRACE"),
(Method::Patch, "PATCH"),
(Method::Head, "HEAD"),
];

for (method, expected) in table {
let result: &str = (&method).into();
assert_eq!(result, expected);
}
}

#[test]
fn test_send_request_body() {
let methods = vec![Method::Post, Method::Put, Method::Patch];

for method in methods {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.path("/");
then.status(200)
.header("content-type", "text/html")
.body("ohi");
});

let conf = Config {
url: server.url("/").into(),
method,
verbose: true,
follow_redirects: true,
request_body: Some("oh".into()),
request_headers: vec![Header::from_str("content-type: text/plain").unwrap()],
..Default::default()
};

let stat = send_request(&conf).unwrap();

mock.assert();

assert_eq!(stat.response_status_code.unwrap(), 200);
assert_eq!(stat.utf8_response_body().unwrap(), "ohi");
}
}

#[test]
fn test_send_request_nobody() {
let methods = vec![
Method::Head,
Method::Options,
Method::Trace,
Method::Connect,
Method::Delete,
Method::Get,
Method::Post,
Method::Put,
];

for method in methods {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.path("/");
then.status(200).header("content-type", "text/html");
});

let conf = Config {
url: server.url("/").into(),
method,
verbose: true,
follow_redirects: true,
..Default::default()
};

let stat = send_request(&conf).unwrap();

mock.assert();

assert_eq!(stat.response_status_code.unwrap(), 200);
}
}

#[test]
fn test_timing_stat() {
let stat = Stat {
name_lookup: Duration::from_secs(1),
connect: Duration::from_secs(2),
app_connect: Duration::from_secs(3),
pre_transfer: Duration::from_secs(4),
start_transfer: Duration::from_secs(5),
total: Duration::from_secs(6),
..Default::default()
};

let one_sec = Duration::from_secs(1);
assert_eq!(stat.dns_lookup().unwrap(), one_sec);
assert_eq!(stat.tcp_handshake().unwrap(), one_sec);
assert_eq!(stat.tls_handshake().unwrap(), one_sec);
assert_eq!(stat.server_processing().unwrap(), one_sec);
assert_eq!(stat.content_transfer().unwrap(), one_sec);
}

#[test]
fn test_no_tls() {
let stat = Stat {
name_lookup: Duration::from_secs(1),
connect: Duration::from_secs(2),
app_connect: Duration::from_secs(1),
pre_transfer: Duration::from_secs(4),
start_transfer: Duration::from_secs(5),
total: Duration::from_secs(6),
..Default::default()
};

assert!(stat.tls_handshake().is_none());
}
}
Loading

0 comments on commit 834d685

Please sign in to comment.