move to monorepo dir

This commit is contained in:
Jai A
2024-10-16 14:11:42 -07:00
parent ff7975773e
commit e3a3379615
756 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
use crate::assert_status;
use crate::common::api_common::ApiProject;
use actix_http::StatusCode;
use actix_web::test;
use bytes::Bytes;
use crate::common::database::USER_USER_PAT;
use crate::common::{
api_v2::ApiV2,
environment::{with_test_environment, TestEnvironment},
};
#[actix_rt::test]
pub async fn error_404_empty() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
// V2 errors should have 404 as blank body, for missing resources
let api = &test_env.api;
let resp = api.get_project("does-not-exist", USER_USER_PAT).await;
assert_status!(&resp, StatusCode::NOT_FOUND);
let body = test::read_body(resp).await;
let empty_bytes = Bytes::from_static(b"");
assert_eq!(body, empty_bytes);
})
.await;
}

View File

@@ -0,0 +1,25 @@
use crate::common::{
api_common::ApiTeams,
api_v2::ApiV2,
database::{FRIEND_USER_ID, FRIEND_USER_PAT, USER_USER_PAT},
environment::{with_test_environment, TestEnvironment},
};
#[actix_rt::test]
pub async fn get_user_notifications_after_team_invitation_returns_notification() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let alpha_team_id = test_env.dummy.project_alpha.team_id.clone();
let api = test_env.api;
api.add_user_to_team(&alpha_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
.await;
let notifications = api
.get_user_notifications_deserialized(FRIEND_USER_ID, FRIEND_USER_PAT)
.await;
assert_eq!(1, notifications.len());
// Check to make sure type_ is correct
assert_eq!(notifications[0].type_.as_ref().unwrap(), "team_invite");
})
.await;
}

View File

