// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use anki::links::HelpPage; #[cfg(test)] mod test { use std::iter; use futures::StreamExt; use itertools::Itertools; use linkcheck::{ validation::{check_web, Context, Reason}, BasicContext, }; use reqwest::Url; use strum::IntoEnumIterator; use super::*; /// Aggregates [`Outcome`]s by collecting the error messages of the invalid ones. #[derive(Default)] struct Outcomes(Vec); enum Outcome { Valid, Invalid(String), } #[tokio::test] async fn check_links() { let ctx = BasicContext::default(); let result = futures::stream::iter(HelpPage::iter()) .map(|page| check_page(page, &ctx)) .buffer_unordered(ctx.concurrency()) .collect::() .await; if !result.0.is_empty() { panic!("{}", result.message()); } } async fn check_page(page: HelpPage, ctx: &BasicContext) -> Outcome { let link = page.to_link(); match Url::parse(&link) { Ok(url) => { if url.as_str() == link { match check_web(&url, ctx).await { Ok(()) => Outcome::Valid, Err(Reason::Dom) => Outcome::Invalid(format!( "'#{}' not found on '{}{}'", url.fragment().unwrap(), url.domain().unwrap(), url.path(), )), Err(Reason::Web(err)) => Outcome::Invalid(err.to_string()), _ => unreachable!(), } } else { Outcome::Invalid(format!( "'{}' is not a valid URL part", page.to_link_suffix(), )) } } Err(err) => Outcome::Invalid(err.to_string()), } } impl Extend for Outcomes { fn extend>(&mut self, items: T) { for outcome in items { match outcome { Outcome::Valid => (), Outcome::Invalid(err) => self.0.push(err), } } } } impl Outcomes { fn message(&self) -> String { iter::once("invalid links found:") .chain(self.0.iter().map(String::as_str)) .join("\n - ") } } }