Tests v2 recreate (#760)

* added common project information; setup for v2 test change

* all tests now use with_test_environment

* progress, failing

* finished re-adding tests

* prepare

* cargo sqlx prepare -- --tests

* fmt; clippy; prepare

* sqlx prepare

* adds version_create fix and corresponding test

* merge fixes; rev

* fmt, clippy, prepare

* test cargo sqlx prepare
This commit is contained in:
Wyatt Verchere
2023-11-25 13:42:39 -08:00
committed by GitHub
parent ade8c162cd
commit 172b93d07f
51 changed files with 7573 additions and 6631 deletions

View File

@@ -0,0 +1,136 @@
use std::collections::HashMap;
use actix_web::dev::ServiceResponse;
use async_trait::async_trait;
use labrinth::{
models::{
projects::{ProjectId, VersionType},
teams::{OrganizationPermissions, ProjectPermissions},
},
search::SearchResults,
};
use crate::common::{api_v2::ApiV2, api_v3::ApiV3, dummy_data::TestFile};
use super::{
models::{CommonImageData, CommonProject, CommonVersion},
Api, ApiProject, ApiTags, ApiTeams, ApiVersion,
};
#[derive(Clone)]
pub enum GenericApi {
V2(ApiV2),
V3(ApiV3),
}
macro_rules! delegate_api_variant {
(
$(#[$meta:meta])*
impl $impl_name:ident for $struct_name:ident {
$(
[$method_name:ident, $ret:ty, $($param_name:ident: $param_type:ty),*]
),* $(,)?
}
) => {
$(#[$meta])*
impl $impl_name for $struct_name {
$(
async fn $method_name(&self, $($param_name: $param_type),*) -> $ret {
match self {
$struct_name::V2(api) => api.$method_name($($param_name),*).await,
$struct_name::V3(api) => api.$method_name($($param_name),*).await,
}
}
)*
}
};
}
#[async_trait(?Send)]
impl Api for GenericApi {
async fn call(&self, req: actix_http::Request) -> ServiceResponse {
match self {
Self::V2(api) => api.call(req).await,
Self::V3(api) => api.call(req).await,
}
}
async fn reset_search_index(&self) -> ServiceResponse {
match self {
Self::V2(api) => api.reset_search_index().await,
Self::V3(api) => api.reset_search_index().await,
}
}
}
delegate_api_variant!(
#[async_trait(?Send)]
impl ApiProject for GenericApi {
[add_public_project, (CommonProject, Vec<CommonVersion>), slug: &str, version_jar: Option<TestFile>, modify_json: Option<json_patch::Patch>, 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<CommonProject>, 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<CommonImageData>, pat: &str],
[search_deserialized_common, SearchResults, query: Option<&str>, facets: Option<serde_json::Value>, pat: &str],
}
);
delegate_api_variant!(
#[async_trait(?Send)]
impl ApiTags for GenericApi {
[get_loaders, ServiceResponse,],
[get_loaders_deserialized_common, Vec<crate::common::api_common::models::CommonLoaderData>,],
[get_categories, ServiceResponse,],
[get_categories_deserialized_common, Vec<crate::common::api_common::models::CommonCategoryData>,],
}
);
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<crate::common::api_common::models::CommonTeamMember>, team_id: &str, pat: &str],
[get_project_members, ServiceResponse, id_or_slug: &str, pat: &str],
[get_project_members_deserialized_common, Vec<crate::common::api_common::models::CommonTeamMember>, id_or_slug: &str, pat: &str],
[get_organization_members, ServiceResponse, id_or_title: &str, pat: &str],
[get_organization_members_deserialized_common, Vec<crate::common::api_common::models::CommonTeamMember>, 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<crate::common::api_common::models::CommonNotification>, 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<ProjectPermissions>, organization_permissions: Option<OrganizationPermissions>, pat: &str],
[delete_notification, ServiceResponse, notification_id: &str, pat: &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<i32>, modify_json: Option<json_patch::Patch>, pat: &str],
[add_public_version_deserialized_common, CommonVersion, project_id: ProjectId, version_number: &str, version_jar: TestFile, ordering: Option<i32>, modify_json: Option<json_patch::Patch>, 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<String>, pat: &str],
[get_versions_deserialized_common, Vec<CommonVersion>, ids_or_slugs: Vec<String>, 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<String, CommonVersion>, hashes: &[&str], algorithm: &str, pat: &str],
[get_update_from_hash, ServiceResponse, hash: &str, algorithm: &str, loaders: Option<Vec<String>>,game_versions: Option<Vec<String>>, version_types: Option<Vec<String>>, pat: &str],
[get_update_from_hash_deserialized_common, CommonVersion, hash: &str, algorithm: &str,loaders: Option<Vec<String>>,game_versions: Option<Vec<String>>,version_types: Option<Vec<String>>, pat: &str],
[update_files, ServiceResponse, algorithm: &str, hashes: Vec<String>, loaders: Option<Vec<String>>, game_versions: Option<Vec<String>>, version_types: Option<Vec<String>>, pat: &str],
[update_files_deserialized_common, HashMap<String, CommonVersion>, algorithm: &str, hashes: Vec<String>, loaders: Option<Vec<String>>, game_versions: Option<Vec<String>>, version_types: Option<Vec<String>>, pat: &str],
[get_project_versions, ServiceResponse, project_id_slug: &str, game_versions: Option<Vec<String>>,loaders: Option<Vec<String>>,featured: Option<bool>, version_type: Option<VersionType>, limit: Option<usize>, offset: Option<usize>,pat: &str],
[get_project_versions_deserialized_common, Vec<CommonVersion>, project_id_slug: &str, game_versions: Option<Vec<String>>, loaders: Option<Vec<String>>,featured: Option<bool>,version_type: Option<VersionType>,limit: Option<usize>,offset: Option<usize>,pat: &str],
[edit_version_ordering, ServiceResponse, version_id: &str,ordering: Option<i32>,pat: &str],
}
);