@@ -0,0 +1,631 @@
use std::sync::Arc;
use crate::{
assert_status,
common::{
api_common::{ApiProject, ApiVersion, AppendsOptionalPat},
api_v2::{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,
},
dummy_data::TestFile,
environment::{with_test_environment, TestEnvironment},
permissions::{PermissionsTest, PermissionsTestContext},
},
};
use actix_http::StatusCode;
use actix_web::test;
use futures::StreamExt;
use itertools::Itertools;
use labrinth::{
database::models::project_item::PROJECTS_SLUGS_NAMESPACE,
models::{ids::base62_impl::parse_base62, projects::ProjectId, teams::ProjectPermissions},
util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData},
};
use serde_json::json;
#[actix_rt::test]
async fn test_project_type_sanity() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
// Perform all other patch tests on both 'mod' and 'modpack'
for (mod_or_modpack, slug, file) in [
("mod", "test-mod", TestFile::build_random_jar()),
("modpack", "test-modpack", TestFile::build_random_mrpack()),
] {
// Create a modpack or mod
// both are 'fabric' (but modpack is actually 'mrpack' behind the scenes, through v3,with fabric as a 'mrpack_loader')
let (test_project, test_version) = api
.add_public_project(slug, Some(file), None, USER_USER_PAT)
.await;
let test_project_slug = test_project.slug.as_ref().unwrap();
// Check that the loader displays correctly as fabric from the version creation
assert_eq!(test_project.loaders, vec!["fabric"]);
assert_eq!(test_version[0].loaders, vec!["fabric"]);
// Check that the project type is correct when getting the project
let project = api
.get_project_deserialized(test_project_slug, USER_USER_PAT)
.await;
assert_eq!(test_project.loaders, vec!["fabric"]);
assert_eq!(project.project_type, mod_or_modpack);
// Check that the project type is correct when getting the version
let version = api
.get_version_deserialized(&test_version[0].id.to_string(), USER_USER_PAT)
.await;
assert_eq!(
version.loaders.iter().map(|x| &x.0).collect_vec(),
vec!["fabric"]
);
// Edit the version loader to change it to 'forge'
let resp = api
.edit_version(
&test_version[0].id.to_string(),
json!({
"loaders": ["forge"],
}),
USER_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
// Check that the project type is still correct when getting the project
let project = api
.get_project_deserialized(test_project_slug, USER_USER_PAT)
.await;
assert_eq!(project.project_type, mod_or_modpack);
assert_eq!(project.loaders, vec!["forge"]);
// Check that the project type is still correct when getting the version
let version = api
.get_version_deserialized(&test_version[0].id.to_string(), USER_USER_PAT)
.await;
assert_eq!(
version.loaders.iter().map(|x| &x.0).collect_vec(),
vec!["forge"]
);
}
// As we get more complicated strucures with as v3 continues to expand, and alpha/beta get more complicated, we should add more tests here,
// to ensure that projects created with v3 routes are still valid and work with v3 routes.
})
.await;
}
#[actix_rt::test]
async fn test_add_remove_project() {
// Test setup and dummy data
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
// Generate test project data.
let mut json_data =
get_public_project_creation_data_json("demo", Some(&TestFile::BasicMod));
// Basic json
let json_segment = MultipartSegment {
name: "data".to_string(),
filename: None,
content_type: Some("application/json".to_string()),
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
};
// Basic json, with a different file
json_data["initial_versions"][0]["file_parts"][0] = json!("basic-mod-different.jar");
let json_diff_file_segment = MultipartSegment {
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
..json_segment.clone()
};
// Basic json, with a different file, and a different slug
json_data["slug"] = json!("new_demo");
json_data["initial_versions"][0]["file_parts"][0] = json!("basic-mod-different.jar");
let json_diff_slug_file_segment = MultipartSegment {
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 {
// '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)
let file_diff_name_segment = MultipartSegment {
// '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 {
// '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_pat(USER_USER_PAT)
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
.to_request();
let resp: actix_web::dev::ServiceResponse = test_env.call(req).await;
assert_status!(&resp, StatusCode::OK);
// Get the project we just made, and confirm that it's correct
let project = api.get_project_deserialized("demo", USER_USER_PAT).await;
assert!(project.versions.len() == 1);
let uploaded_version_id = project.versions[0];
// Checks files to ensure they were uploaded and correctly identify the file
let hash = sha1::Sha1::from(basic_mod_file.bytes())
.digest()
.to_string();
let version = api
.get_version_from_hash_deserialized(&hash, "sha1", USER_USER_PAT)
.await;
assert_eq!(version.id, uploaded_version_id);
// Reusing with a different slug and the same file should fail
// Even if that file is named differently
let req = test::TestRequest::post()
.uri("/v2/project")
.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
])
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
// Reusing with the same slug and a different file should fail
let req = test::TestRequest::post()
.uri("/v2/project")
.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
])
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
// Different slug, different file should succeed
let req = test::TestRequest::post()
.uri("/v2/project")
.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
])
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::OK);
// Get
let project = api.get_project_deserialized("demo", USER_USER_PAT).await;
let id = project.id.to_string();
// Remove the project
let resp = test_env.api.remove_project("demo", USER_USER_PAT).await;
assert_status!(&resp, StatusCode::NO_CONTENT);
// Confirm that the project is gone from the cache
let mut redis_conn = test_env.db.redis_pool.connect().await.unwrap();
assert_eq!(
redis_conn
.get(PROJECTS_SLUGS_NAMESPACE, "demo")
.await
.unwrap()
.map(|x| x.parse::<i64>().unwrap()),
None
);
assert_eq!(
redis_conn
.get(PROJECTS_SLUGS_NAMESPACE, &id)
.await
.unwrap()
.map(|x| x.parse::<i64>().unwrap()),
None
);
// Old slug no longer works
let resp = api.get_project("demo", USER_USER_PAT).await;
assert_status!(&resp, StatusCode::NOT_FOUND);
})
.await;
}
#[actix_rt::test]
async fn permissions_upload_version() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let alpha_project_id = &test_env.dummy.project_alpha.project_id;
let alpha_version_id = &test_env.dummy.project_alpha.version_id;
let alpha_team_id = &test_env.dummy.project_alpha.team_id;
let alpha_file_hash = &test_env.dummy.project_alpha.file_hash;
let api = &test_env.api;
let basic_mod_different_file = TestFile::BasicModDifferent;
let upload_version = ProjectPermissions::UPLOAD_VERSION;
let req_gen = |ctx: PermissionsTestContext| async move {
let project_id = ctx.project_id.unwrap();
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
.unwrap();
// Upload file to existing version
// Uses alpha project, as it has an existing version
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)
.simple_project_permissions_test(upload_version, req_gen)
.await
.unwrap();
// Patch version
// Uses alpha project, as it has an existing version
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)
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
.simple_project_permissions_test(upload_version, req_gen)
.await
.unwrap();
// Delete version file
// Uses alpha project, as it has an existing version
let delete_version = ProjectPermissions::DELETE_VERSION;
let req_gen = |ctx: PermissionsTestContext| async move {
api.remove_version_file(alpha_file_hash, 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)
.simple_project_permissions_test(delete_version, req_gen)
.await
.unwrap();
// Delete version
// Uses alpha project, as it has an existing version
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)
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
.simple_project_permissions_test(delete_version, req_gen)
.await
.unwrap();
})
.await;
}
#[actix_rt::test]
pub async fn test_patch_v2() {
// Hits V3-specific patchable fields
// Other fields are tested in test_patch_project (the v2 version of that test)
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
let alpha_project_slug = &test_env.dummy.project_alpha.project_slug;
// Sucessful request to patch many fields.
let resp = api
.edit_project(
alpha_project_slug,
json!({
"client_side": "unsupported",
"server_side": "required",
}),
USER_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let project = api
.get_project_deserialized(alpha_project_slug, USER_USER_PAT)
.await;
// Note: the original V2 value of this was "optional",
// but Required/Optional is no longer a carried combination in v3, as the changes made were lossy.
// Now, the test Required/Unsupported combination is tested instead.
// Setting Required/Optional in v2 will not work, this is known and accepteed.
assert_eq!(project.client_side.as_str(), "unsupported");
assert_eq!(project.server_side.as_str(), "required");
})
.await;
}
#[actix_rt::test]
async fn permissions_patch_project_v2() {
with_test_environment(Some(8), |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
// For each permission covered by EDIT_DETAILS, ensure the permission is required
let edit_details = ProjectPermissions::EDIT_DETAILS;
let test_pairs = [
("description", json!("description")),
("issues_url", json!("https://issues.com")),
("source_url", json!("https://source.com")),
("wiki_url", json!("https://wiki.com")),
(
"donation_urls",
json!([{
"id": "paypal",
"platform": "Paypal",
"url": "https://paypal.com"
}]),
),
("discord_url", json!("https://discord.com")),
];
futures::stream::iter(test_pairs)
.map(|(key, value)| {
let test_env = test_env.clone();
async move {
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)
.await
.into_iter();
}
})
.buffer_unordered(4)
.collect::<Vec<_>>()
.await;
// Edit body
// Cannot bulk edit body
let edit_body = ProjectPermissions::EDIT_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)
.await
.unwrap();
})
.await;
}
#[actix_rt::test]
pub async fn test_bulk_edit_links() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
let alpha_project_id: &str = &test_env.dummy.project_alpha.project_id;
let beta_project_id: &str = &test_env.dummy.project_beta.project_id;
let resp = api
.edit_project_bulk(
&[alpha_project_id, beta_project_id],
json!({
"issues_url": "https://github.com",
"donation_urls": [
{
"id": "patreon",
"platform": "Patreon",
"url": "https://www.patreon.com/my_user"
}
],
}),
ADMIN_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let alpha_body = api
.get_project_deserialized(alpha_project_id, ADMIN_USER_PAT)
.await;
let donation_urls = alpha_body.donation_urls.unwrap();
assert_eq!(donation_urls.len(), 1);
assert_eq!(donation_urls[0].url, "https://www.patreon.com/my_user");
assert_eq!(
alpha_body.issues_url,
Some("https://github.com".to_string())
);
assert_eq!(alpha_body.discord_url, None);
let beta_body = api
.get_project_deserialized(beta_project_id, ADMIN_USER_PAT)
.await;
let donation_urls = beta_body.donation_urls.unwrap();
assert_eq!(donation_urls.len(), 1);
assert_eq!(donation_urls[0].url, "https://www.patreon.com/my_user");
assert_eq!(beta_body.issues_url, Some("https://github.com".to_string()));
assert_eq!(beta_body.discord_url, None);
let resp = api
.edit_project_bulk(
&[alpha_project_id, beta_project_id],
json!({
"discord_url": "https://discord.gg",
"issues_url": null,
"add_donation_urls": [
{
"id": "bmac",
"platform": "Buy Me a Coffee",
"url": "https://www.buymeacoffee.com/my_user"
}
],
}),
ADMIN_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let alpha_body = api
.get_project_deserialized(alpha_project_id, ADMIN_USER_PAT)
.await;
let donation_urls = alpha_body
.donation_urls
.unwrap()
.into_iter()
.sorted_by_key(|x| x.id.clone())
.collect_vec();
assert_eq!(donation_urls.len(), 2);
assert_eq!(donation_urls[0].url, "https://www.buymeacoffee.com/my_user");
assert_eq!(donation_urls[1].url, "https://www.patreon.com/my_user");
assert_eq!(alpha_body.issues_url, None);
assert_eq!(
alpha_body.discord_url,
Some("https://discord.gg".to_string())
);
let beta_body = api
.get_project_deserialized(beta_project_id, ADMIN_USER_PAT)
.await;
let donation_urls = beta_body
.donation_urls
.unwrap()
.into_iter()
.sorted_by_key(|x| x.id.clone())
.collect_vec();
assert_eq!(donation_urls.len(), 2);
assert_eq!(donation_urls[0].url, "https://www.buymeacoffee.com/my_user");
assert_eq!(donation_urls[1].url, "https://www.patreon.com/my_user");
assert_eq!(alpha_body.issues_url, None);
assert_eq!(
alpha_body.discord_url,
Some("https://discord.gg".to_string())
);
let resp = api
.edit_project_bulk(
&[alpha_project_id, beta_project_id],
json!({
"donation_urls": [
{
"id": "patreon",
"platform": "Patreon",
"url": "https://www.patreon.com/my_user"
},
{
"id": "ko-fi",
"platform": "Ko-fi",
"url": "https://www.ko-fi.com/my_user"
}
],
"add_donation_urls": [
{
"id": "paypal",
"platform": "PayPal",
"url": "https://www.paypal.com/my_user"
}
],
"remove_donation_urls": [
{
"id": "ko-fi",
"platform": "Ko-fi",
"url": "https://www.ko-fi.com/my_user"
}
],
}),
ADMIN_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let alpha_body = api
.get_project_deserialized(alpha_project_id, ADMIN_USER_PAT)
.await;
let donation_urls = alpha_body
.donation_urls
.unwrap()
.into_iter()
.sorted_by_key(|x| x.id.clone())
.collect_vec();
assert_eq!(donation_urls.len(), 2);
assert_eq!(donation_urls[0].url, "https://www.patreon.com/my_user");
assert_eq!(donation_urls[1].url, "https://www.paypal.com/my_user");
let beta_body = api
.get_project_deserialized(beta_project_id, ADMIN_USER_PAT)
.await;
let donation_urls = beta_body
.donation_urls
.unwrap()
.into_iter()
.sorted_by_key(|x| x.id.clone())
.collect_vec();
assert_eq!(donation_urls.len(), 2);
assert_eq!(donation_urls[0].url, "https://www.patreon.com/my_user");
assert_eq!(donation_urls[1].url, "https://www.paypal.com/my_user");
})
.await;
}

