add tests

cleanup-request-rs
Felix Ableitner 2023-10-26 12:32:38 +02:00
parent 388eb42b99
commit 98b5746472
16 changed files with 163 additions and 152 deletions

4
Cargo.lock generated
View File

@ -2578,6 +2578,7 @@ dependencies = [
"activitypub_federation",
"actix-web",
"anyhow",
"async-trait",
"chrono",
"encoding",
"enum-map",
@ -2598,6 +2599,7 @@ dependencies = [
"serde",
"serde_with",
"serial_test",
"task-local-extensions",
"tokio",
"tracing",
"ts-rs",
@ -2653,14 +2655,12 @@ dependencies = [
"moka",
"once_cell",
"reqwest",
"reqwest-middleware",
"serde",
"serde_json",
"serde_with",
"serial_test",
"stringreader",
"strum_macros",
"task-local-extensions",
"tokio",
"tracing",
"url",

View File

@ -63,6 +63,7 @@ once_cell = { workspace = true, optional = true }
actix-web = { workspace = true, optional = true }
enum-map = { workspace = true }
urlencoding = { workspace = true }
async-trait = { workspace = true }
webpage = { version = "1.6", default-features = false, features = [
"serde",
], optional = true }
@ -70,6 +71,7 @@ encoding = { version = "0.2.33", optional = true }
jsonwebtoken = { version = "8.3.0", optional = true }
# necessary for wasmt compilation
getrandom = { version = "0.2.10", features = ["js"] }
task-local-extensions = "0.1.4"
[dev-dependencies]
serial_test = { workspace = true }

View File

@ -1,13 +1,18 @@
use crate::request::client_builder;
use activitypub_federation::config::{Data, FederationConfig};
use anyhow::anyhow;
use lemmy_db_schema::{
source::secret::Secret,
utils::{ActualDbPool, DbPool},
utils::{build_db_pool_for_tests, ActualDbPool, DbPool},
};
use lemmy_utils::{
rate_limit::RateLimitCell,
settings::{structs::Settings, SETTINGS},
};
use reqwest_middleware::ClientWithMiddleware;
use reqwest::{Request, Response};
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, Middleware, Next};
use std::sync::Arc;
use task_local_extensions::Extensions;
#[derive(Clone)]
pub struct LemmyContext {
@ -49,4 +54,46 @@ impl LemmyContext {
pub fn rate_limit_cell(&self) -> &RateLimitCell {
&self.rate_limit_cell
}
/// Initialize a context for use in tests, doesn't allow network requests.
///
/// Do not use this in production code.
pub async fn init_test_context() -> Data<LemmyContext> {
// call this to run migrations
let pool = build_db_pool_for_tests().await;
let client = client_builder(&SETTINGS).build().expect("build client");
let client = ClientBuilder::new(client).with(BlockedMiddleware).build();
let secret = Secret {
id: 0,
jwt_secret: String::new(),
};
let rate_limit_cell = RateLimitCell::with_test_config();
let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone());
let config = FederationConfig::builder()
.domain(context.settings().hostname.clone())
.app_data(context)
.build()
.await
.expect("build federation config");
config.to_request_data()
}
}
struct BlockedMiddleware;
/// A reqwest middleware which blocks all requests
#[async_trait::async_trait]
impl Middleware for BlockedMiddleware {
async fn handle(
&self,
_req: Request,
_extensions: &mut Extensions,
_next: Next<'_>,
) -> reqwest_middleware::Result<Response> {
Err(anyhow!("Network requests not allowed").into())
}
}

View File

@ -24,7 +24,7 @@ use lemmy_db_schema::{
post::{Post, PostRead},
},
traits::Crud,
utils::{diesel_option_overwrite_to_url, DbPool},
utils::DbPool,
};
use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView};
use lemmy_db_views_actor::structs::{
@ -37,7 +37,7 @@ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
location_info,
rate_limit::{ActionType, BucketConfig},
settings::{structs::Settings, SETTINGS},
settings::structs::Settings,
utils::{
markdown::markdown_rewrite_image_links,
slurs::{build_slur_regex, remove_slurs},
@ -815,13 +815,13 @@ pub async fn process_markdown_opt(
pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> {
// Dont rewrite links pointing to local domain.
if link.domain() == Some(&SETTINGS.hostname) {
if link.domain() == Some(&context.settings().hostname) {
return Ok(link.into());
}
let proxied = format!(
"{}/api/v3/image_proxy?url={}",
SETTINGS.get_protocol_and_hostname(),
context.settings().get_protocol_and_hostname(),
encode(link.as_str())
);
RemoteImage::create(&mut context.pool(), vec![link]).await?;
@ -832,21 +832,30 @@ pub async fn proxy_image_link_opt_api(
link: &Option<String>,
context: &LemmyContext,
) -> LemmyResult<Option<Option<DbUrl>>> {
let link = diesel_option_overwrite_to_url(link)?;
if let Some(l) = link {
proxy_image_link_opt_apub(l.map(Into::into), context)
let link: Option<Option<DbUrl>> = match link.as_ref().map(String::as_str) {
// An empty string is an erase
Some("") => Some(None),
Some(str_url) => Url::parse(str_url)
.map(|u| Some(Some(u.into())))
.with_lemmy_type(LemmyErrorType::InvalidUrl)?,
None => None,
};
if let Some(Some(l)) = link {
proxy_image_link(l.into(), context)
.await
.map(Some)
.map(Some)
} else {
Ok(link)
}
}
pub async fn proxy_image_link_opt_apub(
link: Option<Url>,
context: &LemmyContext,
) -> LemmyResult<Option<DbUrl>> {
if let Some(l) = link {
proxy_image_link(l.clone(), context).await.map(Some)
proxy_image_link(l, context).await.map(Some)
} else {
Ok(None)
}
@ -857,8 +866,10 @@ mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::utils::{honeypot_check, limit_expire_time, password_length_check};
use chrono::{Days, Utc};
use serial_test::serial;
#[test]
#[rustfmt::skip]
@ -897,4 +908,55 @@ mod tests {
None
);
}
#[tokio::test]
#[serial]
async fn test_proxy_image_link() {
let context = LemmyContext::init_test_context().await;
// image from local domain is unchanged
let local_url = Url::parse("http://lemmy-alpha/image.png").unwrap();
let proxied = proxy_image_link(local_url.clone(), &context).await.unwrap();
assert_eq!(&local_url, proxied.inner());
// image from remote domain is proxied
let remote_image = Url::parse("http://lemmy-beta/image.png").unwrap();
let proxied = proxy_image_link(remote_image.clone(), &context)
.await
.unwrap();
assert_eq!(
"https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
proxied.as_str()
);
assert!(
RemoteImage::validate(&mut context.pool(), remote_image.into())
.await
.is_ok()
);
}
#[tokio::test]
#[serial]
async fn test_diesel_option_overwrite_to_url() {
let context = LemmyContext::init_test_context().await;
assert!(matches!(
proxy_image_link_opt_api(&None, &context).await,
Ok(None)
));
assert!(matches!(
proxy_image_link_opt_api(&Some(String::new()), &context).await,
Ok(Some(None))
));
assert!(
proxy_image_link_opt_api(&Some("invalid_url".to_string()), &context)
.await
.is_err()
);
let example_url = "https://lemmy-alpha/image.png";
assert!(matches!(
proxy_image_link_opt_api(&Some(example_url.to_string()), &context).await,
Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into()
));
}
}

View File

@ -12,11 +12,12 @@ use lemmy_api_common::{
is_admin,
local_site_to_slur_regex,
process_markdown_opt,
proxy_image_link_opt_api,
proxy_image_link,
EndpointType,
},
};
use lemmy_db_schema::{
newtypes::DbUrl,
source::{
actor_language::{CommunityLanguage, SiteLanguage},
community::{
@ -38,6 +39,7 @@ use lemmy_utils::{
validation::{is_valid_actor_name, is_valid_body_field},
},
};
use url::Url;
#[tracing::instrument(skip(context))]
pub async fn create_community(
@ -56,12 +58,8 @@ pub async fn create_community(
check_slurs(&data.name, &slur_regex)?;
check_slurs(&data.title, &slur_regex)?;
let description = process_markdown_opt(&data.description, &slur_regex, &context).await?;
let icon = proxy_image_link_opt_api(&data.icon, &context)
.await?
.unwrap();
let banner = proxy_image_link_opt_api(&data.banner, &context)
.await?
.unwrap();
let icon = proxy_image_link_create(&data.icon, &context).await?;
let banner = proxy_image_link_create(&data.banner, &context).await?;
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
is_valid_body_field(&data.description, false)?;
@ -138,3 +136,19 @@ pub async fn create_community(
build_community_response(&context, local_user_view, community_id).await
}
async fn proxy_image_link_create(
opt: &Option<String>,
context: &LemmyContext,
) -> Result<Option<DbUrl>, LemmyError> {
match opt.as_ref().map(String::as_str) {
// An empty string is nothing
Some("") => Ok(None),
Some(str_url) => {
let url = Url::parse(str_url).with_lemmy_type(LemmyErrorType::InvalidUrl)?;
let url = proxy_image_link(url, context).await?;
Ok(Some(url))
}
None => Ok(None),
}
}

View File

@ -46,6 +46,4 @@ moka = { version = "0.11", features = ["future"] }
[dev-dependencies]
serial_test = { workspace = true }
reqwest-middleware = { workspace = true }
task-local-extensions = "0.1.4"
assert-json-diff = "2.0.2"

View File

@ -280,10 +280,7 @@ mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
api::user_settings_backup::{export_settings, import_settings},
objects::tests::init_context,
};
use crate::api::user_settings_backup::{export_settings, import_settings};
use activitypub_federation::config::Data;
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
@ -337,7 +334,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_settings_export_import() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let export_user = create_user("hanna".to_string(), Some("my bio".to_string()), &context).await;
@ -398,7 +395,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn disallow_large_backup() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let export_user = create_user("hanna".to_string(), Some("my bio".to_string()), &context).await;

View File

@ -107,11 +107,7 @@ mod tests {
use super::*;
use crate::{
objects::{
community::tests::parse_lemmy_community,
person::tests::parse_lemmy_person,
tests::init_context,
},
objects::{community::tests::parse_lemmy_community, person::tests::parse_lemmy_person},
protocol::tests::file_to_json_object,
};
use lemmy_db_schema::{
@ -128,7 +124,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_parse_lemmy_community_moderators() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let (new_mod, site) = parse_lemmy_person(&context).await;
let community = parse_lemmy_community(&context).await;
let community_id = community.id;

View File

@ -196,7 +196,6 @@ pub(crate) mod tests {
instance::ApubSite,
person::{tests::parse_lemmy_person, ApubPerson},
post::ApubPost,
tests::init_context,
},
protocol::tests::file_to_json_object,
};
@ -234,7 +233,7 @@ pub(crate) mod tests {
#[tokio::test]
#[serial]
pub(crate) async fn test_parse_lemmy_comment() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let url = Url::parse("https://enterprise.lemmy.ml/comment/38741").unwrap();
let data = prepare_comment_test(&url, &context).await;
@ -262,7 +261,7 @@ pub(crate) mod tests {
#[tokio::test]
#[serial]
async fn test_parse_pleroma_comment() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let url = Url::parse("https://enterprise.lemmy.ml/comment/38741").unwrap();
let data = prepare_comment_test(&url, &context).await;

View File

@ -261,7 +261,7 @@ pub(crate) mod tests {
use super::*;
use crate::{
objects::{instance::tests::parse_lemmy_instance, tests::init_context},
objects::instance::tests::parse_lemmy_instance,
protocol::tests::file_to_json_object,
};
use activitypub_federation::fetch::collection_id::CollectionId;
@ -290,7 +290,7 @@ pub(crate) mod tests {
#[tokio::test]
#[serial]
async fn test_parse_lemmy_community() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let site = parse_lemmy_instance(&context).await;
let community = parse_lemmy_community(&context).await;

View File

@ -218,7 +218,7 @@ pub(crate) mod tests {
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::{objects::tests::init_context, protocol::tests::file_to_json_object};
use crate::protocol::tests::file_to_json_object;
use lemmy_db_schema::traits::Crud;
use serial_test::serial;
@ -234,7 +234,7 @@ pub(crate) mod tests {
#[tokio::test]
#[serial]
async fn test_parse_lemmy_instance() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let site = parse_lemmy_instance(&context).await;
assert_eq!(site.name, "Enterprise");

View File

@ -51,58 +51,3 @@ pub(crate) fn verify_is_remote_object(id: &Url, settings: &Settings) -> Result<(
Ok(())
}
}
#[cfg(test)]
pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use activitypub_federation::config::{Data, FederationConfig};
use anyhow::anyhow;
use lemmy_api_common::{context::LemmyContext, request::client_builder};
use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool_for_tests};
use lemmy_utils::{rate_limit::RateLimitCell, settings::SETTINGS};
use reqwest::{Request, Response};
use reqwest_middleware::{ClientBuilder, Middleware, Next};
use task_local_extensions::Extensions;
struct BlockedMiddleware;
/// A reqwest middleware which blocks all requests
#[async_trait::async_trait]
impl Middleware for BlockedMiddleware {
async fn handle(
&self,
_req: Request,
_extensions: &mut Extensions,
_next: Next<'_>,
) -> reqwest_middleware::Result<Response> {
Err(anyhow!("Network requests not allowed").into())
}
}
// TODO: would be nice if we didnt have to use a full context for tests.
pub(crate) async fn init_context() -> Data<LemmyContext> {
// call this to run migrations
let pool = build_db_pool_for_tests().await;
let client = client_builder(&SETTINGS).build().unwrap();
let client = ClientBuilder::new(client).with(BlockedMiddleware).build();
let secret = Secret {
id: 0,
jwt_secret: String::new(),
};
let rate_limit_cell = RateLimitCell::with_test_config();
let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone());
let config = FederationConfig::builder()
.domain("example.com")
.app_data(context)
.build()
.await
.unwrap();
config.to_request_data()
}
}

View File

@ -224,10 +224,7 @@ pub(crate) mod tests {
use super::*;
use crate::{
objects::{
instance::{tests::parse_lemmy_instance, ApubSite},
tests::init_context,
},
objects::instance::{tests::parse_lemmy_instance, ApubSite},
protocol::{objects::instance::Instance, tests::file_to_json_object},
};
use activitypub_federation::fetch::object_id::ObjectId;
@ -247,7 +244,7 @@ pub(crate) mod tests {
#[tokio::test]
#[serial]
async fn test_parse_lemmy_person() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let (person, site) = parse_lemmy_person(&context).await;
assert_eq!(person.display_name, Some("Jean-Luc Picard".to_string()));
@ -260,7 +257,7 @@ pub(crate) mod tests {
#[tokio::test]
#[serial]
async fn test_parse_pleroma_person() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
// create and parse a fake pleroma instance actor, to avoid network request during test
let mut json: Instance = file_to_json_object("assets/lemmy/objects/instance.json").unwrap();

View File

@ -303,7 +303,6 @@ mod tests {
instance::ApubSite,
person::{tests::parse_lemmy_person, ApubPerson},
post::ApubPost,
tests::init_context,
},
protocol::tests::file_to_json_object,
};
@ -313,7 +312,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_parse_lemmy_post() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let (person, site) = parse_lemmy_person(&context).await;
let community = parse_lemmy_community(&context).await;
@ -336,7 +335,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_convert_mastodon_post_title() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let (person, site) = parse_lemmy_person(&context).await;
let community = parse_lemmy_community(&context).await;

View File

@ -156,7 +156,6 @@ mod tests {
objects::{
instance::{tests::parse_lemmy_instance, ApubSite},
person::ApubPerson,
tests::init_context,
},
protocol::tests::file_to_json_object,
};
@ -201,7 +200,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_parse_lemmy_pm() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621").unwrap();
let data = prepare_comment_test(&url, &context).await;
let json: ChatMessage = file_to_json_object("assets/lemmy/objects/chat_message.json").unwrap();
@ -229,7 +228,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_parse_pleroma_pm() {
let context = init_context().await;
let context = LemmyContext::init_test_context().await;
let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621").unwrap();
let data = prepare_comment_test(&url, &context).await;
let pleroma_url = Url::parse("https://queer.hacktivis.me/objects/2").unwrap();

View File

@ -28,10 +28,7 @@ use diesel_async::{
};
use diesel_migrations::EmbeddedMigrations;
use futures_util::{future::BoxFuture, Future, FutureExt};
use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
settings::structs::Settings,
};
use lemmy_utils::{error::LemmyError, settings::structs::Settings};
use once_cell::sync::Lazy;
use regex::Regex;
use rustls::{
@ -212,32 +209,6 @@ pub fn diesel_option_overwrite(opt: Option<String>) -> Option<Option<String>> {
}
}
pub fn diesel_option_overwrite_to_url(
opt: &Option<String>,
) -> Result<Option<Option<DbUrl>>, LemmyError> {
match opt.as_ref().map(String::as_str) {
// An empty string is an erase
Some("") => Ok(Some(None)),
Some(str_url) => Url::parse(str_url)
.map(|u| Some(Some(u.into())))
.with_lemmy_type(LemmyErrorType::InvalidUrl),
None => Ok(None),
}
}
pub fn diesel_option_overwrite_to_url_create(
opt: &Option<String>,
) -> Result<Option<DbUrl>, LemmyError> {
match opt.as_ref().map(String::as_str) {
// An empty string is nothing
Some("") => Ok(None),
Some(str_url) => Url::parse(str_url)
.map(|u| Some(u.into()))
.with_lemmy_type(LemmyErrorType::InvalidUrl),
None => Ok(None),
}
}
async fn build_db_pool_settings_opt(
settings: Option<&Settings>,
) -> Result<ActualDbPool, LemmyError> {
@ -516,19 +487,4 @@ mod tests {
Some(Some("test".to_string()))
);
}
#[test]
fn test_diesel_option_overwrite_to_url() {
assert!(matches!(diesel_option_overwrite_to_url(&None), Ok(None)));
assert!(matches!(
diesel_option_overwrite_to_url(&Some(String::new())),
Ok(Some(None))
));
assert!(diesel_option_overwrite_to_url(&Some("invalid_url".to_string())).is_err());
let example_url = "https://example.com";
assert!(matches!(
diesel_option_overwrite_to_url(&Some(example_url.to_string())),
Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into()
));
}
}