View File

@@ -0,0 +1,262 @@
use std::collections::HashMap;
use self::models::{
CommonCategoryData, CommonImageData, CommonLoaderData, CommonNotification, CommonProject,
CommonTeamMember, CommonVersion,
};
use actix_web::dev::ServiceResponse;
use async_trait::async_trait;
use labrinth::{
models::{
projects::{ProjectId, VersionType},
teams::{OrganizationPermissions, ProjectPermissions},
},
search::SearchResults,
LabrinthConfig,
};
use super::dummy_data::TestFile;
pub mod generic;
pub mod models;
#[async_trait(?Send)]
pub trait ApiBuildable: Api {
async fn build(labrinth_config: LabrinthConfig) -> Self;
}
#[async_trait(?Send)]
pub trait Api: ApiProject + ApiTags + ApiTeams + ApiVersion {
async fn call(&self, req: actix_http::Request) -> ServiceResponse;
async fn reset_search_index(&self) -> ServiceResponse;
}
#[async_trait(?Send)]
pub trait ApiProject {
async fn add_public_project(
&self,
slug: &str,
version_jar: Option<TestFile>,
modify_json: Option<json_patch::Patch>,
pat: &str,
) -> (CommonProject, Vec<CommonVersion>);
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 get_user_projects_deserialized_common(
&self,
user_id_or_username: &str,
pat: &str,
) -> Vec<CommonProject>;
async fn edit_project(
&self,
id_or_slug: &str,
patch: serde_json::Value,
pat: &str,
) -> ServiceResponse;
async fn edit_project_bulk(
&self,
ids_or_slugs: &[&str],
patch: serde_json::Value,
pat: &str,
) -> ServiceResponse;
async fn edit_project_icon(
&self,
id_or_slug: &str,
icon: Option<CommonImageData>,
pat: &str,
) -> ServiceResponse;
async fn search_deserialized_common(
&self,
query: Option<&str>,
facets: Option<serde_json::Value>,
pat: &str,
) -> SearchResults;
}
#[async_trait(?Send)]
pub trait ApiTags {
async fn get_loaders(&self) -> ServiceResponse;
async fn get_loaders_deserialized_common(&self) -> Vec<CommonLoaderData>;
async fn get_categories(&self) -> ServiceResponse;
async fn get_categories_deserialized_common(&self) -> Vec<CommonCategoryData>;
}
#[async_trait(?Send)]
pub trait ApiTeams {
async fn get_team_members(&self, team_id: &str, pat: &str) -> ServiceResponse;
async fn get_team_members_deserialized_common(
&self,
team_id: &str,
pat: &str,
) -> Vec<CommonTeamMember>;
async fn get_project_members(&self, id_or_slug: &str, pat: &str) -> ServiceResponse;
async fn get_project_members_deserialized_common(
&self,
id_or_slug: &str,
pat: &str,
) -> Vec<CommonTeamMember>;
async fn get_organization_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse;
async fn get_organization_members_deserialized_common(
&self,
id_or_title: &str,
pat: &str,
) -> Vec<CommonTeamMember>;
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 edit_team_member(
&self,
team_id: &str,
user_id: &str,
patch: serde_json::Value,
pat: &str,
) -> ServiceResponse;
async fn transfer_team_ownership(
&self,
team_id: &str,
user_id: &str,
pat: &str,
) -> ServiceResponse;
async fn get_user_notifications(&self, user_id: &str, pat: &str) -> ServiceResponse;
async fn get_user_notifications_deserialized_common(
&self,
user_id: &str,
pat: &str,
) -> Vec<CommonNotification>;
async fn mark_notification_read(&self, notification_id: &str, pat: &str) -> ServiceResponse;
async fn add_user_to_team(
&self,
team_id: &str,
user_id: &str,
project_permissions: Option<ProjectPermissions>,
organization_permissions: Option<OrganizationPermissions>,
pat: &str,
) -> ServiceResponse;
async fn delete_notification(&self, notification_id: &str, pat: &str) -> ServiceResponse;
}
#[async_trait(?Send)]
pub trait ApiVersion {
async fn add_public_version(
&self,
project_id: ProjectId,
version_number: &str,
version_jar: TestFile,
ordering: Option<i32>,
modify_json: Option<json_patch::Patch>,
pat: &str,
) -> ServiceResponse;
async fn add_public_version_deserialized_common(
&self,
project_id: ProjectId,
version_number: &str,
version_jar: TestFile,
ordering: Option<i32>,
modify_json: Option<json_patch::Patch>,
pat: &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<String>, pat: &str) -> ServiceResponse;
async fn get_versions_deserialized_common(
&self,
ids_or_slugs: Vec<String>,
pat: &str,
) -> Vec<CommonVersion>;
async fn edit_version(
&self,
id_or_slug: &str,
patch: serde_json::Value,
pat: &str,
) -> ServiceResponse;
async fn get_version_from_hash(
&self,
id_or_slug: &str,
hash: &str,
pat: &str,
) -> ServiceResponse;
async fn get_version_from_hash_deserialized_common(
&self,
id_or_slug: &str,
hash: &str,
pat: &str,
) -> CommonVersion;
async fn get_versions_from_hashes(
&self,
hashes: &[&str],
algorithm: &str,
pat: &str,
) -> ServiceResponse;
async fn get_versions_from_hashes_deserialized_common(
&self,
hashes: &[&str],
algorithm: &str,
pat: &str,
) -> HashMap<String, CommonVersion>;
async fn get_update_from_hash(
&self,
hash: &str,
algorithm: &str,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> ServiceResponse;
async fn get_update_from_hash_deserialized_common(
&self,
hash: &str,
algorithm: &str,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> CommonVersion;
async fn update_files(
&self,
algorithm: &str,
hashes: Vec<String>,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> ServiceResponse;
async fn update_files_deserialized_common(
&self,
algorithm: &str,
hashes: Vec<String>,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> HashMap<String, CommonVersion>;
#[allow(clippy::too_many_arguments)]
async fn get_project_versions(
&self,
project_id_slug: &str,
game_versions: Option<Vec<String>>,
loaders: Option<Vec<String>>,
featured: Option<bool>,
version_type: Option<VersionType>,
limit: Option<usize>,
offset: Option<usize>,
pat: &str,
) -> ServiceResponse;
#[allow(clippy::too_many_arguments)]
async fn get_project_versions_deserialized_common(
&self,
slug: &str,
game_versions: Option<Vec<String>>,
loaders: Option<Vec<String>>,
featured: Option<bool>,
version_type: Option<VersionType>,
limit: Option<usize>,
offset: Option<usize>,
pat: &str,
) -> Vec<CommonVersion>;
async fn edit_version_ordering(
&self,
version_id: &str,
ordering: Option<i32>,
pat: &str,
) -> ServiceResponse;
}

View File

@@ -0,0 +1,141 @@
use chrono::{DateTime, Utc};
use labrinth::models::{
notifications::{NotificationAction, NotificationBody, NotificationId},
organizations::OrganizationId,
projects::{
Dependency, DonationLink, GalleryItem, License, ModeratorMessage, MonetizationStatus,
ProjectId, ProjectStatus, VersionFile, VersionId, VersionStatus, VersionType,
},
teams::{OrganizationPermissions, ProjectPermissions, TeamId},
threads::ThreadId,
users::{User, UserId},
};
use rust_decimal::Decimal;
use serde::Deserialize;
// Fields shared by every version of the API.
// No struct in here should have ANY field that
// is not present in *every* version of the API.
// These are used for common tests- tests that can be used on both V2 AND v3 of the API and have the same results.
// Any test that requires version-specific fields should have its own test that is not done for each version,
// as the environment generator for both uses common fields.
#[derive(Deserialize)]
pub struct CommonProject {
// For example, for CommonProject, we do not include:
// - game_versions (v2 only)
// - loader_fields (v3 only)
// - etc.
// For any tests that require those fields, we make a separate test with separate API functions tht do not use Common models.
pub id: ProjectId,
pub slug: Option<String>,
pub team: TeamId,
pub organization: Option<OrganizationId>,
pub title: String,
pub description: String,
pub body: String,
pub body_url: Option<String>,
pub published: DateTime<Utc>,
pub updated: DateTime<Utc>,
pub approved: Option<DateTime<Utc>>,
pub queued: Option<DateTime<Utc>>,
pub status: ProjectStatus,
pub requested_status: Option<ProjectStatus>,
pub moderator_message: Option<ModeratorMessage>,
pub license: License,
pub downloads: u32,
pub followers: u32,
pub categories: Vec<String>,
pub additional_categories: Vec<String>,
pub loaders: Vec<String>,
pub versions: Vec<VersionId>,
pub icon_url: Option<String>,
pub issues_url: Option<String>,
pub source_url: Option<String>,
pub wiki_url: Option<String>,
pub discord_url: Option<String>,
pub donation_urls: Option<Vec<DonationLink>>,
pub gallery: Vec<GalleryItem>,
pub color: Option<u32>,
pub thread_id: ThreadId,
pub monetization_status: MonetizationStatus,
}
#[derive(Deserialize, Clone)]
pub struct CommonVersion {
pub id: VersionId,
pub loaders: Vec<String>,
pub project_id: ProjectId,
pub author_id: UserId,
pub featured: bool,
pub name: String,
pub version_number: String,
pub changelog: String,
pub changelog_url: Option<String>,
pub date_published: DateTime<Utc>,
pub downloads: u32,
pub version_type: VersionType,
pub status: VersionStatus,
pub requested_status: Option<VersionStatus>,
pub files: Vec<VersionFile>,
pub dependencies: Vec<Dependency>,
// TODO: should ordering be in v2?
pub ordering: Option<i32>,
}
#[derive(Deserialize)]
pub struct CommonImageData {
pub filename: String,
pub extension: String,
pub icon: Vec<u8>,
}
#[derive(Deserialize)]
pub struct CommonLoaderData {
pub icon: String,
pub name: String,
pub supported_project_types: Vec<String>,
}
#[derive(Deserialize)]
pub struct CommonCategoryData {
pub icon: String,
pub name: String,
pub project_type: String,
pub header: String,
}
/// A member of a team
#[derive(Deserialize)]
pub struct CommonTeamMember {
pub team_id: TeamId,
pub user: User,
pub role: String,
// TODO: Should these be removed from the Common?
pub permissions: Option<ProjectPermissions>,
pub organization_permissions: Option<OrganizationPermissions>,
pub accepted: bool,
pub payouts_split: Option<Decimal>,
pub ordering: i64,
}
#[derive(Deserialize)]
pub struct CommonNotification {
pub id: NotificationId,
pub user_id: UserId,
pub read: bool,
pub created: DateTime<Utc>,
pub body: NotificationBody,
// DEPRECATED: use body field instead
#[serde(rename = "type")]
pub type_: Option<String>,
pub title: String,
pub text: String,
pub link: String,
pub actions: Vec<NotificationAction>,
}