Skip to content

Commit

Permalink
Add tests and provide status codes as part of linter errors (#1903)
Browse files Browse the repository at this point in the history
Returns the status code with the offending link when lychee lint checks
fail.
  • Loading branch information
dotdat authored May 15, 2024
1 parent b385628 commit d9013ef
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 8 deletions.
138 changes: 133 additions & 5 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ prettytable-rs = "0.10"
rayon = "1"
regex = "1"
reqwest = { version = "0.11", default-features = false }
rstest = "0.19.0"
semver = "1"
serial_test = "2"
serde = "1.0"
serde_json = "1.0"
serde_json_traversal = "0.2"
serde_yaml = "0.9"
shell-candy = "0.4"
speculoos = "0.11.0"
strip-ansi-escapes = "0.2"
strsim = "0.10"
strum = "0.25"
Expand Down Expand Up @@ -194,4 +196,6 @@ assert_fs = { workspace = true }
assert-json-diff = { workspace = true }
predicates = { workspace = true }
reqwest = { workspace = true, features = ["blocking", "native-tls-vendored"] }
rstest = { workspace = true }
serial_test = { workspace = true }
speculoos = { workspace = true }
5 changes: 5 additions & 0 deletions xtask/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ zip = { workspace = true }

[target.'cfg(not(windows))'.dependencies]
lychee-lib = { version = "0.15", features = ["vendored-openssl"] }

[dev-dependencies]
mockito = "1.4.0"
rstest = { workspace = true }
speculoos = { workspace = true }
108 changes: 105 additions & 3 deletions xtask/src/tools/lychee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,14 @@ impl LycheeRunner {

if !failed_checks.is_empty() {
for failed_check in failed_checks {
crate::info!("❌ {}", failed_check.as_str());
crate::info!(
"❌ [Status Code: {}] {}",
failed_check
.1
.map(|status_code| status_code.to_string())
.unwrap_or("unknown".to_string()),
failed_check.0.as_str()
);
}

Err(anyhow!("Some links in markdown documentation are down."))
Expand All @@ -83,13 +90,17 @@ impl LycheeRunner {
}
}

async fn get_failed_request(lychee_client: Client, link: Request) -> Option<Uri> {
async fn get_failed_request(
lychee_client: Client,
link: Request,
) -> Option<(Uri, Option<StatusCode>)> {
let response = lychee_client
.check(link)
.await
.expect("could not execute lychee request");
if response.status().is_error() {
Some(response.1.uri)
let status_code = response.status().code();
Some((response.1.uri, status_code))
} else {
crate::info!("✅ {}", response.1.uri.as_str());
None
Expand Down Expand Up @@ -137,3 +148,94 @@ fn walk_dir(base_dir: &str, md_files: &mut Vec<Utf8PathBuf>) {
}
}
}

#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};

use anyhow::Result;
use http::StatusCode;
use lychee_lib::{ClientBuilder, InputSource, Request, Result as LycheeResult, Uri};
use rstest::{fixture, rstest};
use speculoos::prelude::*;
use tokio::runtime::Runtime;

use super::get_failed_request;

#[fixture]
fn lychee_client() -> LycheeResult<lychee_lib::Client> {
let accepted = Some(HashSet::from_iter(vec![
StatusCode::OK,
StatusCode::TOO_MANY_REQUESTS,
]));
ClientBuilder::builder()
.exclude_all_private(false)
.retry_wait_time(Duration::from_secs(30))
.max_retries(5u8)
.accepted(accepted)
.build()
.client()
}

#[rstest]
#[case::success(200)]
fn test_get_failed_request_success(
lychee_client: LycheeResult<lychee_lib::Client>,
#[case] response_status_int: usize,
) -> Result<()> {
let lychee_client = lychee_client?;
let mut server = mockito::Server::new();
let url = server.url();
let _mock = server
.mock("GET", "/success")
.with_status(response_status_int)
.create();
let request = Request::new(
Uri::try_from(&format!("{}/{}", url, "success") as &str)?,
InputSource::String("test".to_string()),
None,
None,
None,
);
let rt = Runtime::new()?;
let result = rt.block_on(get_failed_request(lychee_client, request));
assert_that!(result).is_none();

Ok(())
}

#[rstest]
#[case::internal_server_error(400, StatusCode::BAD_REQUEST)]
#[case::internal_server_error(401, StatusCode::UNAUTHORIZED)]
#[case::internal_server_error(403, StatusCode::FORBIDDEN)]
#[case::internal_server_error(404, StatusCode::NOT_FOUND)]
#[case::internal_server_error(500, StatusCode::INTERNAL_SERVER_ERROR)]
fn test_get_failed_request_failure(
lychee_client: LycheeResult<lychee_lib::Client>,
#[case] response_status_int: usize,
#[case] response_status_code: StatusCode,
) -> Result<()> {
let lychee_client = lychee_client?;
let mut server = mockito::Server::new();
let url = server.url();
let _mock = server
.mock("GET", "/success")
.with_status(response_status_int)
.create();
let uri = Uri::try_from(&format!("{}/{}", url, "success") as &str)?;
let request = Request::new(
uri.clone(),
InputSource::String("test".to_string()),
None,
None,
None,
);
let rt = Runtime::new()?;
let result = rt.block_on(get_failed_request(lychee_client, request));
assert_that!(result)
.is_some()
.is_equal_to((uri, Some(response_status_code)));

Ok(())
}
}

0 comments on commit d9013ef

Please sign in to comment.