Expose test utils to Labrinth dependents (#4703)

* Expose test utils to Labrinth dependents

* Feature gate `labrinth::test`

* Unify db migrators

* Expose `NotificationBuilder::insert_many_deliveries`

* Add logging utils to common crate

* Remove unused console-subscriber layer

* fix CI
This commit is contained in:
aecsocket
2025-11-08 12:26:24 -08:00
committed by GitHub
parent 1efdceacfd
commit f8a5a77daa
45 changed files with 378 additions and 567 deletions

View File

@@ -1,9 +1,6 @@
[build]
rustflags = ["--cfg", "tokio_unstable"]
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler # Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
[target.'cfg(windows)'] [target.'cfg(windows)']
rustflags = ["--cfg", "tokio_unstable", "-C", "link-args=/STACK:16777220"] rustflags = ["-C", "link-args=/STACK:16777220"]
[target.x86_64-pc-windows-msvc] [target.x86_64-pc-windows-msvc]
linker = "rust-lld" linker = "rust-lld"

217
Cargo.lock generated
View File

@@ -671,28 +671,6 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "async-stream"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
dependencies = [
"async-stream-impl",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-stream-impl"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]] [[package]]
name = "async-stripe" name = "async-stripe"
version = "0.41.0" version = "0.41.0"
@@ -914,53 +892,6 @@ dependencies = [
"thiserror 2.0.17", "thiserror 2.0.17",
] ]
[[package]]
name = "axum"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core",
"bytes",
"futures-util",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper",
"tower 0.5.2",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.76" version = "0.3.76"
@@ -1697,45 +1628,6 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "console-api"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857"
dependencies = [
"futures-core",
"prost",
"prost-types",
"tonic",
"tracing-core",
]
[[package]]
name = "console-subscriber"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01"
dependencies = [
"console-api",
"crossbeam-channel",
"crossbeam-utils",
"futures-task",
"hdrhistogram",
"humantime",
"hyper-util",
"prost",
"prost-types",
"serde",
"serde_json",
"thread_local",
"tokio",
"tokio-stream",
"tonic",
"tracing",
"tracing-core",
"tracing-subscriber",
]
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.9.6" version = "0.9.6"
@@ -3647,19 +3539,6 @@ dependencies = [
"hashbrown 0.15.5", "hashbrown 0.15.5",
] ]
[[package]]
name = "hdrhistogram"
version = "7.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
dependencies = [
"base64 0.21.7",
"byteorder",
"flate2",
"nom 7.1.3",
"num-traits",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@@ -3875,12 +3754,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.32" version = "0.14.32"
@@ -3962,19 +3835,6 @@ dependencies = [
"webpki-roots 1.0.3", "webpki-roots 1.0.3",
] ]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
"hyper 1.7.0",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]] [[package]]
name = "hyper-tls" name = "hyper-tls"
version = "0.6.0" version = "0.6.0"
@@ -4660,7 +4520,6 @@ dependencies = [
"clickhouse", "clickhouse",
"color-eyre", "color-eyre",
"color-thief", "color-thief",
"console-subscriber",
"const_format", "const_format",
"dashmap", "dashmap",
"deadpool-redis", "deadpool-redis",
@@ -4679,9 +4538,11 @@ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"jemalloc_pprof", "jemalloc_pprof",
"json-patch 4.1.0", "json-patch 4.1.0",
"labrinth",
"lettre", "lettre",
"meilisearch-sdk", "meilisearch-sdk",
"modrinth-maxmind", "modrinth-maxmind",
"modrinth-util",
"muralpay", "muralpay",
"murmur2", "murmur2",
"paste", "paste",
@@ -4715,8 +4576,6 @@ dependencies = [
"totp-rs", "totp-rs",
"tracing", "tracing",
"tracing-actix-web", "tracing-actix-web",
"tracing-ecs",
"tracing-subscriber",
"url", "url",
"urlencoding", "urlencoding",
"utoipa", "utoipa",
@@ -5030,12 +4889,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]] [[package]]
name = "maxminddb" name = "maxminddb"
version = "0.26.0" version = "0.26.0"
@@ -5230,6 +5083,9 @@ dependencies = [
"dotenvy", "dotenvy",
"eyre", "eyre",
"serde", "serde",
"tracing",
"tracing-ecs",
"tracing-subscriber",
] ]
[[package]] [[package]]
@@ -6743,15 +6599,6 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "prost-types"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
dependencies = [
"prost",
]
[[package]] [[package]]
name = "protobuf" name = "protobuf"
version = "3.7.2" version = "3.7.2"
@@ -7332,7 +7179,7 @@ dependencies = [
"tokio-native-tls", "tokio-native-tls",
"tokio-rustls 0.26.4", "tokio-rustls 0.26.4",
"tokio-util", "tokio-util",
"tower 0.5.2", "tower",
"tower-http", "tower-http",
"tower-service", "tower-service",
"url", "url",
@@ -10029,36 +9876,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
[[package]]
name = "tonic"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [
"async-stream",
"async-trait",
"axum",
"base64 0.22.1",
"bytes",
"h2 0.4.12",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
"hyper 1.7.0",
"hyper-timeout",
"hyper-util",
"percent-encoding",
"pin-project",
"prost",
"socket2 0.5.10",
"tokio",
"tokio-stream",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "totp-rs" name = "totp-rs"
version = "5.7.0" version = "5.7.0"
@@ -10073,26 +9890,6 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand 0.8.5",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@@ -10121,7 +9918,7 @@ dependencies = [
"http-body 1.0.1", "http-body 1.0.1",
"iri-string", "iri-string",
"pin-project-lite", "pin-project-lite",
"tower 0.5.2", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
] ]

View File

@@ -55,7 +55,6 @@ clap = "4.5.48"
clickhouse = "0.14.0" clickhouse = "0.14.0"
color-eyre = "0.6.5" color-eyre = "0.6.5"
color-thief = "0.2.2" color-thief = "0.2.2"
console-subscriber = "0.4.1"
const_format = "0.2.34" const_format = "0.2.34"
daedalus = { path = "packages/daedalus" } daedalus = { path = "packages/daedalus" }
dashmap = "6.1.0" dashmap = "6.1.0"

View File