View File

@@ -0,0 +1,81 @@
use crate::common::api_common::ApiProject;
use crate::common::api_common::ApiVersion;
use crate::common::api_v2::request_data::get_public_project_creation_data;
use crate::common::api_v2::ApiV2;
use crate::common::dummy_data::TestFile;
use crate::common::environment::with_test_environment;
use crate::common::environment::TestEnvironment;
use crate::common::scopes::ScopeTest;
use labrinth::models::ids::base62_impl::parse_base62;
use labrinth::models::pats::Scopes;
use labrinth::models::projects::ProjectId;
// Project version creation scopes
#[actix_rt::test]
pub async fn project_version_create_scopes() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
// Create project
let create_project = Scopes::PROJECT_CREATE;
let req_gen = |pat: Option<String>| async move {
let creation_data =
get_public_project_creation_data("demo", Some(TestFile::BasicMod), None);
api.create_project(creation_data, pat.as_deref()).await
};
let (_, success) = ScopeTest::new(&test_env)
.test(req_gen, create_project)
.await
.unwrap();
let project_id = success["id"].as_str().unwrap();
let project_id = ProjectId(parse_base62(project_id).unwrap());
// Add version to project
let create_version = Scopes::VERSION_CREATE;
let req_gen = |pat: Option<String>| async move {
api.add_public_version(
project_id,
"1.2.3.4",
TestFile::BasicModDifferent,
None,
None,
pat.as_deref(),
)
.await
};
ScopeTest::new(&test_env)
.test(req_gen, create_version)
.await
.unwrap();
})
.await;
}
#[actix_rt::test]
pub async fn project_version_reads_scopes() {
with_test_environment(None, |_test_env: TestEnvironment<ApiV2>| async move {
// let api = &test_env.api;
// let beta_file_hash = &test_env.dummy.project_beta.file_hash;
// let read_version = Scopes::VERSION_READ;
// Update individual version file
// TODO: This scope currently fails still as the 'version' field of QueryProject only allows public versions.
// TODO: This will be fixed when the 'extracts_versions' PR is merged.
// let req_gen = |pat : Option<String>| async move {
// api.update_individual_files("sha1", vec![
// FileUpdateData {
// hash: beta_file_hash.clone(),
// loaders: None,
// game_versions: None,
// version_types: None
// }
// ], pat.as_deref())
// .await
// };
// let (failure, success) = ScopeTest::new(&test_env).with_failure_code(200).test(req_gen, read_version).await.unwrap();
// assert!(!failure.as_object().unwrap().contains_key(beta_file_hash));
// assert!(success.as_object().unwrap().contains_key(beta_file_hash));
})
.await;
}

