#![cfg(test)]
use crate::{algorithms::ptd::Type, test::Client, Error};
use super::{endpoint_for, process_incoming_webmention};
use miette::IntoDiagnostic;
use url::Url;
#[macro_export]
macro_rules! run_stock_webmention_test {
($slug: expr, $mock_page: expr, $resulting_url: expr) => {
run_webmention_test_with_client!(
$crate::test::Client::new().await,
$slug,
$mock_page,
$resulting_url
)
};
}
#[macro_export]
macro_rules! run_webmention_test_with_client {
($client: expr, $slug: expr, $mock_page: expr, $resulting_url: expr) => {
let mut client = $client;
let lpath = format!("/page-{}", $slug);
let resource_url = client.merge_into_url(&lpath);
let mut resource_mock = client
.mock_server
.mock("GET", lpath.as_str())
.with_status($mock_page.status().as_u16().into());
for (name, value) in $mock_page.headers().into_iter() {
resource_mock = resource_mock.with_header(name.as_str(), value.to_str().unwrap());
}
resource_mock
.with_body($mock_page.into_body().as_bytes())
.create();
let result = endpoint_for(&client, &resource_url).await;
let compared_url = if $resulting_url.starts_with("/") {
Ok(client.merge_into_url(&$resulting_url))
} else {
Ok($resulting_url.parse::<url::Url>().unwrap())
};
assert_eq!(
result.map(|u| u.to_string()),
compared_url.map(|u| u.to_string()),
"resolution found the URL {resource_url}",
);
};
}
#[tokio::test]
async fn http_link_header_unquoted_rel_relative_url() {
run_stock_webmention_test!(
"1",
::http::Response::builder()
.status(::http::StatusCode::OK)
.header(::http::header::LINK, "</test/1/webmention>; rel=webmention")
.body(crate::http::Body::Empty)
.unwrap_or_default(),
"/test/1/webmention"
);
}
#[tokio::test]
async fn http_link_header_unquoted_rel_absolute_url() {
run_stock_webmention_test!(
"2",
::http::Response::builder()
.status(::http::StatusCode::OK)
.header(::http::header::LINK, "</test/2/webmention>; rel=webmention",)
.body(crate::http::Body::Empty)
.unwrap_or_default(),
"/test/2/webmention"
);
}
#[tokio::test]
async fn http_link_tag_relative_url() {
let whole_url = "/test/3/webmention";
run_stock_webmention_test!(
"3",
::http::Response::builder()
.status(::http::StatusCode::OK)
.header("content-type", "text/html")
.body(crate::http::Body::from(format!(
r#"
<html>
<head>
<link rel="webmention" href="{}" />
</head>
</html>
"#,
whole_url
)))
.unwrap(),
whole_url
);
}
#[tokio::test]
async fn http_link_tag_absolute_url() {
let client = Client::new().await;
let whole_url = format!("{}/test/4/webmention", client.mock_server.url());
run_webmention_test_with_client!(
client,
"4",
::http::Response::builder()
.status(::http::StatusCode::OK)
.body(crate::http::Body::from(format!(
r#"
<html>
<head>
<link rel="webmention" href="{}" />
</head>
</html>
"#,
whole_url
)))
.unwrap_or_default(),
whole_url
);
}
#[tokio::test]
async fn http_a_tag_relative_url() {
let whole_url = "/test/5/webmention";
run_stock_webmention_test!(
"5",
::http::Response::builder()
.status(::http::StatusCode::OK)
.body(crate::http::Body::from(format!(
r#"
<html>
<head>
<link rel="webmention" href="{}" />
</head>
</html>
"#,
whole_url
)))
.unwrap_or_default(),
whole_url
);
}
#[tokio::test]
async fn http_a_tag_absolute_url() {
let client = Client::new().await;
let whole_url: Url = format!("{}/test/6/webmention", client.mock_server.url())
.parse()
.unwrap();
run_webmention_test_with_client!(
client,
"6",
::http::Response::builder()
.status(::http::StatusCode::OK)
.body(crate::http::Body::from(format!(
r#"
<html>
<body>
<a rel="webmention" href="{}" />
</body>
</html>
"#,
whole_url
)))
.unwrap_or_default(),
whole_url.as_str()
);
}
#[tokio::test]
async fn http_link_header_strange_casing() {
let client = Client::new().await;
let whole_url: Url = format!("{}/test/7/webmention", client.mock_server.url())
.parse()
.unwrap();
let resp = ::http::Response::builder()
.header("Link", format!("<{}>; rel=webmention", whole_url).as_str())
.body(crate::http::Body::Empty)
.unwrap_or_default();
run_webmention_test_with_client!(client, "7", resp, whole_url.as_str());
}
#[tracing_test::traced_test]
#[tokio::test]
async fn http_link_header_quoted_rel() {
let client = Client::new().await;
let whole_url: Url = format!("{}/test/8/webmention", client.mock_server.url())
.parse()
.unwrap();
let resp = ::http::Response::builder()
.header(
"Link",
format!("<{}>; rel=\"webmention\"", whole_url).as_str(),
)
.body(crate::http::Body::Empty)
.unwrap_or_default();
run_webmention_test_with_client!(client, "8", resp, whole_url.as_str());
}
#[tracing_test::traced_test]
#[tokio::test]
async fn http_multiple_rel_values_on_link_tag() {
let client = Client::new().await;
let whole_url = client.merge_into_url("/test/9/webmention");
let resp = ::http::Response::builder()
.body(crate::http::Body::Bytes(
format!(
r#"
<html>
<head>
<link rel="webmention something-else" href="{}" />
</head>
</html>
"#,
whole_url
)
.into(),
))
.unwrap_or_default();
run_webmention_test_with_client!(client, "9", resp, whole_url.path());
}
#[tracing_test::traced_test]
#[tokio::test]
async fn http_rel_values_on_rel_headers() {
let client = Client::new().await;
let whole_url: Url = format!("{}/test/10/webmention", client.mock_server.url())
.parse()
.unwrap();
let resp = ::http::Response::builder()
.header(
"LinK",
format!("<{}>; rel=\"webmention somethingelse\"", whole_url).as_str(),
)
.body(crate::http::Body::Empty)
.unwrap_or_default();
run_webmention_test_with_client!(client, "10", resp, whole_url.path());
}
#[tokio::test]
async fn http_rel_multiple_webmention_endpoints_advertised() {
let client = Client::new().await;
let expected_url = format!("{}/test/11/webmention/header", client.mock_server.url());
let resp = ::http::Response::builder()
.header(
"LinK",
format!(
"<{}/test/11/webmention/header>; rel=\"webmention somethingelse\"",
client.mock_server.url()
)
.as_str(),
)
.body(crate::http::Body::from(format!(
r#"
<html>
<head>
<link rel="webmention" href="{0}/test/11/webmention/link-tag" />
</head>
<body>
<a rel="webmention" href="{0}/test/11/webmention/a-tag" />
</body>
</html>
"#,
client.mock_server.url()
)))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"11",
resp,
expected_url
);
}
#[tokio::test]
async fn http_rel_checking_for_exact_match_of_rel_webmention() {
let client = Client::new().await;
let expected_url = format!("{}/test/12/webmention/right", client.mock_server.url());
let resp = ::http::Response::builder()
.body(crate::http::Body::from(format!(
r#"
<html>
<head>
<link rel="webmention" href="{0}/test/12/webmention/right" />
<link rel="not-webmention" href="{0}/test/12/webmention/wrong" />
</head>
</html>
"#,
client.mock_server.url()
)))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"12",
resp,
expected_url
);
}
#[tokio::test]
async fn http_rel_false_endpoint_inside_html_element() {
let client = Client::new().await;
let expected_url = format!("{}/test/13/webmention/right", client.mock_server.url());
let resp = ::http::Response::builder()
.body(crate::http::Body::from(format!(
r#"
<html>
<body>
<!--
<a rel="webmention" href="{0}/test/13/webmention/wrong" />
-->
<a rel="webmention" href="{0}/test/13/webmention/right" />
</body>
</html>
"#,
client.mock_server.url()
)))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"13",
resp,
expected_url
);
}
#[tokio::test]
async fn http_rel_false_endpoint_inside_escaped_html() {
let client = Client::new().await;
let expected_url = format!("{}/test/14/webmention/right", client.mock_server.url());
let resp = ::http::Response::builder()
.body(crate::http::Body::from(
r#"
<html>
<body>
This post contains sample code with escaped HTML which should not
be discovered by the Webmention client.
<code><a href="/test/14/webmention/error" rel="webmention"></a></code>
There is also a <a href="/test/14/webmention/right" rel="webmention">correct endpoint</a>
defined, so if your comment appears below, it means you successfully ignored the false endpoint.
</body>
</html>
"#
.to_string()))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"14",
resp,
expected_url
);
}
#[tokio::test]
async fn http_rel_webmention_href_empty_string() {
let client = Client::new().await;
let expected_url = format!("{}/page-15", client.mock_server.url());
let resp = ::http::Response::builder()
.body(crate::http::Body::from(
r#"
<html>
<head>
<link rel="webmention" href="" />
</head>
</html>
"#
.to_string(),
))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"15",
resp,
expected_url
);
}
#[tracing_test::traced_test]
#[tokio::test]
async fn http_rel_multiple_webmention_endpoints_advertised_link_a() {
let resp = ::http::Response::builder()
.body(crate::http::Body::from(
r#"
<html>
<body>
This post advertises its Webmention endpoint in an HTML
<a href="https://webmention.rocks/test/16/webmention"
rel="webmention"><a> tag</a>, followed by a later
definition in a <link> tag. Your Webmention client
must only send a Webmention to the one in the <a>
tag since it appears first in the document.
<link rel="webmention" href="https://webmention.rocks/test/16/webmention/error" />
</body>
</html>
"#
.to_string(),
))
.expect("yeup");
run_stock_webmention_test!("16", resp, "https://webmention.rocks/test/16/webmention");
}
#[tokio::test]
async fn http_rel_multiple_webmention_endpoints_advertised_a_link() {
let client = Client::new().await;
let expected_url = format!("{}/test/17/webmention", client.mock_server.url());
let resp = ::http::Response::builder()
.body(crate::http::Body::from(
r#"
<html>
<body>
This post advertises its Webmention endpoint in an
HTML <link> tag <link rel="webmention" href="/test/17/webmention">
followed by a later definition in an <a href="/test/17/webmention/error"
rel="webmention"><a> tag</a>. Your Webmention client must only send
a Webmention to the one in the <link> tag since it appears first in
the document
</body>
</html>
"#
.to_string(),
))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"17",
resp,
expected_url
);
}
#[tokio::test]
async fn http_rel_multiple_link_http_headers() {
let client = Client::new().await;
let expected_url = format!("{}/test/18/webmention", client.mock_server.url());
let resp = ::http::Response::builder()
.header(
"LinK",
format!(
r#"<{0}/test/18/webmention>; rel="webmention""#,
client.mock_server.url()
)
.as_str(),
)
.header(
::http::header::LINK,
format!(
r#"<{0}/test/18/webmention/error>; rel="other""#,
client.mock_server.url()
)
.as_str(),
)
.body(crate::http::Body::Empty)
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"18",
resp,
expected_url
);
}
#[tokio::test]
async fn http_rel_single_link_header_multiple_values() {
let client = Client::new().await;
let expected_url = format!("{}/test/19/webmention", client.mock_server.url());
let resp = ::http::Response::builder()
.header(
"LinK",
format!(
r#"<{0}/test/19/webmention/error>; rel="other", <{0}/test/19/webmention>; rel="webmention""#,
client.mock_server.url()
)
.as_str(),
)
.body(crate::http::Body::Empty)
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"18",
resp,
expected_url
);
}
#[tracing_test::traced_test]
#[tokio::test]
async fn link_tag_with_no_href_attribute() {
let client = Client::new().await;
let expected_url = format!("{}/test/20/webmention", client.mock_server.url());
let resp = ::http::Response::builder()
.body(crate::http::Body::from(
r#"
<html>
<body>
This post has a <link> tag <link rel="webmention"> which
has no href attribute. Your Webmention client should not find
this link tag, and should send the webmention to
<a href="/test/20/webmention" rel="webmention">this endpoint</a>
instead.
</body>
</html>
"#
.to_string(),
))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"20",
resp,
expected_url
);
}
#[tokio::test]
async fn webmention_endpoint_with_query_params() {
let client = Client::new().await;
let expected_url = format!("{}/test/21/webmention?query=yes", client.mock_server.url());
let resp = ::http::Response::builder()
.body(crate::http::Body::from(
r#"
<html>
<link rel="webmention" href="/test/21/webmention?query=yes">
</html>
"#
.to_string(),
))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"21",
resp,
expected_url
);
}
#[tokio::test]
async fn webmention_endpoint_relative_to_path() {
let client = Client::new().await;
let expected_url = format!("{}/22/webmention", client.mock_server.url());
let resp = ::http::Response::builder()
.body(crate::http::Body::from(
r#"
<html>
<link rel="webmention" href="22/webmention">
</html>
"#
.to_string(),
))
.unwrap_or_default();
run_webmention_test_with_client!(
client,
"22",
resp,
expected_url
);
}
#[tokio::test]
async fn incoming_ok() -> miette::Result<()> {
let mut test_client = Client::new().await;
let target_url = format!("{}/target", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let source_url = format!("{}/source", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let src_mock = test_client
.mock_server
.mock("GET", "/source")
.with_header("Content-Type", "text/html")
.with_body(format!(
r#"
<html>
<body class="h-entry">
<a href="" class="u-url"></a>
I mentioned <a class="u-mention" href="{target_url}">your page</a>.
</body>
</html>
"#
))
.with_status(200)
.create();
let relation = process_incoming_webmention(
&test_client,
&crate::standards::webmention::Request {
source: source_url,
target: target_url,
private: None,
vouch: Default::default(),
token: None,
},
)
.await;
src_mock.assert();
assert!(relation.is_ok());
let relation = relation?;
assert_eq!(relation.r#type, Type::Note);
assert!(
relation.source.is_some(),
"resolves a MF2 item from the incoming source"
);
Ok(())
}
#[tokio::test]
async fn incoming_deleted() -> miette::Result<()> {
let mut test_client = Client::new().await;
let target_url = format!("{}/target", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let source_url = format!("{}/source", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let src_mock = test_client
.mock_server
.mock("GET", "/source")
.with_status(401)
.with_header("WWW-Authenticate", "fome sit")
.create();
let relation = process_incoming_webmention(
&test_client,
&crate::standards::webmention::Request {
source: source_url,
target: target_url,
private: None,
vouch: Default::default(),
token: None,
},
)
.await;
src_mock.assert();
assert!(relation.is_err());
Ok(())
}
#[tokio::test]
async fn incoming_need_auth() -> miette::Result<()> {
let mut test_client = Client::new().await;
let target_url: Url = format!("{}/target", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let source_url: Url = format!("{}/source", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let src_mock = test_client
.mock_server
.mock("GET", "/source")
.with_status(401)
.create();
let relation = process_incoming_webmention(
&test_client,
&crate::standards::webmention::Request {
source: source_url.clone(),
target: target_url.clone(),
private: None,
vouch: Default::default(),
token: None,
},
)
.await;
src_mock.assert();
assert_eq!(
relation,
Err(Error::WebmentionUnauthorized { url: source_url })
);
Ok(())
}
#[tokio::test]
async fn incoming_invalid_creds() -> miette::Result<()> {
let mut test_client = Client::new().await;
let target_url: url::Url = format!("{}/target", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let source_url: url::Url = format!("{}/source", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let src_mock = test_client
.mock_server
.mock("GET", "/source")
.with_status(401)
.create();
let relation = process_incoming_webmention(
&test_client,
&crate::standards::webmention::Request {
source: source_url.clone(),
target: target_url.clone(),
private: None,
vouch: Default::default(),
token: None,
},
)
.await;
src_mock.assert();
assert_eq!(
relation,
Err(Error::WebmentionUnauthorized { url: source_url })
);
Ok(())
}
#[tokio::test]
async fn private_incoming_process() -> miette::Result<()> {
let mut test_client = Client::new().await;
let target_url: url::Url = format!("{}/target", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let source_url: url::Url = format!("{}/source", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let authenticated_source_mock = test_client
.mock_server
.mock("GET", source_url.path())
.match_header("Authorization", "Bearer the-expected-token")
.with_status(200)
.with_header("Content-Type", "text/html")
.with_body(format!(
r#"
<html>
<body class="h-entry">
<a href="{source_url}" class="u-url"></a>
I mentioned <a class="u-mention" href="{target_url}">your page</a>.
</body>
</html>
"#
))
.create_async()
.await;
let relation = process_incoming_webmention(
&test_client,
&crate::standards::webmention::Request {
source: source_url.clone(),
target: target_url.clone(),
private: Some(crate::standards::webmention::PrivateRequest {
code: "the-expected-code".to_string(),
realm: None,
}),
vouch: Default::default(),
token: Some("the-expected-token".to_string()),
},
)
.await;
authenticated_source_mock.assert();
assert_eq!(relation.err(), None);
Ok(())
}
#[tokio::test]
async fn incoming_not_found() -> miette::Result<()> {
let mut test_client = Client::new().await;
let target_url = format!("{}/target", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let source_url = format!("{}/source", test_client.mock_server.url())
.parse()
.into_diagnostic()?;
let src_mock = test_client
.mock_server
.mock("GET", "/source")
.with_status(404)
.create();
let relation = process_incoming_webmention(
&test_client,
&crate::standards::webmention::Request {
source: source_url,
target: target_url,
private: None,
vouch: Default::default(),
token: None,
},
)
.await;
src_mock.assert();
assert!(relation.is_err());
assert!(matches!(relation.err().unwrap(), crate::Error::WebmentionNotFound { .. }));
Ok(())
}