@@ -12,6 +12,7 @@ path = "src/main.rs"
[dependencies] [dependencies]
actix-cors = { workspace = true } actix-cors = { workspace = true }
actix-files = { workspace = true } actix-files = { workspace = true }
actix-http = { workspace = true, optional = true }
actix-multipart = { workspace = true } actix-multipart = { workspace = true }
actix-rt = { workspace = true } actix-rt = { workspace = true }
actix-web = { workspace = true } actix-web = { workspace = true }
@@ -36,7 +37,6 @@ clap = { workspace = true, features = ["derive"] }
clickhouse = { workspace = true, features = ["time", "uuid"] } clickhouse = { workspace = true, features = ["time", "uuid"] }
color-eyre = { workspace = true } color-eyre = { workspace = true }
color-thief = { workspace = true } color-thief = { workspace = true }
console-subscriber = { workspace = true }
const_format = { workspace = true } const_format = { workspace = true }
dashmap = { workspace = true } dashmap = { workspace = true }
deadpool-redis.workspace = true deadpool-redis.workspace = true
@@ -71,6 +71,7 @@ json-patch = { workspace = true }
lettre = { workspace = true } lettre = { workspace = true }
meilisearch-sdk = { workspace = true, features = ["reqwest"] } meilisearch-sdk = { workspace = true, features = ["reqwest"] }
modrinth-maxmind = { workspace = true } modrinth-maxmind = { workspace = true }
modrinth-util = { workspace = true }
muralpay = { workspace = true, features = ["utoipa"] } muralpay = { workspace = true, features = ["utoipa"] }
murmur2 = { workspace = true } murmur2 = { workspace = true }
paste = { workspace = true } paste = { workspace = true }
@@ -119,8 +120,6 @@ tokio-stream = { workspace = true }
totp-rs = { workspace = true, features = ["gen_secret"] } totp-rs = { workspace = true, features = ["gen_secret"] }
tracing = { workspace = true } tracing = { workspace = true }
tracing-actix-web = { workspace = true } tracing-actix-web = { workspace = true }
tracing-ecs = { workspace = true }
tracing-subscriber = { workspace = true }
url = { workspace = true } url = { workspace = true }
urlencoding = { workspace = true } urlencoding = { workspace = true }
utoipa = { workspace = true } utoipa = { workspace = true }
@@ -135,7 +134,7 @@ zip = { workspace = true }
zxcvbn = { workspace = true } zxcvbn = { workspace = true }
[dev-dependencies] [dev-dependencies]
actix-http = { workspace = true } labrinth = { path = ".", features = ["test"] }
[build-dependencies] [build-dependencies]
chrono = { workspace = true } chrono = { workspace = true }
@@ -150,5 +149,8 @@ tikv-jemallocator = { workspace = true, features = [
"unprefixed_malloc_on_supported_platforms", "unprefixed_malloc_on_supported_platforms",
] } ] }
[features]
test = ["dep:actix-http"]
[lints] [lints]
workspace = true workspace = true

View File

