diff --git a/src/routes/v2/versions.rs b/src/routes/v2/versions.rs index 3ff63e80..283032fd 100644 --- a/src/routes/v2/versions.rs +++ b/src/routes/v2/versions.rs @@ -8,7 +8,8 @@ use crate::models::projects::{Dependency, FileType, Version, VersionStatus, Vers use crate::models::v2::projects::LegacyVersion; use crate::queue::session::AuthQueue; use crate::routes::{v2_reroute, v3}; -use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse}; +use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse, post}; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use validator::Validate; @@ -22,6 +23,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { .service(version_get) .service(version_delete) .service(version_edit) + .service(version_schedule) .service(super::version_creation::upload_file_to_version), ); } @@ -270,3 +272,35 @@ pub async fn version_delete( .await .or_else(v2_reroute::flatten_404_error) } + +#[derive(Deserialize)] +pub struct SchedulingData { + pub time : DateTime, + pub requested_status : VersionStatus, +} + +#[post("{id}/schedule")] +pub async fn version_schedule( + req: HttpRequest, + info: web::Path<(VersionId,)>, + pool: web::Data, + redis: web::Data, + scheduling_data: web::Json, + session_queue: web::Data, +) -> Result { + let scheduling_data = scheduling_data.into_inner(); + let scheduling_data = v3::versions::SchedulingData { + time: scheduling_data.time, + requested_status: scheduling_data.requested_status, + }; + v3::versions::version_schedule( + req, + info, + pool, + redis, + web::Json(scheduling_data), + session_queue, + ) + .await + .or_else(v2_reroute::flatten_404_error) +} \ No newline at end of file diff --git a/tests/analytics.rs b/tests/analytics.rs index b83d7989..7abf6c88 100644 --- a/tests/analytics.rs +++ b/tests/analytics.rs @@ -1,4 +1,3 @@ -use actix_web::test; use chrono::{DateTime, Duration, Utc}; use common::permissions::PermissionsTest; use common::permissions::PermissionsTestContext; @@ -166,15 +165,15 @@ pub async fn permissions_analytics_revenue() { .team_id .clone(); + let api = &test_env.api; + let view_analytics = ProjectPermissions::VIEW_ANALYTICS; // first, do check with a project - let req_gen = |ctx: &PermissionsTestContext| { - let projects_string = serde_json::to_string(&vec![ctx.project_id]).unwrap(); - let projects_string = urlencoding::encode(&projects_string); - test::TestRequest::get().uri(&format!( - "/v3/analytics/revenue?project_ids={projects_string}&resolution_minutes=5", - )) + let req_gen = |ctx: PermissionsTestContext| async move { + let project_id = ctx.project_id.unwrap(); + let ids_or_slugs = vec![project_id.as_str()]; + api.get_analytics_revenue(ids_or_slugs, false, None, None, Some(5), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) @@ -197,13 +196,12 @@ pub async fn permissions_analytics_revenue() { // Now with a version // Need to use alpha - let req_gen = |_: &PermissionsTestContext| { - let versions_string = serde_json::to_string(&vec![alpha_version_id.clone()]).unwrap(); - let versions_string = urlencoding::encode(&versions_string); - test::TestRequest::get().uri(&format!( - "/v3/analytics/revenue?version_ids={versions_string}&resolution_minutes=5", - )) - }; + let req_gen = |ctx: PermissionsTestContext| { + let alpha_version_id = alpha_version_id.clone(); + async move { + let ids_or_slugs = vec![alpha_version_id.as_str()]; + api.get_analytics_revenue(ids_or_slugs, true, None, None, Some(5), ctx.test_pat.as_deref()).await + }}; PermissionsTest::new(&test_env) .with_failure_codes(vec![200, 401]) diff --git a/tests/common/api_common/generic.rs b/tests/common/api_common/generic.rs index 1e991898..c2200357 100644 --- a/tests/common/api_common/generic.rs +++ b/tests/common/api_common/generic.rs @@ -10,8 +10,8 @@ use labrinth::models::{ use crate::common::{api_v2::ApiV2, api_v3::ApiV3, dummy_data::TestFile}; use super::{ - models::{CommonImageData, CommonProject, CommonVersion}, - request_data::ProjectCreationRequestData, + models::{CommonProject, CommonVersion}, + request_data::{ProjectCreationRequestData, ImageData}, Api, ApiProject, ApiTags, ApiTeams, ApiVersion, }; @@ -65,17 +65,21 @@ impl Api for GenericApi { delegate_api_variant!( #[async_trait(?Send)] impl ApiProject for GenericApi { - [add_public_project, (CommonProject, Vec), slug: &str, version_jar: Option, modify_json: Option, pat: &str], + [add_public_project, (CommonProject, Vec), slug: &str, version_jar: Option, modify_json: Option, pat: Option<&str>], [get_public_project_creation_data_json, serde_json::Value, slug: &str, version_jar: Option<&TestFile>], - [create_project, ServiceResponse, creation_data: ProjectCreationRequestData, pat: &str], - [remove_project, ServiceResponse, project_slug_or_id: &str, pat: &str], - [get_project, ServiceResponse, id_or_slug: &str, pat: &str], - [get_project_deserialized_common, CommonProject, id_or_slug: &str, pat: &str], - [get_user_projects, ServiceResponse, user_id_or_username: &str, pat: &str], - [get_user_projects_deserialized_common, Vec, user_id_or_username: &str, pat: &str], - [edit_project, ServiceResponse, id_or_slug: &str, patch: serde_json::Value, pat: &str], - [edit_project_bulk, ServiceResponse, ids_or_slugs: &[&str], patch: serde_json::Value, pat: &str], - [edit_project_icon, ServiceResponse, id_or_slug: &str, icon: Option, pat: &str], + [create_project, ServiceResponse, creation_data: ProjectCreationRequestData, pat: Option<&str>], + [remove_project, ServiceResponse, project_slug_or_id: &str, pat: Option<&str>], + [get_project, ServiceResponse, id_or_slug: &str, pat: Option<&str>], + [get_project_deserialized_common, CommonProject, id_or_slug: &str, pat: Option<&str>], + [get_user_projects, ServiceResponse, user_id_or_username: &str, pat: Option<&str>], + [get_user_projects_deserialized_common, Vec, user_id_or_username: &str, pat: Option<&str>], + [edit_project, ServiceResponse, id_or_slug: &str, patch: serde_json::Value, pat: Option<&str>], + [edit_project_bulk, ServiceResponse, ids_or_slugs: &[&str], patch: serde_json::Value, pat: Option<&str>], + [edit_project_icon, ServiceResponse, id_or_slug: &str, icon: Option, pat: Option<&str>], + [schedule_project, ServiceResponse, id_or_slug: &str, requested_status: &str, date : chrono::DateTime, pat: Option<&str>], + [add_gallery_item, ServiceResponse, id_or_slug: &str, image: ImageData, featured: bool, title: Option, description: Option, ordering: Option, 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, pat: Option<&str>], } ); @@ -92,44 +96,47 @@ delegate_api_variant!( delegate_api_variant!( #[async_trait(?Send)] impl ApiTeams for GenericApi { - [get_team_members, ServiceResponse, team_id: &str, pat: &str], - [get_team_members_deserialized_common, Vec, team_id: &str, pat: &str], - [get_project_members, ServiceResponse, id_or_slug: &str, pat: &str], - [get_project_members_deserialized_common, Vec, id_or_slug: &str, pat: &str], - [get_organization_members, ServiceResponse, id_or_title: &str, pat: &str], - [get_organization_members_deserialized_common, Vec, id_or_title: &str, pat: &str], - [join_team, ServiceResponse, team_id: &str, pat: &str], - [remove_from_team, ServiceResponse, team_id: &str, user_id: &str, pat: &str], - [edit_team_member, ServiceResponse, team_id: &str, user_id: &str, patch: serde_json::Value, pat: &str], - [transfer_team_ownership, ServiceResponse, team_id: &str, user_id: &str, pat: &str], - [get_user_notifications, ServiceResponse, user_id: &str, pat: &str], - [get_user_notifications_deserialized_common, Vec, user_id: &str, pat: &str], - [mark_notification_read, ServiceResponse, notification_id: &str, pat: &str], - [add_user_to_team, ServiceResponse, team_id: &str, user_id: &str, project_permissions: Option, organization_permissions: Option, pat: &str], - [delete_notification, ServiceResponse, notification_id: &str, pat: &str], + [get_team_members, ServiceResponse, team_id: &str, pat: Option<&str>], + [get_team_members_deserialized_common, Vec, team_id: &str, pat: Option<&str>], + [get_project_members, ServiceResponse, id_or_slug: &str, pat: Option<&str>], + [get_project_members_deserialized_common, Vec, id_or_slug: &str, pat: Option<&str>], + [get_organization_members, ServiceResponse, id_or_title: &str, pat: Option<&str>], + [get_organization_members_deserialized_common, Vec, id_or_title: &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>], + [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>], + [get_user_notifications, ServiceResponse, user_id: &str, pat: Option<&str>], + [get_user_notifications_deserialized_common, Vec, user_id: &str, pat: Option<&str>], + [mark_notification_read, ServiceResponse, notification_id: &str, pat: Option<&str>], + [add_user_to_team, ServiceResponse, team_id: &str, user_id: &str, project_permissions: Option, organization_permissions: Option, pat: Option<&str>], + [delete_notification, ServiceResponse, notification_id: &str, pat: Option<&str>], } ); delegate_api_variant!( #[async_trait(?Send)] impl ApiVersion for GenericApi { - [add_public_version, ServiceResponse, project_id: ProjectId, version_number: &str, version_jar: TestFile, ordering: Option, modify_json: Option, pat: &str], - [add_public_version_deserialized_common, CommonVersion, project_id: ProjectId, version_number: &str, version_jar: TestFile, ordering: Option, modify_json: Option, pat: &str], - [get_version, ServiceResponse, id_or_slug: &str, pat: &str], - [get_version_deserialized_common, CommonVersion, id_or_slug: &str, pat: &str], - [get_versions, ServiceResponse, ids_or_slugs: Vec, pat: &str], - [get_versions_deserialized_common, Vec, ids_or_slugs: Vec, pat: &str], - [edit_version, ServiceResponse, id_or_slug: &str, patch: serde_json::Value, pat: &str], - [get_version_from_hash, ServiceResponse, id_or_slug: &str, hash: &str, pat: &str], - [get_version_from_hash_deserialized_common, CommonVersion, id_or_slug: &str, hash: &str, pat: &str], - [get_versions_from_hashes, ServiceResponse, hashes: &[&str], algorithm: &str, pat: &str], - [get_versions_from_hashes_deserialized_common, HashMap, hashes: &[&str], algorithm: &str, pat: &str], - [get_update_from_hash, ServiceResponse, hash: &str, algorithm: &str, loaders: Option>,game_versions: Option>, version_types: Option>, pat: &str], - [get_update_from_hash_deserialized_common, CommonVersion, hash: &str, algorithm: &str,loaders: Option>,game_versions: Option>,version_types: Option>, pat: &str], - [update_files, ServiceResponse, algorithm: &str, hashes: Vec, loaders: Option>, game_versions: Option>, version_types: Option>, pat: &str], - [update_files_deserialized_common, HashMap, algorithm: &str, hashes: Vec, loaders: Option>, game_versions: Option>, version_types: Option>, pat: &str], - [get_project_versions, ServiceResponse, project_id_slug: &str, game_versions: Option>,loaders: Option>,featured: Option, version_type: Option, limit: Option, offset: Option,pat: &str], - [get_project_versions_deserialized_common, Vec, project_id_slug: &str, game_versions: Option>, loaders: Option>,featured: Option,version_type: Option,limit: Option,offset: Option,pat: &str], - [edit_version_ordering, ServiceResponse, version_id: &str,ordering: Option,pat: &str], + [add_public_version, ServiceResponse, project_id: ProjectId, version_number: &str, version_jar: TestFile, ordering: Option, modify_json: Option, pat: Option<&str>], + [add_public_version_deserialized_common, CommonVersion, project_id: ProjectId, version_number: &str, version_jar: TestFile, ordering: Option, modify_json: Option, pat: Option<&str>], + [get_version, ServiceResponse, id_or_slug: &str, pat: Option<&str>], + [get_version_deserialized_common, CommonVersion, id_or_slug: &str, pat: Option<&str>], + [get_versions, ServiceResponse, ids_or_slugs: Vec, pat: Option<&str>], + [get_versions_deserialized_common, Vec, ids_or_slugs: Vec, pat: Option<&str>], + [edit_version, ServiceResponse, id_or_slug: &str, patch: serde_json::Value, pat: Option<&str>], + [get_version_from_hash, ServiceResponse, id_or_slug: &str, hash: &str, pat: Option<&str>], + [get_version_from_hash_deserialized_common, CommonVersion, id_or_slug: &str, hash: &str, pat: Option<&str>], + [get_versions_from_hashes, ServiceResponse, hashes: &[&str], algorithm: &str, pat: Option<&str>], + [get_versions_from_hashes_deserialized_common, HashMap, hashes: &[&str], algorithm: &str, pat: Option<&str>], + [get_update_from_hash, ServiceResponse, hash: &str, algorithm: &str, loaders: Option>,game_versions: Option>, version_types: Option>, pat: Option<&str>], + [get_update_from_hash_deserialized_common, CommonVersion, hash: &str, algorithm: &str,loaders: Option>,game_versions: Option>,version_types: Option>, pat: Option<&str>], + [update_files, ServiceResponse, algorithm: &str, hashes: Vec, loaders: Option>, game_versions: Option>, version_types: Option>, pat: Option<&str>], + [update_files_deserialized_common, HashMap, algorithm: &str, hashes: Vec, loaders: Option>, game_versions: Option>, version_types: Option>, pat: Option<&str>], + [get_project_versions, ServiceResponse, project_id_slug: &str, game_versions: Option>,loaders: Option>,featured: Option, version_type: Option, limit: Option, offset: Option,pat: Option<&str>], + [get_project_versions_deserialized_common, Vec, project_id_slug: &str, game_versions: Option>, loaders: Option>,featured: Option,version_type: Option,limit: Option,offset: Option,pat: Option<&str>], + [edit_version_ordering, ServiceResponse, version_id: &str,ordering: Option,pat: Option<&str>], + [upload_file_to_version, ServiceResponse, version_id: &str, file: &TestFile, pat: Option<&str>], + [remove_version, ServiceResponse, version_id: &str, pat: Option<&str>], + [remove_version_file, ServiceResponse, hash: &str, pat: Option<&str>], } ); diff --git a/tests/common/api_common/mod.rs b/tests/common/api_common/mod.rs index 9ce71c01..35991203 100644 --- a/tests/common/api_common/mod.rs +++ b/tests/common/api_common/mod.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; use self::models::{ - CommonCategoryData, CommonImageData, CommonLoaderData, CommonNotification, CommonProject, + CommonCategoryData, CommonLoaderData, CommonNotification, CommonProject, CommonTeamMember, CommonVersion, }; -use self::request_data::ProjectCreationRequestData; +use self::request_data::{ProjectCreationRequestData, ImageData}; use actix_web::dev::ServiceResponse; use async_trait::async_trait; +use chrono::{DateTime, Utc}; use labrinth::{ models::{ projects::{ProjectId, VersionType}, @@ -38,12 +39,12 @@ pub trait ApiProject { slug: &str, version_jar: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> (CommonProject, Vec); async fn create_project( &self, creation_data: ProjectCreationRequestData, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn get_public_project_creation_data_json( &self, @@ -51,32 +52,62 @@ pub trait ApiProject { version_jar: Option<&TestFile>, ) -> serde_json::Value; - async fn remove_project(&self, id_or_slug: &str, pat: &str) -> ServiceResponse; - async fn get_project(&self, id_or_slug: &str, pat: &str) -> ServiceResponse; - async fn get_project_deserialized_common(&self, id_or_slug: &str, pat: &str) -> CommonProject; - async fn get_user_projects(&self, user_id_or_username: &str, pat: &str) -> ServiceResponse; + async fn remove_project(&self, id_or_slug: &str, pat: Option<&str>) -> ServiceResponse; + async fn get_project(&self, id_or_slug: &str, pat: Option<&str>) -> ServiceResponse; + async fn get_project_deserialized_common(&self, id_or_slug: &str, pat: Option<&str>) -> CommonProject; + async fn get_user_projects(&self, user_id_or_username: &str, pat: Option<&str>) -> ServiceResponse; async fn get_user_projects_deserialized_common( &self, user_id_or_username: &str, - pat: &str, + pat: Option<&str>, ) -> Vec; async fn edit_project( &self, id_or_slug: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn edit_project_bulk( &self, ids_or_slugs: &[&str], patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn edit_project_icon( &self, id_or_slug: &str, - icon: Option, - pat: &str, + icon: Option, + pat: Option<&str>, + ) -> ServiceResponse; + async fn schedule_project( + &self, + id_or_slug: &str, + requested_status: &str, + date: DateTime, + pat: Option<&str>, + ) -> ServiceResponse; + async fn add_gallery_item( + &self, + id_or_slug: &str, + image: ImageData, + featured: bool, + title: Option, + description: Option, + ordering: Option, + pat: Option<&str>, + ) -> ServiceResponse; + async fn remove_gallery_item( + &self, + id_or_slug: &str, + url: &str, + pat: Option<&str>, + ) -> ServiceResponse; + async fn edit_gallery_item( + &self, + id_or_slug: &str, + url: &str, + patch: HashMap, + pat: Option<&str>, ) -> ServiceResponse; } @@ -90,55 +121,55 @@ pub trait ApiTags { #[async_trait(?Send)] pub trait ApiTeams { - async fn get_team_members(&self, team_id: &str, pat: &str) -> ServiceResponse; + async fn get_team_members(&self, team_id: &str, pat: Option<&str>) -> ServiceResponse; async fn get_team_members_deserialized_common( &self, team_id: &str, - pat: &str, + pat: Option<&str>, ) -> Vec; - async fn get_project_members(&self, id_or_slug: &str, pat: &str) -> ServiceResponse; + async fn get_project_members(&self, id_or_slug: &str, pat: Option<&str>) -> ServiceResponse; async fn get_project_members_deserialized_common( &self, id_or_slug: &str, - pat: &str, + pat: Option<&str>, ) -> Vec; - async fn get_organization_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse; + async fn get_organization_members(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse; async fn get_organization_members_deserialized_common( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec; - async fn join_team(&self, team_id: &str, pat: &str) -> ServiceResponse; - async fn remove_from_team(&self, team_id: &str, user_id: &str, pat: &str) -> ServiceResponse; + async fn join_team(&self, team_id: &str, pat: Option<&str>) -> ServiceResponse; + async fn remove_from_team(&self, team_id: &str, user_id: &str, pat: Option<&str>) -> ServiceResponse; async fn edit_team_member( &self, team_id: &str, user_id: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn transfer_team_ownership( &self, team_id: &str, user_id: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; - async fn get_user_notifications(&self, user_id: &str, pat: &str) -> ServiceResponse; + async fn get_user_notifications(&self, user_id: &str, pat: Option<&str>) -> ServiceResponse; async fn get_user_notifications_deserialized_common( &self, user_id: &str, - pat: &str, + pat: Option<&str>, ) -> Vec; - async fn mark_notification_read(&self, notification_id: &str, pat: &str) -> ServiceResponse; + async fn mark_notification_read(&self, notification_id: &str, pat: Option<&str>) -> ServiceResponse; async fn add_user_to_team( &self, team_id: &str, user_id: &str, project_permissions: Option, organization_permissions: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; - async fn delete_notification(&self, notification_id: &str, pat: &str) -> ServiceResponse; + async fn delete_notification(&self, notification_id: &str, pat: Option<&str>) -> ServiceResponse; } #[async_trait(?Send)] @@ -150,7 +181,7 @@ pub trait ApiVersion { version_jar: TestFile, ordering: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn add_public_version_deserialized_common( &self, @@ -159,45 +190,45 @@ pub trait ApiVersion { version_jar: TestFile, ordering: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> CommonVersion; - async fn get_version(&self, id_or_slug: &str, pat: &str) -> ServiceResponse; - async fn get_version_deserialized_common(&self, id_or_slug: &str, pat: &str) -> CommonVersion; - async fn get_versions(&self, ids_or_slugs: Vec, pat: &str) -> ServiceResponse; + async fn get_version(&self, id_or_slug: &str, pat: Option<&str>) -> ServiceResponse; + async fn get_version_deserialized_common(&self, id_or_slug: &str, pat: Option<&str>) -> CommonVersion; + async fn get_versions(&self, ids_or_slugs: Vec, pat: Option<&str>) -> ServiceResponse; async fn get_versions_deserialized_common( &self, ids_or_slugs: Vec, - pat: &str, + pat: Option<&str>, ) -> Vec; async fn edit_version( &self, id_or_slug: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn get_version_from_hash( &self, id_or_slug: &str, hash: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn get_version_from_hash_deserialized_common( &self, id_or_slug: &str, hash: &str, - pat: &str, + pat: Option<&str>, ) -> CommonVersion; async fn get_versions_from_hashes( &self, hashes: &[&str], algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn get_versions_from_hashes_deserialized_common( &self, hashes: &[&str], algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> HashMap; async fn get_update_from_hash( &self, @@ -206,7 +237,7 @@ pub trait ApiVersion { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn get_update_from_hash_deserialized_common( &self, @@ -215,7 +246,7 @@ pub trait ApiVersion { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> CommonVersion; async fn update_files( &self, @@ -224,7 +255,7 @@ pub trait ApiVersion { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; async fn update_files_deserialized_common( &self, @@ -233,7 +264,7 @@ pub trait ApiVersion { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> HashMap; #[allow(clippy::too_many_arguments)] async fn get_project_versions( @@ -245,7 +276,7 @@ pub trait ApiVersion { version_type: Option, limit: Option, offset: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse; #[allow(clippy::too_many_arguments)] async fn get_project_versions_deserialized_common( @@ -257,12 +288,39 @@ pub trait ApiVersion { version_type: Option, limit: Option, offset: Option, - pat: &str, + pat: Option<&str>, ) -> Vec; async fn edit_version_ordering( &self, version_id: &str, ordering: Option, - pat: &str, + pat: Option<&str>, + ) -> ServiceResponse; + async fn upload_file_to_version( + &self, + version_id: &str, + file: &TestFile, + pat: Option<&str>, ) -> ServiceResponse; + async fn remove_version(&self, id_or_slug: &str, pat: Option<&str>) -> ServiceResponse; + async fn remove_version_file( + &self, + hash: &str, + pat: Option<&str>, + ) -> ServiceResponse; +} + + +pub trait AppendsOptionalPat { + fn append_pat(self, pat: Option<&str>) -> Self; +} +// Impl this on all actix_web::test::TestRequest +impl AppendsOptionalPat for actix_web::test::TestRequest { + fn append_pat(self, pat: Option<&str>) -> Self { + if let Some(pat) = pat { + self.append_header(("Authorization", pat)) + } else { + self + } + } } diff --git a/tests/common/api_common/models.rs b/tests/common/api_common/models.rs index cbf7ea96..994a7b5d 100644 --- a/tests/common/api_common/models.rs +++ b/tests/common/api_common/models.rs @@ -74,13 +74,6 @@ pub struct CommonVersion { pub ordering: Option, } -#[derive(Deserialize)] -pub struct CommonImageData { - pub filename: String, - pub extension: String, - pub icon: Vec, -} - #[derive(Deserialize)] pub struct CommonLoaderData { pub icon: String, diff --git a/tests/common/api_v2/project.rs b/tests/common/api_v2/project.rs index 990e5f29..1e6e3f8a 100644 --- a/tests/common/api_v2/project.rs +++ b/tests/common/api_v2/project.rs @@ -1,8 +1,10 @@ +use std::collections::HashMap; + use crate::common::{ api_common::{ - models::{CommonImageData, CommonProject, CommonVersion}, - request_data::ProjectCreationRequestData, - Api, ApiProject, + models::{CommonProject, CommonVersion}, + request_data::{ProjectCreationRequestData, ImageData}, + Api, ApiProject, AppendsOptionalPat, }, dummy_data::TestFile, }; @@ -13,6 +15,7 @@ use actix_web::{ }; use async_trait::async_trait; use bytes::Bytes; +use chrono::{DateTime, Utc}; use labrinth::{ models::v2::{projects::LegacyProject, search::LegacySearchResults}, util::actix::AppendsMultipart, @@ -27,7 +30,7 @@ use super::{ }; impl ApiV2 { - pub async fn get_project_deserialized(&self, id_or_slug: &str, pat: &str) -> LegacyProject { + pub async fn get_project_deserialized(&self, id_or_slug: &str, pat: Option<&str>) -> LegacyProject { let resp = self.get_project(id_or_slug, pat).await; assert_eq!(resp.status(), 200); test::read_body_json(resp).await @@ -36,7 +39,7 @@ impl ApiV2 { pub async fn get_user_projects_deserialized( &self, user_id_or_username: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_user_projects(user_id_or_username, pat).await; assert_eq!(resp.status(), 200); @@ -47,7 +50,7 @@ impl ApiV2 { &self, query: Option<&str>, facets: Option, - pat: &str, + pat: Option<&str>, ) -> LegacySearchResults { let query_field = if let Some(query) = query { format!("&query={}", urlencoding::encode(query)) @@ -63,7 +66,7 @@ impl ApiV2 { let req = test::TestRequest::get() .uri(&format!("/v2/search?{}{}", query_field, facets_field)) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); let resp = self.call(req).await; let status = resp.status(); @@ -79,7 +82,7 @@ impl ApiProject for ApiV2 { slug: &str, version_jar: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> (CommonProject, Vec) { let creation_data = get_public_project_creation_data(slug, version_jar, modify_json); @@ -91,7 +94,7 @@ impl ApiProject for ApiV2 { // Approve as a moderator. let req = TestRequest::patch() .uri(&format!("/v2/project/{}", slug)) - .append_header(("Authorization", MOD_USER_PAT)) + .append_pat(MOD_USER_PAT) .set_json(json!( { "status": "approved" @@ -106,7 +109,7 @@ impl ApiProject for ApiV2 { // Get project's versions let req = TestRequest::get() .uri(&format!("/v2/project/{}/version", slug)) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); let resp = self.call(req).await; let versions: Vec = test::read_body_json(resp).await; @@ -125,35 +128,34 @@ impl ApiProject for ApiV2 { async fn create_project( &self, creation_data: ProjectCreationRequestData, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = TestRequest::post() .uri("/v2/project") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_multipart(creation_data.segment_data) .to_request(); self.call(req).await } - async fn remove_project(&self, project_slug_or_id: &str, pat: &str) -> ServiceResponse { + async fn remove_project(&self, project_slug_or_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::delete() .uri(&format!("/v2/project/{project_slug_or_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); let resp = self.call(req).await; - assert_eq!(resp.status(), 204); resp } - async fn get_project(&self, id_or_slug: &str, pat: &str) -> ServiceResponse { + async fn get_project(&self, id_or_slug: &str, pat: Option<&str>) -> ServiceResponse { let req = TestRequest::get() .uri(&format!("/v2/project/{id_or_slug}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } - async fn get_project_deserialized_common(&self, id_or_slug: &str, pat: &str) -> CommonProject { + async fn get_project_deserialized_common(&self, id_or_slug: &str, pat: Option<&str>) -> CommonProject { let resp = self.get_project(id_or_slug, pat).await; assert_eq!(resp.status(), 200); // First, deserialize to the non-common format (to test the response is valid for this api version) @@ -163,10 +165,10 @@ impl ApiProject for ApiV2 { serde_json::from_value(value).unwrap() } - async fn get_user_projects(&self, user_id_or_username: &str, pat: &str) -> ServiceResponse { + async fn get_user_projects(&self, user_id_or_username: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v2/user/{}/projects", user_id_or_username)) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -174,7 +176,7 @@ impl ApiProject for ApiV2 { async fn get_user_projects_deserialized_common( &self, user_id_or_username: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_user_projects(user_id_or_username, pat).await; assert_eq!(resp.status(), 200); @@ -189,11 +191,11 @@ impl ApiProject for ApiV2 { &self, id_or_slug: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v2/project/{id_or_slug}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); @@ -204,7 +206,7 @@ impl ApiProject for ApiV2 { &self, ids_or_slugs: &[&str], patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let projects_str = ids_or_slugs .iter() @@ -216,7 +218,7 @@ impl ApiProject for ApiV2 { "/v2/projects?ids={encoded}", encoded = urlencoding::encode(&format!("[{projects_str}]")) )) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); @@ -226,8 +228,8 @@ impl ApiProject for ApiV2 { async fn edit_project_icon( &self, id_or_slug: &str, - icon: Option, - pat: &str, + icon: Option, + pat: Option<&str>, ) -> ServiceResponse { if let Some(icon) = icon { // If an icon is provided, upload it @@ -236,7 +238,7 @@ impl ApiProject for ApiV2 { "/v2/project/{id_or_slug}/icon?ext={ext}", ext = icon.extension )) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_payload(Bytes::from(icon.icon)) .to_request(); @@ -245,10 +247,105 @@ impl ApiProject for ApiV2 { // If no icon is provided, delete the icon let req = test::TestRequest::delete() .uri(&format!("/v2/project/{id_or_slug}/icon")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } } + + async fn schedule_project( + &self, + id_or_slug: &str, + requested_status: &str, + date: DateTime, + pat: Option<&str>, + ) -> ServiceResponse { + let req = test::TestRequest::post() + .uri(&format!("/v2/version/{id_or_slug}/schedule")) + .set_json(json!( + { + "requested_status": requested_status, + "time": date, + } + )) + .append_pat(pat) + .to_request(); + + self.call(req).await + } + + async fn add_gallery_item( + &self, + id_or_slug: &str, + image: ImageData, + featured: bool, + title: Option, + description: Option, + ordering: Option, + pat: Option<&str>, + ) -> ServiceResponse { + let mut url = format!( + "/v2/project/{id_or_slug}/gallery?ext={ext}&featured={featured}", + ext = image.extension, + featured = featured + ); + if let Some(title) = title { + url.push_str(&format!("&title={}", title)); + } + if let Some(description) = description { + url.push_str(&format!("&description={}", description)); + } + if let Some(ordering) = ordering { + url.push_str(&format!("&ordering={}", ordering)); + } + + let req = test::TestRequest::post() + .uri(&url) + .append_pat(pat) + .set_payload(Bytes::from(image.icon)) + .to_request(); + + self.call(req).await + } + + async fn edit_gallery_item( + &self, + id_or_slug: &str, + image_url: &str, + patch: HashMap, + pat: Option<&str>, + ) -> ServiceResponse { + let mut url = format!( + "/v2/project/{id_or_slug}/gallery?url={image_url}", + image_url = urlencoding::encode(image_url) + ); + + for (key, value) in patch { + url.push_str(&format!("&{key}={value}", key = key, value = urlencoding::encode(&value))); + } + + let req = test::TestRequest::patch() + .uri(&url) + .append_pat(pat) + .to_request(); + self.call(req).await + } + + async fn remove_gallery_item( + &self, + id_or_slug: &str, + url: &str, + pat: Option<&str>, + ) -> ServiceResponse { + let req = test::TestRequest::delete() + .uri(&format!( + "/v2/project/{id_or_slug}/gallery?url={url}", + url = url + )) + .append_pat(pat) + .to_request(); + + self.call(req).await + } } diff --git a/tests/common/api_v2/request_data.rs b/tests/common/api_v2/request_data.rs index 90bac7e2..4a4b3d36 100644 --- a/tests/common/api_v2/request_data.rs +++ b/tests/common/api_v2/request_data.rs @@ -2,8 +2,8 @@ use serde_json::json; use crate::common::{ - api_common::request_data::{ImageData, ProjectCreationRequestData, VersionCreationRequestData}, - dummy_data::{DummyImage, TestFile}, + api_common::request_data::{ProjectCreationRequestData, VersionCreationRequestData}, + dummy_data::TestFile, }; use labrinth::{ models::projects::ProjectId, @@ -122,12 +122,4 @@ pub fn get_public_creation_data_multipart( } else { vec![json_segment] } -} - -pub fn get_icon_data(dummy_icon: DummyImage) -> ImageData { - ImageData { - filename: dummy_icon.filename(), - extension: dummy_icon.extension(), - icon: dummy_icon.bytes(), - } -} +} \ No newline at end of file diff --git a/tests/common/api_v2/tags.rs b/tests/common/api_v2/tags.rs index c67e9594..62720fde 100644 --- a/tests/common/api_v2/tags.rs +++ b/tests/common/api_v2/tags.rs @@ -8,7 +8,7 @@ use labrinth::routes::v2::tags::{CategoryData, GameVersionQueryData, LoaderData} use crate::common::{ api_common::{ models::{CommonCategoryData, CommonLoaderData}, - Api, ApiTags, + Api, ApiTags, AppendsOptionalPat, }, database::ADMIN_USER_PAT, }; @@ -21,7 +21,7 @@ impl ApiV2 { async fn get_side_types(&self) -> ServiceResponse { let req = TestRequest::get() .uri("/v2/tag/side_type") - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); self.call(req).await } @@ -35,7 +35,7 @@ impl ApiV2 { pub async fn get_game_versions(&self) -> ServiceResponse { let req = TestRequest::get() .uri("/v2/tag/game_version") - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); self.call(req).await } @@ -64,7 +64,7 @@ impl ApiTags for ApiV2 { async fn get_loaders(&self) -> ServiceResponse { let req = TestRequest::get() .uri("/v2/tag/loader") - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); self.call(req).await } @@ -82,7 +82,7 @@ impl ApiTags for ApiV2 { async fn get_categories(&self) -> ServiceResponse { let req = TestRequest::get() .uri("/v2/tag/category") - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); self.call(req).await } diff --git a/tests/common/api_v2/team.rs b/tests/common/api_v2/team.rs index de35f206..88fa1a9c 100644 --- a/tests/common/api_v2/team.rs +++ b/tests/common/api_v2/team.rs @@ -10,7 +10,7 @@ use serde_json::json; use crate::common::{ api_common::{ models::{CommonNotification, CommonTeamMember}, - Api, ApiTeams, + Api, ApiTeams, AppendsOptionalPat, }, asserts::assert_status, }; @@ -21,7 +21,7 @@ impl ApiV2 { pub async fn get_organization_members_deserialized( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_organization_members(id_or_title, pat).await; assert_eq!(resp.status(), 200); @@ -31,7 +31,7 @@ impl ApiV2 { pub async fn get_team_members_deserialized( &self, team_id: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_team_members(team_id, pat).await; assert_eq!(resp.status(), 200); @@ -41,7 +41,7 @@ impl ApiV2 { pub async fn get_user_notifications_deserialized( &self, user_id: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_user_notifications(user_id, pat).await; assert_eq!(resp.status(), 200); @@ -51,10 +51,10 @@ impl ApiV2 { #[async_trait(?Send)] impl ApiTeams for ApiV2 { - async fn get_team_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + async fn get_team_members(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v2/team/{id_or_title}/members")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -62,7 +62,7 @@ impl ApiTeams for ApiV2 { async fn get_team_members_deserialized_common( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_team_members(id_or_title, pat).await; assert_eq!(resp.status(), 200); @@ -72,10 +72,10 @@ impl ApiTeams for ApiV2 { test::read_body_json(resp).await } - async fn get_project_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + async fn get_project_members(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v2/project/{id_or_title}/members")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -83,7 +83,7 @@ impl ApiTeams for ApiV2 { async fn get_project_members_deserialized_common( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_project_members(id_or_title, pat).await; assert_eq!(resp.status(), 200); @@ -93,10 +93,10 @@ impl ApiTeams for ApiV2 { test::read_body_json(resp).await } - async fn get_organization_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + async fn get_organization_members(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v2/organization/{id_or_title}/members")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -104,7 +104,7 @@ impl ApiTeams for ApiV2 { async fn get_organization_members_deserialized_common( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_organization_members(id_or_title, pat).await; assert_eq!(resp.status(), 200); @@ -114,18 +114,18 @@ impl ApiTeams for ApiV2 { test::read_body_json(resp).await } - async fn join_team(&self, team_id: &str, pat: &str) -> ServiceResponse { + async fn join_team(&self, team_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::post() .uri(&format!("/v2/team/{team_id}/join")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } - async fn remove_from_team(&self, team_id: &str, user_id: &str, pat: &str) -> ServiceResponse { + async fn remove_from_team(&self, team_id: &str, user_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::delete() .uri(&format!("/v2/team/{team_id}/members/{user_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -135,11 +135,11 @@ impl ApiTeams for ApiV2 { team_id: &str, user_id: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v2/team/{team_id}/members/{user_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); self.call(req).await @@ -149,11 +149,11 @@ impl ApiTeams for ApiV2 { &self, team_id: &str, user_id: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v2/team/{team_id}/owner")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "user_id": user_id, })) @@ -161,10 +161,10 @@ impl ApiTeams for ApiV2 { self.call(req).await } - async fn get_user_notifications(&self, user_id: &str, pat: &str) -> ServiceResponse { + async fn get_user_notifications(&self, user_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v2/user/{user_id}/notifications")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -172,7 +172,7 @@ impl ApiTeams for ApiV2 { async fn get_user_notifications_deserialized_common( &self, user_id: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_user_notifications(user_id, pat).await; assert_status(&resp, StatusCode::OK); @@ -183,10 +183,10 @@ impl ApiTeams for ApiV2 { serde_json::from_value(value).unwrap() } - async fn mark_notification_read(&self, notification_id: &str, pat: &str) -> ServiceResponse { + async fn mark_notification_read(&self, notification_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v2/notification/{notification_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -197,11 +197,11 @@ impl ApiTeams for ApiV2 { user_id: &str, project_permissions: Option, organization_permissions: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::post() .uri(&format!("/v2/team/{team_id}/members")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!( { "user_id": user_id, "permissions" : project_permissions.map(|p| p.bits()).unwrap_or_default(), @@ -211,10 +211,10 @@ impl ApiTeams for ApiV2 { self.call(req).await } - async fn delete_notification(&self, notification_id: &str, pat: &str) -> ServiceResponse { + async fn delete_notification(&self, notification_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::delete() .uri(&format!("/v2/notification/{notification_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } diff --git a/tests/common/api_v2/version.rs b/tests/common/api_v2/version.rs index cbfcde0b..2ef54c5d 100644 --- a/tests/common/api_v2/version.rs +++ b/tests/common/api_v2/version.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use super::{request_data::get_public_version_creation_data, ApiV2}; +use super::{request_data::{get_public_version_creation_data, self}, ApiV2}; use crate::common::{ - api_common::{models::CommonVersion, Api, ApiVersion}, + api_common::{models::CommonVersion, Api, ApiVersion, AppendsOptionalPat}, asserts::assert_status, dummy_data::TestFile, }; -use actix_http::{header::AUTHORIZATION, StatusCode}; +use actix_http::StatusCode; use actix_web::{ dev::ServiceResponse, test::{self, TestRequest}, @@ -28,7 +28,7 @@ pub fn url_encode_json_serialized_vec(elements: &[String]) -> String { } impl ApiV2 { - pub async fn get_version_deserialized(&self, id: &str, pat: &str) -> LegacyVersion { + pub async fn get_version_deserialized(&self, id: &str, pat: Option<&str>) -> LegacyVersion { let resp = self.get_version(id, pat).await; assert_eq!(resp.status(), 200); test::read_body_json(resp).await @@ -38,7 +38,7 @@ impl ApiV2 { &self, hash: &str, algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> LegacyVersion { let resp = self.get_version_from_hash(hash, algorithm, pat).await; assert_eq!(resp.status(), 200); @@ -49,7 +49,7 @@ impl ApiV2 { &self, hashes: &[&str], algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> HashMap { let resp = self.get_versions_from_hashes(hashes, algorithm, pat).await; assert_eq!(resp.status(), 200); @@ -60,11 +60,11 @@ impl ApiV2 { &self, algorithm: &str, hashes: Vec, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::post() .uri("/v2/version_files/update_individual") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "algorithm": algorithm, "hashes": hashes @@ -77,7 +77,7 @@ impl ApiV2 { &self, algorithm: &str, hashes: Vec, - pat: &str, + pat: Option<&str>, ) -> HashMap { let resp = self.update_individual_files(algorithm, hashes, pat).await; assert_eq!(resp.status(), 200); @@ -94,7 +94,7 @@ impl ApiVersion for ApiV2 { version_jar: TestFile, ordering: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let creation_data = get_public_version_creation_data( project_id, @@ -107,7 +107,7 @@ impl ApiVersion for ApiV2 { // Add a project. let req = TestRequest::post() .uri("/v2/version") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_multipart(creation_data.segment_data) .to_request(); self.call(req).await @@ -120,7 +120,7 @@ impl ApiVersion for ApiV2 { version_jar: TestFile, ordering: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> CommonVersion { let resp = self .add_public_version( @@ -140,15 +140,15 @@ impl ApiVersion for ApiV2 { serde_json::from_value(value).unwrap() } - async fn get_version(&self, id: &str, pat: &str) -> ServiceResponse { + async fn get_version(&self, id: &str, pat: Option<&str>) -> ServiceResponse { let req = TestRequest::get() .uri(&format!("/v2/version/{id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } - async fn get_version_deserialized_common(&self, id: &str, pat: &str) -> CommonVersion { + async fn get_version_deserialized_common(&self, id: &str, pat: Option<&str>) -> CommonVersion { let resp = self.get_version(id, pat).await; assert_eq!(resp.status(), 200); // First, deserialize to the non-common format (to test the response is valid for this api version) @@ -162,11 +162,11 @@ impl ApiVersion for ApiV2 { &self, version_id: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v2/version/{version_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); @@ -177,11 +177,11 @@ impl ApiVersion for ApiV2 { &self, hash: &str, algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v2/version_file/{hash}?algorithm={algorithm}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -190,7 +190,7 @@ impl ApiVersion for ApiV2 { &self, hash: &str, algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> CommonVersion { let resp = self.get_version_from_hash(hash, algorithm, pat).await; assert_eq!(resp.status(), 200); @@ -205,11 +205,11 @@ impl ApiVersion for ApiV2 { &self, hashes: &[&str], algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = TestRequest::post() .uri("/v2/version_files") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "hashes": hashes, "algorithm": algorithm, @@ -222,7 +222,7 @@ impl ApiVersion for ApiV2 { &self, hashes: &[&str], algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> HashMap { let resp = self.get_versions_from_hashes(hashes, algorithm, pat).await; assert_eq!(resp.status(), 200); @@ -240,13 +240,13 @@ impl ApiVersion for ApiV2 { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::post() .uri(&format!( "/v2/version_file/{hash}/update?algorithm={algorithm}" )) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "loaders": loaders, "game_versions": game_versions, @@ -263,7 +263,7 @@ impl ApiVersion for ApiV2 { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> CommonVersion { let resp = self .get_update_from_hash(hash, algorithm, loaders, game_versions, version_types, pat) @@ -283,11 +283,11 @@ impl ApiVersion for ApiV2 { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::post() .uri("/v2/version_files/update") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "algorithm": algorithm, "hashes": hashes, @@ -306,7 +306,7 @@ impl ApiVersion for ApiV2 { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> HashMap { let resp = self .update_files( @@ -337,7 +337,7 @@ impl ApiVersion for ApiV2 { version_type: Option, limit: Option, offset: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let mut query_string = String::new(); if let Some(game_versions) = game_versions { @@ -372,7 +372,7 @@ impl ApiVersion for ApiV2 { "/v2/project/{project_id_slug}/version?{}", query_string.trim_start_matches('&') )) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -387,7 +387,7 @@ impl ApiVersion for ApiV2 { version_type: Option, limit: Option, offset: Option, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self .get_project_versions( @@ -413,7 +413,7 @@ impl ApiVersion for ApiV2 { &self, version_id: &str, ordering: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let request = test::TestRequest::patch() .uri(&format!("/v2/version/{version_id}")) @@ -422,16 +422,16 @@ impl ApiVersion for ApiV2 { "ordering": ordering } )) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(request).await } - async fn get_versions(&self, version_ids: Vec, pat: &str) -> ServiceResponse { + async fn get_versions(&self, version_ids: Vec, pat: Option<&str>) -> ServiceResponse { let ids = url_encode_json_serialized_vec(&version_ids); let request = test::TestRequest::get() .uri(&format!("/v2/versions?ids={}", ids)) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(request).await } @@ -439,7 +439,7 @@ impl ApiVersion for ApiV2 { async fn get_versions_deserialized_common( &self, version_ids: Vec, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_versions(version_ids, pat).await; assert_status(&resp, StatusCode::OK); @@ -449,4 +449,45 @@ impl ApiVersion for ApiV2 { let value = serde_json::to_value(v).unwrap(); serde_json::from_value(value).unwrap() } + + async fn upload_file_to_version( + &self, + version_id: &str, + file: &TestFile, + pat: Option<&str>, + ) -> ServiceResponse { + let m = request_data::get_public_creation_data_multipart(&json!({ + "file_parts": [file.filename()] + }), Some(&file)); + let request = test::TestRequest::post() + .uri(&format!("/v2/version/{version_id}/file")) + .append_pat(pat) + .set_multipart(m) + .to_request(); + self.call(request).await + } + + async fn remove_version( + &self, + version_id: &str, + pat: Option<&str>, + ) -> ServiceResponse { + let request = test::TestRequest::delete() + .uri(&format!("/v2/version/{version_id}")) + .append_pat(pat) + .to_request(); + self.call(request).await + } + + async fn remove_version_file( + &self, + hash: &str, + pat: Option<&str>, + ) -> ServiceResponse { + let request = test::TestRequest::delete() + .uri(&format!("/v2/version_file/{hash}")) + .append_pat(pat) + .to_request(); + self.call(request).await + } } diff --git a/tests/common/api_v3/oauth.rs b/tests/common/api_v3/oauth.rs index a1a93add..6c7120dd 100644 --- a/tests/common/api_v3/oauth.rs +++ b/tests/common/api_v3/oauth.rs @@ -10,7 +10,7 @@ use labrinth::auth::oauth::{ }; use reqwest::header::{AUTHORIZATION, LOCATION}; -use crate::common::{api_common::Api, asserts::assert_status}; +use crate::common::{api_common::{Api, AppendsOptionalPat}, asserts::assert_status}; use super::ApiV3; @@ -22,7 +22,7 @@ impl ApiV3 { scope: Option<&str>, redirect_uri: Option<&str>, state: Option<&str>, - user_pat: &str, + user_pat: Option<&str>, ) -> String { let auth_resp = self .oauth_authorize(client_id, scope, redirect_uri, state, user_pat) @@ -42,21 +42,21 @@ impl ApiV3 { scope: Option<&str>, redirect_uri: Option<&str>, state: Option<&str>, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let uri = generate_authorize_uri(client_id, scope, redirect_uri, state); let req = TestRequest::get() .uri(&uri) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(req).await } - pub async fn oauth_accept(&self, flow: &str, pat: &str) -> ServiceResponse { + pub async fn oauth_accept(&self, flow: &str, pat: Option<&str>) -> ServiceResponse { self.call( TestRequest::post() .uri("/_internal/oauth/accept") - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .set_json(RespondToOAuthClientScopes { flow: flow.to_string(), }) @@ -65,11 +65,11 @@ impl ApiV3 { .await } - pub async fn oauth_reject(&self, flow: &str, pat: &str) -> ServiceResponse { + pub async fn oauth_reject(&self, flow: &str, pat: Option<&str>) -> ServiceResponse { self.call( TestRequest::post() .uri("/_internal/oauth/reject") - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .set_json(RespondToOAuthClientScopes { flow: flow.to_string(), }) diff --git a/tests/common/api_v3/oauth_clients.rs b/tests/common/api_v3/oauth_clients.rs index dfad4fc2..a47a5662 100644 --- a/tests/common/api_v3/oauth_clients.rs +++ b/tests/common/api_v3/oauth_clients.rs @@ -10,10 +10,9 @@ use labrinth::{ }, routes::v3::oauth_clients::OAuthClientEdit, }; -use reqwest::header::AUTHORIZATION; use serde_json::json; -use crate::common::{api_common::Api, asserts::assert_status}; +use crate::common::{api_common::{Api, AppendsOptionalPat}, asserts::assert_status}; use super::ApiV3; @@ -23,12 +22,12 @@ impl ApiV3 { name: String, max_scopes: Scopes, redirect_uris: Vec, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let max_scopes = max_scopes.bits(); let req = TestRequest::post() .uri("/_internal/oauth/app") - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .set_json(json!({ "name": name, "max_scopes": max_scopes, @@ -39,10 +38,10 @@ impl ApiV3 { self.call(req).await } - pub async fn get_user_oauth_clients(&self, user_id: &str, pat: &str) -> Vec { + pub async fn get_user_oauth_clients(&self, user_id: &str, pat: Option<&str>) -> Vec { let req = TestRequest::get() .uri(&format!("/v3/user/{}/oauth_apps", user_id)) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); let resp = self.call(req).await; assert_status(&resp, StatusCode::OK); @@ -50,10 +49,10 @@ impl ApiV3 { test::read_body_json(resp).await } - pub async fn get_oauth_client(&self, client_id: String, pat: &str) -> ServiceResponse { + pub async fn get_oauth_client(&self, client_id: String, pat: Option<&str>) -> ServiceResponse { let req = TestRequest::get() .uri(&format!("/_internal/oauth/app/{}", client_id)) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(req).await @@ -63,7 +62,7 @@ impl ApiV3 { &self, client_id: &str, edit: OAuthClientEdit, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = TestRequest::patch() .uri(&format!( @@ -71,36 +70,36 @@ impl ApiV3 { urlencoding::encode(client_id) )) .set_json(edit) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(req).await } - pub async fn delete_oauth_client(&self, client_id: &str, pat: &str) -> ServiceResponse { + pub async fn delete_oauth_client(&self, client_id: &str, pat: Option<&str>) -> ServiceResponse { let req = TestRequest::delete() .uri(&format!("/_internal/oauth/app/{}", client_id)) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(req).await } - pub async fn revoke_oauth_authorization(&self, client_id: &str, pat: &str) -> ServiceResponse { + pub async fn revoke_oauth_authorization(&self, client_id: &str, pat: Option<&str>) -> ServiceResponse { let req = TestRequest::delete() .uri(&format!( "/_internal/oauth/authorizations?client_id={}", urlencoding::encode(client_id) )) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(req).await } - pub async fn get_user_oauth_authorizations(&self, pat: &str) -> Vec { + pub async fn get_user_oauth_authorizations(&self, pat: Option<&str>) -> Vec { let req = TestRequest::get() .uri("/_internal/oauth/authorizations") - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); let resp = self.call(req).await; assert_status(&resp, StatusCode::OK); diff --git a/tests/common/api_v3/organization.rs b/tests/common/api_v3/organization.rs index 268c9a1c..daf9977b 100644 --- a/tests/common/api_v3/organization.rs +++ b/tests/common/api_v3/organization.rs @@ -6,7 +6,7 @@ use bytes::Bytes; use labrinth::models::{organizations::Organization, v3::projects::Project}; use serde_json::json; -use crate::common::api_common::{request_data::ImageData, Api}; +use crate::common::api_common::{request_data::ImageData, Api, AppendsOptionalPat}; use super::ApiV3; @@ -15,11 +15,11 @@ impl ApiV3 { &self, organization_title: &str, description: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::post() .uri("/v3/organization") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "name": organization_title, "description": description, @@ -28,10 +28,10 @@ impl ApiV3 { self.call(req).await } - pub async fn get_organization(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + pub async fn get_organization(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = TestRequest::get() .uri(&format!("/v3/organization/{id_or_title}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -39,17 +39,17 @@ impl ApiV3 { pub async fn get_organization_deserialized( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Organization { let resp = self.get_organization(id_or_title, pat).await; assert_eq!(resp.status(), 200); test::read_body_json(resp).await } - pub async fn get_organization_projects(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + pub async fn get_organization_projects(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v3/organization/{id_or_title}/projects")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -57,7 +57,7 @@ impl ApiV3 { pub async fn get_organization_projects_deserialized( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_organization_projects(id_or_title, pat).await; assert_eq!(resp.status(), 200); @@ -68,11 +68,11 @@ impl ApiV3 { &self, id_or_title: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v3/organization/{id_or_title}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); @@ -83,7 +83,7 @@ impl ApiV3 { &self, id_or_title: &str, icon: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { if let Some(icon) = icon { // If an icon is provided, upload it @@ -92,7 +92,7 @@ impl ApiV3 { "/v3/organization/{id_or_title}/icon?ext={ext}", ext = icon.extension )) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_payload(Bytes::from(icon.icon)) .to_request(); @@ -101,17 +101,17 @@ impl ApiV3 { // If no icon is provided, delete the icon let req = test::TestRequest::delete() .uri(&format!("/v3/organization/{id_or_title}/icon")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } } - pub async fn delete_organization(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + pub async fn delete_organization(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::delete() .uri(&format!("/v3/organization/{id_or_title}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await @@ -121,11 +121,11 @@ impl ApiV3 { &self, id_or_title: &str, project_id_or_slug: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::post() .uri(&format!("/v3/organization/{id_or_title}/projects")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "project_id": project_id_or_slug, })) @@ -138,13 +138,13 @@ impl ApiV3 { &self, id_or_title: &str, project_id_or_slug: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::delete() .uri(&format!( "/v3/organization/{id_or_title}/projects/{project_id_or_slug}" )) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await diff --git a/tests/common/api_v3/project.rs b/tests/common/api_v3/project.rs index 41734d7f..e5e667a7 100644 --- a/tests/common/api_v3/project.rs +++ b/tests/common/api_v3/project.rs @@ -17,9 +17,9 @@ use serde_json::json; use crate::common::{ api_common::{ - models::{CommonImageData, CommonProject, CommonVersion}, - request_data::ProjectCreationRequestData, - Api, ApiProject, + models::{CommonProject, CommonVersion}, + request_data::{ProjectCreationRequestData, ImageData}, + Api, ApiProject, AppendsOptionalPat, }, asserts::assert_status, database::MOD_USER_PAT, @@ -38,7 +38,7 @@ impl ApiProject for ApiV3 { slug: &str, version_jar: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> (CommonProject, Vec) { let creation_data = get_public_project_creation_data(slug, version_jar, modify_json); @@ -50,7 +50,7 @@ impl ApiProject for ApiV3 { // Approve as a moderator. let req = TestRequest::patch() .uri(&format!("/v3/project/{}", slug)) - .append_header(("Authorization", MOD_USER_PAT)) + .append_pat(MOD_USER_PAT) .set_json(json!( { "status": "approved" @@ -66,7 +66,7 @@ impl ApiProject for ApiV3 { // Get project's versions let req = TestRequest::get() .uri(&format!("/v3/project/{}/version", slug)) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); let resp = self.call(req).await; let versions: Vec = test::read_body_json(resp).await; @@ -85,35 +85,34 @@ impl ApiProject for ApiV3 { async fn create_project( &self, creation_data: ProjectCreationRequestData, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = TestRequest::post() .uri("/v3/project") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_multipart(creation_data.segment_data) .to_request(); self.call(req).await } - async fn remove_project(&self, project_slug_or_id: &str, pat: &str) -> ServiceResponse { + async fn remove_project(&self, project_slug_or_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::delete() .uri(&format!("/v3/project/{project_slug_or_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); let resp = self.call(req).await; - assert_eq!(resp.status(), 204); resp } - async fn get_project(&self, id_or_slug: &str, pat: &str) -> ServiceResponse { + async fn get_project(&self, id_or_slug: &str, pat: Option<&str>) -> ServiceResponse { let req = TestRequest::get() .uri(&format!("/v3/project/{id_or_slug}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } - async fn get_project_deserialized_common(&self, id_or_slug: &str, pat: &str) -> CommonProject { + async fn get_project_deserialized_common(&self, id_or_slug: &str, pat: Option<&str>) -> CommonProject { let resp = self.get_project(id_or_slug, pat).await; assert_eq!(resp.status(), 200); // First, deserialize to the non-common format (to test the response is valid for this api version) @@ -123,10 +122,10 @@ impl ApiProject for ApiV3 { serde_json::from_value(value).unwrap() } - async fn get_user_projects(&self, user_id_or_username: &str, pat: &str) -> ServiceResponse { + async fn get_user_projects(&self, user_id_or_username: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v3/user/{}/projects", user_id_or_username)) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -134,7 +133,7 @@ impl ApiProject for ApiV3 { async fn get_user_projects_deserialized_common( &self, user_id_or_username: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_user_projects(user_id_or_username, pat).await; assert_eq!(resp.status(), 200); @@ -149,11 +148,11 @@ impl ApiProject for ApiV3 { &self, id_or_slug: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v3/project/{id_or_slug}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); @@ -164,7 +163,7 @@ impl ApiProject for ApiV3 { &self, ids_or_slugs: &[&str], patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let projects_str = ids_or_slugs .iter() @@ -176,7 +175,7 @@ impl ApiProject for ApiV3 { "/v3/projects?ids={encoded}", encoded = urlencoding::encode(&format!("[{projects_str}]")) )) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); @@ -186,8 +185,8 @@ impl ApiProject for ApiV3 { async fn edit_project_icon( &self, id_or_slug: &str, - icon: Option, - pat: &str, + icon: Option, + pat: Option<&str>, ) -> ServiceResponse { if let Some(icon) = icon { // If an icon is provided, upload it @@ -196,7 +195,7 @@ impl ApiProject for ApiV3 { "/v3/project/{id_or_slug}/icon?ext={ext}", ext = icon.extension )) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_payload(Bytes::from(icon.icon)) .to_request(); @@ -205,16 +204,116 @@ impl ApiProject for ApiV3 { // If no icon is provided, delete the icon let req = test::TestRequest::delete() .uri(&format!("/v3/project/{id_or_slug}/icon")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } } + + async fn schedule_project( + &self, + id_or_slug: &str, + requested_status: &str, + date: DateTime, + pat: Option<&str>, + ) -> ServiceResponse { + let req = test::TestRequest::post() + .uri(&format!("/v3/version/{id_or_slug}/schedule")) + .set_json(json!( + { + "requested_status": requested_status, + "time": date, + } + )) + .append_pat(pat) + .to_request(); + + self.call(req).await + } + + async fn add_gallery_item( + &self, + id_or_slug: &str, + image: ImageData, + featured: bool, + title: Option, + description: Option, + ordering: Option, + pat: Option<&str>, + ) -> ServiceResponse { + let mut url = format!( + "/v3/project/{id_or_slug}/gallery?ext={ext}&featured={featured}", + ext = image.extension, + featured = featured + ); + if let Some(title) = title { + url.push_str(&format!("&title={}", title)); + } + if let Some(description) = description { + url.push_str(&format!("&description={}", description)); + } + if let Some(ordering) = ordering { + url.push_str(&format!("&ordering={}", ordering)); + } + + let req = test::TestRequest::post() + .uri(&url) + .append_pat(pat) + .set_payload(Bytes::from(image.icon)) + .to_request(); + + self.call(req).await + } + + async fn edit_gallery_item( + &self, + id_or_slug: &str, + image_url: &str, + patch: HashMap, + pat: Option<&str>, + ) -> ServiceResponse { + let mut url = format!( + "/v3/project/{id_or_slug}/gallery?url={image_url}", + image_url = urlencoding::encode(image_url) + ); + + for (key, value) in patch { + url.push_str(&format!("&{key}={value}", key = key, value = urlencoding::encode(&value))); + } + + let req = test::TestRequest::patch() + .uri(&url) + .append_pat(pat) + .to_request(); + + let t = self.call(req).await; + println!("Status: {}", t.status()); + println!("respone Body: {:?}", t.response().body()); + t + } + + async fn remove_gallery_item( + &self, + id_or_slug: &str, + url: &str, + pat: Option<&str>, + ) -> ServiceResponse { + let req = test::TestRequest::delete() + .uri(&format!( + "/v3/project/{id_or_slug}/gallery?url={url}", + url = url + )) + .append_pat(pat) + .to_request(); + + self.call(req).await + } + } impl ApiV3 { - pub async fn get_project_deserialized(&self, id_or_slug: &str, pat: &str) -> Project { + pub async fn get_project_deserialized(&self, id_or_slug: &str, pat: Option<&str>) -> Project { let resp = self.get_project(id_or_slug, pat).await; assert_eq!(resp.status(), 200); test::read_body_json(resp).await @@ -224,7 +323,7 @@ impl ApiV3 { &self, query: Option<&str>, facets: Option, - pat: &str, + pat: Option<&str>, ) -> ReturnSearchResults { let query_field = if let Some(query) = query { format!("&query={}", urlencoding::encode(query)) @@ -240,7 +339,7 @@ impl ApiV3 { let req = test::TestRequest::get() .uri(&format!("/v3/search?{}{}", query_field, facets_field)) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); let resp = self.call(req).await; let status = resp.status(); @@ -255,7 +354,7 @@ impl ApiV3 { start_date: Option>, end_date: Option>, resolution_minutes: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let pv_string = if ids_are_version_ids { let version_string: String = serde_json::to_string(&id_or_slugs).unwrap(); @@ -286,7 +385,7 @@ impl ApiV3 { let req = test::TestRequest::get() .uri(&format!("/v3/analytics/revenue?{pv_string}{extra_args}",)) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await @@ -299,7 +398,7 @@ impl ApiV3 { start_date: Option>, end_date: Option>, resolution_minutes: Option, - pat: &str, + pat: Option<&str>, ) -> HashMap> { let resp = self .get_analytics_revenue( diff --git a/tests/common/api_v3/request_data.rs b/tests/common/api_v3/request_data.rs index 4acc9bfe..95b3abec 100644 --- a/tests/common/api_v3/request_data.rs +++ b/tests/common/api_v3/request_data.rs @@ -2,8 +2,8 @@ use serde_json::json; use crate::common::{ - api_common::request_data::{ImageData, ProjectCreationRequestData, VersionCreationRequestData}, - dummy_data::{DummyImage, TestFile}, + api_common::request_data::{ProjectCreationRequestData, VersionCreationRequestData}, + dummy_data::TestFile, }; use labrinth::{ models::projects::ProjectId, @@ -133,11 +133,3 @@ pub fn get_public_creation_data_multipart( vec![json_segment] } } - -pub fn get_icon_data(dummy_icon: DummyImage) -> ImageData { - ImageData { - filename: dummy_icon.filename(), - extension: dummy_icon.extension(), - icon: dummy_icon.bytes(), - } -} diff --git a/tests/common/api_v3/tags.rs b/tests/common/api_v3/tags.rs index f513e8ea..9539dab2 100644 --- a/tests/common/api_v3/tags.rs +++ b/tests/common/api_v3/tags.rs @@ -11,7 +11,7 @@ use labrinth::{ use crate::common::{ api_common::{ models::{CommonCategoryData, CommonLoaderData}, - Api, ApiTags, + Api, ApiTags, AppendsOptionalPat, }, database::ADMIN_USER_PAT, }; @@ -23,7 +23,7 @@ impl ApiTags for ApiV3 { async fn get_loaders(&self) -> ServiceResponse { let req = TestRequest::get() .uri("/v3/tag/loader") - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); self.call(req).await } @@ -41,7 +41,7 @@ impl ApiTags for ApiV3 { async fn get_categories(&self) -> ServiceResponse { let req = TestRequest::get() .uri("/v3/tag/category") - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); self.call(req).await } @@ -67,7 +67,7 @@ impl ApiV3 { pub async fn get_loader_field_variants(&self, loader_field: &str) -> ServiceResponse { let req = TestRequest::get() .uri(&format!("/v3/loader_field?loader_field={}", loader_field)) - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); self.call(req).await } @@ -85,7 +85,7 @@ impl ApiV3 { async fn get_games(&self) -> ServiceResponse { let req = TestRequest::get() .uri("/v3/games") - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); self.call(req).await } diff --git a/tests/common/api_v3/team.rs b/tests/common/api_v3/team.rs index bb29e932..b0937ea4 100644 --- a/tests/common/api_v3/team.rs +++ b/tests/common/api_v3/team.rs @@ -10,7 +10,7 @@ use serde_json::json; use crate::common::{ api_common::{ models::{CommonNotification, CommonTeamMember}, - Api, ApiTeams, + Api, ApiTeams, AppendsOptionalPat, }, asserts::assert_status, }; @@ -21,14 +21,14 @@ impl ApiV3 { pub async fn get_organization_members_deserialized( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_organization_members(id_or_title, pat).await; assert_eq!(resp.status(), 200); test::read_body_json(resp).await } - pub async fn get_team_members_deserialized(&self, team_id: &str, pat: &str) -> Vec { + pub async fn get_team_members_deserialized(&self, team_id: &str, pat: Option<&str>) -> Vec { let resp = self.get_team_members(team_id, pat).await; assert_eq!(resp.status(), 200); test::read_body_json(resp).await @@ -37,10 +37,10 @@ impl ApiV3 { #[async_trait(?Send)] impl ApiTeams for ApiV3 { - async fn get_team_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + async fn get_team_members(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v3/team/{id_or_title}/members")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -48,7 +48,7 @@ impl ApiTeams for ApiV3 { async fn get_team_members_deserialized_common( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_team_members(id_or_title, pat).await; assert_eq!(resp.status(), 200); @@ -59,10 +59,10 @@ impl ApiTeams for ApiV3 { serde_json::from_value(value).unwrap() } - async fn get_project_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + async fn get_project_members(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v3/project/{id_or_title}/members")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -70,7 +70,7 @@ impl ApiTeams for ApiV3 { async fn get_project_members_deserialized_common( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_project_members(id_or_title, pat).await; assert_eq!(resp.status(), 200); @@ -81,10 +81,10 @@ impl ApiTeams for ApiV3 { serde_json::from_value(value).unwrap() } - async fn get_organization_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse { + async fn get_organization_members(&self, id_or_title: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v3/organization/{id_or_title}/members")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -92,7 +92,7 @@ impl ApiTeams for ApiV3 { async fn get_organization_members_deserialized_common( &self, id_or_title: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_organization_members(id_or_title, pat).await; assert_eq!(resp.status(), 200); @@ -103,18 +103,18 @@ impl ApiTeams for ApiV3 { serde_json::from_value(value).unwrap() } - async fn join_team(&self, team_id: &str, pat: &str) -> ServiceResponse { + async fn join_team(&self, team_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::post() .uri(&format!("/v3/team/{team_id}/join")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } - async fn remove_from_team(&self, team_id: &str, user_id: &str, pat: &str) -> ServiceResponse { + async fn remove_from_team(&self, team_id: &str, user_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::delete() .uri(&format!("/v3/team/{team_id}/members/{user_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -124,11 +124,11 @@ impl ApiTeams for ApiV3 { team_id: &str, user_id: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v3/team/{team_id}/members/{user_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); self.call(req).await @@ -138,11 +138,11 @@ impl ApiTeams for ApiV3 { &self, team_id: &str, user_id: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v3/team/{team_id}/owner")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "user_id": user_id, })) @@ -150,10 +150,10 @@ impl ApiTeams for ApiV3 { self.call(req).await } - async fn get_user_notifications(&self, user_id: &str, pat: &str) -> ServiceResponse { + async fn get_user_notifications(&self, user_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v3/user/{user_id}/notifications")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -161,7 +161,7 @@ impl ApiTeams for ApiV3 { async fn get_user_notifications_deserialized_common( &self, user_id: &str, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_user_notifications(user_id, pat).await; assert_status(&resp, StatusCode::OK); @@ -172,10 +172,10 @@ impl ApiTeams for ApiV3 { serde_json::from_value(value).unwrap() } - async fn mark_notification_read(&self, notification_id: &str, pat: &str) -> ServiceResponse { + async fn mark_notification_read(&self, notification_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v3/notification/{notification_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -185,11 +185,11 @@ impl ApiTeams for ApiV3 { user_id: &str, project_permissions: Option, organization_permissions: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::post() .uri(&format!("/v3/team/{team_id}/members")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!( { "user_id": user_id, "permissions" : project_permissions.map(|p| p.bits()).unwrap_or_default(), @@ -199,10 +199,10 @@ impl ApiTeams for ApiV3 { self.call(req).await } - async fn delete_notification(&self, notification_id: &str, pat: &str) -> ServiceResponse { + async fn delete_notification(&self, notification_id: &str, pat: Option<&str>) -> ServiceResponse { let req = test::TestRequest::delete() .uri(&format!("/v3/notification/{notification_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } diff --git a/tests/common/api_v3/version.rs b/tests/common/api_v3/version.rs index 43226684..eb2146af 100644 --- a/tests/common/api_v3/version.rs +++ b/tests/common/api_v3/version.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use super::{request_data::get_public_version_creation_data, ApiV3}; +use super::{request_data::{get_public_version_creation_data, self}, ApiV3}; use crate::common::{ - api_common::{models::CommonVersion, Api, ApiVersion}, + api_common::{models::CommonVersion, Api, ApiVersion, AppendsOptionalPat}, asserts::assert_status, dummy_data::TestFile, }; -use actix_http::{header::AUTHORIZATION, StatusCode}; +use actix_http::StatusCode; use actix_web::{ dev::ServiceResponse, test::{self, TestRequest}, @@ -35,7 +35,7 @@ impl ApiV3 { version_jar: TestFile, ordering: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> Version { let resp = self .add_public_version( @@ -55,7 +55,7 @@ impl ApiV3 { test::read_body_json(version).await } - pub async fn get_version_deserialized(&self, id: &str, pat: &str) -> Version { + pub async fn get_version_deserialized(&self, id: &str, pat: Option<&str>) -> Version { let resp = self.get_version(id, pat).await; assert_eq!(resp.status(), 200); test::read_body_json(resp).await @@ -65,11 +65,11 @@ impl ApiV3 { &self, algorithm: &str, hashes: Vec, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::post() .uri("/v3/version_files/update_individual") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "algorithm": algorithm, "hashes": hashes @@ -82,7 +82,7 @@ impl ApiV3 { &self, algorithm: &str, hashes: Vec, - pat: &str, + pat: Option<&str>, ) -> HashMap { let resp = self.update_individual_files(algorithm, hashes, pat).await; assert_eq!(resp.status(), 200); @@ -99,7 +99,7 @@ impl ApiVersion for ApiV3 { version_jar: TestFile, ordering: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let creation_data = get_public_version_creation_data( project_id, @@ -112,7 +112,7 @@ impl ApiVersion for ApiV3 { // Add a versiom. let req = TestRequest::post() .uri("/v3/version") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_multipart(creation_data.segment_data) .to_request(); self.call(req).await @@ -125,7 +125,7 @@ impl ApiVersion for ApiV3 { version_jar: TestFile, ordering: Option, modify_json: Option, - pat: &str, + pat: Option<&str>, ) -> CommonVersion { let resp = self .add_public_version( @@ -145,15 +145,15 @@ impl ApiVersion for ApiV3 { serde_json::from_value(value).unwrap() } - async fn get_version(&self, id: &str, pat: &str) -> ServiceResponse { + async fn get_version(&self, id: &str, pat: Option<&str>) -> ServiceResponse { let req = TestRequest::get() .uri(&format!("/v3/version/{id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } - async fn get_version_deserialized_common(&self, id: &str, pat: &str) -> CommonVersion { + async fn get_version_deserialized_common(&self, id: &str, pat: Option<&str>) -> CommonVersion { let resp = self.get_version(id, pat).await; assert_eq!(resp.status(), 200); // First, deserialize to the non-common format (to test the response is valid for this api version) @@ -167,11 +167,11 @@ impl ApiVersion for ApiV3 { &self, version_id: &str, patch: serde_json::Value, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::patch() .uri(&format!("/v3/version/{version_id}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(patch) .to_request(); @@ -182,11 +182,11 @@ impl ApiVersion for ApiV3 { &self, hash: &str, algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = test::TestRequest::get() .uri(&format!("/v3/version_file/{hash}?algorithm={algorithm}")) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -195,7 +195,7 @@ impl ApiVersion for ApiV3 { &self, hash: &str, algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> CommonVersion { let resp = self.get_version_from_hash(hash, algorithm, pat).await; assert_eq!(resp.status(), 200); @@ -210,11 +210,11 @@ impl ApiVersion for ApiV3 { &self, hashes: &[&str], algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let req = TestRequest::post() .uri("/v3/version_files") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json!({ "hashes": hashes, "algorithm": algorithm, @@ -227,7 +227,7 @@ impl ApiVersion for ApiV3 { &self, hashes: &[&str], algorithm: &str, - pat: &str, + pat: Option<&str>, ) -> HashMap { let resp = self.get_versions_from_hashes(hashes, algorithm, pat).await; assert_eq!(resp.status(), 200); @@ -245,7 +245,7 @@ impl ApiVersion for ApiV3 { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let mut json = json!({}); if let Some(loaders) = loaders { @@ -264,7 +264,7 @@ impl ApiVersion for ApiV3 { .uri(&format!( "/v3/version_file/{hash}/update?algorithm={algorithm}" )) - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json) .to_request(); self.call(req).await @@ -277,7 +277,7 @@ impl ApiVersion for ApiV3 { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> CommonVersion { let resp = self .get_update_from_hash(hash, algorithm, loaders, game_versions, version_types, pat) @@ -297,7 +297,7 @@ impl ApiVersion for ApiV3 { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let mut json = json!({ "algorithm": algorithm, @@ -317,7 +317,7 @@ impl ApiVersion for ApiV3 { let req = test::TestRequest::post() .uri("/v3/version_files/update") - .append_header(("Authorization", pat)) + .append_pat(pat) .set_json(json) .to_request(); self.call(req).await @@ -330,7 +330,7 @@ impl ApiVersion for ApiV3 { loaders: Option>, game_versions: Option>, version_types: Option>, - pat: &str, + pat: Option<&str>, ) -> HashMap { let resp = self .update_files( @@ -361,7 +361,7 @@ impl ApiVersion for ApiV3 { version_type: Option, limit: Option, offset: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let mut query_string = String::new(); if let Some(game_versions) = game_versions { @@ -396,7 +396,7 @@ impl ApiVersion for ApiV3 { "/v3/project/{project_id_slug}/version?{}", query_string.trim_start_matches('&') )) - .append_header(("Authorization", pat)) + .append_pat(pat) .to_request(); self.call(req).await } @@ -411,7 +411,7 @@ impl ApiVersion for ApiV3 { version_type: Option, limit: Option, offset: Option, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self .get_project_versions( @@ -438,7 +438,7 @@ impl ApiVersion for ApiV3 { &self, version_id: &str, ordering: Option, - pat: &str, + pat: Option<&str>, ) -> ServiceResponse { let request = test::TestRequest::patch() .uri(&format!("/v3/version/{version_id}")) @@ -447,16 +447,16 @@ impl ApiVersion for ApiV3 { "ordering": ordering } )) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(request).await } - async fn get_versions(&self, version_ids: Vec, pat: &str) -> ServiceResponse { + async fn get_versions(&self, version_ids: Vec, pat: Option<&str>) -> ServiceResponse { let ids = url_encode_json_serialized_vec(&version_ids); let request = test::TestRequest::get() .uri(&format!("/v3/versions?ids={}", ids)) - .append_header((AUTHORIZATION, pat)) + .append_pat(pat) .to_request(); self.call(request).await } @@ -464,7 +464,7 @@ impl ApiVersion for ApiV3 { async fn get_versions_deserialized_common( &self, version_ids: Vec, - pat: &str, + pat: Option<&str>, ) -> Vec { let resp = self.get_versions(version_ids, pat).await; assert_status(&resp, StatusCode::OK); @@ -474,4 +474,42 @@ impl ApiVersion for ApiV3 { let value = serde_json::to_value(v).unwrap(); serde_json::from_value(value).unwrap() } + + async fn upload_file_to_version( + &self, + version_id: &str, + file: &TestFile, + pat: Option<&str>, + ) -> ServiceResponse { + let m = request_data::get_public_creation_data_multipart(&json!({ + "file_parts": [file.filename()] + }), Some(&file)); + let request = test::TestRequest::post() + .uri(&format!("/v3/version/{version_id}/file", version_id = version_id)) + .append_pat(pat) + .set_multipart(m) + .to_request(); + self.call(request).await + } + + async fn remove_version(&self, version_id: &str, pat: Option<&str>) -> ServiceResponse { + let request = test::TestRequest::delete() + .uri(&format!("/v3/version/{version_id}", version_id = version_id)) + .append_pat(pat) + .to_request(); + self.call(request).await + } + + async fn remove_version_file( + &self, + hash: &str, + pat: Option<&str>, + ) -> ServiceResponse { + let request = test::TestRequest::delete() + .uri(&format!("/v3/version_file/{hash}")) + .append_pat(pat) + .to_request(); + self.call(request).await + } + } diff --git a/tests/common/database.rs b/tests/common/database.rs index d3092929..7e0b8218 100644 --- a/tests/common/database.rs +++ b/tests/common/database.rs @@ -27,11 +27,11 @@ pub const FRIEND_USER_ID_PARSED: i64 = 4; pub const ENEMY_USER_ID_PARSED: i64 = 5; // These are full-scoped PATs- as if the user was logged in (including illegal scopes). -pub const ADMIN_USER_PAT: &str = "mrp_patadmin"; -pub const MOD_USER_PAT: &str = "mrp_patmoderator"; -pub const USER_USER_PAT: &str = "mrp_patuser"; -pub const FRIEND_USER_PAT: &str = "mrp_patfriend"; -pub const ENEMY_USER_PAT: &str = "mrp_patenemy"; +pub const ADMIN_USER_PAT: Option<&str> = Some("mrp_patadmin"); +pub const MOD_USER_PAT: Option<&str> = Some("mrp_patmoderator"); +pub const USER_USER_PAT: Option<&str> = Some("mrp_patuser"); +pub const FRIEND_USER_PAT: Option<&str> = Some("mrp_patfriend"); +pub const ENEMY_USER_PAT: Option<&str> = Some("mrp_patenemy"); const TEMPLATE_DATABASE_NAME: &str = "labrinth_tests_template"; diff --git a/tests/common/dummy_data.rs b/tests/common/dummy_data.rs index 44d9e238..5502ba81 100644 --- a/tests/common/dummy_data.rs +++ b/tests/common/dummy_data.rs @@ -16,7 +16,7 @@ use zip::{write::FileOptions, CompressionMethod, ZipWriter}; use crate::common::{api_common::Api, database::USER_USER_PAT}; use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData}; -use super::{api_common::ApiProject, api_v3::ApiV3, database::TemporaryDatabase}; +use super::{api_common::{ApiProject, AppendsOptionalPat, request_data::ImageData}, api_v3::ApiV3, database::TemporaryDatabase}; use super::{asserts::assert_status, database::USER_USER_ID, get_json_val_str}; @@ -36,9 +36,11 @@ pub const DUMMY_CATEGORIES: &[&str] = &[ pub const DUMMY_OAUTH_CLIENT_ALPHA_SECRET: &str = "abcdefghijklmnopqrstuvwxyz"; #[allow(dead_code)] +#[derive(Clone)] pub enum TestFile { DummyProjectAlpha, DummyProjectBeta, + BasicZip, BasicMod, BasicModDifferent, // Randomly generates a valid .jar with a random hash. @@ -380,7 +382,7 @@ pub async fn add_project_beta(api: &ApiV3) -> (Project, Version) { // Add a project. let req = TestRequest::post() .uri("/v3/project") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_multipart(vec![json_segment.clone(), file_segment.clone()]) .to_request(); let resp = api.call(req).await; @@ -393,7 +395,7 @@ pub async fn add_organization_zeta(api: &ApiV3) -> Organization { // Add an organzation. let req = TestRequest::post() .uri("/v3/organization") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "name": "zeta", "description": "A dummy organization for testing with." @@ -410,7 +412,7 @@ pub async fn get_project_alpha(api: &ApiV3) -> (Project, Version) { // Get project let req = TestRequest::get() .uri("/v3/project/alpha") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = api.call(req).await; let project: Project = test::read_body_json(resp).await; @@ -418,7 +420,7 @@ pub async fn get_project_alpha(api: &ApiV3) -> (Project, Version) { // Get project's versions let req = TestRequest::get() .uri("/v3/project/alpha/version") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = api.call(req).await; let versions: Vec = test::read_body_json(resp).await; @@ -431,7 +433,7 @@ pub async fn get_project_beta(api: &ApiV3) -> (Project, Version) { // Get project let req = TestRequest::get() .uri("/v3/project/beta") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = api.call(req).await; assert_status(&resp, StatusCode::OK); @@ -441,7 +443,7 @@ pub async fn get_project_beta(api: &ApiV3) -> (Project, Version) { // Get project's versions let req = TestRequest::get() .uri("/v3/project/beta/version") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = api.call(req).await; assert_status(&resp, StatusCode::OK); @@ -455,7 +457,7 @@ pub async fn get_organization_zeta(api: &ApiV3) -> Organization { // Get organization let req = TestRequest::get() .uri("/v3/organization/zeta") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = api.call(req).await; let organization: Organization = test::read_body_json(resp).await; @@ -475,6 +477,7 @@ impl TestFile { match self { TestFile::DummyProjectAlpha => "dummy-project-alpha.jar", TestFile::DummyProjectBeta => "dummy-project-beta.jar", + TestFile::BasicZip => "simple-zip.zip", TestFile::BasicMod => "basic-mod.jar", TestFile::BasicModDifferent => "basic-mod-different.jar", TestFile::BasicModRandom { filename, .. } => filename, @@ -492,6 +495,7 @@ impl TestFile { include_bytes!("../../tests/files/dummy-project-beta.jar").to_vec() } TestFile::BasicMod => include_bytes!("../../tests/files/basic-mod.jar").to_vec(), + TestFile::BasicZip => include_bytes!("../../tests/files/simple-zip.zip").to_vec(), TestFile::BasicModDifferent => { include_bytes!("../../tests/files/basic-mod-different.jar").to_vec() } @@ -508,10 +512,27 @@ impl TestFile { TestFile::BasicModDifferent => "mod", TestFile::BasicModRandom { .. } => "mod", + TestFile::BasicZip => "resourcepack", + TestFile::BasicModpackRandom { .. } => "modpack", } .to_string() } + + pub fn content_type(&self) -> Option { + match self { + TestFile::DummyProjectAlpha => Some("application/java-archive"), + TestFile::DummyProjectBeta => Some("application/java-archive"), + TestFile::BasicMod => Some("application/java-archive"), + TestFile::BasicModDifferent => Some("application/java-archive"), + TestFile::BasicModRandom { .. } => Some("application/java-archive"), + + TestFile::BasicZip => Some("application/zip"), + + TestFile::BasicModpackRandom { .. } => Some("application/x-modrinth-modpack+zip"), + }.map(|s| s.to_string()) + } + } impl DummyImage { @@ -534,4 +555,13 @@ impl DummyImage { DummyImage::SmallIcon => include_bytes!("../../tests/files/200x200.png").to_vec(), } } + + pub fn get_icon_data(&self) -> ImageData { + ImageData { + filename: self.filename(), + extension: self.extension(), + icon: self.bytes(), + } + } } + diff --git a/tests/common/environment.rs b/tests/common/environment.rs index 13802b04..a3b09e63 100644 --- a/tests/common/environment.rs +++ b/tests/common/environment.rs @@ -122,7 +122,7 @@ impl TestEnvironment { pub async fn assert_read_notifications_status( &self, user_id: &str, - pat: &str, + pat: Option<&str>, status_code: StatusCode, ) { let resp = self.api.get_user_notifications(user_id, pat).await; @@ -133,7 +133,7 @@ impl TestEnvironment { pub async fn assert_read_user_projects_status( &self, user_id: &str, - pat: &str, + pat: Option<&str>, status_code: StatusCode, ) { let resp = self.api.get_user_projects(user_id, pat).await; diff --git a/tests/common/permissions.rs b/tests/common/permissions.rs index 4b55b8b9..337186e4 100644 --- a/tests/common/permissions.rs +++ b/tests/common/permissions.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] use actix_http::StatusCode; -use actix_web::test::{self, TestRequest}; +use actix_web::{test, dev::ServiceResponse}; +use futures::Future; use itertools::Itertools; use labrinth::models::teams::{OrganizationPermissions, ProjectPermissions}; use serde_json::json; @@ -32,7 +33,7 @@ pub struct PermissionsTest<'a, A: Api> { // User ID to use for the test user, and their PAT user_id: &'a str, - user_pat: &'a str, + user_pat: Option<&'a str>, // Whether or not the user ID should be removed from the project/organization team after the test // (This is mostly reelvant if you are also using an existing project/organization, and want to do @@ -58,17 +59,17 @@ pub struct PermissionsTest<'a, A: Api> { failure_json_check: Option, success_json_check: Option, } - -pub struct PermissionsTestContext<'a> { - // pub test_env: &'a TestEnvironment, - pub user_id: &'a str, - pub user_pat: &'a str, - pub project_id: Option<&'a str>, - pub team_id: Option<&'a str>, - pub organization_id: Option<&'a str>, - pub organization_team_id: Option<&'a str>, +#[derive(Clone, Debug)] +pub struct PermissionsTestContext{ + pub test_pat : Option, + pub user_id: String, + pub project_id: Option, + pub team_id: Option, + pub organization_id: Option, + pub organization_team_id: Option, } + impl<'a, A: Api> PermissionsTest<'a, A> { pub fn new(test_env: &'a TestEnvironment) -> Self { Self { @@ -118,7 +119,7 @@ impl<'a, A: Api> PermissionsTest<'a, A> { // Set the user ID to use // (eg: a moderator, or friend) // remove_user: Whether or not the user ID should be removed from the project/organization team after the test - pub fn with_user(mut self, user_id: &'a str, user_pat: &'a str, remove_user: bool) -> Self { + pub fn with_user(mut self, user_id: &'a str, user_pat: Option<&'a str>, remove_user: bool) -> Self { self.user_id = user_id; self.user_pat = user_pat; self.remove_user = remove_user; @@ -149,21 +150,22 @@ impl<'a, A: Api> PermissionsTest<'a, A> { self } - pub async fn simple_project_permissions_test( + pub async fn simple_project_permissions_test( &self, success_permissions: ProjectPermissions, req_gen: T, ) -> Result<(), String> where - T: Fn(&PermissionsTestContext) -> TestRequest, + T: Fn(PermissionsTestContext) -> Fut, + Fut: Future, // Ensure Fut is Send and 'static { - let test_env = self.test_env; + let test_env = self.test_env; let failure_project_permissions = self .failure_project_permissions .unwrap_or(ProjectPermissions::all() ^ success_permissions); let test_context = PermissionsTestContext { - user_id: self.user_id, - user_pat: self.user_pat, + test_pat: None, + user_id: self.user_id.to_string(), project_id: None, team_id: None, organization_id: None, @@ -190,14 +192,11 @@ impl<'a, A: Api> PermissionsTest<'a, A> { .await; // Failure test- not logged in - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Failure permissions test failed. Expected failure codes {} got {}", @@ -215,15 +214,13 @@ impl<'a, A: Api> PermissionsTest<'a, A> { } // Failure test- logged in on a non-team user - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context + let resp = req_gen(PermissionsTestContext { + test_pat: ENEMY_USER_PAT.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() }) - .append_header(("Authorization", ENEMY_USER_PAT)) - .to_request(); - - let resp = test_env.call(request).await; + .await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Failure permissions test failed. Expected failure codes {} got {}", @@ -241,15 +238,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { } // Failure test- logged in with EVERY non-relevant permission - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp: ServiceResponse = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Failure permissions test failed. Expected failure codes {} got {}", @@ -277,15 +271,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { .await; // Successful test - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !resp.status().is_success() { return Err(format!( "Success permissions test failed. Expected success, got {}", @@ -306,21 +297,22 @@ impl<'a, A: Api> PermissionsTest<'a, A> { Ok(()) } - pub async fn simple_organization_permissions_test( + pub async fn simple_organization_permissions_test( &self, success_permissions: OrganizationPermissions, req_gen: T, ) -> Result<(), String> where - T: Fn(&PermissionsTestContext) -> TestRequest, + T: Fn(PermissionsTestContext) -> Fut, + Fut : Future { let test_env = self.test_env; let failure_organization_permissions = self .failure_organization_permissions .unwrap_or(OrganizationPermissions::all() ^ success_permissions); let test_context = PermissionsTestContext { - user_id: self.user_id, - user_pat: self.user_pat, + test_pat: None, + user_id: self.user_id.to_string(), project_id: None, team_id: None, organization_id: None, @@ -348,15 +340,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { .await; // Failure test - let request = req_gen(&PermissionsTestContext { - organization_id: Some(&organization_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + organization_id: Some(organization_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Failure permissions test failed. Expected failure codes {} got {}", @@ -379,15 +368,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { .await; // Successful test - let request = req_gen(&PermissionsTestContext { - organization_id: Some(&organization_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + organization_id: Some(organization_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !resp.status().is_success() { return Err(format!( "Success permissions test failed. Expected success, got {}", @@ -403,21 +389,22 @@ impl<'a, A: Api> PermissionsTest<'a, A> { Ok(()) } - pub async fn full_project_permissions_test( + pub async fn full_project_permissions_test( &self, success_permissions: ProjectPermissions, req_gen: T, ) -> Result<(), String> where - T: Fn(&PermissionsTestContext) -> TestRequest, + T: Fn(PermissionsTestContext) -> Fut, + Fut : Future { let test_env = self.test_env; let failure_project_permissions = self .failure_project_permissions .unwrap_or(ProjectPermissions::all() ^ success_permissions); let test_context = PermissionsTestContext { - user_id: self.user_id, - user_pat: self.user_pat, + test_pat: None, + user_id: self.user_id.to_string(), project_id: None, team_id: None, organization_id: None, @@ -430,14 +417,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { let test_1 = async { let (project_id, team_id) = create_dummy_project(&test_env.setup_api).await; - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: None, + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Test 1 failed. Expected failure codes {} got {}", @@ -471,14 +456,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { let test_2 = async { let (project_id, team_id) = create_dummy_project(&test_env.setup_api).await; - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Test 2 failed. Expected failure codes {} got {}", @@ -521,15 +504,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { ) .await; - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Test 3 failed. Expected failure codes {} got {}", @@ -572,15 +552,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { ) .await; - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !resp.status().is_success() { return Err(format!( "Test 4 failed. Expected success, got {}", @@ -623,15 +600,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { ) .await; - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Test 5 failed. Expected failure codes {} got {}", @@ -678,15 +652,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { ) .await; - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !resp.status().is_success() { return Err(format!( "Test 6 failed. Expected success, got {}", @@ -739,15 +710,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { ) .await; - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Test 7 failed. Expected failure codes {} got {}", @@ -804,15 +772,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { ) .await; - let request = req_gen(&PermissionsTestContext { - project_id: Some(&project_id), - team_id: Some(&team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + project_id: Some(project_id.clone()), + team_id: Some(team_id.clone()), + ..test_context.clone() + }).await; if !resp.status().is_success() { return Err(format!( @@ -844,21 +809,22 @@ impl<'a, A: Api> PermissionsTest<'a, A> { Ok(()) } - pub async fn full_organization_permissions_tests( + pub async fn full_organization_permissions_tests( &self, success_permissions: OrganizationPermissions, req_gen: T, ) -> Result<(), String> where - T: Fn(&PermissionsTestContext) -> TestRequest, + T: Fn(PermissionsTestContext) -> Fut, + Fut : Future { let test_env = self.test_env; let failure_organization_permissions = self .failure_organization_permissions .unwrap_or(OrganizationPermissions::all() ^ success_permissions); let test_context = PermissionsTestContext { - user_id: self.user_id, - user_pat: self.user_pat, + test_pat: None, + user_id: self.user_id.to_string(), project_id: None, // Will be overwritten on each test team_id: None, // Will be overwritten on each test organization_id: None, @@ -871,14 +837,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { let (organization_id, organization_team_id) = create_dummy_org(&test_env.setup_api).await; - let request = req_gen(&PermissionsTestContext { - organization_id: Some(&organization_id), - organization_team_id: Some(&organization_team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + organization_id: Some(organization_id.clone()), + organization_team_id: Some(organization_team_id), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Test 1 failed. Expected failure codes {} got {}", @@ -921,15 +885,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { ) .await; - let request = req_gen(&PermissionsTestContext { - organization_id: Some(&organization_id), - organization_team_id: Some(&organization_team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + organization_id: Some(organization_id.clone()), + organization_team_id: Some(organization_team_id), + ..test_context.clone() + }).await; if !self.allowed_failure_codes.contains(&resp.status().as_u16()) { return Err(format!( "Test 2 failed. Expected failure codes {} got {}", @@ -972,15 +933,12 @@ impl<'a, A: Api> PermissionsTest<'a, A> { ) .await; - let request = req_gen(&PermissionsTestContext { - organization_id: Some(&organization_id), - organization_team_id: Some(&organization_team_id), - ..test_context - }) - .append_header(("Authorization", self.user_pat)) - .to_request(); - - let resp = test_env.call(request).await; + let resp = req_gen(PermissionsTestContext { + test_pat: self.user_pat.map(|s| s.to_string()), + organization_id: Some(organization_id.clone()), + organization_team_id: Some(organization_team_id), + ..test_context.clone() + }).await; if !resp.status().is_success() { return Err(format!( "Test 3 failed. Expected success, got {}", @@ -1054,7 +1012,7 @@ async fn add_project_to_org(setup_api: &ApiV3, project_id: &str, organization_id async fn add_user_to_team( user_id: &str, - user_pat: &str, + user_pat: Option<&str>, team_id: &str, project_permissions: Option, organization_permissions: Option, @@ -1109,7 +1067,7 @@ async fn remove_user_from_team(user_id: &str, team_id: &str, setup_api: &ApiV3) async fn get_project_permissions( user_id: &str, - user_pat: &str, + user_pat: Option<&str>, project_id: &str, setup_api: &ApiV3, ) -> ProjectPermissions { @@ -1132,7 +1090,7 @@ async fn get_project_permissions( async fn get_organization_permissions( user_id: &str, - user_pat: &str, + user_pat: Option<&str>, organization_id: &str, setup_api: &ApiV3, ) -> OrganizationPermissions { diff --git a/tests/common/search.rs b/tests/common/search.rs index 58678f45..87a4a30e 100644 --- a/tests/common/search.rs +++ b/tests/common/search.rs @@ -21,7 +21,7 @@ pub async fn setup_search_projects(test_env: &TestEnvironment) -> Arc| { + |id: u64, pat: Option<&'static str>, is_modpack: bool, modify_json: Option| { let slug = format!("{test_name}-searchable-project-{id}"); let jar = if is_modpack { diff --git a/tests/oauth.rs b/tests/oauth.rs index da6b29a8..879a6537 100644 --- a/tests/oauth.rs +++ b/tests/oauth.rs @@ -71,7 +71,7 @@ async fn oauth_flow_happy_path() { // Validate the token works env.assert_read_notifications_status( FRIEND_USER_ID, - &token_resp.access_token, + Some(&token_resp.access_token), StatusCode::OK, ) .await; @@ -179,16 +179,16 @@ async fn authorize_with_broader_scopes_can_complete_flow() { env.assert_read_notifications_status( USER_USER_ID, - &first_access_token, + Some(&first_access_token), StatusCode::UNAUTHORIZED, ) .await; - env.assert_read_user_projects_status(USER_USER_ID, &first_access_token, StatusCode::OK) + env.assert_read_user_projects_status(USER_USER_ID, Some(&first_access_token), StatusCode::OK) .await; - env.assert_read_notifications_status(USER_USER_ID, &second_access_token, StatusCode::OK) + env.assert_read_notifications_status(USER_USER_ID, Some(&second_access_token), StatusCode::OK) .await; - env.assert_read_user_projects_status(USER_USER_ID, &second_access_token, StatusCode::OK) + env.assert_read_user_projects_status(USER_USER_ID, Some(&second_access_token), StatusCode::OK) .await; }) .await; @@ -278,7 +278,7 @@ async fn revoke_authorization_after_issuing_token_revokes_token() { USER_USER_PAT, ) .await; - env.assert_read_notifications_status(USER_USER_ID, &access_token, StatusCode::OK) + env.assert_read_notifications_status(USER_USER_ID, Some(&access_token), StatusCode::OK) .await; let resp = env @@ -287,7 +287,7 @@ async fn revoke_authorization_after_issuing_token_revokes_token() { .await; assert_status(&resp, StatusCode::OK); - env.assert_read_notifications_status(USER_USER_ID, &access_token, StatusCode::UNAUTHORIZED) + env.assert_read_notifications_status(USER_USER_ID, Some(&access_token), StatusCode::UNAUTHORIZED) .await; }) .await; diff --git a/tests/oauth_clients.rs b/tests/oauth_clients.rs index e601b6c0..dc44c02d 100644 --- a/tests/oauth_clients.rs +++ b/tests/oauth_clients.rs @@ -167,7 +167,7 @@ async fn delete_oauth_client_after_issuing_access_tokens_revokes_tokens() { env.api.delete_oauth_client(&client_id, USER_USER_PAT).await; - env.assert_read_notifications_status(USER_USER_ID, &access_token, StatusCode::UNAUTHORIZED) + env.assert_read_notifications_status(USER_USER_ID, Some(&access_token), StatusCode::UNAUTHORIZED) .await; }) .await; diff --git a/tests/organizations.rs b/tests/organizations.rs index 7597ef8d..b7b6a41b 100644 --- a/tests/organizations.rs +++ b/tests/organizations.rs @@ -1,11 +1,8 @@ use crate::common::{ api_common::ApiTeams, - api_v3::request_data::get_icon_data, database::{generate_random_name, ADMIN_USER_PAT, MOD_USER_ID, MOD_USER_PAT, USER_USER_ID}, dummy_data::DummyImage, }; -use actix_web::test; -use bytes::Bytes; use common::{ api_v3::ApiV3, database::{FRIEND_USER_ID, FRIEND_USER_PAT, USER_USER_PAT}, @@ -191,7 +188,7 @@ async fn add_remove_icon() { let resp = api .edit_organization_icon( zeta_organization_id, - Some(get_icon_data(DummyImage::SmallIcon)), + Some(DummyImage::SmallIcon.get_icon_data()), USER_USER_PAT, ) .await; @@ -294,8 +291,9 @@ async fn add_remove_organization_projects() { #[actix_rt::test] async fn permissions_patch_organization() { - with_test_environment_all(None, |test_env| async move { + with_test_environment(None, |test_env: TestEnvironment| async move { // For each permission covered by EDIT_DETAILS, ensure the permission is required + let api = &test_env.api; let edit_details = OrganizationPermissions::EDIT_DETAILS; let test_pairs = [ ("name", json!("")), // generated in the test to not collide slugs @@ -303,20 +301,17 @@ async fn permissions_patch_organization() { ]; for (key, value) in test_pairs { - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!( - "/v3/organization/{}", - ctx.organization_id.unwrap() - )) - .set_json(json!({ - key: if key == "name" { - json!(generate_random_name("randomslug")) - } else { - value.clone() - }, - })) - }; + let req_gen = |ctx: PermissionsTestContext| { + let value = value.clone(); + async move{ + api.edit_organization(&ctx.organization_id.unwrap(), json!({ + key: if key == "name" { + json!(generate_random_name("randomslug")) + } else { + value.clone() + }, + }), ctx.test_pat.as_deref()).await + }}; PermissionsTest::new(&test_env) .simple_organization_permissions_test(edit_details, req_gen) .await @@ -329,7 +324,7 @@ async fn permissions_patch_organization() { // Not covered by PATCH /organization #[actix_rt::test] async fn permissions_edit_details() { - with_test_environment_all(None, |test_env| async move { + with_test_environment(None, |test_env: TestEnvironment| async move { let zeta_organization_id = &test_env .dummy .as_ref() @@ -337,20 +332,14 @@ async fn permissions_edit_details() { .organization_zeta .organization_id; let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id; - + + let api = &test_env.api; let edit_details = OrganizationPermissions::EDIT_DETAILS; // Icon edit // Uses alpha organization to delete this icon - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!( - "/v3/organization/{}/icon?ext=png", - ctx.organization_id.unwrap() - )) - .set_payload(Bytes::from( - include_bytes!("../tests/files/200x200.png") as &[u8] - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_organization_icon(&ctx.organization_id.unwrap(), Some(DummyImage::SmallIcon.get_icon_data()), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -361,11 +350,8 @@ async fn permissions_edit_details() { // Icon delete // Uses alpha project to delete added icon - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/organization/{}/icon?ext=png", - ctx.organization_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_organization_icon(&ctx.organization_id.unwrap(), None, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -394,14 +380,9 @@ async fn permissions_manage_invites() { let manage_invites = OrganizationPermissions::MANAGE_INVITES; // Add member - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::post() - .uri(&format!("/v3/team/{}/members", ctx.team_id.unwrap())) - .set_json(json!({ - "user_id": MOD_USER_ID, - "permissions": 0, - "organization_permissions": 0, - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.add_user_to_team(&ctx.team_id.unwrap(), MOD_USER_ID, Some(ProjectPermissions::empty()), Some(OrganizationPermissions::empty()), ctx.test_pat.as_deref()) + .await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -412,15 +393,10 @@ async fn permissions_manage_invites() { // Edit member let edit_member = OrganizationPermissions::EDIT_MEMBER; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!( - "/v3/team/{}/members/{MOD_USER_ID}", - ctx.team_id.unwrap() - )) - .set_json(json!({ - "organization_permissions": 0, - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_team_member(&ctx.team_id.unwrap(), MOD_USER_ID, json!({ + "organization_permissions": 0, + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -431,11 +407,8 @@ async fn permissions_manage_invites() { // remove member // requires manage_invites if they have not yet accepted the invite - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/team/{}/members/{MOD_USER_ID}", - ctx.team_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_from_team(&ctx.team_id.unwrap(), MOD_USER_ID, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -454,11 +427,8 @@ async fn permissions_manage_invites() { // remove existing member (requires remove_member) let remove_member = OrganizationPermissions::REMOVE_MEMBER; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/team/{}/members/{MOD_USER_ID}", - ctx.team_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_from_team(&ctx.team_id.unwrap(), MOD_USER_ID, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) @@ -473,7 +443,7 @@ async fn permissions_manage_invites() { #[actix_rt::test] async fn permissions_add_remove_project() { - with_test_environment_all(None, |test_env| async move { + with_test_environment(None, |test_env: TestEnvironment| async move { let api = &test_env.api; let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id; @@ -503,15 +473,8 @@ async fn permissions_add_remove_project() { // Now, FRIEND_USER_ID owns the alpha project // Add alpha project to zeta organization - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::post() - .uri(&format!( - "/v3/organization/{}/projects", - ctx.organization_id.unwrap() - )) - .set_json(json!({ - "project_id": alpha_project_id, - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.organization_add_project(&ctx.organization_id.unwrap(), alpha_project_id, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -522,11 +485,8 @@ async fn permissions_add_remove_project() { // Remove alpha project from zeta organization let remove_project = OrganizationPermissions::REMOVE_PROJECT; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/organization/{}/projects/{alpha_project_id}", - ctx.organization_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.organization_remove_project(&ctx.organization_id.unwrap(), alpha_project_id, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -540,16 +500,15 @@ async fn permissions_add_remove_project() { #[actix_rt::test] async fn permissions_delete_organization() { - with_test_environment_all(None, |test_env| async move { + with_test_environment(None, |test_env: TestEnvironment| async move { + let api = &test_env.api; let delete_organization = OrganizationPermissions::DELETE_ORGANIZATION; // Now, FRIEND_USER_ID owns the alpha project // Add alpha project to zeta organization - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/organization/{}", - ctx.organization_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.delete_organization(&ctx.organization_id.unwrap(), ctx.test_pat.as_deref()) + .await }; PermissionsTest::new(&test_env) .simple_organization_permissions_test(delete_organization, req_gen) @@ -570,6 +529,8 @@ async fn permissions_add_default_project_permissions() { .organization_id; let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id; + let api = &test_env.api; + // Add member let add_member_default_permissions = OrganizationPermissions::MANAGE_INVITES | OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS; @@ -579,14 +540,14 @@ async fn permissions_add_default_project_permissions() { let failure_with_add_member = (OrganizationPermissions::all() ^ add_member_default_permissions) | OrganizationPermissions::MANAGE_INVITES; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::post() - .uri(&format!("/v3/team/{}/members", ctx.team_id.unwrap())) - .set_json(json!({ - "user_id": MOD_USER_ID, - "permissions": (ProjectPermissions::UPLOAD_VERSION | ProjectPermissions::DELETE_VERSION).bits(), - "organization_permissions": 0, - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.add_user_to_team( + &ctx.team_id.unwrap(), + MOD_USER_ID, + Some(ProjectPermissions::UPLOAD_VERSION | ProjectPermissions::DELETE_VERSION), + Some(OrganizationPermissions::empty()), + ctx.test_pat.as_deref(), + ).await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -606,15 +567,10 @@ async fn permissions_add_default_project_permissions() { ^ add_member_default_permissions) | OrganizationPermissions::EDIT_MEMBER; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!( - "/v3/team/{}/members/{MOD_USER_ID}", - ctx.team_id.unwrap() - )) - .set_json(json!({ - "permissions": ProjectPermissions::EDIT_DETAILS.bits(), - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_team_member(&ctx.team_id.unwrap(), MOD_USER_ID, json!({ + "permissions": ProjectPermissions::EDIT_DETAILS.bits(), + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_organization(zeta_organization_id, zeta_team_id) @@ -629,19 +585,15 @@ async fn permissions_add_default_project_permissions() { #[actix_rt::test] async fn permissions_organization_permissions_consistency_test() { - with_test_environment_all(None, |test_env| async move { + with_test_environment(None, |test_env: TestEnvironment| async move { + let api = &test_env.api; // Ensuring that permission are as we expect them to be // Full organization permissions test let success_permissions = OrganizationPermissions::EDIT_DETAILS; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!( - "/v3/organization/{}", - ctx.organization_id.unwrap() - )) - .set_json(json!({ - "description": "Example description - changed.", - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_organization(&ctx.organization_id.unwrap(), json!({ + "description": "Example description - changed.", + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .full_organization_permissions_tests(success_permissions, req_gen) diff --git a/tests/pats.rs b/tests/pats.rs index 68dfacad..37e95c26 100644 --- a/tests/pats.rs +++ b/tests/pats.rs @@ -5,6 +5,8 @@ use common::{database::*, environment::with_test_environment_all}; use labrinth::models::pats::Scopes; use serde_json::json; +use crate::common::api_common::AppendsOptionalPat; + mod common; // Full pat test: @@ -20,7 +22,7 @@ pub async fn pat_full_test() { // Create a PAT for a full test let req = test::TestRequest::post() .uri("/_internal/pat") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example "name": "test_pat_scopes Test", @@ -42,7 +44,7 @@ pub async fn pat_full_test() { // Get PAT again let req = test::TestRequest::get() - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .uri("/_internal/pat") .to_request(); let resp = test_env.call(req).await; @@ -76,7 +78,7 @@ pub async fn pat_full_test() { // Change scopes and test again let req = test::TestRequest::patch() .uri(&format!("/_internal/pat/{}", id)) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": 0, })) @@ -88,7 +90,7 @@ pub async fn pat_full_test() { // Change scopes back, and set expiry to the past, and test again let req = test::TestRequest::patch() .uri(&format!("/_internal/pat/{}", id)) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": Scopes::COLLECTION_CREATE, "expires": Utc::now() + Duration::seconds(1), // expires in 1 second @@ -104,7 +106,7 @@ pub async fn pat_full_test() { // Change everything back to normal and test again let req = test::TestRequest::patch() .uri(&format!("/_internal/pat/{}", id)) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "expires": Utc::now() + Duration::days(1), // no longer expired! })) @@ -116,7 +118,7 @@ pub async fn pat_full_test() { // Patching to a bad expiry should fail let req = test::TestRequest::patch() .uri(&format!("/_internal/pat/{}", id)) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "expires": Utc::now() - Duration::days(1), // Past })) @@ -133,7 +135,7 @@ pub async fn pat_full_test() { let req = test::TestRequest::patch() .uri(&format!("/_internal/pat/{}", id)) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": scope.bits(), })) @@ -147,7 +149,7 @@ pub async fn pat_full_test() { // Delete PAT let req = test::TestRequest::delete() - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .uri(&format!("/_internal/pat/{}", id)) .to_request(); let resp = test_env.call(req).await; @@ -163,7 +165,7 @@ pub async fn bad_pats() { // Creating a PAT with no name should fail let req = test::TestRequest::post() .uri("/_internal/pat") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example "expires": Utc::now() + Duration::days(1), @@ -176,7 +178,7 @@ pub async fn bad_pats() { for name in ["n", "this_name_is_too_long".repeat(16).as_str()] { let req = test::TestRequest::post() .uri("/_internal/pat") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "name": name, "scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example @@ -190,7 +192,7 @@ pub async fn bad_pats() { // Creating a PAT with an expiry in the past should fail let req = test::TestRequest::post() .uri("/_internal/pat") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example "name": "test_pat_scopes Test", @@ -208,7 +210,7 @@ pub async fn bad_pats() { } let req = test::TestRequest::post() .uri("/_internal/pat") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": scope.bits(), "name": format!("test_pat_scopes Name {}", i), @@ -225,7 +227,7 @@ pub async fn bad_pats() { // Create a 'good' PAT for patching let req = test::TestRequest::post() .uri("/_internal/pat") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": Scopes::COLLECTION_CREATE, "name": "test_pat_scopes Test", @@ -241,7 +243,7 @@ pub async fn bad_pats() { for name in ["n", "this_name_is_too_long".repeat(16).as_str()] { let req = test::TestRequest::post() .uri("/_internal/pat") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "name": name, })) @@ -253,7 +255,7 @@ pub async fn bad_pats() { // Patching to a bad expiry should fail let req = test::TestRequest::patch() .uri(&format!("/_internal/pat/{}", id)) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "expires": Utc::now() - Duration::days(1), // Past })) @@ -270,7 +272,7 @@ pub async fn bad_pats() { let req = test::TestRequest::patch() .uri(&format!("/_internal/pat/{}", id)) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "scopes": scope.bits(), })) diff --git a/tests/project.rs b/tests/project.rs index 32ba8616..8083bf81 100644 --- a/tests/project.rs +++ b/tests/project.rs @@ -2,9 +2,7 @@ use std::collections::HashMap; use actix_http::StatusCode; use actix_web::test; -use bytes::Bytes; use chrono::{Duration, Utc}; -use common::api_v3::request_data::get_public_version_creation_data; use common::api_v3::ApiV3; use common::database::*; use common::dummy_data::DUMMY_CATEGORIES; @@ -17,12 +15,12 @@ use labrinth::database::models::project_item::{PROJECTS_NAMESPACE, PROJECTS_SLUG use labrinth::models::ids::base62_impl::parse_base62; use labrinth::models::projects::{Project, ProjectId}; use labrinth::models::teams::ProjectPermissions; -use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData}; +use labrinth::util::actix::{MultipartSegment, MultipartSegmentData}; use serde_json::json; use crate::common::api_common::request_data::ProjectCreationRequestData; -use crate::common::api_common::{ApiProject, ApiVersion}; -use crate::common::dummy_data::TestFile; +use crate::common::api_common::{ApiProject, ApiVersion, AppendsOptionalPat, ApiTeams}; +use crate::common::dummy_data::{TestFile, DummyImage}; mod common; #[actix_rt::test] @@ -37,7 +35,7 @@ async fn test_get_project() { // Perform request on dummy data let req = test::TestRequest::get() .uri(&format!("/v3/project/{alpha_project_id}")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = test_env.call(req).await; let status = resp.status(); @@ -74,7 +72,7 @@ async fn test_get_project() { // Make the request again, this time it should be cached let req = test::TestRequest::get() .uri(&format!("/v3/project/{alpha_project_id}")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = test_env.call(req).await; let status = resp.status(); @@ -87,7 +85,7 @@ async fn test_get_project() { // Request should fail on non-existent project let req = test::TestRequest::get() .uri("/v3/project/nonexistent") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = test_env.call(req).await; @@ -96,7 +94,7 @@ async fn test_get_project() { // Similarly, request should fail on non-authorized user, on a yet-to-be-approved or hidden project, with a 404 (hiding the existence of the project) let req = test::TestRequest::get() .uri(&format!("/v3/project/{beta_project_id}")) - .append_header(("Authorization", ENEMY_USER_PAT)) + .append_pat(ENEMY_USER_PAT) .to_request(); let resp = test_env.call(req).await; @@ -138,36 +136,36 @@ async fn test_add_remove_project() { data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()), ..json_segment.clone() }; + + let basic_mod_file = TestFile::BasicMod; + let basic_mod_different_file = TestFile::BasicModDifferent; // Basic file let file_segment = MultipartSegment { - name: "basic-mod.jar".to_string(), - filename: Some("basic-mod.jar".to_string()), - content_type: Some("application/java-archive".to_string()), - // TODO: look at these: can be used in the reuse data - data: MultipartSegmentData::Binary( - include_bytes!("../tests/files/basic-mod.jar").to_vec(), - ), + // 'Basic' + name: basic_mod_file.filename(), + filename: Some(basic_mod_file.filename()), + content_type: basic_mod_file.content_type(), + data: MultipartSegmentData::Binary(basic_mod_file.bytes()), }; - // Differently named file, with the same content (for hash testing) + // Differently named file, with the SAME content (for hash testing) let file_diff_name_segment = MultipartSegment { - name: "basic-mod-different.jar".to_string(), - filename: Some("basic-mod-different.jar".to_string()), - content_type: Some("application/java-archive".to_string()), - data: MultipartSegmentData::Binary( - include_bytes!("../tests/files/basic-mod.jar").to_vec(), - ), + // 'Different' + name: basic_mod_different_file.filename(), + filename: Some(basic_mod_different_file.filename()), + content_type: basic_mod_different_file.content_type(), + // 'Basic' + data: MultipartSegmentData::Binary(basic_mod_file.bytes()), }; // Differently named file, with different content let file_diff_name_content_segment = MultipartSegment { - name: "basic-mod-different.jar".to_string(), - filename: Some("basic-mod-different.jar".to_string()), - content_type: Some("application/java-archive".to_string()), - data: MultipartSegmentData::Binary( - include_bytes!("../tests/files/basic-mod-different.jar").to_vec(), - ), + // 'Different' + name: basic_mod_different_file.filename(), + filename: Some(basic_mod_different_file.filename()), + content_type: basic_mod_different_file.content_type(), + data: MultipartSegmentData::Binary(basic_mod_different_file.bytes()), }; // Add a project- simple, should work. @@ -193,7 +191,7 @@ async fn test_add_remove_project() { let uploaded_version_id = project.versions[0]; // Checks files to ensure they were uploaded and correctly identify the file - let hash = sha1::Sha1::from(include_bytes!("../tests/files/basic-mod.jar")) + let hash = sha1::Sha1::from(basic_mod_file.bytes()) .digest() .to_string(); let version = api @@ -598,6 +596,7 @@ async fn permissions_patch_project_v3() { let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id; let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id; + let api = &test_env.api; // TODO: This should be a separate test from v3 // - only a couple of these fields are v3-specific // once we have permissions/scope tests setup to not just take closures, we can split this up @@ -625,17 +624,18 @@ async fn permissions_patch_project_v3() { .map(|(key, value)| { let test_env = test_env.clone(); async move { - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v3/project/{}", ctx.project_id.unwrap())) - .set_json(json!({ + let req_gen = |ctx: PermissionsTestContext| { + let value = value.clone(); + async move { + api.edit_project( + &ctx.project_id.unwrap(),json!({ key: if key == "slug" { json!(generate_random_name("randomslug")) } else { value.clone() }, - })) - }; + }), ctx.test_pat.as_deref()).await + }}; PermissionsTest::new(&test_env) .simple_project_permissions_test(edit_details, req_gen) .await @@ -648,13 +648,15 @@ async fn permissions_patch_project_v3() { // Test with status and requested_status // This requires a project with a version, so we use alpha_project_id - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v3/project/{}", ctx.project_id.unwrap())) - .set_json(json!({ + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_project( + &ctx.project_id.unwrap(), + json!({ "status": "private", "requested_status": "private", - })) + }), + ctx.test_pat.as_deref(), + ).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -664,15 +666,10 @@ async fn permissions_patch_project_v3() { .unwrap(); // Bulk patch projects - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!( - "/v3/projects?ids=[{uri}]", - uri = urlencoding::encode(&format!("\"{}\"", ctx.project_id.unwrap())) - )) - .set_json(json!({ - "name": "randomname", - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_project_bulk(&[&ctx.project_id.unwrap()], json!({ + "name": "randomname", + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .simple_project_permissions_test(edit_details, req_gen) @@ -682,12 +679,10 @@ async fn permissions_patch_project_v3() { // Edit body // Cannot bulk edit body let edit_body = ProjectPermissions::EDIT_BODY; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v3/project/{}", ctx.project_id.unwrap())) - .set_json(json!({ - "description": "new description!", - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_project(&ctx.project_id.unwrap(), json!({ + "description": "new description!", + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .simple_project_permissions_test(edit_body, req_gen) @@ -706,12 +701,14 @@ async fn permissions_edit_details() { let beta_project_id = &test_env.dummy.as_ref().unwrap().project_beta.project_id; let beta_team_id = &test_env.dummy.as_ref().unwrap().project_beta.team_id; let beta_version_id = &test_env.dummy.as_ref().unwrap().project_beta.version_id; + let edit_details = ProjectPermissions::EDIT_DETAILS; + let api = &test_env.api; // Approve beta version as private so we can schedule it let req = test::TestRequest::patch() .uri(&format!("/v3/version/{beta_version_id}")) - .append_header(("Authorization", MOD_USER_PAT)) + .append_pat(MOD_USER_PAT) .set_json(json!({ "status": "unlisted" })) @@ -720,15 +717,8 @@ async fn permissions_edit_details() { assert_eq!(resp.status(), 204); // Schedule version - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::post() - .uri(&format!("/v3/version/{beta_version_id}/schedule")) // beta_version_id is an *approved* version, so we can schedule it - .set_json(json!( - { - "requested_status": "archived", - "time": Utc::now() + Duration::days(1), - } - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.schedule_project(beta_version_id, "archived", Utc::now() + Duration::days(1), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(beta_project_id, beta_team_id) @@ -739,15 +729,8 @@ async fn permissions_edit_details() { // Icon edit // Uses alpha project to delete this icon - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!( - "/v3/project/{}/icon?ext=png", - ctx.project_id.unwrap() - )) - .set_payload(Bytes::from( - include_bytes!("../tests/files/200x200.png") as &[u8] - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_project_icon(&ctx.project_id.unwrap(), Some(DummyImage::SmallIcon.get_icon_data()), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -758,11 +741,8 @@ async fn permissions_edit_details() { // Icon delete // Uses alpha project to delete added icon - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/project/{}/icon?ext=png", - ctx.project_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_project_icon(&ctx.project_id.unwrap(), None, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -773,15 +753,8 @@ async fn permissions_edit_details() { // Add gallery item // Uses alpha project to add gallery item so we can get its url - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::post() - .uri(&format!( - "/v3/project/{}/gallery?ext=png&featured=true", - ctx.project_id.unwrap() - )) - .set_payload(Bytes::from( - include_bytes!("../tests/files/200x200.png") as &[u8] - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.add_gallery_item(&ctx.project_id.unwrap(), DummyImage::SmallIcon.get_icon_data(), true, None, None, None, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -792,7 +765,7 @@ async fn permissions_edit_details() { // Get project, as we need the gallery image url let req = test::TestRequest::get() .uri(&format!("/v3/project/{alpha_project_id}")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = test_env.call(req).await; let project: serde_json::Value = test::read_body_json(resp).await; @@ -800,11 +773,10 @@ async fn permissions_edit_details() { // Edit gallery item // Uses alpha project to edit gallery item - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch().uri(&format!( - "/v3/project/{}/gallery?url={gallery_url}", - ctx.project_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_gallery_item(&ctx.project_id.unwrap(), gallery_url, + vec![("description".to_string(), "new caption!".to_string())].into_iter().collect(), + ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -815,11 +787,8 @@ async fn permissions_edit_details() { // Remove gallery item // Uses alpha project to remove gallery item - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/project/{}/gallery?url={gallery_url}", - ctx.project_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_gallery_item(&ctx.project_id.unwrap(), gallery_url, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -839,21 +808,15 @@ async fn permissions_upload_version() { let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id; let alpha_file_hash = &test_env.dummy.as_ref().unwrap().project_alpha.file_hash; + let api = &test_env.api; + let upload_version = ProjectPermissions::UPLOAD_VERSION; // Upload version with basic-mod.jar - let req_gen = |ctx: &PermissionsTestContext| { + let req_gen = |ctx: PermissionsTestContext| async move { let project_id = ctx.project_id.unwrap(); - let project_id = ProjectId(parse_base62(project_id).unwrap()); - let multipart = get_public_version_creation_data( - project_id, - "1.0.0", - TestFile::BasicMod, - None, - None, - ); - test::TestRequest::post() - .uri("/v3/version") - .set_multipart(multipart.segment_data) + let project_id = ProjectId(parse_base62(&project_id).unwrap()); + api.add_public_version(project_id, "1.0.0", TestFile::BasicMod, None, None, ctx.test_pat.as_deref()) + .await }; PermissionsTest::new(&test_env) .simple_project_permissions_test(upload_version, req_gen) @@ -862,30 +825,8 @@ async fn permissions_upload_version() { // Upload file to existing version // Uses alpha project, as it has an existing version - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::post() - .uri(&format!("/v3/version/{}/file", alpha_version_id)) - .set_multipart([ - MultipartSegment { - name: "data".to_string(), - filename: None, - content_type: Some("application/json".to_string()), - data: MultipartSegmentData::Text( - serde_json::to_string(&json!({ - "file_parts": ["basic-mod-different.jar"], - })) - .unwrap(), - ), - }, - MultipartSegment { - name: "basic-mod-different.jar".to_string(), - filename: Some("basic-mod-different.jar".to_string()), - content_type: Some("application/java-archive".to_string()), - data: MultipartSegmentData::Binary( - include_bytes!("../tests/files/basic-mod-different.jar").to_vec(), - ), - }, - ]) + let req_gen = |ctx: PermissionsTestContext| async move { + api.upload_file_to_version(alpha_version_id, &TestFile::BasicModDifferent, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -896,12 +837,10 @@ async fn permissions_upload_version() { // Patch version // Uses alpha project, as it has an existing version - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v3/version/{}", alpha_version_id)) - .set_json(json!({ - "name": "Basic Mod", - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_version(alpha_version_id, json!({ + "name": "Basic Mod", + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -913,8 +852,8 @@ async fn permissions_upload_version() { // Delete version file // Uses alpha project, as it has an existing version let delete_version = ProjectPermissions::DELETE_VERSION; - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!("/v3/version_file/{}", alpha_file_hash)) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_version_file(alpha_file_hash, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) @@ -926,8 +865,8 @@ async fn permissions_upload_version() { // Delete version // Uses alpha project, as it has an existing version - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!("/v3/version/{}", alpha_version_id)) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_version(alpha_version_id, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -946,16 +885,12 @@ async fn permissions_manage_invites() { let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id; let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id; + let api = &test_env.api; let manage_invites = ProjectPermissions::MANAGE_INVITES; // Add member - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::post() - .uri(&format!("/v3/team/{}/members", ctx.team_id.unwrap())) - .set_json(json!({ - "user_id": MOD_USER_ID, - "permissions": 0, - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.add_user_to_team(&ctx.team_id.unwrap(), MOD_USER_ID, Some(ProjectPermissions::empty()), None, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -966,15 +901,10 @@ async fn permissions_manage_invites() { // Edit member let edit_member = ProjectPermissions::EDIT_MEMBER; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!( - "/v3/team/{}/members/{MOD_USER_ID}", - ctx.team_id.unwrap() - )) - .set_json(json!({ - "permissions": 0, - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_team_member(&ctx.team_id.unwrap(), MOD_USER_ID, json!({ + "permissions": 0, + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -985,11 +915,8 @@ async fn permissions_manage_invites() { // remove member // requires manage_invites if they have not yet accepted the invite - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/team/{}/members/{MOD_USER_ID}", - ctx.team_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_from_team(&ctx.team_id.unwrap(), MOD_USER_ID, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -1001,7 +928,7 @@ async fn permissions_manage_invites() { // re-add member for testing let req = test::TestRequest::post() .uri(&format!("/v3/team/{}/members", alpha_team_id)) - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .set_json(json!({ "user_id": MOD_USER_ID, })) @@ -1012,18 +939,15 @@ async fn permissions_manage_invites() { // Accept invite let req = test::TestRequest::post() .uri(&format!("/v3/team/{}/join", alpha_team_id)) - .append_header(("Authorization", MOD_USER_PAT)) + .append_pat(MOD_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 204); // remove existing member (requires remove_member) let remove_member = ProjectPermissions::REMOVE_MEMBER; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!( - "/v3/team/{}/members/{MOD_USER_ID}", - ctx.team_id.unwrap() - )) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_from_team(&ctx.team_id.unwrap(), MOD_USER_ID, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) @@ -1041,10 +965,10 @@ async fn permissions_delete_project() { // Add member, remove member, edit member with_test_environment_all(None, |test_env| async move { let delete_project = ProjectPermissions::DELETE_PROJECT; - + let api = &test_env.api; // Delete project - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!("/v3/project/{}", ctx.project_id.unwrap())) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_project(&ctx.project_id.unwrap(), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .simple_project_permissions_test(delete_project, req_gen) @@ -1061,15 +985,13 @@ async fn project_permissions_consistency_test() { with_test_environment_all(Some(10), |test_env| async move { // Test that the permissions are consistent with each other // For example, if we get the projectpermissions directly, from an organization's defaults, overriden, etc, they should all be correct & consistent - + let api = &test_env.api; // Full project permissions test with EDIT_DETAILS let success_permissions = ProjectPermissions::EDIT_DETAILS; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v3/project/{}", ctx.project_id.unwrap())) - .set_json(json!({ - "name": "Example title - changed.", - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_project(&ctx.project_id.unwrap(), json!({ + "categories": [], + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .full_project_permissions_test(success_permissions, req_gen) @@ -1081,12 +1003,10 @@ async fn project_permissions_consistency_test() { | ProjectPermissions::REMOVE_MEMBER | ProjectPermissions::DELETE_VERSION | ProjectPermissions::VIEW_PAYOUTS; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v3/project/{}", ctx.project_id.unwrap())) - .set_json(json!({ - "name": "Example title - changed.", - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_project(&ctx.project_id.unwrap(), json!({ + "categories": [], + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .full_project_permissions_test(success_permissions, req_gen) diff --git a/tests/scopes.rs b/tests/scopes.rs index 335621ce..684dcb5d 100644 --- a/tests/scopes.rs +++ b/tests/scopes.rs @@ -14,7 +14,8 @@ use labrinth::models::projects::ProjectId; use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData}; use serde_json::json; -use crate::common::api_common::ApiTeams; +use crate::common::api_common::{ApiTeams, AppendsOptionalPat}; +use crate::common::dummy_data::DummyImage; // For each scope, we (using test_scope): // - create a PAT with a given set of scopes for a function @@ -386,7 +387,7 @@ pub async fn project_version_reads_scopes() { let read_version = Scopes::VERSION_READ; let req = test::TestRequest::patch() .uri(&format!("/v3/version/{beta_version_id}")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "status": "draft" })) @@ -505,6 +506,8 @@ pub async fn project_write_scopes() { .team_id .clone(); + let test_icon = DummyImage::SmallIcon; + // Projects writing let write_project = Scopes::PROJECT_WRITE; let req_gen = || { @@ -541,7 +544,7 @@ pub async fn project_write_scopes() { // Approve beta as private so we can schedule it let req = test::TestRequest::patch() .uri(&format!("/v3/project/{beta_project_id}")) - .append_header(("Authorization", MOD_USER_PAT)) + .append_pat(MOD_USER_PAT) .set_json(json!({ "status": "private" })) @@ -567,10 +570,10 @@ pub async fn project_write_scopes() { // Icons and gallery images let req_gen = || { test::TestRequest::patch() - .uri(&format!("/v3/project/{beta_project_id}/icon?ext=png")) - .set_payload(Bytes::from( - include_bytes!("../tests/files/200x200.png") as &[u8] - )) + .uri(&format!("/v3/project/{beta_project_id}/icon?ext={ext}" + , ext = test_icon.extension() + )) + .set_payload(test_icon.bytes()) }; ScopeTest::new(&test_env) .test(req_gen, write_project) @@ -587,11 +590,9 @@ pub async fn project_write_scopes() { let req_gen = || { test::TestRequest::post() .uri(&format!( - "/v3/project/{beta_project_id}/gallery?ext=png&featured=true" - )) - .set_payload(Bytes::from( - include_bytes!("../tests/files/200x200.png") as &[u8] + "/v3/project/{beta_project_id}/gallery?ext={ext}&featured=true", ext = test_icon.extension() )) + .set_payload(test_icon.bytes()) }; ScopeTest::new(&test_env) .test(req_gen, write_project) @@ -601,7 +602,7 @@ pub async fn project_write_scopes() { // Get project, as we need the gallery image url let req_gen = test::TestRequest::get() .uri(&format!("/v3/project/{beta_project_id}")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = test_env.call(req_gen).await; let project: serde_json::Value = test::read_body_json(resp).await; @@ -728,12 +729,14 @@ pub async fn version_write_scopes() { .file_hash .clone(); + let basic_zip = TestFile::BasicZip; + let write_version = Scopes::VERSION_WRITE; // Approve beta version as private so we can schedule it let req = test::TestRequest::patch() .uri(&format!("/v3/version/{beta_version_id}")) - .append_header(("Authorization", MOD_USER_PAT)) + .append_pat(MOD_USER_PAT) .set_json(json!({ "status": "unlisted" })) @@ -782,7 +785,7 @@ pub async fn version_write_scopes() { serde_json::to_string(&json!( { "file_types": { - "simple-zip.zip": "required-resource-pack" + basic_zip.filename(): "required-resource-pack" }, } )) @@ -792,12 +795,10 @@ pub async fn version_write_scopes() { // Differently named file, with different content let content_segment = MultipartSegment { - name: "simple-zip.zip".to_string(), - filename: Some("simple-zip.zip".to_string()), - content_type: Some("application/zip".to_string()), - data: MultipartSegmentData::Binary( - include_bytes!("../tests/files/simple-zip.zip").to_vec(), - ), + name: basic_zip.filename(), + filename: Some(basic_zip.filename()), + content_type: basic_zip.content_type(), + data: MultipartSegmentData::Binary(basic_zip.bytes()) }; // Upload version file @@ -995,7 +996,7 @@ pub async fn thread_scopes() { // First, get message id let req_gen = test::TestRequest::get() .uri(&format!("/v3/thread/{thread_id}")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .to_request(); let resp = test_env.call(req_gen).await; let success: serde_json::Value = test::read_body_json(resp).await; @@ -1077,6 +1078,8 @@ pub async fn collections_scopes() { .project_id .clone(); + let small_icon = DummyImage::SmallIcon; + // Create collection let collection_create = Scopes::COLLECTION_CREATE; let req_gen = || { @@ -1145,10 +1148,10 @@ pub async fn collections_scopes() { let req_gen = || { test::TestRequest::patch() - .uri(&format!("/v3/collection/{collection_id}/icon?ext=png")) - .set_payload(Bytes::from( - include_bytes!("../tests/files/200x200.png") as &[u8] + .uri(&format!("/v3/collection/{collection_id}/icon?ext={ext}", + ext = small_icon.extension() )) + .set_payload(Bytes::from(small_icon.bytes())) }; ScopeTest::new(&test_env) .test(req_gen, collection_write) @@ -1178,6 +1181,8 @@ pub async fn organization_scopes() { .project_id .clone(); + let icon = DummyImage::SmallIcon; + // Create organization let organization_create = Scopes::ORGANIZATION_CREATE; let req_gen = || { @@ -1210,10 +1215,9 @@ pub async fn organization_scopes() { let req_gen = || { test::TestRequest::patch() - .uri(&format!("/v3/organization/{organization_id}/icon?ext=png")) - .set_payload(Bytes::from( - include_bytes!("../tests/files/200x200.png") as &[u8] - )) + .uri(&format!("/v3/organization/{organization_id}/icon?ext={ext}", + ext = icon.extension())) + .set_payload(Bytes::from(icon.bytes())) }; ScopeTest::new(&test_env) .test(req_gen, organization_edit) diff --git a/tests/teams.rs b/tests/teams.rs index c4bf7cda..6fe994b0 100644 --- a/tests/teams.rs +++ b/tests/teams.rs @@ -1,4 +1,4 @@ -use crate::common::{api_common::ApiTeams, database::*}; +use crate::common::{api_common::{ApiTeams, AppendsOptionalPat}, database::*}; use actix_web::test; use common::{ api_v3::ApiV3, @@ -36,7 +36,7 @@ async fn test_get_team() { ] { let req = test::TestRequest::get() .uri(&uri) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; @@ -51,7 +51,7 @@ async fn test_get_team() { // - should not appear in the team members list to enemy users let req = test::TestRequest::post() .uri(&format!("/v3/team/{team_id}/members")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(&json!({ "user_id": FRIEND_USER_ID, })) @@ -65,7 +65,7 @@ async fn test_get_team() { ] { let req = test::TestRequest::get() .uri(&uri) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 200); @@ -87,7 +87,7 @@ async fn test_get_team() { let req = test::TestRequest::get() .uri(&uri) - .append_header(("Authorization", ENEMY_USER_PAT)) + .append_pat(ENEMY_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 200); @@ -101,7 +101,7 @@ async fn test_get_team() { // and should be able to see private data about the team let req = test::TestRequest::post() .uri(&format!("/v3/team/{team_id}/join")) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 204); @@ -112,7 +112,7 @@ async fn test_get_team() { ] { let req = test::TestRequest::get() .uri(&uri) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 200); @@ -154,7 +154,7 @@ async fn test_get_team_project_orgs() { // Attach alpha to zeta let req = test::TestRequest::post() .uri(&format!("/v3/organization/{zeta_organization_id}/projects")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "project_id": alpha_project_id, })) @@ -165,7 +165,7 @@ async fn test_get_team_project_orgs() { // Invite and add friend to zeta let req = test::TestRequest::post() .uri(&format!("/v3/team/{zeta_team_id}/members")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "user_id": FRIEND_USER_ID, })) @@ -175,7 +175,7 @@ async fn test_get_team_project_orgs() { let req = test::TestRequest::post() .uri(&format!("/v3/team/{zeta_team_id}/join")) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 204); @@ -185,7 +185,7 @@ async fn test_get_team_project_orgs() { // - not the ones from the organization let req = test::TestRequest::get() .uri(&format!("/v3/team/{alpha_team_id}/members")) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 200); @@ -197,7 +197,7 @@ async fn test_get_team_project_orgs() { // - the members of the project team including the ones from the organization let req = test::TestRequest::get() .uri(&format!("/v3/project/{alpha_project_id}/members")) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 200); @@ -290,7 +290,7 @@ async fn test_patch_organization_team_member() { let req = test::TestRequest::patch() .uri(&format!("/v3/team/{zeta_team_id}/members/{USER_USER_ID}")) .set_json(json!({})) - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 204); @@ -298,7 +298,7 @@ async fn test_patch_organization_team_member() { // As a non-owner with full permissions, attempt to edit the owner's permissions let req = test::TestRequest::patch() .uri(&format!("/v3/team/{zeta_team_id}/members/{USER_USER_ID}")) - .append_header(("Authorization", ADMIN_USER_PAT)) + .append_pat(ADMIN_USER_PAT) .set_json(json!({ "permissions": 0 })) @@ -312,7 +312,7 @@ async fn test_patch_organization_team_member() { // first, invite friend let req = test::TestRequest::post() .uri(&format!("/v3/team/{zeta_team_id}/members")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "user_id": FRIEND_USER_ID, "organization_permissions": (OrganizationPermissions::EDIT_MEMBER | OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS).bits(), @@ -323,7 +323,7 @@ async fn test_patch_organization_team_member() { // accept let req = test::TestRequest::post() .uri(&format!("/v3/team/{zeta_team_id}/join")) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 204); @@ -331,7 +331,7 @@ async fn test_patch_organization_team_member() { // try to add permissions- fails, as we do not have EDIT_DETAILS let req = test::TestRequest::patch() .uri(&format!("/v3/team/{zeta_team_id}/members/{FRIEND_USER_ID}")) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .set_json(json!({ "organization_permissions": (OrganizationPermissions::EDIT_MEMBER | OrganizationPermissions::EDIT_DETAILS).bits() })) @@ -344,7 +344,7 @@ async fn test_patch_organization_team_member() { for payout in [-1, 5001] { let req = test::TestRequest::patch() .uri(&format!("/v3/team/{zeta_team_id}/members/{FRIEND_USER_ID}")) - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_json(json!({ "payouts_split": payout })) @@ -356,7 +356,7 @@ async fn test_patch_organization_team_member() { // Successful patch let req = test::TestRequest::patch() .uri(&format!("/v3/team/{zeta_team_id}/members/{FRIEND_USER_ID}")) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .set_json(json!({ "payouts_split": 51, "organization_permissions": (OrganizationPermissions::EDIT_MEMBER).bits(), // reduces permissions @@ -372,7 +372,7 @@ async fn test_patch_organization_team_member() { // Check results let req = test::TestRequest::get() .uri(&format!("/v3/team/{zeta_team_id}/members")) - .append_header(("Authorization", FRIEND_USER_PAT)) + .append_pat(FRIEND_USER_PAT) .to_request(); let resp = test_env.call(req).await; assert_eq!(resp.status(), 200); diff --git a/tests/v2/project.rs b/tests/v2/project.rs index ad2ee21f..e3f34201 100644 --- a/tests/v2/project.rs +++ b/tests/v2/project.rs @@ -1,8 +1,10 @@ +use std::sync::Arc; + use crate::common::{ - api_common::ApiProject, + api_common::{ApiProject, ApiVersion, AppendsOptionalPat}, api_v2::{ - request_data::{get_public_project_creation_data_json, get_public_version_creation_data}, - ApiV2, + request_data::get_public_project_creation_data_json, + ApiV2 }, database::{ generate_random_name, ADMIN_USER_PAT, FRIEND_USER_ID, FRIEND_USER_PAT, USER_USER_PAT, @@ -96,41 +98,41 @@ async fn test_add_remove_project() { ..json_segment.clone() }; + let basic_mod_file = TestFile::BasicMod; + let basic_mod_different_file = TestFile::BasicModDifferent; + // Basic file let file_segment = MultipartSegment { - name: "basic-mod.jar".to_string(), - filename: Some("basic-mod.jar".to_string()), - content_type: Some("application/java-archive".to_string()), - // TODO: look at these: can be simplified with TestFile - data: MultipartSegmentData::Binary( - include_bytes!("../../tests/files/basic-mod.jar").to_vec(), - ), + // 'Basic' + name: basic_mod_file.filename(), + filename: Some(basic_mod_file.filename()), + content_type: basic_mod_file.content_type(), + data: MultipartSegmentData::Binary(basic_mod_file.bytes()), }; - // Differently named file, with the same content (for hash testing) + // Differently named file, with the SAME content (for hash testing) let file_diff_name_segment = MultipartSegment { - name: "basic-mod-different.jar".to_string(), - filename: Some("basic-mod-different.jar".to_string()), - content_type: Some("application/java-archive".to_string()), - data: MultipartSegmentData::Binary( - include_bytes!("../../tests/files/basic-mod.jar").to_vec(), - ), + // 'Different' + name: basic_mod_different_file.filename(), + filename: Some(basic_mod_different_file.filename()), + content_type: basic_mod_different_file.content_type(), + // 'Basic' + data: MultipartSegmentData::Binary(basic_mod_file.bytes()), }; // Differently named file, with different content let file_diff_name_content_segment = MultipartSegment { - name: "basic-mod-different.jar".to_string(), - filename: Some("basic-mod-different.jar".to_string()), - content_type: Some("application/java-archive".to_string()), - data: MultipartSegmentData::Binary( - include_bytes!("../../tests/files/basic-mod-different.jar").to_vec(), - ), + // 'Different' + name: basic_mod_different_file.filename(), + filename: Some(basic_mod_different_file.filename()), + content_type: basic_mod_different_file.content_type(), + data: MultipartSegmentData::Binary(basic_mod_different_file.bytes()), }; // Add a project- simple, should work. let req = test::TestRequest::post() .uri("/v2/project") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_multipart(vec![json_segment.clone(), file_segment.clone()]) .to_request(); let resp = test_env.call(req).await; @@ -144,7 +146,7 @@ async fn test_add_remove_project() { let uploaded_version_id = project.versions[0]; // Checks files to ensure they were uploaded and correctly identify the file - let hash = sha1::Sha1::from(include_bytes!("../../tests/files/basic-mod.jar")) + let hash = sha1::Sha1::from(basic_mod_file.bytes()) .digest() .to_string(); let version = api @@ -156,7 +158,7 @@ async fn test_add_remove_project() { // Even if that file is named differently let req = test::TestRequest::post() .uri("/v2/project") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_multipart(vec![ json_diff_slug_file_segment.clone(), // Different slug, different file name file_diff_name_segment.clone(), // Different file name, same content @@ -169,7 +171,7 @@ async fn test_add_remove_project() { // Reusing with the same slug and a different file should fail let req = test::TestRequest::post() .uri("/v2/project") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_multipart(vec![ json_diff_file_segment.clone(), // Same slug, different file name file_diff_name_content_segment.clone(), // Different file name, different content @@ -182,7 +184,7 @@ async fn test_add_remove_project() { // Different slug, different file should succeed let req = test::TestRequest::post() .uri("/v2/project") - .append_header(("Authorization", USER_USER_PAT)) + .append_pat(USER_USER_PAT) .set_multipart(vec![ json_diff_slug_file_segment.clone(), // Different slug, different file name file_diff_name_content_segment.clone(), // Different file name, same content @@ -234,23 +236,18 @@ async fn permissions_upload_version() { let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id; let alpha_file_hash = &test_env.dummy.as_ref().unwrap().project_alpha.file_hash; + let api = &test_env.api; + let basic_mod_different_file = TestFile::BasicModDifferent; let upload_version = ProjectPermissions::UPLOAD_VERSION; - // Upload version with basic-mod.jar - let req_gen = |ctx: &PermissionsTestContext| { + let req_gen = |ctx: PermissionsTestContext| async move { let project_id = ctx.project_id.unwrap(); - let project_id = ProjectId(parse_base62(project_id).unwrap()); - let multipart = get_public_version_creation_data( - project_id, - "1.0.0", - TestFile::BasicMod, - None, - None, - ); - test::TestRequest::post() - .uri("/v2/version") - .set_multipart(multipart.segment_data) + let project_id = ProjectId(parse_base62(&project_id).unwrap()); + api.add_public_version(project_id, "1.0.0", TestFile::BasicMod, None, None, ctx.test_pat.as_deref()) + .await }; + + PermissionsTest::new(&test_env) .simple_project_permissions_test(upload_version, req_gen) .await @@ -258,31 +255,12 @@ async fn permissions_upload_version() { // Upload file to existing version // Uses alpha project, as it has an existing version - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::post() - .uri(&format!("/v2/version/{}/file", alpha_version_id)) - .set_multipart([ - MultipartSegment { - name: "data".to_string(), - filename: None, - content_type: Some("application/json".to_string()), - data: MultipartSegmentData::Text( - serde_json::to_string(&json!({ - "file_parts": ["basic-mod-different.jar"], - })) - .unwrap(), - ), - }, - MultipartSegment { - name: "basic-mod-different.jar".to_string(), - filename: Some("basic-mod-different.jar".to_string()), - content_type: Some("application/java-archive".to_string()), - data: MultipartSegmentData::Binary( - include_bytes!("../../tests/files/basic-mod-different.jar").to_vec(), - ), - }, - ]) - }; + let file_ref = Arc::new(basic_mod_different_file); + let req_gen = |ctx: PermissionsTestContext| { + let file_ref = file_ref.clone(); + async move { + api.upload_file_to_version(alpha_version_id, &file_ref, ctx.test_pat.as_deref()).await + }}; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) .with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true) @@ -290,16 +268,16 @@ async fn permissions_upload_version() { .await .unwrap(); + // Patch version // Uses alpha project, as it has an existing version - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v2/version/{}", alpha_version_id)) - .set_json(json!({ - "name": "Basic Mod", - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_version(alpha_version_id, json!({ + "name": "Basic Mod", + }), ctx.test_pat.as_deref()).await }; - PermissionsTest::new(&test_env) + + PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) .with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true) .simple_project_permissions_test(upload_version, req_gen) @@ -309,8 +287,8 @@ async fn permissions_upload_version() { // Delete version file // Uses alpha project, as it has an existing version let delete_version = ProjectPermissions::DELETE_VERSION; - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!("/v2/version_file/{}", alpha_file_hash)) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_version_file(alpha_file_hash, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) @@ -322,8 +300,8 @@ async fn permissions_upload_version() { // Delete version // Uses alpha project, as it has an existing version - let req_gen = |_: &PermissionsTestContext| { - test::TestRequest::delete().uri(&format!("/v2/version/{}", alpha_version_id)) + let req_gen = |ctx: PermissionsTestContext| async move { + api.remove_version(alpha_version_id, ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .with_existing_project(alpha_project_id, alpha_team_id) @@ -374,6 +352,8 @@ pub async fn test_patch_v2() { #[actix_rt::test] async fn permissions_patch_project_v2() { with_test_environment(Some(8), |test_env: TestEnvironment| async move { + let api = &test_env.api; + // TODO: This only includes v2 ones (as it should. See v3) // For each permission covered by EDIT_DETAILS, ensure the permission is required let edit_details = ProjectPermissions::EDIT_DETAILS; @@ -397,16 +377,14 @@ async fn permissions_patch_project_v2() { .map(|(key, value)| { let test_env = test_env.clone(); async move { - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v2/project/{}", ctx.project_id.unwrap())) - .set_json(json!({ - key: if key == "slug" { - json!(generate_random_name("randomslug")) - } else { - value.clone() - }, - })) + let req_gen = |ctx: PermissionsTestContext| async { + api.edit_project(&ctx.project_id.unwrap(), json!({ + key: if key == "slug" { + json!(generate_random_name("randomslug")) + } else { + value.clone() + }, + }), ctx.test_pat.as_deref()).await }; PermissionsTest::new(&test_env) .simple_project_permissions_test(edit_details, req_gen) @@ -421,12 +399,11 @@ async fn permissions_patch_project_v2() { // Edit body // Cannot bulk edit body let edit_body = ProjectPermissions::EDIT_BODY; - let req_gen = |ctx: &PermissionsTestContext| { - test::TestRequest::patch() - .uri(&format!("/v2/project/{}", ctx.project_id.unwrap())) - .set_json(json!({ - "body": "new body!", // new body - })) + let req_gen = |ctx: PermissionsTestContext| async move { + api.edit_project(&ctx.project_id.unwrap(), json!({ + "body": "new body!", // new body + }), ctx.test_pat.as_deref()).await + }; PermissionsTest::new(&test_env) .simple_project_permissions_test(edit_body, req_gen) diff --git a/tests/v2/search.rs b/tests/v2/search.rs index 1598ad3e..d2aff7ef 100644 --- a/tests/v2/search.rs +++ b/tests/v2/search.rs @@ -20,7 +20,7 @@ async fn search_projects() { // Test setup and dummy data with_test_environment(Some(10), |test_env: TestEnvironment| async move { - let api = &test_env.api; + let api = &test_env.api; let test_name = test_env.db.database_name.clone(); // Add dummy projects of various categories for searchability @@ -28,7 +28,7 @@ async fn search_projects() { let create_async_future = |id: u64, - pat: &'static str, + pat: Option<&'static str>, is_modpack: bool, modify_json: Option| { let slug = format!("{test_name}-searchable-project-{id}");