fix: fix testsuite

This commit is contained in:
2025-05-26 02:44:46 +08:00
parent 313b1bf1ba
commit 22a2ce0559
1001 changed files with 299176 additions and 5417 deletions

View File

@@ -1,14 +1,22 @@
use std::collections::HashMap;
use std::{
collections::HashMap,
path::{self, Path},
};
use chrono::{Duration, Utc};
use fetch::{FetchError, HttpClientConfig, IntoUrl, get_random_ua};
use percent_encoding::{AsciiSet, CONTROLS, percent_decode, utf8_percent_encode};
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
crypto::UserPassCredential,
errors::RecorderResult,
extract::mikan::{
MIKAN_ACCOUNT_MANAGE_PAGE_PATH, MIKAN_LOGIN_PAGE_PATH, MikanClient, MikanConfig,
MikanCredentialForm,
MIKAN_ACCOUNT_MANAGE_PAGE_PATH, MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH,
MIKAN_BANGUMI_HOMEPAGE_PATH, MIKAN_BANGUMI_POSTER_PATH, MIKAN_BANGUMI_RSS_PATH,
MIKAN_EPISODE_HOMEPAGE_PATH, MIKAN_EPISODE_TORRENT_PATH, MIKAN_LOGIN_PAGE_PATH,
MIKAN_SEASON_FLOW_PAGE_PATH, MikanClient, MikanConfig, MikanCredentialForm,
},
};
@@ -17,6 +25,20 @@ const TESTING_MIKAN_PASSWORD: &str = "test_password";
const TESTING_MIKAN_ANTIFORGERY: &str = "test_antiforgery";
const TESTING_MIKAN_IDENTITY: &str = "test_identity";
const FILE_UNSAFE: &AsciiSet = &CONTROLS
.add(b'<')
.add(b'>')
.add(b':')
.add(b'"')
.add(b'|')
.add(b'?')
.add(b'*')
.add(b'\\')
.add(b'/')
.add(b'&')
.add(b'=')
.add(b'#');
pub async fn build_testing_mikan_client(
base_mikan_url: impl IntoUrl,
) -> RecorderResult<MikanClient> {
@@ -38,6 +60,120 @@ pub fn build_testing_mikan_credential_form() -> MikanCredentialForm {
}
}
pub fn build_testing_mikan_credential() -> UserPassCredential {
UserPassCredential {
username: String::from(TESTING_MIKAN_USERNAME),
password: String::from(TESTING_MIKAN_PASSWORD),
user_agent: None,
cookies: None,
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MikanDoppelMeta {
pub status: u16,
}
pub struct MikanDoppelPath {
path: path::PathBuf,
}
impl MikanDoppelPath {
pub fn new(source: impl Into<Self>) -> Self {
source.into()
}
pub fn exists_any(&self) -> bool {
self.exists() || self.exists_meta()
}
pub fn exists(&self) -> bool {
self.path().exists()
}
pub fn exists_meta(&self) -> bool {
self.meta_path().exists()
}
pub fn write(&self, content: impl AsRef<[u8]>) -> std::io::Result<()> {
if let Some(parent) = self.as_ref().parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(self.as_ref(), content)?;
Ok(())
}
pub fn write_meta(&self, meta: MikanDoppelMeta) -> std::io::Result<()> {
self.write(serde_json::to_string(&meta)?)
}
pub fn read(&self) -> std::io::Result<Vec<u8>> {
let content = std::fs::read(self.as_ref())?;
Ok(content)
}
pub fn read_meta(&self) -> std::io::Result<MikanDoppelMeta> {
let content = std::fs::read(self.meta_path())?;
Ok(serde_json::from_slice(&content)?)
}
pub fn encode_path_component(component: &str) -> String {
utf8_percent_encode(component, FILE_UNSAFE).to_string()
}
pub fn decode_path_component(component: &str) -> Result<String, std::str::Utf8Error> {
Ok(percent_decode(component.as_bytes())
.decode_utf8()?
.to_string())
}
pub fn meta_path(&self) -> path::PathBuf {
let extension = if let Some(ext) = self.path().extension() {
format!("{}.meta.json", ext.to_string_lossy())
} else {
String::from("meta.json")
};
self.path.to_path_buf().with_extension(extension)
}
pub fn path(&self) -> &path::Path {
&self.path
}
}
impl AsRef<path::Path> for MikanDoppelPath {
fn as_ref(&self) -> &path::Path {
self.path()
}
}
impl From<Url> for MikanDoppelPath {
fn from(value: Url) -> Self {
let base_path =
Path::new("tests/resources/mikan/doppel").join(value.path().trim_matches('/'));
let dirname = base_path.parent();
let stem = base_path.file_stem();
debug_assert!(dirname.is_some() && stem.is_some());
let extension = if let Some(ext) = base_path.extension() {
ext.to_string_lossy().to_string()
} else {
String::from("html")
};
let mut filename = stem.unwrap().to_string_lossy().to_string();
if let Some(query) = value.query() {
filename.push_str(&format!("-{}", Self::encode_path_component(query)));
}
if let Some(fragment) = value.fragment() {
filename.push_str(&format!("-{}", Self::encode_path_component(fragment)));
}
filename.push_str(&format!(".{extension}"));
Self {
path: dirname.unwrap().join(filename),
}
}
}
pub struct MikanMockServerLoginMock {
pub login_get_mock: mockito::Mock,
pub login_post_success_mock: mockito::Mock,
@@ -46,6 +182,14 @@ pub struct MikanMockServerLoginMock {
pub account_get_failed_mock: mockito::Mock,
}
pub struct MikanMockServerResourcesMock {
pub shared_resource_mock: mockito::Mock,
pub shared_resource_not_found_mock: mockito::Mock,
pub user_resource_mock: mockito::Mock,
pub expand_bangumi_noauth_mock: mockito::Mock,
pub season_flow_noauth_mock: mockito::Mock,
}
pub struct MikanMockServer {
pub server: mockito::ServerGuard,
base_url: Url,
@@ -80,7 +224,7 @@ impl MikanMockServer {
.server
.mock("GET", MIKAN_LOGIN_PAGE_PATH)
.match_query(mockito::Matcher::Any)
.with_status(201)
.with_status(200)
.with_header("Content-Type", "text/html; charset=utf-8")
.with_header(
"Set-Cookie",
@@ -89,6 +233,7 @@ impl MikanMockServer {
SameSite=Strict; Path=/"
),
)
.with_body_from_file("tests/resources/mikan/LoginPage.html")
.create();
let test_identity_expires = (Utc::now() + Duration::days(30)).to_rfc2822();
@@ -170,4 +315,138 @@ impl MikanMockServer {
account_get_failed_mock,
}
}
pub fn mock_resources_with_doppel(&mut self) -> MikanMockServerResourcesMock {
let shared_resource_mock = self
.server
.mock("GET", mockito::Matcher::Any)
.match_request({
let mikan_base_url = self.base_url().clone();
move |request| {
let path = request.path();
if path.starts_with(MIKAN_BANGUMI_RSS_PATH)
|| path.starts_with(MIKAN_BANGUMI_HOMEPAGE_PATH)
|| path.starts_with(MIKAN_EPISODE_HOMEPAGE_PATH)
|| path.starts_with(MIKAN_BANGUMI_POSTER_PATH)
|| path.starts_with(MIKAN_EPISODE_TORRENT_PATH)
{
if let Ok(url) = mikan_base_url.join(request.path_and_query()) {
let doppel_path = MikanDoppelPath::from(url);
doppel_path.exists()
} else {
false
}
} else {
false
}
}
})
.with_status(200)
.with_body_from_request({
let mikan_base_url = self.base_url().clone();
move |req| {
let path_and_query = req.path_and_query();
let url = mikan_base_url.join(path_and_query).unwrap();
let doppel_path = MikanDoppelPath::from(url);
doppel_path.read().unwrap()
}
})
.create();
let shared_resource_not_found_mock = self
.server
.mock("GET", mockito::Matcher::Any)
.match_request({
let mikan_base_url = self.base_url().clone();
move |request| {
let path = request.path();
if path.starts_with(MIKAN_BANGUMI_RSS_PATH)
|| path.starts_with(MIKAN_BANGUMI_HOMEPAGE_PATH)
|| path.starts_with(MIKAN_EPISODE_HOMEPAGE_PATH)
|| path.starts_with(MIKAN_BANGUMI_POSTER_PATH)
|| path.starts_with(MIKAN_EPISODE_TORRENT_PATH)
{
if let Ok(url) = mikan_base_url.join(request.path_and_query()) {
let doppel_path = MikanDoppelPath::from(url);
doppel_path.exists_meta()
&& doppel_path.read_meta().unwrap().status == 404
} else {
false
}
} else {
false
}
}
})
.with_status(404)
.create();
let user_resource_mock = self
.server
.mock("GET", mockito::Matcher::Any)
.match_request({
let mikan_base_url = self.base_url().clone();
move |req| {
if !Self::get_has_auth_matcher()(req) {
return false;
}
let path = req.path();
if path.starts_with(MIKAN_SEASON_FLOW_PAGE_PATH)
|| path.starts_with(MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH)
{
if let Ok(url) = mikan_base_url.join(req.path_and_query()) {
let doppel_path = MikanDoppelPath::from(url);
doppel_path.exists()
} else {
false
}
} else {
false
}
}
})
.with_status(200)
.with_body_from_request({
let mikan_base_url = self.base_url().clone();
move |req| {
let path_and_query = req.path_and_query();
let url = mikan_base_url.join(path_and_query).unwrap();
let doppel_path = MikanDoppelPath::from(url);
doppel_path.read().unwrap()
}
})
.create();
let expand_bangumi_noauth_mock = self
.server
.mock("GET", mockito::Matcher::Any)
.match_request(move |req| {
!Self::get_has_auth_matcher()(req)
&& req
.path()
.starts_with(MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH)
})
.with_status(200)
.with_body_from_file("tests/resources/mikan/ExpandBangumi-noauth.html")
.create();
let season_flow_noauth_mock = self
.server
.mock("GET", mockito::Matcher::Any)
.match_request(move |req| {
!Self::get_has_auth_matcher()(req)
&& req.path().starts_with(MIKAN_SEASON_FLOW_PAGE_PATH)
})
.with_status(200)
.with_body_from_file("tests/resources/mikan/BangumiCoverFlow-noauth.html")
.create();
MikanMockServerResourcesMock {
shared_resource_mock,
shared_resource_not_found_mock,
user_resource_mock,
expand_bangumi_noauth_mock,
season_flow_noauth_mock,
}
}
}