Skip to content

Commit

Permalink
Cleanup outputs; update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mre committed Oct 13, 2024
1 parent 4d5aebd commit 28ac497
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 36 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lychee-bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ tabled = "0.16.0"
tokio = { version = "1.40.0", features = ["full"] }
tokio-stream = "0.1.16"
toml = "0.8.19"
url = "2.5.2"

[dev-dependencies]
assert_cmd = "2.0.16"
Expand Down
2 changes: 1 addition & 1 deletion lychee-bin/src/commands/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ mod tests {

assert!(!buf.is_empty());
let buf = String::from_utf8_lossy(&buf);
assert_eq!(buf, "[200] http://127.0.0.1/ | Cached: OK (cached)\n");
assert_eq!(buf, "[200] http://127.0.0.1/ | OK (cached)\n");
}

#[tokio::test]
Expand Down
26 changes: 14 additions & 12 deletions lychee-bin/src/formatters/response/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ mod tests {
use super::*;
use http::StatusCode;
use lychee_lib::{ErrorKind, Status, Uri};
use pretty_assertions::assert_eq;

#[cfg(test)]
/// Helper function to strip ANSI color codes for tests
fn strip_ansi_codes(s: &str) -> String {
console::strip_ansi_codes(s).to_string()
}

// Helper function to create a ResponseBody with a given status and URI
fn mock_response_body(status: Status, uri: &str) -> ResponseBody {
Expand All @@ -91,10 +98,8 @@ mod tests {
fn test_format_response_with_ok_status() {
let formatter = ColorFormatter;
let body = mock_response_body(Status::Ok(StatusCode::OK), "https://example.com");
assert_eq!(
formatter.format_response(&body),
"\u{1b}[38;5;2m\u{1b}[1m [200]\u{1b}[0m https://example.com/"
);
let formatted_response = strip_ansi_codes(&formatter.format_response(&body));
assert_eq!(formatted_response, " [200] https://example.com/");
}

#[test]
Expand All @@ -104,10 +109,8 @@ mod tests {
Status::Error(ErrorKind::InvalidUrlHost),
"https://example.com/404",
);
assert_eq!(
formatter.format_response(&body),
"\u{1b}[38;5;197m [ERROR]\u{1b}[0m https://example.com/404"
);
let formatted_response = strip_ansi_codes(&formatter.format_response(&body));
assert_eq!(formatted_response, " [ERROR] https://example.com/404");
}

#[test]
Expand All @@ -116,7 +119,7 @@ mod tests {
let long_uri =
"https://example.com/some/very/long/path/to/a/resource/that/exceeds/normal/lengths";
let body = mock_response_body(Status::Ok(StatusCode::OK), long_uri);
let formatted_response = formatter.format_response(&body);
let formatted_response = strip_ansi_codes(&formatter.format_response(&body));
assert!(formatted_response.contains(long_uri));
}

Expand All @@ -128,11 +131,10 @@ mod tests {
"https://example.com/404",
);

let response = formatter.format_detailed_response(&body);

let response = strip_ansi_codes(&formatter.format_detailed_response(&body));
assert_eq!(
response,
"\u{1b}[38;5;197m [ERROR]\u{1b}[0m [ERROR] https://example.com/404 | URL is missing a host"
" [ERROR] https://example.com/404 | URL is missing a host"
);
}
}
6 changes: 1 addition & 5 deletions lychee-bin/src/formatters/response/plain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub(crate) struct PlainFormatter;

impl ResponseFormatter for PlainFormatter {
fn format_response(&self, body: &ResponseBody) -> String {
body.to_string()
format!("[{}] {}", body.status.code_as_string(), body)
}
}

