155 lines
6.5 KiB
Rust
155 lines
6.5 KiB
Rust
// SPDX-License-Identifier: GPL-2.0-only
|
|
use actix_web::{dev::ServerHandle, test, web, App, HttpRequest, HttpResponse, HttpServer};
|
|
use autocomic_rss::{
|
|
config::{Config, QbitConfig},
|
|
http,
|
|
service::Service,
|
|
storage::Store,
|
|
};
|
|
use serde_json::{json, Value};
|
|
use std::{net::TcpListener, sync::Arc, time::Duration};
|
|
|
|
async fn server(proxy: bool) -> (String, ServerHandle) {
|
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
let base = format!("http://{}", listener.local_addr().unwrap());
|
|
let server = HttpServer::new(move || App::new().default_service(web::to(move |req: HttpRequest| async move {
|
|
if proxy {
|
|
assert_eq!(req.headers().get("Proxy-Authorization").and_then(|h| h.to_str().ok()), Some("Basic dXNlcjpwYXNz"), "request {}", req.uri());
|
|
if req.uri().host() == Some("127.0.0.1") { return HttpResponse::BadGateway().finish(); }
|
|
match req.path() {
|
|
"/to-direct" => {
|
|
let query = web::Query::<std::collections::HashMap<String, String>>::from_query(req.query_string()).unwrap();
|
|
HttpResponse::Found().insert_header(("Location", query.get("target").unwrap().as_str())).finish()
|
|
},
|
|
"/Home/Search" => HttpResponse::Found().insert_header(("Location", "http://assets.invalid/results")).finish(),
|
|
"/results" => {
|
|
assert_eq!(req.uri().host(), Some("assets.invalid"));
|
|
HttpResponse::Ok().content_type("text/html").body(r#"<form action="/Home/Search"><input name="searchstr"></form><ul class="an-ul"><a href="/Home/Bangumi/101"><div class="an-text">经代理搜索</div></a></ul>"#)
|
|
},
|
|
"/feed" => HttpResponse::Ok().body(include_str!("fixtures/releases.xml")),
|
|
_ => HttpResponse::BadGateway().finish(),
|
|
}
|
|
} else {
|
|
assert!(req.headers().get("Proxy-Authorization").is_none());
|
|
match req.path() {
|
|
"/api/v2/auth/login" => HttpResponse::Ok().insert_header(("Set-Cookie", "SID=test; Path=/")).body("Ok."),
|
|
"/api/v2/torrents/info" => HttpResponse::Ok().json(json!([])),
|
|
"/feed" => HttpResponse::Ok().body(include_str!("fixtures/releases.xml")),
|
|
_ => HttpResponse::NotFound().finish(),
|
|
}
|
|
}
|
|
}))).workers(1).listen(listener).unwrap().run();
|
|
let handle = server.handle();
|
|
actix_web::rt::spawn(server);
|
|
(base, handle)
|
|
}
|
|
|
|
#[actix_web::test]
|
|
async fn direct_exceptions_are_explicit_and_proxy_failure_never_falls_back() {
|
|
let (proxy, proxy_server) = server(true).await;
|
|
let (direct, direct_server) = server(false).await;
|
|
let proxy = proxy.replacen("http://", "http://user:pass@", 1);
|
|
for (direct_hosts, status) in [("127.0.0.1", 200), ("", 502)] {
|
|
let config = Config {
|
|
database_url: "sqlite::memory:".into(),
|
|
port: 8080,
|
|
poll_interval: Duration::from_secs(300),
|
|
qbit: None,
|
|
mikan_base_url: "http://mikan.invalid".into(),
|
|
website: autocomic_rss::config::WebsiteConfig {
|
|
proxy_url: Some(proxy.clone()),
|
|
direct_hosts: direct_hosts.into(),
|
|
},
|
|
};
|
|
let service = Service::new(Store::open("sqlite::memory:").await.unwrap(), &config)
|
|
.await
|
|
.unwrap();
|
|
let app = test::init_service(
|
|
App::new()
|
|
.app_data(web::Data::new(Arc::new(service)))
|
|
.configure(http::configure),
|
|
)
|
|
.await;
|
|
let mut redirect = reqwest::Url::parse("http://unresolvable.invalid/to-direct").unwrap();
|
|
redirect
|
|
.query_pairs_mut()
|
|
.append_pair("target", &format!("{direct}/feed"));
|
|
let response = test::call_service(
|
|
&app,
|
|
test::TestRequest::post()
|
|
.uri("/api/preview")
|
|
.set_json(json!({"title":"Test", "rss_url": redirect.as_str()}))
|
|
.to_request(),
|
|
)
|
|
.await;
|
|
assert_eq!(response.status(), status);
|
|
if status == 200 {
|
|
let preview: Value = test::read_body_json(response).await;
|
|
assert_eq!(preview[0]["episode"], 3);
|
|
} else {
|
|
let body = String::from_utf8(test::read_body(response).await.to_vec()).unwrap();
|
|
assert!(!body.contains("user:pass"));
|
|
}
|
|
}
|
|
proxy_server.stop(false).await;
|
|
direct_server.stop(false).await;
|
|
}
|
|
|
|
#[actix_web::test]
|
|
async fn website_proxy_handles_auth_redirects_and_rss_while_downloader_stays_direct() {
|
|
let (proxy, proxy_server) = server(true).await;
|
|
let (direct, direct_server) = server(false).await;
|
|
let mut proxy_url = reqwest::Url::parse(&proxy).unwrap();
|
|
proxy_url.set_username("user").unwrap();
|
|
proxy_url.set_password(Some("pass")).unwrap();
|
|
let config = Config {
|
|
database_url: "sqlite::memory:".into(),
|
|
port: 8080,
|
|
poll_interval: Duration::from_secs(300),
|
|
mikan_base_url: "http://mikan.invalid".into(),
|
|
website: autocomic_rss::config::WebsiteConfig {
|
|
proxy_url: Some(proxy_url.into()),
|
|
direct_hosts: String::new(),
|
|
},
|
|
qbit: Some(QbitConfig {
|
|
url: direct.clone(),
|
|
username: "test".into(),
|
|
password: "test".into(),
|
|
save_path: "/downloads".into(),
|
|
}),
|
|
};
|
|
let service = Service::new(Store::open("sqlite::memory:").await.unwrap(), &config)
|
|
.await
|
|
.unwrap();
|
|
let app = test::init_service(
|
|
App::new()
|
|
.app_data(web::Data::new(Arc::new(service)))
|
|
.configure(http::configure),
|
|
)
|
|
.await;
|
|
let search: Value = test::call_and_read_body_json(
|
|
&app,
|
|
test::TestRequest::get()
|
|
.uri("/api/sources/mikan/search?q=test")
|
|
.to_request(),
|
|
)
|
|
.await;
|
|
assert_eq!(search["series"][0]["title"], "经代理搜索");
|
|
let preview: Value = test::call_and_read_body_json(
|
|
&app,
|
|
test::TestRequest::post()
|
|
.uri("/api/preview")
|
|
.set_json(json!({"title":"Test", "rss_url":"http://unresolvable.invalid/feed"}))
|
|
.to_request(),
|
|
)
|
|
.await;
|
|
assert_eq!(preview[0]["episode"], 3);
|
|
let cycle: Value =
|
|
test::call_and_read_body_json(&app, test::TestRequest::post().uri("/api/run").to_request())
|
|
.await;
|
|
assert_eq!(cycle["errors"], json!([]));
|
|
assert_eq!(cycle["downloader_configured"], true);
|
|
proxy_server.stop(false).await;
|
|
direct_server.stop(false).await;
|
|
}
|