@@ -160,7 +160,7 @@ impl NotificationBuilder {
Ok(()) Ok(())
} }
async fn insert_many_deliveries( pub async fn insert_many_deliveries(
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
redis: &RedisPool, redis: &RedisPool,
notification_ids: &[i64], notification_ids: &[i64],

View File

@@ -37,10 +37,12 @@ pub mod routes;
pub mod scheduler; pub mod scheduler;
pub mod search; pub mod search;
pub mod sync; pub mod sync;
pub mod test;
pub mod util; pub mod util;
pub mod validate; pub mod validate;
#[cfg(feature = "test")]
pub mod test;
#[derive(Clone)] #[derive(Clone)]
pub struct Pepper { pub struct Pepper {
pub pepper: String, pub pepper: String,

View File

@@ -17,15 +17,9 @@ use labrinth::util::ratelimit::rate_limit_middleware;
use labrinth::utoipa_app_config; use labrinth::utoipa_app_config;
use labrinth::{check_env_vars, clickhouse, database, file_hosting}; use labrinth::{check_env_vars, clickhouse, database, file_hosting};
use std::ffi::CStr; use std::ffi::CStr;
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use tracing::level_filters::LevelFilter;
use tracing::{Instrument, error, info, info_span}; use tracing::{Instrument, error, info, info_span};
use tracing_actix_web::TracingLogger; use tracing_actix_web::TracingLogger;
use tracing_ecs::ECSLayerBuilder;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use utoipa::OpenApi; use utoipa::OpenApi;
use utoipa_actix_web::AppExt; use utoipa_actix_web::AppExt;
use utoipa_swagger_ui::SwaggerUi; use utoipa_swagger_ui::SwaggerUi;
@@ -59,59 +53,13 @@ struct Args {
run_background_task: Option<BackgroundTask>, run_background_task: Option<BackgroundTask>,
} }
#[derive(Debug, Clone, Default, PartialEq, Eq)]
enum OutputFormat {
#[default]
Human,
Json,
}
impl FromStr for OutputFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"human" => Ok(Self::Human),
"json" => Ok(Self::Json),
_ => Err(()),
}
}
}
#[actix_rt::main] #[actix_rt::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
let args = Args::parse(); let args = Args::parse();
color_eyre::install().expect("failed to install `color-eyre`"); color_eyre::install().expect("failed to install `color-eyre`");
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let console_layer = console_subscriber::spawn(); modrinth_util::log::init().expect("failed to initialize logging");
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
let output_format =
dotenvy::var("LABRINTH_FORMAT").map_or(OutputFormat::Human, |format| {
format
.parse::<OutputFormat>()
.unwrap_or_else(|_| panic!("invalid output format '{format}'"))
});
match output_format {
OutputFormat::Human => {
tracing_subscriber::registry()
.with(console_layer)
.with(env_filter)
.with(tracing_subscriber::fmt::layer())
.init();
}
OutputFormat::Json => {
tracing_subscriber::registry()
.with(console_layer)
.with(env_filter)
.with(ECSLayerBuilder::default().stdout())
.init();
}
}
if check_env_vars() { if check_env_vars() {
error!("Some environment variables are missing!"); error!("Some environment variables are missing!");

View File

@@ -1,13 +1,13 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::common::{api_v2::ApiV2, api_v3::ApiV3, dummy_data::TestFile}; use crate::models::ids::ProjectId;
use actix_web::dev::ServiceResponse; use crate::models::{
use async_trait::async_trait;
use labrinth::models::ids::ProjectId;
use labrinth::models::{
projects::VersionType, projects::VersionType,
teams::{OrganizationPermissions, ProjectPermissions}, teams::{OrganizationPermissions, ProjectPermissions},
}; };
use crate::test::{api_v2::ApiV2, api_v3::ApiV3, dummy_data::TestFile};
use actix_web::dev::ServiceResponse;
use async_trait::async_trait;
use super::{ use super::{
Api, ApiProject, ApiTags, ApiTeams, ApiUser, ApiVersion, Api, ApiProject, ApiTags, ApiTeams, ApiUser, ApiVersion,
@@ -81,7 +81,7 @@ delegate_api_variant!(
[add_gallery_item, ServiceResponse, id_or_slug: &str, image: ImageData, featured: bool, title: Option<String>, description: Option<String>, ordering: Option<i32>, pat: Option<&str>], [add_gallery_item, ServiceResponse, id_or_slug: &str, image: ImageData, featured: bool, title: Option<String>, description: Option<String>, ordering: Option<i32>, pat: Option<&str>],
[remove_gallery_item, ServiceResponse, id_or_slug: &str, image_url: &str, pat: Option<&str>], [remove_gallery_item, ServiceResponse, id_or_slug: &str, image_url: &str, pat: Option<&str>],
[edit_gallery_item, ServiceResponse, id_or_slug: &str, image_url: &str, patch: HashMap<String, String>, pat: Option<&str>], [edit_gallery_item, ServiceResponse, id_or_slug: &str, image_url: &str, patch: HashMap<String, String>, pat: Option<&str>],
[create_report, ServiceResponse, report_type: &str, id: &str, item_type: crate::common::api_common::models::CommonItemType, body: &str, pat: Option<&str>], [create_report, ServiceResponse, report_type: &str, id: &str, item_type: crate::test::api_common::models::CommonItemType, body: &str, pat: Option<&str>],
[get_report, ServiceResponse, id: &str, pat: Option<&str>], [get_report, ServiceResponse, id: &str, pat: Option<&str>],
[get_reports, ServiceResponse, ids: &[&str], pat: Option<&str>], [get_reports, ServiceResponse, ids: &[&str], pat: Option<&str>],
[get_user_reports, ServiceResponse, pat: Option<&str>], [get_user_reports, ServiceResponse, pat: Option<&str>],
@@ -100,9 +100,9 @@ delegate_api_variant!(
#[async_trait(?Send)] #[async_trait(?Send)]
impl ApiTags for GenericApi { impl ApiTags for GenericApi {
[get_loaders, ServiceResponse,], [get_loaders, ServiceResponse,],
[get_loaders_deserialized_common, Vec<crate::common::api_common::models::CommonLoaderData>,], [get_loaders_deserialized_common, Vec<crate::test::api_common::models::CommonLoaderData>,],
[get_categories, ServiceResponse,], [get_categories, ServiceResponse,],
[get_categories_deserialized_common, Vec<crate::common::api_common::models::CommonCategoryData>,], [get_categories_deserialized_common, Vec<crate::test::api_common::models::CommonCategoryData>,],
} }
); );
@@ -110,18 +110,18 @@ delegate_api_variant!(
#[async_trait(?Send)] #[async_trait(?Send)]
impl ApiTeams for GenericApi { impl ApiTeams for GenericApi {
[get_team_members, ServiceResponse, team_id: &str, pat: Option<&str>], [get_team_members, ServiceResponse, team_id: &str, pat: Option<&str>],
[get_team_members_deserialized_common, Vec<crate::common::api_common::models::CommonTeamMember>, team_id: &str, pat: Option<&str>], [get_team_members_deserialized_common, Vec<crate::test::api_common::models::CommonTeamMember>, team_id: &str, pat: Option<&str>],
[get_teams_members, ServiceResponse, ids: &[&str], pat: Option<&str>], [get_teams_members, ServiceResponse, ids: &[&str], pat: Option<&str>],
[get_project_members, ServiceResponse, id_or_slug: &str, pat: Option<&str>], [get_project_members, ServiceResponse, id_or_slug: &str, pat: Option<&str>],
[get_project_members_deserialized_common, Vec<crate::common::api_common::models::CommonTeamMember>, id_or_slug: &str, pat: Option<&str>], [get_project_members_deserialized_common, Vec<crate::test::api_common::models::CommonTeamMember>, id_or_slug: &str, pat: Option<&str>],
[get_organization_members, ServiceResponse, id_or_title: &str, pat: Option<&str>], [get_organization_members, ServiceResponse, id_or_title: &str, pat: Option<&str>],
[get_organization_members_deserialized_common, Vec<crate::common::api_common::models::CommonTeamMember>, id_or_title: &str, pat: Option<&str>], [get_organization_members_deserialized_common, Vec<crate::test::api_common::models::CommonTeamMember>, id_or_title: &str, pat: Option<&str>],
[join_team, ServiceResponse, team_id: &str, pat: Option<&str>], [join_team, ServiceResponse, team_id: &str, pat: Option<&str>],
[remove_from_team, ServiceResponse, team_id: &str, user_id: &str, pat: Option<&str>], [remove_from_team, ServiceResponse, team_id: &str, user_id: &str, pat: Option<&str>],
[edit_team_member, ServiceResponse, team_id: &str, user_id: &str, patch: serde_json::Value, pat: Option<&str>], [edit_team_member, ServiceResponse, team_id: &str, user_id: &str, patch: serde_json::Value, pat: Option<&str>],
[transfer_team_ownership, ServiceResponse, team_id: &str, user_id: &str, pat: Option<&str>], [transfer_team_ownership, ServiceResponse, team_id: &str, user_id: &str, pat: Option<&str>],
[get_user_notifications, ServiceResponse, user_id: &str, pat: Option<&str>], [get_user_notifications, ServiceResponse, user_id: &str, pat: Option<&str>],
[get_user_notifications_deserialized_common, Vec<crate::common::api_common::models::CommonNotification>, user_id: &str, pat: Option<&str>], [get_user_notifications_deserialized_common, Vec<crate::test::api_common::models::CommonNotification>, user_id: &str, pat: Option<&str>],
[get_notification, ServiceResponse, notification_id: &str, pat: Option<&str>], [get_notification, ServiceResponse, notification_id: &str, pat: Option<&str>],
[get_notifications, ServiceResponse, ids: &[&str], pat: Option<&str>], [get_notifications, ServiceResponse, ids: &[&str], pat: Option<&str>],
[mark_notification_read, ServiceResponse, notification_id: &str, pat: Option<&str>], [mark_notification_read, ServiceResponse, notification_id: &str, pat: Option<&str>],

View File

@@ -6,16 +6,16 @@ use self::models::{
}; };
use self::request_data::{ImageData, ProjectCreationRequestData}; use self::request_data::{ImageData, ProjectCreationRequestData};
use super::dummy_data::TestFile; use super::dummy_data::TestFile;
use actix_web::dev::ServiceResponse; use crate::models::ids::ProjectId;
use async_trait::async_trait; use crate::{
use labrinth::models::ids::ProjectId;
use labrinth::{
LabrinthConfig, LabrinthConfig,
models::{ models::{
projects::VersionType, projects::VersionType,
teams::{OrganizationPermissions, ProjectPermissions}, teams::{OrganizationPermissions, ProjectPermissions},
}, },
}; };
use actix_web::dev::ServiceResponse;
use async_trait::async_trait;
pub mod generic; pub mod generic;
pub mod models; pub mod models;

View File

@@ -1,10 +1,8 @@
use ariadne::ids::UserId; use crate::models::ids::{
use chrono::{DateTime, Utc};
use labrinth::models::ids::{
ImageId, NotificationId, OrganizationId, ProjectId, ReportId, TeamId, ImageId, NotificationId, OrganizationId, ProjectId, ReportId, TeamId,
ThreadId, ThreadMessageId, VersionId, ThreadId, ThreadMessageId, VersionId,
}; };
use labrinth::{ use crate::{
auth::AuthProvider, auth::AuthProvider,
models::{ models::{
projects::{ projects::{
@@ -16,6 +14,8 @@ use labrinth::{
users::{Badges, Role, User, UserPayoutData}, users::{Badges, Role, User, UserPayoutData},
}, },
}; };
use ariadne::ids::UserId;
use chrono::{DateTime, Utc};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use serde::Deserialize; use serde::Deserialize;
// Fields shared by every version of the API. // Fields shared by every version of the API.

View File

@@ -1,9 +1,9 @@
// The structures for project/version creation. // The structures for project/version creation.
// These are created differently, but are essentially the same between versions. // These are created differently, but are essentially the same between versions.
use labrinth::util::actix::MultipartSegment; use crate::util::actix::MultipartSegment;
use crate::common::dummy_data::TestFile; use crate::test::dummy_data::TestFile;
pub struct ProjectCreationRequestData { pub struct ProjectCreationRequestData {
pub slug: String, pub slug: String,

View File

@@ -2,9 +2,9 @@ use super::{
api_common::{Api, ApiBuildable}, api_common::{Api, ApiBuildable},
environment::LocalService, environment::LocalService,
}; };
use crate::LabrinthConfig;
use actix_web::{App, dev::ServiceResponse, test}; use actix_web::{App, dev::ServiceResponse, test};
use async_trait::async_trait; use async_trait::async_trait;
use labrinth::LabrinthConfig;
use std::rc::Rc; use std::rc::Rc;
use utoipa_actix_web::AppExt; use utoipa_actix_web::AppExt;
@@ -26,12 +26,10 @@ impl ApiBuildable for ApiV2 {
let app = App::new() let app = App::new()
.into_utoipa_app() .into_utoipa_app()
.configure(|cfg| { .configure(|cfg| {
labrinth::utoipa_app_config(cfg, labrinth_config.clone()) crate::utoipa_app_config(cfg, labrinth_config.clone())
}) })
.into_app() .into_app()
.configure(|cfg| { .configure(|cfg| crate::app_config(cfg, labrinth_config.clone()));
labrinth::app_config(cfg, labrinth_config.clone())
});
let test_app: Rc<dyn LocalService> = let test_app: Rc<dyn LocalService> =
Rc::new(test::init_service(app).await); Rc::new(test::init_service(app).await);

View File

@@ -1,15 +1,17 @@
use std::{collections::HashMap, fmt::Write}; use std::{collections::HashMap, fmt::Write};
use crate::{ use crate::test::asserts::assert_status;
assert_status, use crate::test::{
common::{ api_common::{
api_common::{ Api, ApiProject, AppendsOptionalPat,
Api, ApiProject, AppendsOptionalPat, models::{CommonItemType, CommonProject, CommonVersion},
models::{CommonItemType, CommonProject, CommonVersion}, request_data::{ImageData, ProjectCreationRequestData},
request_data::{ImageData, ProjectCreationRequestData},
},
dummy_data::TestFile,
}, },
dummy_data::TestFile,
};
use crate::{
models::v2::{projects::LegacyProject, search::LegacySearchResults},
util::actix::AppendsMultipart,
}; };
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{ use actix_web::{
@@ -18,13 +20,9 @@ use actix_web::{
}; };
use async_trait::async_trait; use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use labrinth::{
models::v2::{projects::LegacyProject, search::LegacySearchResults},
util::actix::AppendsMultipart,
};
use serde_json::json; use serde_json::json;
use crate::common::database::MOD_USER_PAT; use crate::test::database::MOD_USER_PAT;
use super::{ use super::{
ApiV2, ApiV2,

View File

@@ -1,13 +1,13 @@
use serde_json::json; use serde_json::json;
use crate::common::{ use crate::models::ids::ProjectId;
use crate::test::{
api_common::request_data::{ api_common::request_data::{
ProjectCreationRequestData, VersionCreationRequestData, ProjectCreationRequestData, VersionCreationRequestData,
}, },
dummy_data::TestFile, dummy_data::TestFile,
}; };
use labrinth::models::ids::ProjectId; use crate::util::actix::{MultipartSegment, MultipartSegmentData};
use labrinth::util::actix::{MultipartSegment, MultipartSegmentData};
pub fn get_public_project_creation_data( pub fn get_public_project_creation_data(
slug: &str, slug: &str,

View File

@@ -1,22 +1,20 @@
use crate::routes::v2::tags::{
CategoryData, DonationPlatformQueryData, GameVersionQueryData, LoaderData,
};
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{ use actix_web::{
dev::ServiceResponse, dev::ServiceResponse,
test::{self, TestRequest}, test::{self, TestRequest},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use labrinth::routes::v2::tags::{
CategoryData, DonationPlatformQueryData, GameVersionQueryData, LoaderData,
};
use crate::{ use crate::test::asserts::assert_status;
assert_status, use crate::test::{
common::{ api_common::{
api_common::{ Api, ApiTags, AppendsOptionalPat,
Api, ApiTags, AppendsOptionalPat, models::{CommonCategoryData, CommonLoaderData},
models::{CommonCategoryData, CommonLoaderData},
},
database::ADMIN_USER_PAT,
}, },
database::ADMIN_USER_PAT,
}; };
use super::ApiV2; use super::ApiV2;

View File

@@ -1,19 +1,17 @@
use actix_http::StatusCode; use crate::models::{
use actix_web::{dev::ServiceResponse, test};
use async_trait::async_trait;
use labrinth::models::{
teams::{OrganizationPermissions, ProjectPermissions}, teams::{OrganizationPermissions, ProjectPermissions},
v2::{notifications::LegacyNotification, teams::LegacyTeamMember}, v2::{notifications::LegacyNotification, teams::LegacyTeamMember},
}; };
use actix_http::StatusCode;
use actix_web::{dev::ServiceResponse, test};
use async_trait::async_trait;
use serde_json::json; use serde_json::json;
use crate::{ use crate::test::api_common::{
assert_status, Api, ApiTeams, AppendsOptionalPat,
common::api_common::{ models::{CommonNotification, CommonTeamMember},
Api, ApiTeams, AppendsOptionalPat,
models::{CommonNotification, CommonTeamMember},
},
}; };
use crate::test::asserts::assert_status;
use super::ApiV2; use super::ApiV2;

View File

@@ -1,5 +1,5 @@
use super::ApiV2; use super::ApiV2;
use crate::common::api_common::{Api, ApiUser, AppendsOptionalPat}; use crate::test::api_common::{Api, ApiUser, AppendsOptionalPat};
use actix_web::{dev::ServiceResponse, test}; use actix_web::{dev::ServiceResponse, test};
use async_trait::async_trait; use async_trait::async_trait;

View File

@@ -5,14 +5,16 @@ use super::{
ApiV2, ApiV2,
request_data::{self, get_public_version_creation_data}, request_data::{self, get_public_version_creation_data},
}; };
use crate::models::ids::ProjectId;
use crate::test::asserts::assert_status;
use crate::test::{
api_common::{Api, ApiVersion, AppendsOptionalPat, models::CommonVersion},
dummy_data::TestFile,
};
use crate::{ use crate::{
assert_status, models::{projects::VersionType, v2::projects::LegacyVersion},
common::{ routes::v2::version_file::FileUpdateData,
api_common::{ util::actix::AppendsMultipart,
Api, ApiVersion, AppendsOptionalPat, models::CommonVersion,
},
dummy_data::TestFile,
},
}; };
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{ use actix_web::{
@@ -20,12 +22,6 @@ use actix_web::{
test::{self, TestRequest}, test::{self, TestRequest},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use labrinth::models::ids::ProjectId;
use labrinth::{
models::{projects::VersionType, v2::projects::LegacyVersion},
routes::v2::version_file::FileUpdateData,
util::actix::AppendsMultipart,
};
use serde_json::json; use serde_json::json;
pub fn url_encode_json_serialized_vec(elements: &[String]) -> String { pub fn url_encode_json_serialized_vec(elements: &[String]) -> String {

View File

@@ -1,16 +1,16 @@
use crate::models::{collections::Collection, v3::projects::Project};
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{ use actix_web::{
dev::ServiceResponse, dev::ServiceResponse,
test::{self, TestRequest}, test::{self, TestRequest},
}; };
use bytes::Bytes; use bytes::Bytes;
use labrinth::models::{collections::Collection, v3::projects::Project};
use serde_json::json; use serde_json::json;
use crate::{ use crate::test::api_common::{
assert_status, Api, AppendsOptionalPat, request_data::ImageData,
common::api_common::{Api, AppendsOptionalPat, request_data::ImageData},
}; };
use crate::test::asserts::assert_status;
use super::ApiV3; use super::ApiV3;

View File

@@ -1,13 +1,11 @@
use crate::models::v3::user_limits::UserLimits;
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::test; use actix_web::test;
use labrinth::models::v3::user_limits::UserLimits;
use crate::{ use crate::test::asserts::assert_status;
assert_status, use crate::test::{
common::{ api_common::{Api, AppendsOptionalPat},
api_common::{Api, AppendsOptionalPat}, api_v3::ApiV3,
api_v3::ApiV3,
},
}; };
impl ApiV3 { impl ApiV3 {

View File

@@ -2,9 +2,9 @@ use super::{
api_common::{Api, ApiBuildable}, api_common::{Api, ApiBuildable},
environment::LocalService, environment::LocalService,
}; };
use crate::LabrinthConfig;
use actix_web::{App, dev::ServiceResponse, test}; use actix_web::{App, dev::ServiceResponse, test};
use async_trait::async_trait; use async_trait::async_trait;
use labrinth::LabrinthConfig;
use std::rc::Rc; use std::rc::Rc;
use utoipa_actix_web::AppExt; use utoipa_actix_web::AppExt;
@@ -31,12 +31,10 @@ impl ApiBuildable for ApiV3 {
let app = App::new() let app = App::new()
.into_utoipa_app() .into_utoipa_app()
.configure(|cfg| { .configure(|cfg| {
labrinth::utoipa_app_config(cfg, labrinth_config.clone()) crate::utoipa_app_config(cfg, labrinth_config.clone())
}) })
.into_app() .into_app()
.configure(|cfg| { .configure(|cfg| crate::app_config(cfg, labrinth_config.clone()));
labrinth::app_config(cfg, labrinth_config.clone())
});
let test_app: Rc<dyn LocalService> = let test_app: Rc<dyn LocalService> =
Rc::new(test::init_service(app).await); Rc::new(test::init_service(app).await);

View File

@@ -1,20 +1,18 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::auth::oauth::{
OAuthClientAccessRequest, RespondToOAuthClientScopes, TokenRequest,
TokenResponse,
};
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::http::header::{AUTHORIZATION, LOCATION}; use actix_web::http::header::{AUTHORIZATION, LOCATION};
use actix_web::{ use actix_web::{
dev::ServiceResponse, dev::ServiceResponse,
test::{self, TestRequest}, test::{self, TestRequest},
}; };
use labrinth::auth::oauth::{
OAuthClientAccessRequest, RespondToOAuthClientScopes, TokenRequest,
TokenResponse,
};
use crate::{ use crate::test::api_common::{Api, AppendsOptionalPat};
assert_status, use crate::test::asserts::assert_status;
common::api_common::{Api, AppendsOptionalPat},
};
use super::ApiV3; use super::ApiV3;

View File

@@ -1,21 +1,19 @@
use actix_http::StatusCode; use crate::{
use actix_web::{
dev::ServiceResponse,
test::{self, TestRequest},
};
use labrinth::{
models::{ models::{
oauth_clients::{OAuthClient, OAuthClientAuthorization}, oauth_clients::{OAuthClient, OAuthClientAuthorization},
pats::Scopes, pats::Scopes,
}, },
routes::v3::oauth_clients::OAuthClientEdit, routes::v3::oauth_clients::OAuthClientEdit,
}; };
use actix_http::StatusCode;
use actix_web::{
dev::ServiceResponse,
test::{self, TestRequest},
};
use serde_json::json; use serde_json::json;
use crate::{ use crate::test::api_common::{Api, AppendsOptionalPat};
assert_status, use crate::test::asserts::assert_status;
common::api_common::{Api, AppendsOptionalPat},
};
use super::ApiV3; use super::ApiV3;

View File

@@ -1,7 +1,8 @@
use crate::{ use crate::models::{organizations::Organization, v3::projects::Project};
assert_status, use crate::test::api_common::{
common::api_common::{Api, AppendsOptionalPat, request_data::ImageData}, Api, AppendsOptionalPat, request_data::ImageData,
}; };
use crate::test::asserts::assert_status;
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{ use actix_web::{
dev::ServiceResponse, dev::ServiceResponse,
@@ -9,7 +10,6 @@ use actix_web::{
}; };
use ariadne::ids::UserId; use ariadne::ids::UserId;
use bytes::Bytes; use bytes::Bytes;
use labrinth::models::{organizations::Organization, v3::projects::Project};
use serde_json::json; use serde_json::json;
use super::ApiV3; use super::ApiV3;

View File

@@ -1,5 +1,10 @@
use std::{collections::HashMap, fmt::Write}; use std::{collections::HashMap, fmt::Write};
use crate::{
models::{organizations::Organization, projects::Project},
search::SearchResults,
util::actix::AppendsMultipart,
};
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{ use actix_web::{
dev::ServiceResponse, dev::ServiceResponse,
@@ -8,25 +13,18 @@ use actix_web::{
use async_trait::async_trait; use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use labrinth::{
models::{organizations::Organization, projects::Project},
search::SearchResults,
util::actix::AppendsMultipart,
};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use serde_json::json; use serde_json::json;
use crate::{ use crate::test::asserts::assert_status;
assert_status, use crate::test::{
common::{ api_common::{
api_common::{ Api, ApiProject, AppendsOptionalPat,
Api, ApiProject, AppendsOptionalPat, models::{CommonItemType, CommonProject, CommonVersion},
models::{CommonItemType, CommonProject, CommonVersion}, request_data::{ImageData, ProjectCreationRequestData},
request_data::{ImageData, ProjectCreationRequestData},
},
database::MOD_USER_PAT,
dummy_data::TestFile,
}, },
database::MOD_USER_PAT,
dummy_data::TestFile,
}; };
use super::{ use super::{

View File

@@ -1,13 +1,13 @@
use serde_json::json; use serde_json::json;
use crate::common::{ use crate::models::ids::ProjectId;
use crate::test::{
api_common::request_data::{ api_common::request_data::{
ProjectCreationRequestData, VersionCreationRequestData, ProjectCreationRequestData, VersionCreationRequestData,
}, },
dummy_data::TestFile, dummy_data::TestFile,
}; };
use labrinth::models::ids::ProjectId; use crate::util::actix::{MultipartSegment, MultipartSegmentData};
use labrinth::util::actix::{MultipartSegment, MultipartSegmentData};
pub fn get_public_project_creation_data( pub fn get_public_project_creation_data(
slug: &str, slug: &str,

View File

@@ -1,24 +1,22 @@
use crate::routes::v3::tags::{GameData, LoaderData};
use crate::{
database::models::loader_fields::LoaderFieldEnumValue,
routes::v3::tags::CategoryData,
};
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{ use actix_web::{
dev::ServiceResponse, dev::ServiceResponse,
test::{self, TestRequest}, test::{self, TestRequest},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use labrinth::routes::v3::tags::{GameData, LoaderData};
use labrinth::{
database::models::loader_fields::LoaderFieldEnumValue,
routes::v3::tags::CategoryData,
};
use crate::{ use crate::test::asserts::assert_status;
assert_status, use crate::test::{
common::{ api_common::{
api_common::{ Api, ApiTags, AppendsOptionalPat,
Api, ApiTags, AppendsOptionalPat, models::{CommonCategoryData, CommonLoaderData},
models::{CommonCategoryData, CommonLoaderData},
},
database::ADMIN_USER_PAT,
}, },
database::ADMIN_USER_PAT,
}; };
use super::ApiV3; use super::ApiV3;

View File

@@ -1,19 +1,17 @@
use actix_http::StatusCode; use crate::models::{
use actix_web::{dev::ServiceResponse, test};
use async_trait::async_trait;
use labrinth::models::{
notifications::Notification, notifications::Notification,
teams::{OrganizationPermissions, ProjectPermissions, TeamMember}, teams::{OrganizationPermissions, ProjectPermissions, TeamMember},
}; };
use actix_http::StatusCode;
use actix_web::{dev::ServiceResponse, test};
use async_trait::async_trait;
use serde_json::json; use serde_json::json;
use crate::{ use crate::test::api_common::{
assert_status, Api, ApiTeams, AppendsOptionalPat,
common::api_common::{ models::{CommonNotification, CommonTeamMember},
Api, ApiTeams, AppendsOptionalPat,
models::{CommonNotification, CommonTeamMember},
},
}; };
use crate::test::asserts::assert_status;
use super::ApiV3; use super::ApiV3;

View File

@@ -1,7 +1,7 @@
use actix_web::{dev::ServiceResponse, test}; use actix_web::{dev::ServiceResponse, test};
use async_trait::async_trait; use async_trait::async_trait;
use crate::common::api_common::{Api, ApiUser, AppendsOptionalPat}; use crate::test::api_common::{Api, ApiUser, AppendsOptionalPat};
use super::ApiV3; use super::ApiV3;

View File

@@ -5,14 +5,16 @@ use super::{
ApiV3, ApiV3,
request_data::{self, get_public_version_creation_data}, request_data::{self, get_public_version_creation_data},
}; };
use crate::models::ids::ProjectId;
use crate::test::asserts::assert_status;
use crate::test::{
api_common::{Api, ApiVersion, AppendsOptionalPat, models::CommonVersion},
dummy_data::TestFile,
};
use crate::{ use crate::{
assert_status, models::{projects::VersionType, v3::projects::Version},
common::{ routes::v3::version_file::FileUpdateData,
api_common::{ util::actix::AppendsMultipart,
Api, ApiVersion, AppendsOptionalPat, models::CommonVersion,
},
dummy_data::TestFile,
},
}; };
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{ use actix_web::{
@@ -20,12 +22,6 @@ use actix_web::{
test::{self, TestRequest}, test::{self, TestRequest},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use labrinth::models::ids::ProjectId;
use labrinth::{
models::{projects::VersionType, v3::projects::Version},
routes::v3::version_file::FileUpdateData,
util::actix::AppendsMultipart,
};
use serde_json::json; use serde_json::json;
pub fn url_encode_json_serialized_vec(elements: &[String]) -> String { pub fn url_encode_json_serialized_vec(elements: &[String]) -> String {

View File

@@ -1,10 +1,9 @@
use crate::common::get_json_val_str; use crate::models::v3::projects::Version;
use crate::test::get_json_val_str;
use itertools::Itertools; use itertools::Itertools;
use labrinth::models::v3::projects::Version;
use super::api_common::models::CommonVersion; use super::api_common::models::CommonVersion;
#[macro_export]
macro_rules! assert_status { macro_rules! assert_status {
($response:expr, $status:expr) => { ($response:expr, $status:expr) => {
assert_eq!( assert_eq!(
@@ -16,17 +15,7 @@ macro_rules! assert_status {
}; };
} }
#[macro_export] pub(crate) use assert_status;
macro_rules! assert_any_status_except {
($response:expr, $status:expr) => {
assert_ne!(
$response.status(),
$status,
"{:#?}",
$response.response().body()
);
};
}
pub fn assert_version_ids(versions: &[Version], expected_ids: Vec<String>) { pub fn assert_version_ids(versions: &[Version], expected_ids: Vec<String>) {
let version_ids = versions let version_ids = versions

View File

@@ -1,11 +1,11 @@
use labrinth::database::ReadOnlyPgPool; use crate::database::redis::RedisPool;
use labrinth::database::redis::RedisPool; use crate::database::{MIGRATOR, ReadOnlyPgPool};
use labrinth::search; use crate::search;
use sqlx::{PgPool, postgres::PgPoolOptions}; use sqlx::{PgPool, postgres::PgPoolOptions};
use std::time::Duration; use std::time::Duration;
use url::Url; use url::Url;
use crate::common::{dummy_data, environment::TestEnvironment}; use crate::test::{dummy_data, environment::TestEnvironment};
use super::{api_v3::ApiV3, dummy_data::DUMMY_DATA_UPDATE}; use super::{api_v3::ApiV3, dummy_data::DUMMY_DATA_UPDATE};
@@ -40,7 +40,7 @@ pub struct TemporaryDatabase {
pub pool: PgPool, pub pool: PgPool,
pub ro_pool: ReadOnlyPgPool, pub ro_pool: ReadOnlyPgPool,
pub redis_pool: RedisPool, pub redis_pool: RedisPool,
pub search_config: labrinth::search::SearchConfig, pub search_config: crate::search::SearchConfig,
pub database_name: String, pub database_name: String,
} }
@@ -82,8 +82,7 @@ impl TemporaryDatabase {
println!("Running migrations on temporary database"); println!("Running migrations on temporary database");
// Performs migrations // Performs migrations
let migrations = sqlx::migrate!("./migrations"); MIGRATOR.run(&pool).await.expect("Migrations failed");
migrations.run(&pool).await.expect("Migrations failed");
println!("Migrations complete"); println!("Migrations complete");
@@ -182,8 +181,7 @@ impl TemporaryDatabase {
} }
// Run migrations on the template // Run migrations on the template
let migrations = sqlx::migrate!("./migrations"); MIGRATOR.run(&pool).await.expect("Migrations failed");
migrations.run(&pool).await.expect("Migrations failed");
if !dummy_data_exists { if !dummy_data_exists {
// Add dummy data // Add dummy data

View File

@@ -1,17 +1,15 @@
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
use crate::{ use crate::models::ids::ProjectId;
assert_status, use crate::models::{
common::{api_common::Api, api_v3, database::USER_USER_PAT},
};
use actix_http::StatusCode;
use actix_web::test::{self, TestRequest};
use labrinth::models::ids::ProjectId;
use labrinth::models::{
oauth_clients::OAuthClient, oauth_clients::OAuthClient,
organizations::Organization, organizations::Organization,
projects::{Project, Version}, projects::{Project, Version},
}; };
use crate::test::asserts::assert_status;
use crate::test::{api_common::Api, api_v3, database::USER_USER_PAT};
use actix_http::StatusCode;
use actix_web::test::{self, TestRequest};
use serde_json::json; use serde_json::json;
use zip::{CompressionMethod, ZipWriter, write::FileOptions}; use zip::{CompressionMethod, ZipWriter, write::FileOptions};
@@ -289,7 +287,7 @@ pub async fn add_dummy_data(api: &ApiV3, db: TemporaryDatabase) -> DummyData {
// Adds basic dummy data to the database directly with sql (user, pats) // Adds basic dummy data to the database directly with sql (user, pats)
let pool = &db.pool.clone(); let pool = &db.pool.clone();
labrinth::test::db::add_dummy_data(pool).await.unwrap(); crate::test::db::add_dummy_data(pool).await.unwrap();
let (alpha_project, alpha_version) = add_project_alpha(api).await; let (alpha_project, alpha_version) = add_project_alpha(api).await;
let (beta_project, beta_version) = add_project_beta(api).await; let (beta_project, beta_version) = add_project_beta(api).await;

View File

@@ -5,7 +5,8 @@ use super::{
database::{FRIEND_USER_ID, TemporaryDatabase, USER_USER_PAT}, database::{FRIEND_USER_ID, TemporaryDatabase, USER_USER_PAT},
dummy_data, dummy_data,
}; };
use crate::{assert_status, common::setup}; use crate::test::asserts::assert_status;
use crate::test::setup;
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::dev::ServiceResponse; use actix_web::dev::ServiceResponse;
use futures::Future; use futures::Future;

View File

@@ -1 +1,76 @@
use crate::queue::email::EmailQueue;
use crate::util::anrok;
use crate::util::gotenberg::GotenbergClient;
use crate::{LabrinthConfig, file_hosting};
use crate::{check_env_vars, clickhouse};
use modrinth_maxmind::MaxMind;
use std::sync::Arc;
pub mod api_common;
pub mod api_v2;
pub mod api_v3;
pub mod asserts;
pub mod database;
pub mod db; pub mod db;
pub mod dummy_data;
pub mod environment;
pub mod pats;
pub mod permissions;
pub mod scopes;
pub mod search;
// Testing equivalent to 'setup' function, producing a LabrinthConfig
// If making a test, you should probably use environment::TestEnvironment::build() (which calls this)
pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig {
println!("Setting up labrinth config");
dotenvy::dotenv().ok();
if check_env_vars() {
println!("Some environment variables are missing!");
}
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let pool = db.pool.clone();
let ro_pool = db.ro_pool.clone();
let redis_pool = db.redis_pool.clone();
let search_config = db.search_config.clone();
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> =
Arc::new(file_hosting::MockHost::new());
let mut clickhouse = clickhouse::init_client().await.unwrap();
let maxmind_reader = MaxMind::new().await;
let stripe_client =
stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
let anrok_client = anrok::Client::from_env().unwrap();
let email_queue =
EmailQueue::init(pool.clone(), redis_pool.clone()).unwrap();
let gotenberg_client =
GotenbergClient::from_env().expect("Failed to create Gotenberg client");
crate::app_setup(
pool.clone(),
ro_pool.clone(),
redis_pool.clone(),
search_config,
&mut clickhouse,
file_host.clone(),
maxmind_reader,
stripe_client,
anrok_client,
email_queue,
gotenberg_client,
false,
)
}
pub fn get_json_val_str(val: impl serde::Serialize) -> String {
serde_json::to_value(val)
.unwrap()
.as_str()
.unwrap()
.to_string()
}

View File

@@ -1,8 +1,8 @@
use chrono::Utc; use crate::{
use labrinth::{
database::{self, models::generate_pat_id}, database::{self, models::generate_pat_id},
models::pats::Scopes, models::pats::Scopes,
}; };
use chrono::Utc;
use super::database::TemporaryDatabase; use super::database::TemporaryDatabase;

View File

@@ -1,11 +1,11 @@
use crate::models::teams::{OrganizationPermissions, ProjectPermissions};
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::{dev::ServiceResponse, test}; use actix_web::{dev::ServiceResponse, test};
use futures::Future; use futures::Future;
use itertools::Itertools; use itertools::Itertools;
use labrinth::models::teams::{OrganizationPermissions, ProjectPermissions};
use serde_json::json; use serde_json::json;
use crate::common::{ use crate::test::{
api_common::ApiTeams, api_common::ApiTeams,
database::{ADMIN_USER_PAT, generate_random_name}, database::{ADMIN_USER_PAT, generate_random_name},
}; };

View File

@@ -1,6 +1,6 @@
use crate::models::pats::Scopes;
use actix_web::{dev::ServiceResponse, test}; use actix_web::{dev::ServiceResponse, test};
use futures::Future; use futures::Future;
use labrinth::models::pats::Scopes;
use super::{ use super::{
api_common::Api, database::USER_USER_ID_PARSED, api_common::Api, database::USER_USER_ID_PARSED,

View File

@@ -3,13 +3,11 @@ use std::{collections::HashMap, sync::Arc};
use actix_http::StatusCode; use actix_http::StatusCode;
use serde_json::json; use serde_json::json;
use crate::{ use crate::test::asserts::assert_status;
assert_status, use crate::test::{
common::{ api_common::{Api, ApiProject, ApiVersion},
api_common::{Api, ApiProject, ApiVersion}, database::{FRIEND_USER_PAT, MOD_USER_PAT, USER_USER_PAT},
database::{FRIEND_USER_PAT, MOD_USER_PAT, USER_USER_PAT}, dummy_data::{DUMMY_CATEGORIES, TestFile},
dummy_data::{DUMMY_CATEGORIES, TestFile},
},
}; };
use super::{api_v3::ApiV3, environment::TestEnvironment}; use super::{api_v3::ApiV3, environment::TestEnvironment};

View File

@@ -0,0 +1,32 @@
//! Re-exports all [`labrinth::test`] items for compatibility.
//!
//! Previously, tests used `mod common` and `common::item` imports for testing.
//! This has been moved into [`labrinth::test`] under a feature flag, and this
//! module remains for backwards compatibility with tests which expect the
//! `common` module.
pub use labrinth::test::*;
#[macro_export]
macro_rules! assert_status {
($response:expr, $status:expr) => {
assert_eq!(
$response.status(),
$status,
"{:#?}",
$response.response().body()
);
};
}
#[macro_export]
macro_rules! assert_any_status_except {
($response:expr, $status:expr) => {
assert_ne!(
$response.status(),
$status,
"{:#?}",
$response.response().body()
);
};
}

View File

@@ -1,75 +0,0 @@
use labrinth::queue::email::EmailQueue;
use labrinth::util::anrok;
use labrinth::util::gotenberg::GotenbergClient;
use labrinth::{LabrinthConfig, file_hosting};
use labrinth::{check_env_vars, clickhouse};
use modrinth_maxmind::MaxMind;
use std::sync::Arc;
pub mod api_common;
pub mod api_v2;
pub mod api_v3;
pub mod asserts;
pub mod database;
pub mod dummy_data;
pub mod environment;
pub mod pats;
pub mod permissions;
pub mod scopes;
pub mod search;
// Testing equivalent to 'setup' function, producing a LabrinthConfig
// If making a test, you should probably use environment::TestEnvironment::build() (which calls this)
pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig {
println!("Setting up labrinth config");
dotenvy::dotenv().ok();
if check_env_vars() {
println!("Some environment variables are missing!");
}
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let pool = db.pool.clone();
let ro_pool = db.ro_pool.clone();
let redis_pool = db.redis_pool.clone();
let search_config = db.search_config.clone();
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> =
Arc::new(file_hosting::MockHost::new());
let mut clickhouse = clickhouse::init_client().await.unwrap();
let maxmind_reader = MaxMind::new().await;
let stripe_client =
stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
let anrok_client = anrok::Client::from_env().unwrap();
let email_queue =
EmailQueue::init(pool.clone(), redis_pool.clone()).unwrap();
let gotenberg_client =
GotenbergClient::from_env().expect("Failed to create Gotenberg client");
labrinth::app_setup(
pool.clone(),
ro_pool.clone(),
redis_pool.clone(),
search_config,
&mut clickhouse,
file_host.clone(),
maxmind_reader,
stripe_client,
anrok_client,
email_queue,
gotenberg_client,
false,
)
}
pub fn get_json_val_str(val: impl serde::Serialize) -> String {
serde_json::to_value(val)
.unwrap()
.as_str()
.unwrap()
.to_string()
}

View File

@@ -1,15 +1,16 @@
use crate::assert_status;
use crate::common::api_common::ApiProject; use crate::common::api_common::ApiProject;
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::test; use actix_web::test;
use bytes::Bytes; use bytes::Bytes;
use crate::assert_status;
use crate::common::database::USER_USER_PAT; use crate::common::database::USER_USER_PAT;
use crate::common::{ use crate::common::{
api_v2::ApiV2, api_v2::ApiV2,
environment::{TestEnvironment, with_test_environment}, environment::{TestEnvironment, with_test_environment},
}; };
#[actix_rt::test] #[actix_rt::test]
pub async fn error_404_empty() { pub async fn error_404_empty() {
with_test_environment( with_test_environment(

View File

@@ -10,6 +10,9 @@ derive_more = { workspace = true, features = ["display", "error", "from"] }
dotenvy = { workspace = true } dotenvy = { workspace = true }
eyre = { workspace = true } eyre = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
tracing = { workspace = true }
tracing-ecs = { workspace = true }
tracing-subscriber = { workspace = true }
[lints] [lints]
workspace = true workspace = true

View File

@@ -1,6 +1,8 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
mod error; mod error;
pub mod log;
pub use error::*; pub use error::*;
use eyre::{Result, eyre}; use eyre::{Result, eyre};

View File

@@ -0,0 +1,74 @@
//! Service logging utilities.
use std::str::FromStr;
use eyre::{Result, eyre};
use tracing::level_filters::LevelFilter;
use tracing_ecs::ECSLayerBuilder;
use tracing_subscriber::{
EnvFilter, layer::SubscriberExt, util::SubscriberInitExt,
};
use crate::{Context, env_var};
/// How this service will output logs to the terminal output.
///
/// See [`init`].
#[derive(Debug, Clone, Default, PartialEq, Eq)]
enum OutputFormat {
/// Human-readable format using [`tracing_subscriber::fmt::layer`].
#[default]
Human,
/// Elastic Common Schema JSON output using [`ECSLayerBuilder`].
Json,
}
impl FromStr for OutputFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"human" => Ok(Self::Human),
"json" => Ok(Self::Json),
_ => Err(()),
}
}
}
/// Key for the environment variable that determines the output format.
pub const OUTPUT_FORMAT_ENV_VAR: &str = "MODRINTH_OUTPUT_FORMAT";
/// Initializes logging for Modrinth services.
///
/// This uses [`OUTPUT_FORMAT_ENV_VAR`] to determine the [`OutputFormat`] to
/// use - see that type for details of each possible format.
///
/// # Errors
///
/// Errors if logging could not be initialized.
pub fn init() -> Result<()> {
let output_format = match env_var(OUTPUT_FORMAT_ENV_VAR) {
Ok(format) => format
.parse::<OutputFormat>()
.map_err(|_| eyre!("invalid output format '{format}'"))?,
Err(_) => OutputFormat::Human,
};
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
let result = match output_format {
OutputFormat::Human => tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer())
.try_init(),
OutputFormat::Json => tracing_subscriber::registry()
.with(env_filter)
.with(ECSLayerBuilder::default().stdout())
.try_init(),
};
result.wrap_err("failed to initialize tracing registry")?;
Ok(())
}