View File

@@ -0,0 +1,408 @@
use crate::assert_status;
use crate::common::api_common::Api;
use crate::common::api_common::ApiProject;
use crate::common::api_common::ApiVersion;
use crate::common::api_v2::ApiV2;
use crate::common::database::*;
use crate::common::dummy_data::TestFile;
use crate::common::dummy_data::DUMMY_CATEGORIES;
use crate::common::environment::with_test_environment;
use crate::common::environment::TestEnvironment;
use actix_http::StatusCode;
use futures::stream::StreamExt;
use labrinth::models::ids::base62_impl::parse_base62;
use serde_json::json;
use std::collections::HashMap;
use std::sync::Arc;
#[actix_rt::test]
async fn search_projects() {
// Test setup and dummy data
with_test_environment(Some(10), |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
let test_name = test_env.db.database_name.clone();
// Add dummy projects of various categories for searchability
let mut project_creation_futures = vec![];
let create_async_future =
|id: u64,
pat: Option<&'static str>,
is_modpack: bool,
modify_json: Option<json_patch::Patch>| {
let slug = format!("{test_name}-searchable-project-{id}");
let jar = if is_modpack {
TestFile::build_random_mrpack()
} else {
TestFile::build_random_jar()
};
async move {
// Add a project- simple, should work.
let req = api.add_public_project(&slug, Some(jar), modify_json, pat);
let (project, _) = req.await;
// Approve, so that the project is searchable
let resp = api
.edit_project(
&project.id.to_string(),
json!({
"status": "approved"
}),
MOD_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
(project.id.0, id)
}
};
// Test project 0
let id = 0;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[4..6] },
{ "op": "add", "path": "/server_side", "value": "required" },
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
USER_USER_PAT,
false,
Some(modify_json),
));
// Test project 1
let id = 1;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..2] },
{ "op": "add", "path": "/client_side", "value": "optional" },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
USER_USER_PAT,
false,
Some(modify_json),
));
// Test project 2
let id = 2;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..2] },
{ "op": "add", "path": "/server_side", "value": "required" },
{ "op": "add", "path": "/title", "value": "Mysterious Project" },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
USER_USER_PAT,
false,
Some(modify_json),
));
// Test project 3
let id = 3;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..3] },
{ "op": "add", "path": "/server_side", "value": "required" },
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.4"] },
{ "op": "add", "path": "/title", "value": "Mysterious Project" },
{ "op": "add", "path": "/license_id", "value": "LicenseRef-All-Rights-Reserved" },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
FRIEND_USER_PAT,
false,
Some(modify_json),
));
// Test project 4
let id = 4;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..3] },
{ "op": "add", "path": "/client_side", "value": "optional" },
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.5"] },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
USER_USER_PAT,
true,
Some(modify_json),
));
// Test project 5
let id = 5;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
{ "op": "add", "path": "/client_side", "value": "optional" },
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.5"] },
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
USER_USER_PAT,
false,
Some(modify_json),
));
// Test project 6
let id = 6;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
{ "op": "add", "path": "/client_side", "value": "optional" },
{ "op": "add", "path": "/server_side", "value": "required" },
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
FRIEND_USER_PAT,
false,
Some(modify_json),
));
// Test project 7 (testing the search bug)
// This project has an initial private forge version that is 1.20.2, and a fabric 1.20.1 version.
// This means that a search for fabric + 1.20.1 or forge + 1.20.1 should not return this project,
// but a search for fabric + 1.20.1 should, and it should include both versions in the data.
let id = 7;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
{ "op": "add", "path": "/client_side", "value": "optional" },
{ "op": "add", "path": "/server_side", "value": "required" },
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
{ "op": "add", "path": "/initial_versions/0/loaders", "value": ["forge"] },
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.2"] },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
USER_USER_PAT,
false,
Some(modify_json),
));
// Test project 8
// Server side unsupported
let id = 8;
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/server_side", "value": "unsupported" },
]))
.unwrap();
project_creation_futures.push(create_async_future(
id,
USER_USER_PAT,
false,
Some(modify_json),
));
// Await all project creation
// Returns a mapping of:
// project id -> test id
let id_conversion: Arc<HashMap<u64, u64>> = Arc::new(
futures::future::join_all(project_creation_futures)
.await
.into_iter()
.collect(),
);
// Create a second version for project 7
let project_7 = api
.get_project_deserialized(&format!("{test_name}-searchable-project-7"), USER_USER_PAT)
.await;
api.add_public_version(
project_7.id,
"1.0.0",
TestFile::build_random_jar(),
None,
None,
USER_USER_PAT,
)
.await;
// Pairs of:
// 1. vec of search facets
// 2. expected project ids to be returned by this search
let pairs = vec![
(
json!([["categories:fabric"]]),
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
),
(json!([["categories:forge"]]), vec![7]),
(
json!([["categories:fabric", "categories:forge"]]),
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
),
(json!([["categories:fabric"], ["categories:forge"]]), vec![]),
(
json!([
["categories:fabric"],
[&format!("categories:{}", DUMMY_CATEGORIES[0])],
]),
vec![1, 2, 3, 4],
),
(json!([["project_types:modpack"]]), vec![4]),
// Formerly included 7, but with v2 changes, this is no longer the case.
// This is because we assume client_side/server_side with subsequent versions.
(json!([["client_side:required"]]), vec![0, 2, 3, 8]),
(json!([["server_side:required"]]), vec![0, 2, 3, 6, 7]),
(json!([["open_source:true"]]), vec![0, 1, 2, 4, 5, 6, 7, 8]),
(json!([["license:MIT"]]), vec![1, 2, 4, 8]),
(json!([[r#"title:'Mysterious Project'"#]]), vec![2, 3]),
(json!([["author:user"]]), vec![0, 1, 2, 4, 5, 7, 8]),
(json!([["versions:1.20.5"]]), vec![4, 5]),
// bug fix
(
json!([
// Only the forge one has 1.20.2, so its true that this project 'has'
// 1.20.2 and a fabric version, but not true that it has a 1.20.2 fabric version.
["categories:fabric"],
["versions:1.20.2"]
]),
vec![],
),
(
json!([
// But it does have a 1.20.2 forge version, so this should return it.
["categories:forge"],
["versions:1.20.2"]
]),
vec![7],
),
// Project type change
// Modpack should still be able to search based on former loader, even though technically the loader is 'mrpack'
// (json!([["categories:mrpack"]]), vec![4]),
// (
// json!([["categories:mrpack"], ["categories:fabric"]]),
// vec![4],
// ),
(
json!([
// ["categories:mrpack"],
["categories:fabric"],
["project_type:modpack"]
]),
vec![4],
),
(
json!([["client_side:optional"], ["server_side:optional"]]),
vec![1, 4, 5],
),
(json!([["server_side:optional"]]), vec![1, 4, 5]),
(json!([["server_side:unsupported"]]), vec![8]),
];
// TODO: Untested:
// - downloads (not varied)
// - color (not varied)
// - created_timestamp (not varied)
// - modified_timestamp (not varied)
// Forcibly reset the search index
let resp = api.reset_search_index().await;
assert_status!(&resp, StatusCode::NO_CONTENT);
// Test searches
let stream = futures::stream::iter(pairs);
stream
.for_each_concurrent(1, |(facets, mut expected_project_ids)| {
let id_conversion = id_conversion.clone();
let test_name = test_name.clone();
async move {
let projects = api
.search_deserialized(
Some(&format!("\"&{test_name}\"")),
Some(facets.clone()),
USER_USER_PAT,
)
.await;
let mut found_project_ids: Vec<u64> = projects
.hits
.into_iter()
.map(|p| id_conversion[&parse_base62(&p.project_id).unwrap()])
.collect();
expected_project_ids.sort();
found_project_ids.sort();
println!("Facets: {:?}", facets);
assert_eq!(found_project_ids, expected_project_ids);
}
})
.await;
// A couple additional tests for the search type returned, making sure it is properly translated back
let client_side_required = api
.search_deserialized(
Some(&format!("\"&{test_name}\"")),
Some(json!([["client_side:required"]])),
USER_USER_PAT,
)
.await;
for hit in client_side_required.hits {
assert_eq!(hit.client_side, "required".to_string());
}
let server_side_required = api
.search_deserialized(
Some(&format!("\"&{test_name}\"")),
Some(json!([["server_side:required"]])),
USER_USER_PAT,
)
.await;
for hit in server_side_required.hits {
assert_eq!(hit.server_side, "required".to_string());
}
let client_side_unsupported = api
.search_deserialized(
Some(&format!("\"&{test_name}\"")),
Some(json!([["client_side:unsupported"]])),
USER_USER_PAT,
)
.await;
for hit in client_side_unsupported.hits {
assert_eq!(hit.client_side, "unsupported".to_string());
}
let client_side_optional_server_side_optional = api
.search_deserialized(
Some(&format!("\"&{test_name}\"")),
Some(json!([["client_side:optional"], ["server_side:optional"]])),
USER_USER_PAT,
)
.await;
for hit in client_side_optional_server_side_optional.hits {
assert_eq!(hit.client_side, "optional".to_string());
assert_eq!(hit.server_side, "optional".to_string());
}
// Ensure game_versions return correctly, but also correctly aggregated
// over all versions of a project
let game_versions = api
.search_deserialized(
Some(&format!("\"&{test_name}\"")),
Some(json!([["categories:forge"], ["versions:1.20.2"]])),
USER_USER_PAT,
)
.await;
assert_eq!(game_versions.hits.len(), 1);
for hit in game_versions.hits {
assert_eq!(
hit.versions,
vec!["1.20.1".to_string(), "1.20.2".to_string()]
);
assert!(hit.categories.contains(&"forge".to_string()));
assert!(hit.categories.contains(&"fabric".to_string()));
assert!(hit.display_categories.contains(&"forge".to_string()));
assert!(hit.display_categories.contains(&"fabric".to_string()));
// Also, ensure author is correctly capitalized
assert_eq!(hit.author, "User".to_string());
}
})
.await;
}

View File

@@ -0,0 +1,107 @@
use itertools::Itertools;
use labrinth::routes::v2::tags::DonationPlatformQueryData;
use std::collections::HashSet;
use crate::common::{
api_v2::ApiV2,
environment::{with_test_environment, TestEnvironment},
};
#[actix_rt::test]
async fn get_tags() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
let game_versions = api.get_game_versions_deserialized().await;
let loaders = api.get_loaders_deserialized().await;
let side_types = api.get_side_types_deserialized().await;
// These tests match dummy data and will need to be updated if the dummy data changes
// Versions should be ordered by:
// - ordering
// - ordering ties settled by date added to database
// - We also expect presentation of NEWEST to OLDEST
// - All null orderings are treated as older than any non-null ordering
// (for this test, the 1.20.1, etc, versions are all null ordering)
let game_version_versions = game_versions
.into_iter()
.map(|x| x.version)
.collect::<Vec<_>>();
assert_eq!(
game_version_versions,
[
"Ordering_Negative1",
"Ordering_Positive100",
"1.20.5",
"1.20.4",
"1.20.3",
"1.20.2",
"1.20.1"
]
.iter()
.map(|s| s.to_string())
.collect_vec()
);
let loader_names = loaders.into_iter().map(|x| x.name).collect::<HashSet<_>>();
assert_eq!(
loader_names,
["fabric", "forge", "bukkit", "waterfall"]
.iter()
.map(|s| s.to_string())
.collect()
);
let side_type_names = side_types.into_iter().collect::<HashSet<_>>();
assert_eq!(
side_type_names,
["unknown", "required", "optional", "unsupported"]
.iter()
.map(|s| s.to_string())
.collect()
);
})
.await;
}
#[actix_rt::test]
async fn get_donation_platforms() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
let mut donation_platforms_unsorted = api.get_donation_platforms_deserialized().await;
// These tests match dummy data and will need to be updated if the dummy data changes
let mut included = vec![
DonationPlatformQueryData {
short: "patreon".to_string(),
name: "Patreon".to_string(),
},
DonationPlatformQueryData {
short: "ko-fi".to_string(),
name: "Ko-fi".to_string(),
},
DonationPlatformQueryData {
short: "paypal".to_string(),
name: "PayPal".to_string(),
},
DonationPlatformQueryData {
short: "bmac".to_string(),
name: "Buy Me A Coffee".to_string(),
},
DonationPlatformQueryData {
short: "github".to_string(),
name: "GitHub Sponsors".to_string(),
},
DonationPlatformQueryData {
short: "other".to_string(),
name: "Other".to_string(),
},
];
included.sort_by(|a, b| a.short.cmp(&b.short));
donation_platforms_unsorted.sort_by(|a, b| a.short.cmp(&b.short));
assert_eq!(donation_platforms_unsorted, included);
})
.await;
}

