2020-10-17 08:01:06 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod cli {
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
use std::{
|
2023-02-25 16:57:57 +00:00
|
|
|
collections::{HashMap, HashSet},
|
2022-02-19 00:44:00 +00:00
|
|
|
error::Error,
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
fs::{self, File},
|
|
|
|
|
io::Write,
|
|
|
|
|
path::{Path, PathBuf},
|
2024-10-27 00:19:35 +00:00
|
|
|
time::Duration,
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
};
|
2021-04-12 12:40:39 +00:00
|
|
|
|
2024-11-12 22:51:39 +00:00
|
|
|
use anyhow::anyhow;
|
2020-10-17 08:01:06 +00:00
|
|
|
use assert_cmd::Command;
|
2023-05-05 22:47:32 +00:00
|
|
|
use assert_json_diff::assert_json_include;
|
2025-05-23 11:37:32 +00:00
|
|
|
use http::{Method, StatusCode};
|
2023-02-25 16:57:57 +00:00
|
|
|
use lychee_lib::{InputSource, ResponseBody};
|
2024-01-04 21:17:14 +00:00
|
|
|
use predicates::{
|
2025-07-17 15:49:04 +00:00
|
|
|
ord::eq,
|
2025-05-24 16:23:23 +00:00
|
|
|
prelude::{PredicateBooleanExt, predicate},
|
2024-01-04 21:17:14 +00:00
|
|
|
str::{contains, is_empty},
|
|
|
|
|
};
|
2023-02-25 16:57:57 +00:00
|
|
|
use pretty_assertions::assert_eq;
|
2023-08-17 14:54:59 +00:00
|
|
|
use regex::Regex;
|
2023-05-05 22:47:32 +00:00
|
|
|
use serde::Serialize;
|
|
|
|
|
use serde_json::Value;
|
2023-07-13 15:32:41 +00:00
|
|
|
use tempfile::NamedTempFile;
|
2020-12-14 00:15:14 +00:00
|
|
|
use uuid::Uuid;
|
2025-07-03 11:45:30 +00:00
|
|
|
use wiremock::{
|
|
|
|
|
Mock, ResponseTemplate,
|
|
|
|
|
matchers::{basic_auth, method},
|
|
|
|
|
};
|
2020-12-02 22:28:37 +00:00
|
|
|
|
2022-02-19 00:44:00 +00:00
|
|
|
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
|
|
|
|
|
2022-07-17 16:40:45 +00:00
|
|
|
// The lychee cache file name is used for some tests.
|
|
|
|
|
// Since it is currently static and can't be overwritten, declare it as a
|
|
|
|
|
// constant.
|
|
|
|
|
const LYCHEE_CACHE_FILE: &str = ".lycheecache";
|
|
|
|
|
|
2023-01-03 23:38:19 +00:00
|
|
|
/// Helper macro to create a mock server which returns a custom status code.
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
macro_rules! mock_server {
|
|
|
|
|
($status:expr $(, $func:tt ($($arg:expr),*))*) => {{
|
|
|
|
|
let mock_server = wiremock::MockServer::start().await;
|
2023-12-01 13:21:49 +00:00
|
|
|
let response_template = wiremock::ResponseTemplate::new(http::StatusCode::from($status));
|
|
|
|
|
let template = response_template$(.$func($($arg),*))*;
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET")).respond_with(template).mount(&mock_server).await;
|
|
|
|
|
mock_server
|
|
|
|
|
}};
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 23:38:19 +00:00
|
|
|
/// Helper macro to create a mock server which returns a 200 OK and a custom response body.
|
|
|
|
|
macro_rules! mock_response {
|
|
|
|
|
($body:expr) => {{
|
|
|
|
|
let mock_server = wiremock::MockServer::start().await;
|
|
|
|
|
let template = wiremock::ResponseTemplate::new(200).set_body_string($body);
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.respond_with(template)
|
|
|
|
|
.mount(&mock_server)
|
|
|
|
|
.await;
|
|
|
|
|
mock_server
|
|
|
|
|
}};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the "main" binary name (e.g. `lychee`)
|
2020-12-02 22:28:37 +00:00
|
|
|
fn main_command() -> Command {
|
|
|
|
|
Command::cargo_bin(env!("CARGO_PKG_NAME")).expect("Couldn't get cargo package name")
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-01 19:55:22 +00:00
|
|
|
/// Helper function to get the root path of the project.
|
|
|
|
|
fn root_path() -> PathBuf {
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
|
|
|
.parent()
|
|
|
|
|
.unwrap()
|
2023-03-01 19:55:22 +00:00
|
|
|
.to_path_buf()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Helper function to get the path to the fixtures directory.
|
|
|
|
|
fn fixtures_path() -> PathBuf {
|
|
|
|
|
root_path().join("fixtures")
|
2020-12-02 22:28:37 +00:00
|
|
|
}
|
|
|
|
|
|
2023-05-05 22:47:32 +00:00
|
|
|
#[derive(Default, Serialize)]
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
struct MockResponseStats {
|
2023-02-25 16:57:57 +00:00
|
|
|
detailed_stats: bool,
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
total: usize,
|
|
|
|
|
successful: usize,
|
2021-09-08 23:49:25 +00:00
|
|
|
unknown: usize,
|
2023-02-25 16:57:57 +00:00
|
|
|
unsupported: usize,
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
timeouts: usize,
|
|
|
|
|
redirects: usize,
|
|
|
|
|
excludes: usize,
|
|
|
|
|
errors: usize,
|
2022-01-14 14:25:51 +00:00
|
|
|
cached: usize,
|
2023-02-25 16:57:57 +00:00
|
|
|
success_map: HashMap<InputSource, HashSet<ResponseBody>>,
|
2024-11-08 08:02:33 +00:00
|
|
|
error_map: HashMap<InputSource, HashSet<ResponseBody>>,
|
2023-03-27 22:45:06 +00:00
|
|
|
suggestion_map: HashMap<InputSource, HashSet<ResponseBody>>,
|
2023-02-25 16:57:57 +00:00
|
|
|
excluded_map: HashMap<InputSource, HashSet<ResponseBody>>,
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
}
|
2020-10-17 08:01:06 +00:00
|
|
|
|
2023-01-03 23:38:19 +00:00
|
|
|
/// Helper macro to test the output of the JSON format.
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
macro_rules! test_json_output {
|
|
|
|
|
($test_file:expr, $expected:expr $(, $arg:expr)*) => {{
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_path = fixtures_path().join($test_file);
|
|
|
|
|
let outfile = format!("{}.json", uuid::Uuid::new_v4());
|
2021-03-29 21:28:17 +00:00
|
|
|
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
cmd$(.arg($arg))*.arg("--output").arg(&outfile).arg("--format").arg("json").arg(test_path).assert().success();
|
|
|
|
|
|
|
|
|
|
let output = std::fs::read_to_string(&outfile)?;
|
|
|
|
|
std::fs::remove_file(outfile)?;
|
2023-05-05 22:47:32 +00:00
|
|
|
|
|
|
|
|
let actual: Value = serde_json::from_str(&output)?;
|
|
|
|
|
let expected: Value = serde_json::to_value(&$expected)?;
|
|
|
|
|
|
|
|
|
|
assert_json_include!(actual: actual, expected: expected);
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
Ok(())
|
|
|
|
|
}};
|
2021-03-29 21:28:17 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-06 23:22:50 +00:00
|
|
|
/// Test that the default report output format (compact) and mode (color)
|
|
|
|
|
/// prints the failed URLs as well as their status codes on error. Make
|
|
|
|
|
/// sure that the status code only occurs once.
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_compact_output_format_contains_status() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path().join("TEST_INVALID_URLS.html");
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--format")
|
|
|
|
|
.arg("compact")
|
|
|
|
|
.arg("--mode")
|
|
|
|
|
.arg("color")
|
|
|
|
|
.arg(test_path)
|
|
|
|
|
.env("FORCE_COLOR", "1")
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2);
|
|
|
|
|
|
|
|
|
|
let output = cmd.output()?;
|
|
|
|
|
|
|
|
|
|
// Check that the output contains the status code (once) and the URL
|
|
|
|
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
|
|
|
|
|
|
|
|
|
// The expected output is as follows:
|
|
|
|
|
// "Find details below."
|
|
|
|
|
// [EMPTY LINE]
|
|
|
|
|
// [path/to/file]:
|
|
|
|
|
// [400] https://httpbin.org/status/404
|
|
|
|
|
// [500] https://httpbin.org/status/500
|
|
|
|
|
// [502] https://httpbin.org/status/502
|
|
|
|
|
// (the order of the URLs may vary)
|
|
|
|
|
|
|
|
|
|
// Check that the output contains the file path
|
|
|
|
|
assert!(output_str.contains("TEST_INVALID_URLS.html"));
|
|
|
|
|
|
|
|
|
|
let re = Regex::new(r"\s{5}\[\d{3}\] https://httpbin\.org/status/\d{3}").unwrap();
|
|
|
|
|
let matches: Vec<&str> = re.find_iter(&output_str).map(|m| m.as_str()).collect();
|
|
|
|
|
|
|
|
|
|
// Check that the status code occurs only once
|
|
|
|
|
assert_eq!(matches.len(), 3);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-08 08:02:33 +00:00
|
|
|
/// Test JSON output format
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_json_output() -> Result<()> {
|
|
|
|
|
// Server that returns a bunch of 200 OK responses
|
|
|
|
|
let mock_server_ok = mock_server!(StatusCode::OK);
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--format")
|
|
|
|
|
.arg("json")
|
|
|
|
|
.arg("-vv")
|
|
|
|
|
.arg("--no-progress")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server_ok.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
let output = cmd.output().unwrap();
|
|
|
|
|
let output_json = serde_json::from_slice::<Value>(&output.stdout)?;
|
|
|
|
|
|
|
|
|
|
// Check that the output is valid JSON
|
|
|
|
|
assert!(output_json.is_object());
|
|
|
|
|
// Check that the output contains the expected keys
|
|
|
|
|
assert!(output_json.get("detailed_stats").is_some());
|
|
|
|
|
assert!(output_json.get("success_map").is_some());
|
|
|
|
|
assert!(output_json.get("error_map").is_some());
|
|
|
|
|
assert!(output_json.get("excluded_map").is_some());
|
|
|
|
|
|
|
|
|
|
// Check the success map
|
|
|
|
|
let success_map = output_json["success_map"].as_object().unwrap();
|
|
|
|
|
assert_eq!(success_map.len(), 1);
|
|
|
|
|
|
|
|
|
|
// Get the actual URL from the mock server for comparison
|
|
|
|
|
let mock_url = mock_server_ok.uri();
|
|
|
|
|
|
|
|
|
|
// Create the expected success map structure
|
|
|
|
|
let expected_success_map = serde_json::json!({
|
|
|
|
|
"stdin": [
|
|
|
|
|
{
|
|
|
|
|
"status": {
|
|
|
|
|
"code": 200,
|
|
|
|
|
"text": "200 OK"
|
|
|
|
|
},
|
|
|
|
|
"url": format!("{mock_url}/"),
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Compare the actual success map with the expected one
|
|
|
|
|
assert_eq!(
|
|
|
|
|
success_map,
|
|
|
|
|
expected_success_map.as_object().unwrap(),
|
|
|
|
|
"Success map doesn't match expected structure"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 12:12:55 +00:00
|
|
|
/// JSON-formatted output should always be valid JSON.
|
|
|
|
|
/// Additional hints and error messages should be printed to `stderr`.
|
|
|
|
|
/// See https://github.com/lycheeverse/lychee/issues/1355
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_valid_json_output_to_stdout_on_error() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path().join("TEST_GITHUB_404.md");
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--format")
|
|
|
|
|
.arg("json")
|
|
|
|
|
.arg(test_path)
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2);
|
|
|
|
|
|
|
|
|
|
let output = cmd.output()?;
|
|
|
|
|
|
|
|
|
|
// Check that the output is valid JSON
|
|
|
|
|
assert!(serde_json::from_slice::<Value>(&output.stdout).is_ok());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2024-01-30 22:58:18 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_detailed_json_output_on_error() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path().join("TEST_DETAILED_JSON_OUTPUT_ERROR.md");
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--format")
|
|
|
|
|
.arg("json")
|
|
|
|
|
.arg(&test_path)
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2);
|
|
|
|
|
|
|
|
|
|
let output = cmd.output()?;
|
|
|
|
|
|
|
|
|
|
// Check that the output is valid JSON
|
|
|
|
|
assert!(serde_json::from_slice::<Value>(&output.stdout).is_ok());
|
|
|
|
|
|
2024-11-08 08:02:33 +00:00
|
|
|
// Parse site error status from the error_map
|
2024-01-30 22:58:18 +00:00
|
|
|
let output_json = serde_json::from_slice::<Value>(&output.stdout).unwrap();
|
2024-11-08 08:02:33 +00:00
|
|
|
let site_error_status =
|
|
|
|
|
&output_json["error_map"][&test_path.to_str().unwrap()][0]["status"];
|
2024-01-30 22:58:18 +00:00
|
|
|
|
2024-06-14 17:47:52 +00:00
|
|
|
assert_eq!(
|
2024-11-06 23:22:50 +00:00
|
|
|
"error sending request for url (https://expired.badssl.com/) Maybe a certificate error?",
|
2024-06-14 17:47:52 +00:00
|
|
|
site_error_status["details"]
|
|
|
|
|
);
|
2024-01-30 22:58:18 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2024-01-24 12:12:55 +00:00
|
|
|
|
2020-10-26 22:31:31 +00:00
|
|
|
#[test]
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
fn test_exclude_all_private() -> Result<()> {
|
|
|
|
|
test_json_output!(
|
|
|
|
|
"TEST_ALL_PRIVATE.md",
|
|
|
|
|
MockResponseStats {
|
|
|
|
|
total: 7,
|
|
|
|
|
excludes: 7,
|
|
|
|
|
..MockResponseStats::default()
|
|
|
|
|
},
|
2023-02-25 16:57:57 +00:00
|
|
|
"--exclude-all-private"
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
)
|
|
|
|
|
}
|
2020-10-26 22:31:31 +00:00
|
|
|
|
2025-07-18 15:49:18 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_local_directories() -> Result<()> {
|
|
|
|
|
test_json_output!(
|
|
|
|
|
"TEST_LOCAL_DIRECTORIES.md",
|
|
|
|
|
MockResponseStats {
|
|
|
|
|
total: 4,
|
|
|
|
|
successful: 4,
|
|
|
|
|
..MockResponseStats::default()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
#[test]
|
2023-07-19 17:58:38 +00:00
|
|
|
fn test_email() -> Result<()> {
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
test_json_output!(
|
|
|
|
|
"TEST_EMAIL.md",
|
|
|
|
|
MockResponseStats {
|
2023-07-19 17:58:38 +00:00
|
|
|
total: 5,
|
|
|
|
|
excludes: 0,
|
|
|
|
|
successful: 5,
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
..MockResponseStats::default()
|
|
|
|
|
},
|
2023-07-19 17:58:38 +00:00
|
|
|
"--include-mail"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_exclude_email_by_default() -> Result<()> {
|
|
|
|
|
test_json_output!(
|
|
|
|
|
"TEST_EMAIL.md",
|
|
|
|
|
MockResponseStats {
|
|
|
|
|
total: 5,
|
|
|
|
|
excludes: 3,
|
|
|
|
|
successful: 2,
|
|
|
|
|
..MockResponseStats::default()
|
|
|
|
|
}
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
)
|
2020-10-26 22:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
2022-11-05 22:40:33 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_email_html_with_subject() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("TEST_EMAIL_QUERY_PARAMS.html");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg(input)
|
2023-07-19 17:58:38 +00:00
|
|
|
.arg("--include-mail")
|
2022-11-05 22:40:33 +00:00
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("hello@example.org?subject=%5BHello%5D"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_email_markdown_with_subject() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("TEST_EMAIL_QUERY_PARAMS.md");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg(input)
|
2023-07-19 17:58:38 +00:00
|
|
|
.arg("--include-mail")
|
2022-11-05 22:40:33 +00:00
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("hello@example.org?subject=%5BHello%5D"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 23:32:11 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_stylesheet_misinterpreted_as_email() -> Result<()> {
|
|
|
|
|
test_json_output!(
|
|
|
|
|
"TEST_STYLESHEET_LINK.md",
|
|
|
|
|
MockResponseStats {
|
|
|
|
|
total: 0,
|
|
|
|
|
..MockResponseStats::default()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
/// Test that a GitHub link can be checked without specifying the token.
|
2021-02-28 18:09:11 +00:00
|
|
|
#[test]
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
fn test_check_github_no_token() -> Result<()> {
|
|
|
|
|
test_json_output!(
|
|
|
|
|
"TEST_GITHUB.md",
|
|
|
|
|
MockResponseStats {
|
|
|
|
|
total: 1,
|
|
|
|
|
successful: 1,
|
|
|
|
|
..MockResponseStats::default()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
2021-02-28 18:09:11 +00:00
|
|
|
|
2021-04-26 15:16:58 +00:00
|
|
|
/// Test unsupported URI schemes
|
|
|
|
|
#[test]
|
2023-03-10 21:36:45 +00:00
|
|
|
fn test_unsupported_uri_schemes_are_ignored() {
|
2021-07-04 23:35:36 +00:00
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_schemes_path = fixtures_path().join("TEST_SCHEMES.txt");
|
|
|
|
|
|
|
|
|
|
// Exclude file link because it doesn't exist on the filesystem.
|
|
|
|
|
// (File URIs are absolute paths, which we don't have.)
|
|
|
|
|
// Nevertheless, the `file` scheme should be recognized.
|
|
|
|
|
cmd.arg(test_schemes_path)
|
|
|
|
|
.arg("--exclude")
|
|
|
|
|
.arg("file://")
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2023-02-27 21:58:03 +00:00
|
|
|
.stdout(contains("3 Total"))
|
2021-11-17 23:44:48 +00:00
|
|
|
.stdout(contains("1 OK"))
|
|
|
|
|
.stdout(contains("1 Excluded"));
|
2021-04-26 15:16:58 +00:00
|
|
|
}
|
|
|
|
|
|
2021-09-02 23:48:50 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_resolve_paths() {
|
|
|
|
|
let mut cmd = main_command();
|
2024-10-26 02:07:37 +00:00
|
|
|
let dir = fixtures_path().join("resolve_paths");
|
2021-09-02 23:48:50 +00:00
|
|
|
|
|
|
|
|
cmd.arg("--offline")
|
2025-02-16 00:41:32 +00:00
|
|
|
.arg("--base-url")
|
2024-10-26 02:07:37 +00:00
|
|
|
.arg(&dir)
|
|
|
|
|
.arg(dir.join("index.html"))
|
2021-09-02 23:48:50 +00:00
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2024-10-26 02:07:37 +00:00
|
|
|
.stdout(contains("3 Total"))
|
|
|
|
|
.stdout(contains("3 OK"));
|
2021-09-02 23:48:50 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-13 13:36:33 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_resolve_paths_from_root_dir() {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let dir = fixtures_path().join("resolve_paths_from_root_dir");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--offline")
|
|
|
|
|
.arg("--include-fragments")
|
|
|
|
|
.arg("--root-dir")
|
|
|
|
|
.arg(&dir)
|
|
|
|
|
.arg(dir.join("nested").join("index.html"))
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.stdout(contains("7 Total"))
|
|
|
|
|
.stdout(contains("5 OK"))
|
|
|
|
|
.stdout(contains("2 Errors"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_resolve_paths_from_root_dir_and_base_url() {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let dir = fixtures_path();
|
|
|
|
|
|
|
|
|
|
cmd.arg("--offline")
|
|
|
|
|
.arg("--root-dir")
|
|
|
|
|
.arg("/resolve_paths")
|
2025-02-16 00:41:32 +00:00
|
|
|
.arg("--base-url")
|
2024-12-13 13:36:33 +00:00
|
|
|
.arg(&dir)
|
|
|
|
|
.arg(dir.join("resolve_paths").join("index.html"))
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("3 Total"))
|
|
|
|
|
.stdout(contains("3 OK"));
|
|
|
|
|
}
|
|
|
|
|
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
#[test]
|
2023-03-02 16:26:00 +00:00
|
|
|
fn test_youtube_quirk() {
|
|
|
|
|
let url = "https://www.youtube.com/watch?v=NlKuICiT470&list=PLbWDhxwM_45mPVToqaIZNbZeIzFchsKKQ&index=7";
|
|
|
|
|
|
2023-03-02 16:11:26 +00:00
|
|
|
main_command()
|
2023-03-02 16:26:00 +00:00
|
|
|
.write_stdin(url)
|
2023-03-02 16:11:26 +00:00
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--no-progress")
|
2023-03-02 16:26:00 +00:00
|
|
|
.arg("-")
|
2023-03-02 16:11:26 +00:00
|
|
|
.assert()
|
|
|
|
|
.success()
|
2023-03-02 16:26:00 +00:00
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("1 OK"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2023-07-13 19:48:46 +00:00
|
|
|
fn test_crates_io_quirk() {
|
2023-03-02 16:26:00 +00:00
|
|
|
let url = "https://crates.io/crates/lychee";
|
|
|
|
|
|
|
|
|
|
main_command()
|
|
|
|
|
.write_stdin(url)
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--no-progress")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("1 OK"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2023-07-13 15:31:59 +00:00
|
|
|
// Exclude Twitter links because they require login to view tweets.
|
|
|
|
|
// https://techcrunch.com/2023/06/30/twitter-now-requires-an-account-to-view-tweets/
|
|
|
|
|
// https://github.com/zedeus/nitter/issues/919
|
|
|
|
|
fn test_ignored_hosts() {
|
2023-03-02 16:26:00 +00:00
|
|
|
let url = "https://twitter.com/zarfeblong/status/1339742840142872577";
|
|
|
|
|
|
|
|
|
|
main_command()
|
|
|
|
|
.write_stdin(url)
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--no-progress")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("1 Total"))
|
2023-07-13 15:31:59 +00:00
|
|
|
.stdout(contains("1 Excluded"));
|
2021-02-28 18:09:11 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-02 22:28:37 +00:00
|
|
|
#[tokio::test]
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
async fn test_failure_404_link() -> Result<()> {
|
|
|
|
|
let mock_server = mock_server!(StatusCode::NOT_FOUND);
|
|
|
|
|
let dir = tempfile::tempdir()?;
|
2020-12-02 22:28:37 +00:00
|
|
|
let file_path = dir.path().join("test.txt");
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
let mut file = File::create(&file_path)?;
|
|
|
|
|
writeln!(file, "{}", mock_server.uri())?;
|
2020-10-26 22:31:31 +00:00
|
|
|
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
let mut cmd = main_command();
|
2020-12-02 22:28:37 +00:00
|
|
|
cmd.arg(file_path)
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2);
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
2020-10-26 22:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
2021-04-26 16:24:54 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_schemes() {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_schemes_path = fixtures_path().join("TEST_SCHEMES.md");
|
|
|
|
|
|
|
|
|
|
cmd.arg(test_schemes_path)
|
|
|
|
|
.arg("--scheme")
|
|
|
|
|
.arg("https")
|
|
|
|
|
.arg("--scheme")
|
|
|
|
|
.arg("http")
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2021-11-17 23:44:48 +00:00
|
|
|
.stdout(contains("3 Total"))
|
|
|
|
|
.stdout(contains("2 OK"))
|
|
|
|
|
.stdout(contains("1 Excluded"));
|
2021-04-26 16:24:54 +00:00
|
|
|
}
|
|
|
|
|
|
2021-05-04 11:28:39 +00:00
|
|
|
#[test]
|
2021-10-07 16:07:18 +00:00
|
|
|
fn test_caching_single_file() {
|
2021-05-04 11:28:39 +00:00
|
|
|
let mut cmd = main_command();
|
2021-10-07 16:07:18 +00:00
|
|
|
// Repetitions in one file shall all be checked and counted only once.
|
|
|
|
|
let test_schemes_path_1 = fixtures_path().join("TEST_REPETITION_1.txt");
|
2021-05-04 11:28:39 +00:00
|
|
|
|
2021-10-07 16:07:18 +00:00
|
|
|
cmd.arg(&test_schemes_path_1)
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2021-11-17 23:44:48 +00:00
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("1 OK"));
|
2021-10-07 16:07:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
// Test that two identical requests don't get executed twice.
|
2022-01-14 14:25:51 +00:00
|
|
|
fn test_caching_across_files() -> Result<()> {
|
|
|
|
|
// Repetitions across multiple files shall all be checked only once.
|
|
|
|
|
let repeated_uris = fixtures_path().join("TEST_REPETITION_*.txt");
|
2021-10-07 16:07:18 +00:00
|
|
|
|
2022-01-14 14:25:51 +00:00
|
|
|
test_json_output!(
|
|
|
|
|
repeated_uris,
|
|
|
|
|
MockResponseStats {
|
|
|
|
|
total: 2,
|
|
|
|
|
cached: 1,
|
|
|
|
|
successful: 2,
|
|
|
|
|
excludes: 0,
|
|
|
|
|
..MockResponseStats::default()
|
|
|
|
|
},
|
|
|
|
|
// Two requests to the same URI may be executed in parallel. As a
|
|
|
|
|
// result, the response might not be cached and the test would be
|
|
|
|
|
// flaky. Therefore limit the concurrency to one request at a time.
|
|
|
|
|
"--max-concurrency",
|
|
|
|
|
"1"
|
|
|
|
|
)
|
2021-05-04 11:28:39 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-26 22:31:31 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_failure_github_404_no_token() {
|
2020-12-02 22:28:37 +00:00
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_github_404_path = fixtures_path().join("TEST_GITHUB_404.md");
|
2020-10-26 22:31:31 +00:00
|
|
|
|
|
|
|
|
cmd.arg(test_github_404_path)
|
2021-02-21 16:19:32 +00:00
|
|
|
.arg("--no-progress")
|
2020-11-24 20:30:06 +00:00
|
|
|
.env_clear()
|
2020-10-26 22:31:31 +00:00
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2)
|
2022-03-03 09:04:55 +00:00
|
|
|
.stdout(contains(
|
2025-05-12 07:32:39 +00:00
|
|
|
r#"[404] https://github.com/mre/idiomatic-rust-doesnt-exist-man | Rejected status code (this depends on your "accept" configuration): Not Found"#
|
2022-03-03 09:04:55 +00:00
|
|
|
))
|
2024-01-28 01:52:45 +00:00
|
|
|
.stderr(contains(
|
2024-04-25 20:44:24 +00:00
|
|
|
"There were issues with GitHub URLs. You could try setting a GitHub token and running lychee again.",
|
2022-03-03 09:04:55 +00:00
|
|
|
));
|
2020-10-26 22:31:31 +00:00
|
|
|
}
|
2020-12-02 22:28:37 +00:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_stdin_input() {
|
|
|
|
|
let mut cmd = main_command();
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
2020-12-02 22:28:37 +00:00
|
|
|
|
|
|
|
|
cmd.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_stdin_input_failure() {
|
|
|
|
|
let mut cmd = main_command();
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
let mock_server = mock_server!(StatusCode::INTERNAL_SERVER_ERROR);
|
2020-12-02 22:28:37 +00:00
|
|
|
|
|
|
|
|
cmd.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_stdin_input_multiple() {
|
|
|
|
|
let mut cmd = main_command();
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
let mock_server_a = mock_server!(StatusCode::OK);
|
|
|
|
|
let mock_server_b = mock_server!(StatusCode::OK);
|
2020-12-02 22:28:37 +00:00
|
|
|
|
|
|
|
|
// this behavior (treating multiple `-` as separate inputs) is the same as most CLI tools
|
|
|
|
|
// that accept `-` as stdin, e.g. `cat`, `bat`, `grep` etc.
|
|
|
|
|
cmd.arg("-")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server_a.uri())
|
|
|
|
|
.write_stdin(mock_server_b.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_missing_file_ok_if_skip_missing() {
|
|
|
|
|
let mut cmd = main_command();
|
2021-11-17 23:44:48 +00:00
|
|
|
let filename = format!("non-existing-file-{}", uuid::Uuid::new_v4());
|
2020-12-02 22:28:37 +00:00
|
|
|
|
|
|
|
|
cmd.arg(&filename).arg("--skip-missing").assert().success();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 08:26:22 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_skips_hidden_files_by_default() {
|
|
|
|
|
main_command()
|
|
|
|
|
.arg(fixtures_path().join("hidden/"))
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2024-09-20 13:07:39 +00:00
|
|
|
.stdout(contains("0 Total"));
|
2024-09-20 08:26:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_include_hidden_file() {
|
|
|
|
|
main_command()
|
|
|
|
|
.arg(fixtures_path().join("hidden/"))
|
|
|
|
|
.arg("--hidden")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2024-09-20 13:07:39 +00:00
|
|
|
.stdout(contains("1 Total"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_skips_ignored_files_by_default() {
|
|
|
|
|
main_command()
|
|
|
|
|
.arg(fixtures_path().join("ignore/"))
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("0 Total"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_include_ignored_file() {
|
|
|
|
|
main_command()
|
|
|
|
|
.arg(fixtures_path().join("ignore/"))
|
|
|
|
|
.arg("--no-ignore")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("1 Total"));
|
2024-09-20 08:26:22 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-02 22:28:37 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_glob() -> Result<()> {
|
|
|
|
|
// using Result to be able to use `?`
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
let dir = tempfile::tempdir()?;
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
let mock_server_a = mock_server!(StatusCode::OK);
|
|
|
|
|
let mock_server_b = mock_server!(StatusCode::OK);
|
2020-12-02 22:28:37 +00:00
|
|
|
let mut file_a = File::create(dir.path().join("a.md"))?;
|
|
|
|
|
let mut file_b = File::create(dir.path().join("b.md"))?;
|
|
|
|
|
|
|
|
|
|
writeln!(file_a, "{}", mock_server_a.uri().as_str())?;
|
|
|
|
|
writeln!(file_b, "{}", mock_server_b.uri().as_str())?;
|
|
|
|
|
|
|
|
|
|
cmd.arg(dir.path().join("*.md"))
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2021-11-17 23:44:48 +00:00
|
|
|
.stdout(contains("2 Total"));
|
2020-12-02 22:28:37 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")] // MacOS and Windows have case-insensitive filesystems
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_glob_ignore_case() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
let dir = tempfile::tempdir()?;
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
let mock_server_a = mock_server!(StatusCode::OK);
|
|
|
|
|
let mock_server_b = mock_server!(StatusCode::OK);
|
2020-12-02 22:28:37 +00:00
|
|
|
let mut file_a = File::create(dir.path().join("README.md"))?;
|
|
|
|
|
let mut file_b = File::create(dir.path().join("readme.md"))?;
|
|
|
|
|
|
|
|
|
|
writeln!(file_a, "{}", mock_server_a.uri().as_str())?;
|
|
|
|
|
writeln!(file_b, "{}", mock_server_b.uri().as_str())?;
|
|
|
|
|
|
|
|
|
|
cmd.arg(dir.path().join("[r]eadme.md"))
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--glob-ignore-case")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2021-11-17 23:44:48 +00:00
|
|
|
.stdout(contains("2 Total"));
|
2020-12-02 22:28:37 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_glob_recursive() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
let dir = tempfile::tempdir()?;
|
|
|
|
|
let subdir_level_1 = tempfile::tempdir_in(&dir)?;
|
|
|
|
|
let subdir_level_2 = tempfile::tempdir_in(&subdir_level_1)?;
|
|
|
|
|
|
Major refactor of codebase (#208)
- The binary component and library component are separated as two
packages in the same workspace.
- `lychee` is the binary component, in `lychee-bin/*`.
- `lychee-lib` is the library component, in `lychee-lib/*`.
- Users can now install only the `lychee-lib`, instead of both
components, that would require fewer dependencies and faster
compilation.
- Dependencies for each component are adjusted and updated. E.g.,
no CLI dependencies for `lychee-lib`.
- CLI tests are only moved to `lychee`, as it has nothing to do
with the library component.
- `Status::Error` is refactored to contain dedicated error enum,
`ErrorKind`.
- The motivation is to delay the formatting of errors to strings.
Note that `e.to_string()` is not necessarily cheap (though
trivial in many cases). The formatting is no delayed until the
error is needed to be displayed to users. So in some cases, if
the error is never used, it means that it won't be formatted at
all.
- Replaced `regex` based matching with one of the following:
- Simple string equality test in the case of 'false positivie'.
- URL parsing based test, in the case of extracting repository and
user name for GitHub links.
- Either cases would be much more efficient than `regex` based
matching. First, there's no need to construct a state machine for
regex. Second, URL is already verified and parsed on its creation,
and extracting its components is fairly cheap. Also, this removes
the dependency on `lazy-static` in `lychee-lib`.
- `types` module now has a sub-directory, and its components are now
separated into their own modules (in that sub-directory).
- `lychee-lib::test_utils` module is only compiled for tests.
- `wiremock` is moved to `dev-dependency` as it's only needed for
`test` modules.
- Dependencies are listed in alphabetical order.
- Imports are organized in the following fashion:
- Imports from `std`
- Imports from 3rd-party crates, and `lychee-lib`.
- Imports from `crate::*` or `super::*`.
- No glob import.
- I followed suggestion from `cargo clippy`, with `clippy::all` and
`clippy:pedantic`.
Co-authored-by: Lucius Hu <lebensterben@users.noreply.github.com>
2021-04-14 23:24:11 +00:00
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
2020-12-02 22:28:37 +00:00
|
|
|
let mut file = File::create(subdir_level_2.path().join("test.md"))?;
|
|
|
|
|
|
|
|
|
|
writeln!(file, "{}", mock_server.uri().as_str())?;
|
|
|
|
|
|
|
|
|
|
// ** should be a recursive glob
|
|
|
|
|
cmd.arg(dir.path().join("**/*.md"))
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2021-11-17 23:44:48 +00:00
|
|
|
.stdout(contains("1 Total"));
|
2020-12-02 22:28:37 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-12-14 00:15:14 +00:00
|
|
|
|
|
|
|
|
/// Test formatted file output
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_formatted_file_output() -> Result<()> {
|
2023-05-05 22:47:32 +00:00
|
|
|
test_json_output!(
|
|
|
|
|
"TEST.md",
|
|
|
|
|
MockResponseStats {
|
2023-07-31 14:04:00 +00:00
|
|
|
total: 12,
|
|
|
|
|
successful: 10,
|
2023-07-19 17:58:38 +00:00
|
|
|
excludes: 2,
|
2023-05-05 22:47:32 +00:00
|
|
|
..MockResponseStats::default()
|
|
|
|
|
}
|
|
|
|
|
)
|
2020-12-14 00:15:14 +00:00
|
|
|
}
|
2021-09-01 15:37:31 +00:00
|
|
|
|
2022-11-07 23:33:16 +00:00
|
|
|
/// Test writing output of `--dump` command to file
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_dump_to_file() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_path = fixtures_path().join("TEST.md");
|
|
|
|
|
let outfile = format!("{}", Uuid::new_v4());
|
|
|
|
|
|
|
|
|
|
cmd.arg("--output")
|
|
|
|
|
.arg(&outfile)
|
|
|
|
|
.arg("--dump")
|
2023-07-19 17:58:38 +00:00
|
|
|
.arg("--include-mail")
|
2022-11-07 23:33:16 +00:00
|
|
|
.arg(test_path)
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
let output = fs::read_to_string(&outfile)?;
|
|
|
|
|
|
|
|
|
|
// We expect 11 links in the test file
|
|
|
|
|
// Running the command from the command line will print 9 links,
|
|
|
|
|
// because the actual `--dump` command filters out the two
|
|
|
|
|
// http(s)://example.com links
|
2023-07-31 14:04:00 +00:00
|
|
|
assert_eq!(output.lines().count(), 12);
|
2022-11-07 23:33:16 +00:00
|
|
|
fs::remove_file(outfile)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-01 15:37:31 +00:00
|
|
|
/// Test excludes
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_exclude_wildcard() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_path = fixtures_path().join("TEST.md");
|
|
|
|
|
|
|
|
|
|
cmd.arg(test_path)
|
|
|
|
|
.arg("--exclude")
|
|
|
|
|
.arg(".*")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2023-07-31 14:04:00 +00:00
|
|
|
.stdout(contains("12 Excluded"));
|
2021-09-01 15:37:31 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_exclude_multiple_urls() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_path = fixtures_path().join("TEST.md");
|
|
|
|
|
|
|
|
|
|
cmd.arg(test_path)
|
|
|
|
|
.arg("--exclude")
|
|
|
|
|
.arg("https://en.wikipedia.org/*")
|
2023-08-15 14:41:58 +00:00
|
|
|
.arg("--exclude")
|
2021-09-01 15:37:31 +00:00
|
|
|
.arg("https://ldra.com/")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2023-07-19 17:58:38 +00:00
|
|
|
.stdout(contains("4 Excluded"));
|
2021-09-01 15:37:31 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
2021-09-03 00:12:03 +00:00
|
|
|
}
|
2021-09-04 01:21:54 +00:00
|
|
|
|
2022-02-07 22:17:50 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_empty_config() -> Result<()> {
|
|
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
|
|
|
|
let config = fixtures_path().join("configs").join("empty.toml");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config)
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("1 OK"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-25 11:10:58 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_invalid_default_config() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path().join("configs");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.current_dir(test_path)
|
|
|
|
|
.arg(".")
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.stderr(contains("Cannot load default configuration file"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 12:39:43 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_include_mail_config() -> Result<()> {
|
|
|
|
|
let test_mail_address = "mailto:hello-test@testingabc.io";
|
|
|
|
|
|
|
|
|
|
let mut config = NamedTempFile::new()?;
|
|
|
|
|
writeln!(config, "include_mail = false")?;
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config.path().to_str().unwrap())
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(test_mail_address)
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("1 Excluded"));
|
|
|
|
|
|
|
|
|
|
let mut config = NamedTempFile::new()?;
|
|
|
|
|
writeln!(config, "include_mail = true")?;
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config.path().to_str().unwrap())
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(test_mail_address)
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("1 Error"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-07 22:17:50 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_cache_config() -> Result<()> {
|
|
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
|
|
|
|
let config = fixtures_path().join("configs").join("cache.toml");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config)
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("1 OK"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_invalid_config() {
|
|
|
|
|
let config = fixtures_path().join("configs").join("invalid.toml");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config)
|
|
|
|
|
.arg("-")
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
2024-01-04 21:17:14 +00:00
|
|
|
.failure()
|
|
|
|
|
.stderr(predicate::str::contains("Cannot load configuration file"))
|
|
|
|
|
.stderr(predicate::str::contains("Failed to parse"))
|
|
|
|
|
.stderr(predicate::str::contains("TOML parse error"))
|
|
|
|
|
.stderr(predicate::str::contains("expected newline"));
|
2022-02-07 22:17:50 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-01 19:55:22 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_missing_config_error() {
|
|
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg("config.does.not.exist.toml")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.failure();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_config_example() {
|
|
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
|
|
|
|
let config = root_path().join("lychee.example.toml");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config)
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 17:05:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_config_smoketest() {
|
|
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
|
|
|
|
let config = fixtures_path().join("configs").join("smoketest.toml");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config)
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-09 23:10:22 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_config_accept() {
|
|
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
|
|
|
|
let config = fixtures_path().join("configs").join("accept.toml");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config)
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-23 00:39:53 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_lycheeignore_file() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
2024-09-20 13:07:39 +00:00
|
|
|
let test_path = fixtures_path().join("lycheeignore");
|
2021-11-23 00:39:53 +00:00
|
|
|
|
2022-05-13 16:51:58 +00:00
|
|
|
let cmd = cmd
|
|
|
|
|
.current_dir(test_path)
|
|
|
|
|
.arg("--dump")
|
2021-11-23 00:39:53 +00:00
|
|
|
.arg("TEST.md")
|
|
|
|
|
.assert()
|
2022-05-13 16:51:58 +00:00
|
|
|
.stdout(contains("https://example.com"))
|
|
|
|
|
.stdout(contains("https://example.com/bar"))
|
|
|
|
|
.stdout(contains("https://example.net"));
|
|
|
|
|
|
|
|
|
|
let output = cmd.get_output();
|
|
|
|
|
let output = std::str::from_utf8(&output.stdout).unwrap();
|
|
|
|
|
assert_eq!(output.lines().count(), 3);
|
2021-11-23 00:39:53 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_lycheeignore_and_exclude_file() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
2024-09-20 13:07:39 +00:00
|
|
|
let test_path = fixtures_path().join("lycheeignore");
|
2021-11-23 00:39:53 +00:00
|
|
|
let excludes_path = test_path.join("normal-exclude-file");
|
|
|
|
|
|
|
|
|
|
cmd.current_dir(test_path)
|
|
|
|
|
.arg("TEST.md")
|
|
|
|
|
.arg("--exclude-file")
|
|
|
|
|
.arg(excludes_path)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2022-05-13 16:51:58 +00:00
|
|
|
.stdout(contains("8 Total"))
|
2022-02-18 09:29:49 +00:00
|
|
|
.stdout(contains("6 Excluded"));
|
2021-11-23 00:39:53 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-15 20:45:24 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_lycheecache_file() -> Result<()> {
|
|
|
|
|
let base_path = fixtures_path().join("cache");
|
2022-07-17 16:40:45 +00:00
|
|
|
let cache_file = base_path.join(LYCHEE_CACHE_FILE);
|
|
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Ensure clean state
|
|
|
|
|
if cache_file.exists() {
|
2025-06-27 10:34:48 +00:00
|
|
|
println!("Removing cache file before test: {cache_file:?}");
|
2024-10-27 00:19:35 +00:00
|
|
|
fs::remove_file(&cache_file)?;
|
2024-11-08 05:15:26 +00:00
|
|
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
2024-10-27 00:19:35 +00:00
|
|
|
}
|
2022-07-15 20:45:24 +00:00
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Setup mock servers
|
2022-07-15 20:45:24 +00:00
|
|
|
let mock_server_ok = mock_server!(StatusCode::OK);
|
|
|
|
|
let mock_server_err = mock_server!(StatusCode::NOT_FOUND);
|
|
|
|
|
let mock_server_exclude = mock_server!(StatusCode::OK);
|
|
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Create test file
|
2022-07-15 20:45:24 +00:00
|
|
|
let dir = tempfile::tempdir()?;
|
2024-10-27 00:19:35 +00:00
|
|
|
let file_path = dir.path().join("c.md");
|
|
|
|
|
let mut file = File::create(&file_path)?;
|
2022-07-15 20:45:24 +00:00
|
|
|
writeln!(file, "{}", mock_server_ok.uri().as_str())?;
|
|
|
|
|
writeln!(file, "{}", mock_server_err.uri().as_str())?;
|
|
|
|
|
writeln!(file, "{}", mock_server_exclude.uri().as_str())?;
|
2024-10-27 00:19:35 +00:00
|
|
|
file.sync_all()?;
|
2022-07-15 20:45:24 +00:00
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Create and run command
|
2022-07-15 20:45:24 +00:00
|
|
|
let mut cmd = main_command();
|
2024-10-27 00:19:35 +00:00
|
|
|
cmd.current_dir(&base_path)
|
|
|
|
|
.arg(&file_path)
|
2022-07-15 20:45:24 +00:00
|
|
|
.arg("--verbose")
|
2023-02-27 20:36:39 +00:00
|
|
|
.arg("--no-progress")
|
2022-07-15 20:45:24 +00:00
|
|
|
.arg("--cache")
|
|
|
|
|
.arg("--exclude")
|
|
|
|
|
.arg(mock_server_exclude.uri());
|
|
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Note: Don't check output.status.success() since we expect
|
|
|
|
|
// a non-zero exit code (2) when lychee finds broken links
|
|
|
|
|
let _output = cmd.output()?;
|
2022-07-15 20:45:24 +00:00
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Wait for cache file to be written
|
2024-11-08 05:15:26 +00:00
|
|
|
for _ in 0..20 {
|
2024-10-27 00:19:35 +00:00
|
|
|
if cache_file.exists() {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
|
|
|
}
|
2022-07-15 20:45:24 +00:00
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Check cache contents
|
2022-07-15 20:45:24 +00:00
|
|
|
let data = fs::read_to_string(&cache_file)?;
|
2025-06-27 10:34:48 +00:00
|
|
|
println!("Cache file contents: {data}");
|
2024-11-08 05:15:26 +00:00
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
assert!(
|
|
|
|
|
data.contains(&format!("{}/,200", mock_server_ok.uri())),
|
|
|
|
|
"Missing OK entry in cache"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
data.contains(&format!("{}/,404", mock_server_err.uri())),
|
|
|
|
|
"Missing error entry in cache"
|
|
|
|
|
);
|
2022-07-15 20:45:24 +00:00
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Run again to verify cache behavior
|
|
|
|
|
cmd.assert()
|
2023-04-11 21:43:33 +00:00
|
|
|
.stderr(contains(format!(
|
2024-11-06 23:22:50 +00:00
|
|
|
"[200] {}/ | OK (cached)\n",
|
2022-07-15 20:45:24 +00:00
|
|
|
mock_server_ok.uri()
|
|
|
|
|
)))
|
2023-04-11 21:43:33 +00:00
|
|
|
.stderr(contains(format!(
|
2024-11-06 23:22:50 +00:00
|
|
|
"[404] {}/ | Error (cached)\n",
|
2022-07-15 20:45:24 +00:00
|
|
|
mock_server_err.uri()
|
|
|
|
|
)));
|
|
|
|
|
|
2024-10-27 00:19:35 +00:00
|
|
|
// Clean up
|
2024-11-08 05:15:26 +00:00
|
|
|
fs::remove_file(&cache_file).map_err(|e| {
|
|
|
|
|
anyhow::anyhow!(
|
|
|
|
|
"Failed to remove cache file: {:?}, error: {}",
|
|
|
|
|
cache_file,
|
|
|
|
|
e
|
|
|
|
|
)
|
|
|
|
|
})?;
|
2022-07-15 20:45:24 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-14 00:41:56 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_lycheecache_exclude_custom_status_codes() -> Result<()> {
|
|
|
|
|
let base_path = fixtures_path().join("cache");
|
|
|
|
|
let cache_file = base_path.join(LYCHEE_CACHE_FILE);
|
|
|
|
|
|
|
|
|
|
// Unconditionally remove cache file if it exists
|
|
|
|
|
let _ = fs::remove_file(&cache_file);
|
|
|
|
|
|
|
|
|
|
let mock_server_ok = mock_server!(StatusCode::OK);
|
|
|
|
|
let mock_server_no_content = mock_server!(StatusCode::NO_CONTENT);
|
|
|
|
|
let mock_server_too_many_requests = mock_server!(StatusCode::TOO_MANY_REQUESTS);
|
|
|
|
|
|
|
|
|
|
let dir = tempfile::tempdir()?;
|
|
|
|
|
let mut file = File::create(dir.path().join("c.md"))?;
|
|
|
|
|
|
|
|
|
|
writeln!(file, "{}", mock_server_ok.uri().as_str())?;
|
|
|
|
|
writeln!(file, "{}", mock_server_no_content.uri().as_str())?;
|
|
|
|
|
writeln!(file, "{}", mock_server_too_many_requests.uri().as_str())?;
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_cmd = cmd
|
|
|
|
|
.current_dir(&base_path)
|
|
|
|
|
.arg(dir.path().join("c.md"))
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--no-progress")
|
|
|
|
|
.arg("--cache")
|
|
|
|
|
.arg("--cache-exclude-status")
|
|
|
|
|
.arg("204,429");
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
!cache_file.exists(),
|
|
|
|
|
"cache file should not exist before this test"
|
|
|
|
|
);
|
|
|
|
|
|
2024-11-07 15:24:13 +00:00
|
|
|
// Run first without cache to generate the cache file
|
2024-10-14 00:41:56 +00:00
|
|
|
test_cmd
|
|
|
|
|
.assert()
|
|
|
|
|
.stderr(contains(format!("[200] {}/\n", mock_server_ok.uri())))
|
|
|
|
|
.stderr(contains(format!(
|
2024-11-06 23:22:50 +00:00
|
|
|
"[204] {}/ | 204 No Content: No Content\n",
|
2024-10-14 00:41:56 +00:00
|
|
|
mock_server_no_content.uri()
|
|
|
|
|
)))
|
|
|
|
|
.stderr(contains(format!(
|
2025-05-12 07:57:18 +00:00
|
|
|
"[429] {}/ | Rejected status code (this depends on your \"accept\" configuration): Too Many Requests\n",
|
2024-10-14 00:41:56 +00:00
|
|
|
mock_server_too_many_requests.uri()
|
|
|
|
|
)));
|
|
|
|
|
|
2024-11-07 15:24:13 +00:00
|
|
|
// Check content of cache file
|
2024-10-14 00:41:56 +00:00
|
|
|
let data = fs::read_to_string(&cache_file)?;
|
2024-10-26 02:07:37 +00:00
|
|
|
|
|
|
|
|
if data.is_empty() {
|
|
|
|
|
println!("Cache file is empty!");
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-14 00:41:56 +00:00
|
|
|
assert!(data.contains(&format!("{}/,200", mock_server_ok.uri())));
|
|
|
|
|
assert!(!data.contains(&format!("{}/,204", mock_server_no_content.uri())));
|
|
|
|
|
assert!(!data.contains(&format!("{}/,429", mock_server_too_many_requests.uri())));
|
|
|
|
|
|
2024-11-07 15:24:13 +00:00
|
|
|
// Unconditionally remove the cache file
|
|
|
|
|
let _ = fs::remove_file(&cache_file);
|
2024-10-14 00:41:56 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-28 11:23:07 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_lycheecache_accept_custom_status_codes() -> Result<()> {
|
2023-02-27 22:27:29 +00:00
|
|
|
let base_path = fixtures_path().join("cache_accept_custom_status_codes");
|
2022-11-28 11:23:07 +00:00
|
|
|
let cache_file = base_path.join(LYCHEE_CACHE_FILE);
|
|
|
|
|
|
|
|
|
|
// Unconditionally remove cache file if it exists
|
|
|
|
|
let _ = fs::remove_file(&cache_file);
|
|
|
|
|
|
|
|
|
|
let mock_server_ok = mock_server!(StatusCode::OK);
|
|
|
|
|
let mock_server_teapot = mock_server!(StatusCode::IM_A_TEAPOT);
|
|
|
|
|
let mock_server_server_error = mock_server!(StatusCode::INTERNAL_SERVER_ERROR);
|
|
|
|
|
|
|
|
|
|
let dir = tempfile::tempdir()?;
|
|
|
|
|
let mut file = File::create(dir.path().join("c.md"))?;
|
|
|
|
|
|
|
|
|
|
writeln!(file, "{}", mock_server_ok.uri().as_str())?;
|
|
|
|
|
writeln!(file, "{}", mock_server_teapot.uri().as_str())?;
|
|
|
|
|
writeln!(file, "{}", mock_server_server_error.uri().as_str())?;
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_cmd = cmd
|
|
|
|
|
.current_dir(&base_path)
|
|
|
|
|
.arg(dir.path().join("c.md"))
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--cache");
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
!cache_file.exists(),
|
|
|
|
|
"cache file should not exist before this test"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// run first without cache to generate the cache file
|
|
|
|
|
// ignore exit code
|
|
|
|
|
test_cmd
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2)
|
|
|
|
|
.stdout(contains(format!(
|
2025-05-12 07:57:18 +00:00
|
|
|
r#"[418] {}/ | Rejected status code (this depends on your "accept" configuration): I'm a teapot"#,
|
2022-11-28 11:23:07 +00:00
|
|
|
mock_server_teapot.uri()
|
|
|
|
|
)))
|
|
|
|
|
.stdout(contains(format!(
|
2025-05-12 07:57:18 +00:00
|
|
|
r#"[500] {}/ | Rejected status code (this depends on your "accept" configuration): Internal Server Error"#,
|
2022-11-28 11:23:07 +00:00
|
|
|
mock_server_server_error.uri()
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
|
// check content of cache file
|
|
|
|
|
let data = fs::read_to_string(&cache_file)?;
|
|
|
|
|
assert!(data.contains(&format!("{}/,200", mock_server_ok.uri())));
|
|
|
|
|
assert!(data.contains(&format!("{}/,418", mock_server_teapot.uri())));
|
|
|
|
|
assert!(data.contains(&format!("{}/,500", mock_server_server_error.uri())));
|
|
|
|
|
|
|
|
|
|
// run again to verify cache behavior
|
|
|
|
|
// this time accept 418 and 500 as valid status codes
|
|
|
|
|
test_cmd
|
|
|
|
|
.arg("--no-progress")
|
|
|
|
|
.arg("--accept")
|
2023-02-22 23:25:53 +00:00
|
|
|
.arg("418,500")
|
2022-11-28 11:23:07 +00:00
|
|
|
.assert()
|
|
|
|
|
.success()
|
2023-04-11 21:43:33 +00:00
|
|
|
.stderr(contains(format!(
|
2024-11-06 23:22:50 +00:00
|
|
|
"[418] {}/ | OK (cached)",
|
2022-11-28 11:23:07 +00:00
|
|
|
mock_server_teapot.uri()
|
|
|
|
|
)))
|
2023-04-11 21:43:33 +00:00
|
|
|
.stderr(contains(format!(
|
2024-11-06 23:22:50 +00:00
|
|
|
"[500] {}/ | OK (cached)",
|
2022-11-28 11:23:07 +00:00
|
|
|
mock_server_server_error.uri()
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
|
// clear the cache file
|
|
|
|
|
fs::remove_file(&cache_file)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 09:11:19 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_accept_overrides_defaults_not_additive() -> Result<()> {
|
|
|
|
|
let mock_server_200 = mock_server!(StatusCode::OK);
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--accept")
|
|
|
|
|
.arg("404") // ONLY accept 404 - should reject 200 as we overwrite the default
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server_200.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2)
|
|
|
|
|
.stdout(contains(format!(
|
|
|
|
|
r#"[200] {}/ | Rejected status code (this depends on your "accept" configuration): OK"#,
|
|
|
|
|
mock_server_200.uri()
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-27 22:27:29 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_skip_cache_unsupported() -> Result<()> {
|
|
|
|
|
let base_path = fixtures_path().join("cache");
|
|
|
|
|
let cache_file = base_path.join(LYCHEE_CACHE_FILE);
|
|
|
|
|
|
|
|
|
|
// Unconditionally remove cache file if it exists
|
|
|
|
|
let _ = fs::remove_file(&cache_file);
|
|
|
|
|
|
|
|
|
|
let unsupported_url = "slack://user".to_string();
|
|
|
|
|
let excluded_url = "https://example.com/";
|
|
|
|
|
|
|
|
|
|
// run first without cache to generate the cache file
|
|
|
|
|
main_command()
|
|
|
|
|
.current_dir(&base_path)
|
|
|
|
|
.write_stdin(format!("{unsupported_url}\n{excluded_url}"))
|
|
|
|
|
.arg("--cache")
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--no-progress")
|
|
|
|
|
.arg("--exclude")
|
|
|
|
|
.arg(excluded_url)
|
|
|
|
|
.arg("--")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.assert()
|
2023-04-11 21:43:33 +00:00
|
|
|
.stderr(contains(format!(
|
2023-03-10 21:36:45 +00:00
|
|
|
"[IGNORED] {unsupported_url} | Unsupported: Error creating request client"
|
2023-02-27 22:27:29 +00:00
|
|
|
)))
|
2024-11-06 23:22:50 +00:00
|
|
|
.stderr(contains(format!("[EXCLUDED] {excluded_url}\n")));
|
2023-02-27 22:27:29 +00:00
|
|
|
|
|
|
|
|
// The cache file should be empty, because the only checked URL is
|
|
|
|
|
// unsupported and we don't want to cache that. It might be supported in
|
|
|
|
|
// future versions.
|
|
|
|
|
let buf = fs::read(&cache_file).unwrap();
|
|
|
|
|
assert!(buf.is_empty());
|
|
|
|
|
|
|
|
|
|
// clear the cache file
|
|
|
|
|
fs::remove_file(&cache_file)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-02 00:46:20 +00:00
|
|
|
/// Unknown status codes should be skipped and not cached by default
|
|
|
|
|
/// The reason is that we don't know if they are valid or not
|
|
|
|
|
/// and even if they are invalid, we don't know if they will be valid in the
|
|
|
|
|
/// future.
|
|
|
|
|
///
|
2023-07-13 15:32:41 +00:00
|
|
|
/// Since we cannot test this with our mock server (because hyper panics on
|
|
|
|
|
/// invalid status codes) we use LinkedIn as a test target.
|
|
|
|
|
///
|
|
|
|
|
/// Unfortunately, LinkedIn does not always return 999, so this is a flaky
|
|
|
|
|
/// test. We only check that the cache file doesn't contain any invalid
|
|
|
|
|
/// status codes.
|
2023-06-02 00:46:20 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_skip_cache_unknown_status_code() -> Result<()> {
|
|
|
|
|
let base_path = fixtures_path().join("cache");
|
|
|
|
|
let cache_file = base_path.join(LYCHEE_CACHE_FILE);
|
|
|
|
|
|
|
|
|
|
// Unconditionally remove cache file if it exists
|
|
|
|
|
let _ = fs::remove_file(&cache_file);
|
|
|
|
|
|
|
|
|
|
// https://linkedin.com returns 999 for unknown status codes
|
|
|
|
|
// use this as a test target
|
|
|
|
|
let unknown_url = "https://www.linkedin.com/company/corrode";
|
|
|
|
|
|
|
|
|
|
// run first without cache to generate the cache file
|
|
|
|
|
main_command()
|
|
|
|
|
.current_dir(&base_path)
|
|
|
|
|
.write_stdin(unknown_url.to_string())
|
|
|
|
|
.arg("--cache")
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--no-progress")
|
|
|
|
|
.arg("--")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.assert()
|
2023-07-13 15:32:41 +00:00
|
|
|
// LinkedIn does not always return 999, so we cannot check for that
|
|
|
|
|
// .stderr(contains(format!("[999] {unknown_url} | Unknown status")))
|
|
|
|
|
;
|
2023-06-02 00:46:20 +00:00
|
|
|
|
2023-07-13 15:32:41 +00:00
|
|
|
// If the status code was 999, the cache file should be empty
|
|
|
|
|
// because we do not want to cache unknown status codes
|
2023-06-02 00:46:20 +00:00
|
|
|
let buf = fs::read(&cache_file).unwrap();
|
2023-07-13 15:32:41 +00:00
|
|
|
if !buf.is_empty() {
|
|
|
|
|
let data = String::from_utf8(buf)?;
|
|
|
|
|
// The cache file should not contain any invalid status codes
|
|
|
|
|
// In that case, we expect a single entry with status code 200
|
|
|
|
|
assert!(!data.contains("999"));
|
|
|
|
|
assert!(data.contains("200"));
|
|
|
|
|
}
|
2023-06-02 00:46:20 +00:00
|
|
|
|
|
|
|
|
// clear the cache file
|
|
|
|
|
fs::remove_file(&cache_file)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 23:38:19 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_verbatim_skipped_by_default() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("TEST_CODE_BLOCKS.md");
|
|
|
|
|
|
|
|
|
|
cmd.arg(input)
|
|
|
|
|
.arg("--dump")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(is_empty());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-26 09:42:56 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_include_verbatim() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("TEST_CODE_BLOCKS.md");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--include-verbatim")
|
|
|
|
|
.arg(input)
|
|
|
|
|
.arg("--dump")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("http://127.0.0.1/block"))
|
|
|
|
|
.stdout(contains("http://127.0.0.1/inline"))
|
|
|
|
|
.stdout(contains("http://127.0.0.1/bash"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-03-11 14:18:25 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_verbatim_skipped_by_default_via_file() -> Result<()> {
|
|
|
|
|
let file = fixtures_path().join("TEST_VERBATIM.html");
|
|
|
|
|
|
|
|
|
|
main_command()
|
|
|
|
|
.arg("--dump")
|
|
|
|
|
.arg(file)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(is_empty());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-03-26 09:42:56 +00:00
|
|
|
|
2023-01-03 23:38:19 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_verbatim_skipped_by_default_via_remote_url() -> Result<()> {
|
2022-03-26 09:42:56 +00:00
|
|
|
let mut cmd = main_command();
|
2023-01-03 23:38:19 +00:00
|
|
|
let file = fixtures_path().join("TEST_VERBATIM.html");
|
|
|
|
|
let body = fs::read_to_string(file)?;
|
|
|
|
|
let mock_server = mock_response!(body);
|
2022-03-26 09:42:56 +00:00
|
|
|
|
2023-01-03 23:38:19 +00:00
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg(mock_server.uri())
|
2022-03-26 09:42:56 +00:00
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(is_empty());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 23:38:19 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_include_verbatim_via_remote_url() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let file = fixtures_path().join("TEST_VERBATIM.html");
|
|
|
|
|
let body = fs::read_to_string(file)?;
|
|
|
|
|
let mock_server = mock_response!(body);
|
|
|
|
|
|
|
|
|
|
cmd.arg("--include-verbatim")
|
|
|
|
|
.arg("--dump")
|
|
|
|
|
.arg(mock_server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("http://www.example.com/pre"))
|
|
|
|
|
.stdout(contains("http://www.example.com/code"))
|
|
|
|
|
.stdout(contains("http://www.example.com/samp"))
|
|
|
|
|
.stdout(contains("http://www.example.com/kbd"))
|
|
|
|
|
.stdout(contains("http://www.example.com/var"))
|
|
|
|
|
.stdout(contains("http://www.example.com/script"));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-04 01:21:54 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_require_https() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let test_path = fixtures_path().join("TEST_HTTP.html");
|
|
|
|
|
cmd.arg(&test_path).assert().success();
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--require-https").arg(test_path).assert().failure();
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2021-09-20 09:13:30 +00:00
|
|
|
|
2024-10-26 02:07:37 +00:00
|
|
|
/// If `base-dir` is not set, don't throw an error in case we encounter
|
|
|
|
|
/// an absolute local link (e.g. `/about`) within a file.
|
|
|
|
|
/// Instead, simply ignore the link.
|
2021-09-20 09:13:30 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_ignore_absolute_local_links_without_base() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
let offline_dir = fixtures_path().join("offline");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--offline")
|
2024-08-06 13:43:34 +00:00
|
|
|
.arg(offline_dir.join("index.html"))
|
2021-09-20 09:13:30 +00:00
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2021-11-17 23:44:48 +00:00
|
|
|
.stdout(contains("0 Total"));
|
2021-09-20 09:13:30 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-03-27 00:27:27 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_inputs_without_scheme() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path().join("TEST_HTTP.html");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg("example.com")
|
|
|
|
|
.arg(&test_path)
|
|
|
|
|
.arg("https://example.org")
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-05-13 16:53:16 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_print_excluded_links_in_verbose_mode() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path().join("TEST_DUMP_EXCLUDE.txt");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--exclude")
|
2022-05-29 19:41:22 +00:00
|
|
|
.arg("example.com")
|
2022-05-13 16:53:16 +00:00
|
|
|
.arg("--")
|
|
|
|
|
.arg(&test_path)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains(format!(
|
|
|
|
|
"https://example.com/ ({}) [excluded]",
|
|
|
|
|
test_path.display()
|
|
|
|
|
)))
|
|
|
|
|
.stdout(contains(format!(
|
|
|
|
|
"https://example.org/ ({})",
|
|
|
|
|
test_path.display()
|
|
|
|
|
)))
|
|
|
|
|
.stdout(contains(format!(
|
|
|
|
|
"https://example.com/foo/bar ({}) [excluded]",
|
|
|
|
|
test_path.display()
|
|
|
|
|
)));
|
2022-05-29 19:41:22 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_remap_uri() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg("--remap")
|
|
|
|
|
.arg("https://example.com http://127.0.0.1:8080")
|
|
|
|
|
.arg("--remap")
|
|
|
|
|
.arg("https://example.org https://staging.example.com")
|
|
|
|
|
.arg("--")
|
|
|
|
|
.arg("-")
|
2023-02-27 20:22:43 +00:00
|
|
|
.write_stdin("https://example.com\nhttps://example.org\nhttps://example.net\n")
|
2022-05-29 19:41:22 +00:00
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("http://127.0.0.1:8080/"))
|
|
|
|
|
.stdout(contains("https://staging.example.com/"))
|
|
|
|
|
.stdout(contains("https://example.net/"));
|
2022-05-13 16:53:16 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-05-29 15:27:09 +00:00
|
|
|
|
2023-02-27 20:22:43 +00:00
|
|
|
#[test]
|
|
|
|
|
#[ignore = "Skipping test until https://github.com/robinst/linkify/pull/58 is merged"]
|
|
|
|
|
fn test_remap_path() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg("--remap")
|
|
|
|
|
.arg("../../issues https://github.com/usnistgov/OSCAL/issues")
|
|
|
|
|
.arg("--")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin("../../issues\n")
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("https://github.com/usnistgov/OSCAL/issues"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-05 13:05:19 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_remap_capture() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg("--remap")
|
|
|
|
|
.arg("https://example.com/(.*) http://example.org/$1")
|
|
|
|
|
.arg("--")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin("https://example.com/foo\n")
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("http://example.org/foo"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_remap_named_capture() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg("--remap")
|
|
|
|
|
.arg("https://github.com/(?P<org>.*)/(?P<repo>.*) https://gitlab.com/$org/$repo")
|
|
|
|
|
.arg("--")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin("https://github.com/lycheeverse/lychee\n")
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("https://gitlab.com/lycheeverse/lychee"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 09:29:17 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_excluded_paths_regex() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path().join("exclude-path");
|
2025-07-17 15:49:04 +00:00
|
|
|
let excluded_path_1 = "\\/excluded?\\/"; // exclude paths containing a directory "exclude" and "excluded"
|
|
|
|
|
let excluded_path_2 = "(\\.mdx|\\.txt)$"; // exclude .mdx and .txt files
|
2025-07-11 09:29:17 +00:00
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.arg("--exclude-path")
|
2025-07-17 15:49:04 +00:00
|
|
|
.arg(excluded_path_1)
|
|
|
|
|
.arg("--exclude-path")
|
|
|
|
|
.arg(excluded_path_2)
|
|
|
|
|
.arg("--dump")
|
2025-07-11 09:29:17 +00:00
|
|
|
.arg("--")
|
|
|
|
|
.arg(&test_path)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
2025-07-17 15:49:04 +00:00
|
|
|
.stdout(eq(
|
|
|
|
|
"https://test.md/to-be-included-inner\nhttps://test.md/to-be-included-outer\n",
|
|
|
|
|
));
|
2025-07-11 09:29:17 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-22 15:15:55 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_handle_relative_paths_as_input() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path();
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.current_dir(&test_path)
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--exclude")
|
|
|
|
|
.arg("example.*")
|
|
|
|
|
.arg("--")
|
|
|
|
|
.arg("./TEST_DUMP_EXCLUDE.txt")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("3 Total"))
|
|
|
|
|
.stdout(contains("3 Excluded"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_handle_nonexistent_relative_paths_as_input() -> Result<()> {
|
|
|
|
|
let test_path = fixtures_path();
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
|
|
|
|
|
cmd.current_dir(&test_path)
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--exclude")
|
|
|
|
|
.arg("example.*")
|
|
|
|
|
.arg("--")
|
|
|
|
|
.arg("./NOT-A-REAL-TEST-FIXTURE.md")
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
2024-10-26 02:07:37 +00:00
|
|
|
.stderr(contains("Invalid file path: ./NOT-A-REAL-TEST-FIXTURE.md"));
|
2022-07-22 15:15:55 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-03-10 14:15:37 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_prevent_too_many_redirects() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
2025-06-25 09:10:39 +00:00
|
|
|
let url = "https://http.codes/308";
|
2023-03-10 14:15:37 +00:00
|
|
|
|
|
|
|
|
cmd.write_stdin(url)
|
|
|
|
|
.arg("--max-redirects")
|
|
|
|
|
.arg("0")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.assert()
|
|
|
|
|
.failure();
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-05-11 18:20:27 +00:00
|
|
|
|
|
|
|
|
#[test]
|
2024-08-27 09:43:39 +00:00
|
|
|
#[ignore = "Skipping test because it is flaky"]
|
2023-05-11 18:20:27 +00:00
|
|
|
fn test_suggests_url_alternatives() -> Result<()> {
|
2025-02-06 13:51:44 +00:00
|
|
|
let re = Regex::new(r"http://web\.archive\.org/web/.*google\.com/jobs\.html").unwrap();
|
|
|
|
|
|
2023-08-17 14:54:59 +00:00
|
|
|
for _ in 0..3 {
|
|
|
|
|
// This can be flaky. Try up to 3 times
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("INTERNET_ARCHIVE.md");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--no-progress").arg("--suggest").arg(input);
|
|
|
|
|
|
|
|
|
|
// Run he command and check if the output contains the expected
|
|
|
|
|
// suggestions
|
|
|
|
|
let assert = cmd.assert();
|
|
|
|
|
let output = assert.get_output();
|
|
|
|
|
|
|
|
|
|
// We're looking for a suggestion that
|
|
|
|
|
// - starts with http://web.archive.org/web/
|
|
|
|
|
// - ends with google.com/jobs.html
|
|
|
|
|
if re.is_match(&String::from_utf8_lossy(&output.stdout)) {
|
|
|
|
|
// Test passed
|
|
|
|
|
return Ok(());
|
|
|
|
|
} else {
|
|
|
|
|
// Wait for a second before retrying
|
|
|
|
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-11 18:20:27 +00:00
|
|
|
|
2023-08-17 14:54:59 +00:00
|
|
|
// If we reached here, it means the test did not pass after multiple attempts
|
|
|
|
|
Err("Did not get the expected command output after multiple attempts.".into())
|
2023-05-11 18:20:27 +00:00
|
|
|
}
|
2023-06-26 10:06:24 +00:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_basic_auth() -> Result<()> {
|
|
|
|
|
let username = "username";
|
|
|
|
|
let password = "password123";
|
|
|
|
|
|
|
|
|
|
let mock_server = wiremock::MockServer::start().await;
|
2025-07-03 11:45:30 +00:00
|
|
|
|
|
|
|
|
Mock::given(method("GET"))
|
|
|
|
|
.and(basic_auth(username, password))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200)) // Authenticated requests are accepted
|
|
|
|
|
.mount(&mock_server)
|
|
|
|
|
.await;
|
|
|
|
|
Mock::given(method("GET"))
|
|
|
|
|
.respond_with(|_: &_| panic!("Received unauthenticated request"))
|
2023-06-26 10:06:24 +00:00
|
|
|
.mount(&mock_server)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
// Configure the command to use the BasicAuthExtractor
|
|
|
|
|
main_command()
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--basic-auth")
|
|
|
|
|
.arg(format!("{} {username}:{password}", mock_server.uri()))
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("1 OK"));
|
|
|
|
|
|
2025-07-03 11:45:30 +00:00
|
|
|
// Websites as direct arguments must also use authentication
|
|
|
|
|
main_command()
|
|
|
|
|
.arg(mock_server.uri())
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--basic-auth")
|
|
|
|
|
.arg(format!("{} {username}:{password}", mock_server.uri()))
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("0 Total")); // Mock server returns no body, so there are no URLs to check
|
|
|
|
|
|
2023-06-26 10:06:24 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_multi_basic_auth() -> Result<()> {
|
|
|
|
|
let username1 = "username";
|
|
|
|
|
let password1 = "password123";
|
|
|
|
|
let mock_server1 = wiremock::MockServer::start().await;
|
|
|
|
|
Mock::given(basic_auth(username1, password1))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200))
|
|
|
|
|
.mount(&mock_server1)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let username2 = "admin_user";
|
|
|
|
|
let password2 = "admin_pw";
|
|
|
|
|
let mock_server2 = wiremock::MockServer::start().await;
|
|
|
|
|
|
|
|
|
|
Mock::given(basic_auth(username2, password2))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200))
|
|
|
|
|
.mount(&mock_server2)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
// Configure the command to use the BasicAuthExtractor
|
|
|
|
|
main_command()
|
|
|
|
|
.arg("--verbose")
|
|
|
|
|
.arg("--basic-auth")
|
|
|
|
|
.arg(format!("{} {username1}:{password1}", mock_server1.uri()))
|
|
|
|
|
.arg("--basic-auth")
|
|
|
|
|
.arg(format!("{} {username2}:{password2}", mock_server2.uri()))
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(format!("{}\n{}", mock_server1.uri(), mock_server2.uri()))
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("2 Total"))
|
|
|
|
|
.stdout(contains("2 OK"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-07-13 15:32:41 +00:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_cookie_jar() -> Result<()> {
|
|
|
|
|
// Create a random cookie jar file
|
|
|
|
|
let cookie_jar = NamedTempFile::new()?;
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--cookie-jar")
|
|
|
|
|
.arg(cookie_jar.path().to_str().unwrap())
|
|
|
|
|
.arg("-")
|
|
|
|
|
// Using Google as a test target because I couldn't
|
|
|
|
|
// get the mock server to work with the cookie jar
|
2024-01-06 11:31:23 +00:00
|
|
|
.write_stdin("https://google.com/ncr")
|
2023-07-13 15:32:41 +00:00
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
// check that the cookie jar file contains the expected cookies
|
|
|
|
|
let file = std::fs::File::open(cookie_jar.path()).map(std::io::BufReader::new)?;
|
2024-11-12 22:51:39 +00:00
|
|
|
let cookie_store = cookie_store::serde::json::load(file)
|
|
|
|
|
.map_err(|e| anyhow!("Failed to load cookie jar: {e}"))?;
|
2023-07-13 15:32:41 +00:00
|
|
|
let all_cookies = cookie_store.iter_any().collect::<Vec<_>>();
|
|
|
|
|
assert!(!all_cookies.is_empty());
|
|
|
|
|
assert!(all_cookies.iter().all(|c| c.domain() == Some("google.com")));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-07-16 16:08:14 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_dump_inputs_glob_md() -> Result<()> {
|
|
|
|
|
let pattern = fixtures_path().join("**/*.md");
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--dump-inputs")
|
|
|
|
|
.arg(pattern)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/subfolder/file2.md"))
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/markdown.md"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_dump_inputs_glob_all() -> Result<()> {
|
|
|
|
|
let pattern = fixtures_path().join("**/*");
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--dump-inputs")
|
|
|
|
|
.arg(pattern)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/subfolder/test.html"))
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/subfolder/file2.md"))
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/subfolder"))
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/markdown.md"))
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/subfolder/example.bin"))
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/some_file.txt"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-07 15:32:32 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_dump_inputs_glob_exclude_path() -> Result<()> {
|
|
|
|
|
let pattern = fixtures_path().join("**/*");
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--dump-inputs")
|
|
|
|
|
.arg(pattern)
|
|
|
|
|
.arg("--exclude-path")
|
|
|
|
|
.arg(fixtures_path().join("dump_inputs/subfolder"))
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/subfolder/test.html").not())
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/subfolder/file2.md").not())
|
|
|
|
|
.stdout(contains("fixtures/dump_inputs/subfolder").not());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-16 16:08:14 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_dump_inputs_url() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--dump-inputs")
|
|
|
|
|
.arg("https://example.com")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("https://example.com"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_dump_inputs_path() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--dump-inputs")
|
|
|
|
|
.arg("fixtures")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("fixtures"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_dump_inputs_stdin() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--dump-inputs")
|
|
|
|
|
.arg("-")
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("Stdin"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-07-31 14:04:00 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_fragments() {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("fragments");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--verbose")
|
|
|
|
|
.arg("--include-fragments")
|
|
|
|
|
.arg(input)
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
2023-08-22 14:44:45 +00:00
|
|
|
.stderr(contains("fixtures/fragments/file1.md#fragment-1"))
|
2023-07-31 14:04:00 +00:00
|
|
|
.stderr(contains("fixtures/fragments/file1.md#fragment-2"))
|
2024-02-05 00:07:56 +00:00
|
|
|
.stderr(contains("fixtures/fragments/file1.md#code-heading"))
|
2023-07-31 14:04:00 +00:00
|
|
|
.stderr(contains("fixtures/fragments/file2.md#custom-id"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/file1.md#missing-fragment"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/file2.md#fragment-1"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/file1.md#kebab-case-fragment"))
|
2024-01-05 14:46:09 +00:00
|
|
|
.stderr(contains(
|
|
|
|
|
"fixtures/fragments/file1.md#lets-wear-a-hat-%C3%AAtre",
|
|
|
|
|
))
|
2023-07-31 14:04:00 +00:00
|
|
|
.stderr(contains("fixtures/fragments/file2.md#missing-fragment"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/empty_file#fragment"))
|
2023-08-22 14:44:45 +00:00
|
|
|
.stderr(contains("fixtures/fragments/file.html#a-word"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/file.html#in-the-beginning"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/file.html#in-the-end"))
|
2023-07-31 14:04:00 +00:00
|
|
|
.stderr(contains(
|
|
|
|
|
"fixtures/fragments/file1.md#kebab-case-fragment-1",
|
|
|
|
|
))
|
2025-02-06 14:09:59 +00:00
|
|
|
.stderr(contains("fixtures/fragments/file.html#top"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/file2.md#top"))
|
2025-05-13 23:52:08 +00:00
|
|
|
.stderr(contains(
|
2025-07-04 20:58:47 +00:00
|
|
|
"https://github.com/lycheeverse/lychee#table-of-contents",
|
2025-05-13 23:52:08 +00:00
|
|
|
))
|
|
|
|
|
.stderr(contains(
|
|
|
|
|
"https://github.com/lycheeverse/lychee#non-existent-anchor",
|
|
|
|
|
))
|
2025-07-04 08:25:57 +00:00
|
|
|
.stderr(contains("fixtures/fragments/sub_dir#non-existing-fragment-1"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/sub_dir#non-existing-fragment-2"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/sub_dir_non_existing_1"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/sub_dir_non_existing_2"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/empty_dir"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/empty_dir#non-existing-fragment-3"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/empty_dir#non-existing-fragment-4"))
|
2025-06-20 15:47:35 +00:00
|
|
|
.stderr(contains("fixtures/fragments/zero.bin"))
|
|
|
|
|
.stderr(contains("fixtures/fragments/zero.bin#"))
|
|
|
|
|
.stderr(contains(
|
2025-06-20 16:06:34 +00:00
|
|
|
"https://raw.githubusercontent.com/lycheeverse/lychee/master/fixtures/fragments/zero.bin",
|
2025-06-20 15:47:35 +00:00
|
|
|
))
|
|
|
|
|
.stderr(contains(
|
2025-06-20 16:06:34 +00:00
|
|
|
"https://raw.githubusercontent.com/lycheeverse/lychee/master/fixtures/fragments/zero.bin#",
|
2025-06-20 15:47:35 +00:00
|
|
|
))
|
|
|
|
|
.stderr(contains("fixtures/fragments/zero.bin#fragment"))
|
|
|
|
|
.stderr(contains(
|
2025-06-20 16:06:34 +00:00
|
|
|
"https://raw.githubusercontent.com/lycheeverse/lychee/master/fixtures/fragments/zero.bin#fragment",
|
2025-06-20 15:47:35 +00:00
|
|
|
))
|
2025-07-04 08:25:57 +00:00
|
|
|
.stdout(contains("42 Total"))
|
feat: skip fragment checking for unsupported MIME types (#1744)
* feat: skip fragment checking for unsupported MIME types
The remote URL/website checker currently passes all URLs with fragments to the fragment checker as HTML document, even if it is a different or unsupported MIME type. This can cause false fragment checking for Markdown documents, failures for other MIME types, especially binaries, and unnecessary traffic for large downloads, which are always finished completely, if the fragment checker is invoked.
This commit checks the Content-Type header of the response:
- Only if it is `text/html`, it is passed to the fragment checker as HTML type.
- Only if it is `text/markdown`, of `text/plain` and URL path ends on `.md`, it is passed to the fragment checker as Markdown type.
- In all other cases, the fragment checker is skipped and the HTTP status is returned.
To invoke the fragment checker with a variable document type, a new `FileType` argument is added to the `check_html_fragment()` function.
The fragment checker test and fixture are adjusted to match the expected result: checking a binary file via remote URL with fragment is now expected to succeed, since its Content-Type header does not invoke the fragment checker anymore.
Signed-off-by: MichaIng <micha@dietpi.com>
* Update fixtures/fragments/file1.md
Co-authored-by: MichaIng <micha@dietpi.com>
---------
Signed-off-by: MichaIng <micha@dietpi.com>
Co-authored-by: Matthias Endler <matthias@endler.dev>
2025-07-06 08:46:06 +00:00
|
|
|
.stdout(contains("30 OK"))
|
2025-06-20 15:47:35 +00:00
|
|
|
// Failures because of missing fragments or failed binary body scan
|
feat: skip fragment checking for unsupported MIME types (#1744)
* feat: skip fragment checking for unsupported MIME types
The remote URL/website checker currently passes all URLs with fragments to the fragment checker as HTML document, even if it is a different or unsupported MIME type. This can cause false fragment checking for Markdown documents, failures for other MIME types, especially binaries, and unnecessary traffic for large downloads, which are always finished completely, if the fragment checker is invoked.
This commit checks the Content-Type header of the response:
- Only if it is `text/html`, it is passed to the fragment checker as HTML type.
- Only if it is `text/markdown`, of `text/plain` and URL path ends on `.md`, it is passed to the fragment checker as Markdown type.
- In all other cases, the fragment checker is skipped and the HTTP status is returned.
To invoke the fragment checker with a variable document type, a new `FileType` argument is added to the `check_html_fragment()` function.
The fragment checker test and fixture are adjusted to match the expected result: checking a binary file via remote URL with fragment is now expected to succeed, since its Content-Type header does not invoke the fragment checker anymore.
Signed-off-by: MichaIng <micha@dietpi.com>
* Update fixtures/fragments/file1.md
Co-authored-by: MichaIng <micha@dietpi.com>
---------
Signed-off-by: MichaIng <micha@dietpi.com>
Co-authored-by: Matthias Endler <matthias@endler.dev>
2025-07-06 08:46:06 +00:00
|
|
|
.stdout(contains("12 Errors"));
|
2023-07-31 14:04:00 +00:00
|
|
|
}
|
2024-06-11 14:11:24 +00:00
|
|
|
|
2025-07-10 04:32:15 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_fragments_when_accept_error_status_codes() {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("TEST_FRAGMENT_ERR_CODE.md");
|
|
|
|
|
|
|
|
|
|
// it's common for user to accept 429, but let's test with 404 since
|
|
|
|
|
// triggering 429 may annoy the server
|
|
|
|
|
cmd.arg("--verbose")
|
|
|
|
|
.arg("--accept=200,404")
|
|
|
|
|
.arg("--include-fragments")
|
|
|
|
|
.arg(input)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stderr(contains(
|
|
|
|
|
"https://en.wikipedia.org/wiki/Should404#ignore-fragment",
|
|
|
|
|
))
|
|
|
|
|
.stdout(contains("0 Errors"))
|
|
|
|
|
.stdout(contains("1 OK"))
|
|
|
|
|
.stdout(contains("1 Total"));
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-11 14:11:24 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_fallback_extensions() {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("fallback-extensions");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--verbose")
|
|
|
|
|
.arg("--fallback-extensions=htm,html")
|
|
|
|
|
.arg(input)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("0 Errors"));
|
|
|
|
|
}
|
2024-10-26 02:07:37 +00:00
|
|
|
|
2025-07-03 14:55:57 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_fragments_fallback_extensions() {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let input = fixtures_path().join("fragments-fallback-extensions");
|
|
|
|
|
|
|
|
|
|
cmd.arg("--include-fragments")
|
|
|
|
|
.arg("--fallback-extensions=html")
|
|
|
|
|
.arg("--no-progress")
|
|
|
|
|
.arg("--offline")
|
|
|
|
|
.arg("-v")
|
|
|
|
|
.arg(input)
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.stdout(contains("3 Total"))
|
|
|
|
|
.stdout(contains("1 OK"))
|
|
|
|
|
.stdout(contains("2 Errors"));
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 02:07:37 +00:00
|
|
|
/// Test relative paths
|
|
|
|
|
///
|
|
|
|
|
/// Imagine a web server hosting a site with the following structure:
|
|
|
|
|
/// root
|
|
|
|
|
/// └── test
|
|
|
|
|
/// ├── index.html
|
|
|
|
|
/// └── next.html
|
|
|
|
|
///
|
|
|
|
|
/// where `root/test/index.html` contains `<a href="next.html">next</a>`
|
|
|
|
|
/// When checking the link in `root/test/index.html` we should be able to
|
|
|
|
|
/// resolve the relative path to `root/test/next.html`
|
|
|
|
|
///
|
|
|
|
|
/// Note that the relative path is not resolved to the root of the server
|
|
|
|
|
/// but relative to the file that contains the link.
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_resolve_relative_paths_in_subfolder() -> Result<()> {
|
|
|
|
|
let mock_server = wiremock::MockServer::start().await;
|
|
|
|
|
|
|
|
|
|
let body = r#"<a href="next.html">next</a>"#;
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.and(wiremock::matchers::path("/test/index.html"))
|
|
|
|
|
.respond_with(wiremock::ResponseTemplate::new(200).set_body_string(body))
|
|
|
|
|
.mount(&mock_server)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.and(wiremock::matchers::path("/test/next.html"))
|
|
|
|
|
.respond_with(wiremock::ResponseTemplate::new(200))
|
|
|
|
|
.mount(&mock_server)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--verbose")
|
|
|
|
|
.arg(format!("{}/test/index.html", mock_server.uri()))
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("1 Total"))
|
|
|
|
|
.stdout(contains("0 Errors"));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2024-10-27 00:17:00 +00:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_json_format_in_config() -> Result<()> {
|
|
|
|
|
let mock_server = mock_server!(StatusCode::OK);
|
|
|
|
|
let config = fixtures_path().join("configs").join("format.toml");
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--config")
|
|
|
|
|
.arg(config)
|
|
|
|
|
.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.env_clear()
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
// Check that the output is in JSON format
|
|
|
|
|
let output = cmd.output().unwrap();
|
|
|
|
|
let output = std::str::from_utf8(&output.stdout).unwrap();
|
|
|
|
|
let json: serde_json::Value = serde_json::from_str(output)?;
|
|
|
|
|
assert_eq!(json["total"], 1);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2024-11-27 21:58:32 +00:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_retry() -> Result<()> {
|
|
|
|
|
let mock_server = wiremock::MockServer::start().await;
|
|
|
|
|
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.respond_with(ResponseTemplate::new(429))
|
|
|
|
|
.up_to_n_times(1)
|
|
|
|
|
.mount(&mock_server)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200))
|
|
|
|
|
.mount(&mock_server)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("-")
|
|
|
|
|
.write_stdin(mock_server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-02-14 23:10:59 +00:00
|
|
|
|
2025-05-23 11:37:32 +00:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_no_header_set_on_input() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let server = wiremock::MockServer::start().await;
|
|
|
|
|
server
|
|
|
|
|
.register(
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.respond_with(wiremock::ResponseTemplate::new(200))
|
|
|
|
|
.expect(1),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
cmd.arg("--verbose").arg(server.uri()).assert().success();
|
|
|
|
|
|
|
|
|
|
let received_requests = server.received_requests().await.unwrap();
|
|
|
|
|
assert_eq!(received_requests.len(), 1);
|
|
|
|
|
|
|
|
|
|
let received_request = &received_requests[0];
|
|
|
|
|
assert_eq!(received_request.method, Method::GET);
|
|
|
|
|
assert_eq!(received_request.url.path(), "/");
|
|
|
|
|
|
|
|
|
|
// Make sure the request does not contain the custom header
|
|
|
|
|
assert!(!received_request.headers.contains_key("X-Foo"));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_header_set_on_input() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let server = wiremock::MockServer::start().await;
|
|
|
|
|
server
|
|
|
|
|
.register(
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.and(wiremock::matchers::header("X-Foo", "Bar"))
|
|
|
|
|
.respond_with(wiremock::ResponseTemplate::new(200))
|
|
|
|
|
// We expect the mock to be called exactly least once.
|
|
|
|
|
.expect(1)
|
|
|
|
|
.named("GET expecting custom header"),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
cmd.arg("--verbose")
|
|
|
|
|
.arg("--header")
|
|
|
|
|
.arg("X-Foo: Bar")
|
|
|
|
|
.arg(server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
// Check that the server received the request with the header
|
|
|
|
|
server.verify().await;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_multi_header_set_on_input() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let server = wiremock::MockServer::start().await;
|
|
|
|
|
server
|
|
|
|
|
.register(
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.and(wiremock::matchers::header("X-Foo", "Bar"))
|
|
|
|
|
.and(wiremock::matchers::header("X-Bar", "Baz"))
|
|
|
|
|
.respond_with(wiremock::ResponseTemplate::new(200))
|
|
|
|
|
// We expect the mock to be called exactly least once.
|
|
|
|
|
.expect(1)
|
|
|
|
|
.named("GET expecting custom header"),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
cmd.arg("--verbose")
|
|
|
|
|
.arg("--header")
|
|
|
|
|
.arg("X-Foo: Bar")
|
|
|
|
|
.arg("--header")
|
|
|
|
|
.arg("X-Bar: Baz")
|
|
|
|
|
.arg(server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
// Check that the server received the request with the header
|
|
|
|
|
server.verify().await;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_header_set_in_config() -> Result<()> {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let server = wiremock::MockServer::start().await;
|
|
|
|
|
server
|
|
|
|
|
.register(
|
|
|
|
|
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
|
|
|
.and(wiremock::matchers::header("X-Foo", "Bar"))
|
|
|
|
|
.and(wiremock::matchers::header("X-Bar", "Baz"))
|
|
|
|
|
.respond_with(wiremock::ResponseTemplate::new(200))
|
|
|
|
|
// We expect the mock to be called exactly least once.
|
|
|
|
|
.expect(1)
|
|
|
|
|
.named("GET expecting custom header"),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let config = fixtures_path().join("configs").join("headers.toml");
|
|
|
|
|
cmd.arg("--verbose")
|
|
|
|
|
.arg("--config")
|
|
|
|
|
.arg(config)
|
|
|
|
|
.arg(server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
// Check that the server received the request with the header
|
|
|
|
|
server.verify().await;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-14 23:10:59 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_sorted_error_output() -> Result<()> {
|
|
|
|
|
let test_files = ["TEST_GITHUB_404.md", "TEST_INVALID_URLS.html"];
|
|
|
|
|
|
|
|
|
|
let test_urls = [
|
|
|
|
|
"https://httpbin.org/status/404",
|
|
|
|
|
"https://httpbin.org/status/500",
|
|
|
|
|
"https://httpbin.org/status/502",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let cmd = &mut main_command()
|
|
|
|
|
.arg("--format")
|
|
|
|
|
.arg("compact")
|
|
|
|
|
.arg(fixtures_path().join(test_files[1]))
|
|
|
|
|
.arg(fixtures_path().join(test_files[0]))
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.code(2);
|
|
|
|
|
|
|
|
|
|
let output = String::from_utf8_lossy(&cmd.get_output().stdout);
|
|
|
|
|
let mut position: usize = 0;
|
|
|
|
|
|
|
|
|
|
// Check that the input sources are sorted
|
|
|
|
|
for file in test_files {
|
|
|
|
|
assert!(output.contains(file));
|
|
|
|
|
|
|
|
|
|
let next_position = output.find(file).unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(next_position > position);
|
|
|
|
|
position = next_position;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
position = 0;
|
|
|
|
|
|
|
|
|
|
// Check that the responses are sorted
|
|
|
|
|
for url in test_urls {
|
|
|
|
|
assert!(output.contains(url));
|
|
|
|
|
|
|
|
|
|
let next_position = output.find(url).unwrap();
|
|
|
|
|
|
|
|
|
|
assert!(next_position > position);
|
|
|
|
|
position = next_position;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-02-24 07:48:58 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_url_ending_with_period_file() {
|
|
|
|
|
let test_path = fixtures_path().join("LINK_PERIOD.html");
|
|
|
|
|
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg(test_path)
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("https://www.example.com/smth."));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_extract_url_ending_with_period_webserver() {
|
|
|
|
|
let mut cmd = main_command();
|
|
|
|
|
let body = r#"<a href="https://www.example.com/smth.">link</a>"#;
|
|
|
|
|
let mock_server = mock_response!(body);
|
|
|
|
|
|
|
|
|
|
cmd.arg("--dump")
|
|
|
|
|
.arg(mock_server.uri())
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("https://www.example.com/smth."));
|
|
|
|
|
}
|
2020-10-17 08:01:06 +00:00
|
|
|
}
|