Expand Down Expand Up @@ -59,7 +59,6 @@ mod plain_tests {
fn test_format_response_with_excluded_status() {
let formatter = PlainFormatter;
let body = mock_response_body(Status::Excluded, "https://example.com/not-checked");
assert_eq!(formatter.format_response(&body), body.to_string());
assert_eq!(
formatter.format_response(&body),
"[EXCLUDED] https://example.com/not-checked | Excluded"
Expand All @@ -73,7 +72,6 @@ mod plain_tests {
Status::Redirected(StatusCode::MOVED_PERMANENTLY),
"https://example.com/redirect",
);
assert_eq!(formatter.format_response(&body), body.to_string());
assert_eq!(
formatter.format_response(&body),
"[301] https://example.com/redirect | Redirect (301 Moved Permanently): Moved Permanently"
Expand All @@ -87,8 +85,6 @@ mod plain_tests {
Status::UnknownStatusCode(StatusCode::from_u16(999).unwrap()),
"https://example.com/unknown",
);
assert_eq!(formatter.format_response(&body), body.to_string());
// Check the actual string representation of the status code
assert_eq!(
formatter.format_response(&body),
"[999] https://example.com/unknown | Unknown status (999 <unknown status code>)"
Expand Down
76 changes: 75 additions & 1 deletion lychee-bin/src/formatters/stats/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ impl Display for CompactResponseStats {
for response in responses {
writeln!(
f,
"{}",
"[{}] {}",
response.status.code_as_string(),
response_formatter.format_detailed_response(response)
)?;
}
Expand Down Expand Up @@ -110,3 +111,76 @@ impl StatsFormatter for Compact {
Ok(Some(compact.to_string()))
}
}

#[cfg(test)]
mod tests {
use crate::formatters::stats::StatsFormatter;
use crate::{options::OutputMode, stats::ResponseStats};
use http::StatusCode;
use lychee_lib::{InputSource, ResponseBody, Status, Uri};
use std::collections::{HashMap, HashSet};
use url::Url;

use super::*;

#[test]
fn test_formatter() {
// A couple of dummy successes
let mut success_map: HashMap<InputSource, HashSet<ResponseBody>> = HashMap::new();

success_map.insert(
InputSource::RemoteUrl(Box::new(Url::parse("https://example.com").unwrap())),
HashSet::from_iter(vec![ResponseBody {
uri: Uri::from(Url::parse("https://example.com").unwrap()),
status: Status::Ok(StatusCode::OK),
}]),
);

let err1 = ResponseBody {
uri: Uri::try_from("https://github.com/mre/idiomatic-rust-doesnt-exist-man").unwrap(),
status: Status::Ok(StatusCode::NOT_FOUND),
};

let err2 = ResponseBody {
uri: Uri::try_from("https://github.com/mre/boom").unwrap(),
status: Status::Ok(StatusCode::INTERNAL_SERVER_ERROR),
};

let mut fail_map: HashMap<InputSource, HashSet<ResponseBody>> = HashMap::new();
let source = InputSource::RemoteUrl(Box::new(Url::parse("https://example.com").unwrap()));
fail_map.insert(source, HashSet::from_iter(vec![err1, err2]));

let stats = ResponseStats {
total: 1,
successful: 1,
errors: 0,
unknown: 0,
excludes: 0,
timeouts: 0,
duration_secs: 0,
fail_map,
suggestion_map: HashMap::default(),
unsupported: 0,
redirects: 0,
cached: 0,
success_map,
excluded_map: HashMap::default(),
detailed_stats: false,
};

let formatter = Compact::new(OutputMode::Plain);

let result = formatter.format(stats).unwrap().unwrap();

println!("{result}");

assert!(result.contains("🔍 1 Total"));
assert!(result.contains("✅ 1 OK"));
assert!(result.contains("🚫 0 Errors"));

assert!(result.contains("[https://example.com/]:"));
assert!(result
.contains("https://github.com/mre/idiomatic-rust-doesnt-exist-man | 404 Not Found"));
assert!(result.contains("https://github.com/mre/boom | 500 Internal Server Error"));
}
}
68 changes: 67 additions & 1 deletion lychee-bin/src/formatters/stats/detailed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ impl Display for DetailedResponseStats {
write!(f, "\n\nErrors in {source}")?;

for response in responses {
write!(f, "\n{}", response_formatter.format_response(response))?;
write!(
f,
"\n{}",
response_formatter.format_detailed_response(response)
)?;

if let Some(suggestions) = &stats.suggestion_map.get(source) {
writeln!(f, "\nSuggestions in {source}")?;
Expand Down Expand Up @@ -89,3 +93,65 @@ impl StatsFormatter for Detailed {
Ok(Some(detailed.to_string()))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::options::OutputMode;
use http::StatusCode;
use lychee_lib::{InputSource, ResponseBody, Status, Uri};
use std::collections::{HashMap, HashSet};
use url::Url;

#[test]
fn test_detailed_formatter_github_404() {
let err1 = ResponseBody {
uri: Uri::try_from("https://github.com/mre/idiomatic-rust-doesnt-exist-man").unwrap(),
status: Status::Ok(StatusCode::NOT_FOUND),
};

let err2 = ResponseBody {
uri: Uri::try_from("https://github.com/mre/boom").unwrap(),
status: Status::Ok(StatusCode::INTERNAL_SERVER_ERROR),
};

let mut fail_map: HashMap<InputSource, HashSet<ResponseBody>> = HashMap::new();
let source = InputSource::RemoteUrl(Box::new(Url::parse("https://example.com").unwrap()));
fail_map.insert(source, HashSet::from_iter(vec![err1, err2]));

let stats = ResponseStats {
total: 2,
successful: 0,
errors: 2,
unknown: 0,
excludes: 0,
timeouts: 0,
duration_secs: 0,
unsupported: 0,
redirects: 0,
cached: 0,
suggestion_map: HashMap::default(),
success_map: HashMap::default(),
fail_map,
excluded_map: HashMap::default(),
detailed_stats: true,
};

let formatter = Detailed::new(OutputMode::Plain);
let result = formatter.format(stats).unwrap().unwrap();

// Check for the presence of expected content
assert!(result.contains("📝 Summary"));
assert!(result.contains("🔍 Total............2"));
assert!(result.contains("✅ Successful.......0"));
assert!(result.contains("⏳ Timeouts.........0"));
assert!(result.contains("🔀 Redirected.......0"));
assert!(result.contains("👻 Excluded.........0"));
assert!(result.contains("❓ Unknown..........0"));
assert!(result.contains("🚫 Errors...........2"));
assert!(result.contains("Errors in https://example.com/"));
assert!(result
.contains("https://github.com/mre/idiomatic-rust-doesnt-exist-man | 404 Not Found"));
assert!(result.contains("https://github.com/mre/boom | 500 Internal Server Error"));
}
}
6 changes: 3 additions & 3 deletions lychee-bin/src/formatters/stats/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ mod tests {
let markdown = markdown_response(&response).unwrap();
assert_eq!(
markdown,
"* [200] [http://example.com/](http://example.com/) | Cached: OK (cached)"
"* [200] [http://example.com/](http://example.com/) | OK (cached)"
);
}

Expand All @@ -199,7 +199,7 @@ mod tests {
let markdown = markdown_response(&response).unwrap();
assert_eq!(
markdown,
"* [400] [http://example.com/](http://example.com/) | Cached: Error (cached)"
"* [400] [http://example.com/](http://example.com/) | Error (cached)"
);
}

Expand Down Expand Up @@ -253,7 +253,7 @@ mod tests {
### Errors in stdin
* [404] [http://127.0.0.1/](http://127.0.0.1/) | Cached: Error (cached)
* [404] [http://127.0.0.1/](http://127.0.0.1/) | Error (cached)
## Suggestions per input
Expand Down
14 changes: 7 additions & 7 deletions lychee-bin/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ mod cli {
.assert()
.stderr(contains(format!("[200] {}/\n", mock_server_ok.uri())))
.stderr(contains(format!(
"[404] {}/ | Failed: Network error: Not Found\n",
"[404] {}/ | Network error: Not Found\n",
mock_server_err.uri()
)));

Expand All @@ -881,11 +881,11 @@ mod cli {
test_cmd
.assert()
.stderr(contains(format!(
"[200] {}/ | Cached: OK (cached)\n",
"[200] {}/ | OK (cached)\n",
mock_server_ok.uri()
)))
.stderr(contains(format!(
"[404] {}/ | Cached: Error (cached)\n",
"[404] {}/ | Error (cached)\n",
mock_server_err.uri()
)));

Expand Down Expand Up @@ -933,11 +933,11 @@ mod cli {
.failure()
.code(2)
.stdout(contains(format!(
"[418] {}/ | Failed: Network error: I\'m a teapot",
"[418] {}/ | Network error: I\'m a teapot",
mock_server_teapot.uri()
)))
.stdout(contains(format!(
"[500] {}/ | Failed: Network error: Internal Server Error",
"[500] {}/ | Network error: Internal Server Error",
mock_server_server_error.uri()
)));

Expand All @@ -956,11 +956,11 @@ mod cli {
.assert()
.success()
.stderr(contains(format!(
"[418] {}/ | Cached: OK (cached)",
"[418] {}/ | OK (cached)",
mock_server_teapot.uri()
)))
.stderr(contains(format!(
"[500] {}/ | Cached: OK (cached)",
"[500] {}/ | OK (cached)",
mock_server_server_error.uri()
)));

Expand Down
9 changes: 3 additions & 6 deletions lychee-lib/src/types/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ pub enum Status {
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Status::Ok(code) => write!(f, "OK ({code})"),
Status::Ok(code) => write!(f, "{code}"),
Status::Redirected(code) => write!(f, "Redirect ({code})"),
Status::UnknownStatusCode(code) => write!(f, "Unknown status ({code})"),
Status::Excluded => f.write_str("Excluded"),
Status::Timeout(Some(code)) => write!(f, "Timeout ({code})"),
Status::Timeout(None) => f.write_str("Timeout"),
Status::Unsupported(e) => write!(f, "Unsupported: {e}"),
Status::Error(e) => write!(f, "{e}"),
Status::Cached(status) => write!(f, "Cached: {status}"),
Status::Cached(status) => write!(f, "{status}"),
}
}
}
Expand Down Expand Up @@ -310,10 +310,7 @@ mod tests {
fn test_status_serialization() {
let status_ok = Status::Ok(StatusCode::from_u16(200).unwrap());
let serialized_with_code = serde_json::to_string(&status_ok).unwrap();
assert_eq!(
"{\"text\":\"OK (200 OK)\",\"code\":200}",
serialized_with_code
);
assert_eq!("{\"text\":\"200 OK\",\"code\":200}", serialized_with_code);

let status_timeout = Status::Timeout(None);
let serialized_without_code = serde_json::to_string(&status_timeout).unwrap();
Expand Down

0 comments on commit 28ac497

Please sign in to comment.