View File

@@ -0,0 +1,110 @@
use actix_http::StatusCode;
use labrinth::models::teams::ProjectPermissions;
use serde_json::json;
use crate::{
assert_status,
common::{
api_common::ApiTeams,
api_v2::ApiV2,
database::{
FRIEND_USER_ID, FRIEND_USER_ID_PARSED, FRIEND_USER_PAT, USER_USER_ID_PARSED,
USER_USER_PAT,
},
environment::{with_test_environment, TestEnvironment},
},
};
// trasnfer ownership (requires being owner, etc)
#[actix_rt::test]
async fn transfer_ownership_v2() {
// Test setup and dummy data
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
let alpha_team_id = &test_env.dummy.project_alpha.team_id;
// Cannot set friend as owner (not a member)
let resp = api
.transfer_team_ownership(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
.await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
// first, invite friend
let resp = api
.add_user_to_team(alpha_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
// still cannot set friend as owner (not accepted)
let resp = api
.transfer_team_ownership(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
.await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
// accept
let resp = api.join_team(alpha_team_id, FRIEND_USER_PAT).await;
assert_status!(&resp, StatusCode::NO_CONTENT);
// Cannot set ourselves as owner if we are not owner
let resp = api
.transfer_team_ownership(alpha_team_id, FRIEND_USER_ID, FRIEND_USER_PAT)
.await;
assert_status!(&resp, StatusCode::UNAUTHORIZED);
// Can set friend as owner
let resp = api
.transfer_team_ownership(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
// Check
let members = api
.get_team_members_deserialized(alpha_team_id, USER_USER_PAT)
.await;
let friend_member = members
.iter()
.find(|x| x.user.id.0 == FRIEND_USER_ID_PARSED as u64)
.unwrap();
assert_eq!(friend_member.role, "Owner");
assert_eq!(
friend_member.permissions.unwrap(),
ProjectPermissions::all()
);
let user_member = members
.iter()
.find(|x| x.user.id.0 == USER_USER_ID_PARSED as u64)
.unwrap();
assert_eq!(user_member.role, "Member");
assert_eq!(user_member.permissions.unwrap(), ProjectPermissions::all());
// Confirm that user, a user who still has full permissions, cannot then remove the owner
let resp = api
.remove_from_team(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
.await;
assert_status!(&resp, StatusCode::UNAUTHORIZED);
// V2 only- confirm the owner changing the role to member does nothing
let resp = api
.edit_team_member(
alpha_team_id,
FRIEND_USER_ID,
json!({
"role": "Member"
}),
FRIEND_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let members = api
.get_team_members_deserialized(alpha_team_id, USER_USER_PAT)
.await;
let friend_member = members
.iter()
.find(|x| x.user.id.0 == FRIEND_USER_ID_PARSED as u64)
.unwrap();
assert_eq!(friend_member.role, "Owner");
})
.await;
}

View File

@@ -0,0 +1,517 @@
use actix_http::StatusCode;
use actix_web::test;
use futures::StreamExt;
use labrinth::models::projects::VersionId;
use labrinth::{
models::projects::{Loader, VersionStatus, VersionType},
routes::v2::version_file::FileUpdateData,
};
use serde_json::json;
use crate::assert_status;
use crate::common::api_common::{ApiProject, ApiVersion};
use crate::common::api_v2::ApiV2;
use crate::common::api_v2::request_data::get_public_project_creation_data;
use crate::common::dummy_data::{DummyProjectAlpha, DummyProjectBeta};
use crate::common::environment::{with_test_environment, TestEnvironment};
use crate::common::{
database::{ENEMY_USER_PAT, USER_USER_PAT},
dummy_data::TestFile,
};
#[actix_rt::test]
pub async fn test_patch_version() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
let alpha_version_id = &test_env.dummy.project_alpha.version_id;
// // First, we do some patch requests that should fail.
// // Failure because the user is not authorized.
let resp = api
.edit_version(
alpha_version_id,
json!({
"name": "test 1",
}),
ENEMY_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::UNAUTHORIZED);
// Failure because these are illegal requested statuses for a normal user.
for req in ["unknown", "scheduled"] {
let resp = api
.edit_version(
alpha_version_id,
json!({
"status": req,
// requested status it not set here, but in /schedule
}),
USER_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
}
// Sucessful request to patch many fields.
let resp = api
.edit_version(
alpha_version_id,
json!({
"name": "new version name",
"version_number": "1.3.0",
"changelog": "new changelog",
"version_type": "beta",
// // "dependencies": [], TODO: test this
"game_versions": ["1.20.5"],
"loaders": ["forge"],
"featured": false,
// "primary_file": [], TODO: test this
// // "downloads": 0, TODO: moderator exclusive
"status": "draft",
// // "filetypes": ["jar"], TODO: test this
}),
USER_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let version = api
.get_version_deserialized(alpha_version_id, USER_USER_PAT)
.await;
assert_eq!(version.name, "new version name");
assert_eq!(version.version_number, "1.3.0");
assert_eq!(version.changelog, "new changelog");
assert_eq!(
version.version_type,
serde_json::from_str::<VersionType>("\"beta\"").unwrap()
);
assert_eq!(version.game_versions, vec!["1.20.5"]);
assert_eq!(version.loaders, vec![Loader("forge".to_string())]);
assert!(!version.featured);
assert_eq!(version.status, VersionStatus::from_string("draft"));
// These ones are checking the v2-v3 rerouting, we eneusre that only 'game_versions'
// works as expected, as well as only 'loaders'
let resp = api
.edit_version(
alpha_version_id,
json!({
"game_versions": ["1.20.1", "1.20.2", "1.20.4"],
}),
USER_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let version = api
.get_version_deserialized(alpha_version_id, USER_USER_PAT)
.await;
assert_eq!(version.game_versions, vec!["1.20.1", "1.20.2", "1.20.4"]);
assert_eq!(version.loaders, vec![Loader("forge".to_string())]); // From last patch
let resp = api
.edit_version(
alpha_version_id,
json!({
"loaders": ["fabric"],
}),
USER_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let version = api
.get_version_deserialized(alpha_version_id, USER_USER_PAT)
.await;
assert_eq!(version.game_versions, vec!["1.20.1", "1.20.2", "1.20.4"]); // From last patch
assert_eq!(version.loaders, vec![Loader("fabric".to_string())]);
})
.await;
}
#[actix_rt::test]
async fn version_updates() {
// Test setup and dummy data
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
let DummyProjectAlpha {
project_id: alpha_project_id,
project_id_parsed: alpha_project_id_parsed,
version_id: alpha_version_id,
file_hash: alpha_version_hash,
..
} = &test_env.dummy.project_alpha;
let DummyProjectBeta {
version_id: beta_version_id,
file_hash: beta_version_hash,
..
} = &test_env.dummy.project_beta;
// Quick test, using get version from hash
let version = api
.get_version_from_hash_deserialized(alpha_version_hash, "sha1", USER_USER_PAT)
.await;
assert_eq!(&version.id.to_string(), alpha_version_id);
// Get versions from hash
let versions = api
.get_versions_from_hashes_deserialized(
&[alpha_version_hash.as_str(), beta_version_hash.as_str()],
"sha1",
USER_USER_PAT,
)
.await;
assert_eq!(versions.len(), 2);
assert_eq!(
&versions[alpha_version_hash].id.to_string(),
alpha_version_id
);
assert_eq!(&versions[beta_version_hash].id.to_string(), beta_version_id);
// When there is only the one version, there should be no updates
let version = api
.get_update_from_hash_deserialized_common(
alpha_version_hash,
"sha1",
None,
None,
None,
USER_USER_PAT,
)
.await;
assert_eq!(&version.id.to_string(), alpha_version_id);
let versions = api
.update_files_deserialized_common(
"sha1",
vec![alpha_version_hash.to_string()],
None,
None,
None,
USER_USER_PAT,
)
.await;
assert_eq!(versions.len(), 1);
assert_eq!(
&versions[alpha_version_hash].id.to_string(),
alpha_version_id
);
// Add 3 new versions, 1 before, and 2 after, with differing game_version/version_types/loaders
let mut update_ids = vec![];
for (version_number, patch_value) in [
(
"0.9.9",
json!({
"game_versions": ["1.20.1"],
}),
),
(
"1.5.0",
json!({
"game_versions": ["1.20.3"],
"loaders": ["fabric"],
}),
),
(
"1.5.1",
json!({
"game_versions": ["1.20.4"],
"loaders": ["forge"],
"version_type": "beta"
}),
),
]
.iter()
{
let version = api
.add_public_version_deserialized_common(
*alpha_project_id_parsed,
version_number,
TestFile::build_random_jar(),
None,
None,
USER_USER_PAT,
)
.await;
update_ids.push(version.id);
// Patch using json
api.edit_version(&version.id.to_string(), patch_value.clone(), USER_USER_PAT)
.await;
}
let check_expected = |game_versions: Option<Vec<String>>,
loaders: Option<Vec<String>>,
version_types: Option<Vec<String>>,
result_id: Option<VersionId>| async move {
let (success, result_id) = match result_id {
Some(id) => (true, id),
None => (false, VersionId(0)),
};
// get_update_from_hash
let resp = api
.get_update_from_hash(
alpha_version_hash,
"sha1",
loaders.clone(),
game_versions.clone(),
version_types.clone(),
USER_USER_PAT,
)
.await;
if success {
assert_status!(&resp, StatusCode::OK);
let body: serde_json::Value = test::read_body_json(resp).await;
let id = body["id"].as_str().unwrap();
assert_eq!(id, &result_id.to_string());
} else {
assert_status!(&resp, StatusCode::NOT_FOUND);
}
// update_files
let versions = api
.update_files_deserialized_common(
"sha1",
vec![alpha_version_hash.to_string()],
loaders.clone(),
game_versions.clone(),
version_types.clone(),
USER_USER_PAT,
)
.await;
if success {
assert_eq!(versions.len(), 1);
let first = versions.iter().next().unwrap();
assert_eq!(first.1.id, result_id);
} else {
assert_eq!(versions.len(), 0);
}
// update_individual_files
let hashes = vec![FileUpdateData {
hash: alpha_version_hash.to_string(),
loaders,
game_versions,
version_types: version_types.map(|v| {
v.into_iter()
.map(|v| serde_json::from_str(&format!("\"{v}\"")).unwrap())
.collect()
}),
}];
let versions = api
.update_individual_files_deserialized("sha1", hashes, USER_USER_PAT)
.await;
if success {
assert_eq!(versions.len(), 1);
let first = versions.iter().next().unwrap();
assert_eq!(first.1.id, result_id);
} else {
assert_eq!(versions.len(), 0);
}
};
let tests = vec![
check_expected(
Some(vec!["1.20.1".to_string()]),
None,
None,
Some(update_ids[0]),
),
check_expected(
Some(vec!["1.20.3".to_string()]),
None,
None,
Some(update_ids[1]),
),
check_expected(
Some(vec!["1.20.4".to_string()]),
None,
None,
Some(update_ids[2]),
),
// Loader restrictions
check_expected(
None,
Some(vec!["fabric".to_string()]),
None,
Some(update_ids[1]),
),
check_expected(
None,
Some(vec!["forge".to_string()]),
None,
Some(update_ids[2]),
),
// Version type restrictions
check_expected(
None,
None,
Some(vec!["release".to_string()]),
Some(update_ids[1]),
),
check_expected(
None,
None,
Some(vec!["beta".to_string()]),
Some(update_ids[2]),
),
// Specific combination
check_expected(
None,
Some(vec!["fabric".to_string()]),
Some(vec!["release".to_string()]),
Some(update_ids[1]),
),
// Impossible combination
check_expected(
None,
Some(vec!["fabric".to_string()]),
Some(vec!["beta".to_string()]),
None,
),
// No restrictions, should do the last one
check_expected(None, None, None, Some(update_ids[2])),
];
// Wait on all tests, 4 at a time
futures::stream::iter(tests)
.buffer_unordered(4)
.collect::<Vec<_>>()
.await;
// We do a couple small tests for get_project_versions_deserialized as well
// TODO: expand this more.
let versions = api
.get_project_versions_deserialized_common(
alpha_project_id,
None,
None,
None,
None,
None,
None,
USER_USER_PAT,
)
.await;
assert_eq!(versions.len(), 4);
let versions = api
.get_project_versions_deserialized_common(
alpha_project_id,
None,
Some(vec!["forge".to_string()]),
None,
None,
None,
None,
USER_USER_PAT,
)
.await;
assert_eq!(versions.len(), 1);
})
.await;
}
#[actix_rt::test]
async fn add_version_project_types_v2() {
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
// Since v2 no longer keeps project_type at the project level but the version level,
// we have to test that the project_type is set correctly when adding a version, if its done in separate requests.
let api = &test_env.api;
// Create a project in v2 with project_type = modpack, and no initial version set.
let (test_project, test_versions) = api
.add_public_project("test-modpack", None, None, USER_USER_PAT)
.await;
assert_eq!(test_versions.len(), 0); // No initial version set
// Get as v2 project
let test_project = api
.get_project_deserialized(&test_project.slug.unwrap(), USER_USER_PAT)
.await;
assert_eq!(test_project.project_type, "project"); // No project_type set, as no versions are set
// Default to 'project' if none are found
// This is a known difference between older v2 ,but is acceptable.
// This would be the appropriate test on older v2:
// assert_eq!(test_project.project_type, "modpack");
// Create a version with a modpack file attached
let test_version = api
.add_public_version_deserialized_common(
test_project.id,
"1.0.0",
TestFile::build_random_mrpack(),
None,
None,
USER_USER_PAT,
)
.await;
// When we get the version as v2, it should display 'fabric' as the loader (and no project_type)
let test_version = api
.get_version_deserialized(&test_version.id.to_string(), USER_USER_PAT)
.await;
assert_eq!(test_version.loaders, vec![Loader("fabric".to_string())]);
// When we get the project as v2, it should display 'modpack' as the project_type, and 'fabric' as the loader
let test_project = api
.get_project_deserialized(&test_project.slug.unwrap(), USER_USER_PAT)
.await;
assert_eq!(test_project.project_type, "modpack");
assert_eq!(test_project.loaders, vec!["fabric"]);
// When we get the version as v3, it should display 'mrpack' as the loader, and 'modpack' as the project_type
// When we get the project as v3, it should display 'modpack' as the project_type, and 'mrpack' as the loader
// The project should be a modpack project
})
.await;
}
#[actix_rt::test]
async fn test_incorrect_file_parts() {
// Ensures that a version get that 'should' have mrpack_loaders does still display them
// if the file is 'mrpack' but the file_parts are incorrect
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
// Patch to set the file_parts to something incorrect
let patch = json!([{
"op": "add",
"path": "/file_parts",
"value": ["invalid.zip"] // one file, wrong non-mrpack extension
}]);
// Create an empty project
let slug = "test-project";
let creation_data = get_public_project_creation_data(slug, None, None);
let resp = api.create_project(creation_data, USER_USER_PAT).await;
assert_status!(&resp, StatusCode::OK);
// Get the project
let project = api.get_project_deserialized(slug, USER_USER_PAT).await;
assert_eq!(project.project_type, "project");
// Create a version with a mrpack file, but incorrect file_parts
let resp = api
.add_public_version(
project.id,
"1.0.0",
TestFile::build_random_mrpack(),
None,
Some(serde_json::from_value(patch).unwrap()),
USER_USER_PAT,
)
.await;
assert_status!(&resp, StatusCode::OK);
// Get the project now, which should be now correctly identified as a modpack
let project = api.get_project_deserialized(slug, USER_USER_PAT).await;
assert_eq!(project.project_type, "modpack");
assert_eq!(project.loaders, vec!["fabric"]);
})
.await;
}