* chore(clippy): enable and fix many stricter lints These ensure that the codebase uses more idiomatic, performant, and concise language constructions. * chore: make non-Clippy compiler warnings also deny by default
1236 lines
42 KiB
Rust
1236 lines
42 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use crate::common::api_common::{
|
|
ApiProject, ApiTeams, ApiUser, ApiVersion, AppendsOptionalPat,
|
|
};
|
|
use crate::common::dummy_data::{
|
|
DummyImage, DummyProjectAlpha, DummyProjectBeta,
|
|
};
|
|
use actix_http::StatusCode;
|
|
use actix_web::test;
|
|
use ariadne::ids::UserId;
|
|
use ariadne::ids::base62_impl::parse_base62;
|
|
use chrono::{Duration, Utc};
|
|
use common::api_common::Api;
|
|
use common::api_common::models::CommonItemType;
|
|
use common::api_v3::ApiV3;
|
|
use common::api_v3::request_data::get_public_project_creation_data;
|
|
use common::dummy_data::TestFile;
|
|
use common::environment::{
|
|
TestEnvironment, with_test_environment, with_test_environment_all,
|
|
};
|
|
use common::{database::*, scopes::ScopeTest};
|
|
use labrinth::models::ids::ProjectId;
|
|
use labrinth::models::pats::Scopes;
|
|
use serde_json::json;
|
|
// For each scope, we (using test_scope):
|
|
// - create a PAT with a given set of scopes for a function
|
|
// - create a PAT with all other scopes for a function
|
|
// - test the function with the PAT with the given scopes
|
|
// - test the function with the PAT with all other scopes
|
|
|
|
pub mod common;
|
|
|
|
// Test for users, emails, and payout scopes (not user auth scope or notifs)
|
|
#[actix_rt::test]
|
|
async fn user_scopes() {
|
|
// Test setup and dummy data
|
|
with_test_environment_all(None, |test_env| async move {
|
|
let api = &test_env.api;
|
|
// User reading
|
|
let read_user = Scopes::USER_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_current_user(pat.as_deref()).await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.test(req_gen, read_user)
|
|
.await
|
|
.unwrap();
|
|
assert!(success["email"].as_str().is_none()); // email should not be present
|
|
assert!(success["payout_data"].as_object().is_none()); // payout should not be present
|
|
|
|
// Email reading
|
|
let read_email = Scopes::USER_READ | Scopes::USER_READ_EMAIL;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_current_user(pat.as_deref()).await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.test(req_gen, read_email)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(success["email"], json!("user@modrinth.com")); // email should be present
|
|
|
|
// Payout reading
|
|
let read_payout = Scopes::USER_READ | Scopes::PAYOUTS_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_current_user(pat.as_deref()).await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.test(req_gen, read_payout)
|
|
.await
|
|
.unwrap();
|
|
assert!(success["payout_data"].as_object().is_some()); // payout should be present
|
|
|
|
// User writing
|
|
// We use the Admin PAT for this test, on the 'user' user
|
|
let write_user = Scopes::USER_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_user(
|
|
"user",
|
|
json!( {
|
|
// Do not include 'username', as to not change the rest of the tests
|
|
"name": "NewName",
|
|
"bio": "New bio",
|
|
"location": "New location",
|
|
"role": "admin",
|
|
"badges": 5,
|
|
// Do not include payout info, different scope
|
|
}),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(ADMIN_USER_ID_PARSED)
|
|
.test(req_gen, write_user)
|
|
.await
|
|
.unwrap();
|
|
|
|
// User deletion
|
|
// (The failure is first, and this is the last test for this test function, we can delete it and use the same PAT for both tests)
|
|
let delete_user = Scopes::USER_DELETE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.delete_user("enemy", pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(ENEMY_USER_ID_PARSED)
|
|
.test(req_gen, delete_user)
|
|
.await
|
|
.unwrap();
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Notifications
|
|
#[actix_rt::test]
|
|
pub async fn notifications_scopes() {
|
|
with_test_environment_all(None, |test_env| async move {
|
|
let api = &test_env.api;
|
|
let alpha_team_id = &test_env.dummy.project_alpha.team_id;
|
|
|
|
// We will invite user 'friend' to project team, and use that as a notification
|
|
// Get notifications
|
|
let resp = test_env
|
|
.api
|
|
.add_user_to_team(
|
|
alpha_team_id,
|
|
FRIEND_USER_ID,
|
|
None,
|
|
None,
|
|
USER_USER_PAT,
|
|
)
|
|
.await;
|
|
assert_status!(&resp, StatusCode::NO_CONTENT);
|
|
|
|
// Notification get
|
|
let read_notifications = Scopes::NOTIFICATION_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_user_notifications(FRIEND_USER_ID, pat.as_deref())
|
|
.await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.with_user_id(FRIEND_USER_ID_PARSED)
|
|
.test(req_gen, read_notifications)
|
|
.await
|
|
.unwrap();
|
|
let notification_id = success[0]["id"].as_str().unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_notifications(&[notification_id], pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(FRIEND_USER_ID_PARSED)
|
|
.test(req_gen, read_notifications)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_notification(notification_id, pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(FRIEND_USER_ID_PARSED)
|
|
.test(req_gen, read_notifications)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Notification mark as read
|
|
let write_notifications = Scopes::NOTIFICATION_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.mark_notifications_read(&[notification_id], pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(FRIEND_USER_ID_PARSED)
|
|
.test(req_gen, write_notifications)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.mark_notification_read(notification_id, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(FRIEND_USER_ID_PARSED)
|
|
.test(req_gen, write_notifications)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Notification delete
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.delete_notification(notification_id, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(FRIEND_USER_ID_PARSED)
|
|
.test(req_gen, write_notifications)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Mass notification delete
|
|
// We invite mod, get the notification ID, and do mass delete using that
|
|
let resp = test_env
|
|
.api
|
|
.add_user_to_team(
|
|
alpha_team_id,
|
|
MOD_USER_ID,
|
|
None,
|
|
None,
|
|
USER_USER_PAT,
|
|
)
|
|
.await;
|
|
assert_status!(&resp, StatusCode::NO_CONTENT);
|
|
let read_notifications = Scopes::NOTIFICATION_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_user_notifications(MOD_USER_ID, pat.as_deref())
|
|
.await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.with_user_id(MOD_USER_ID_PARSED)
|
|
.test(req_gen, read_notifications)
|
|
.await
|
|
.unwrap();
|
|
let notification_id = success[0]["id"].as_str().unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.delete_notifications(&[notification_id], pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(MOD_USER_ID_PARSED)
|
|
.test(req_gen, write_notifications)
|
|
.await
|
|
.unwrap();
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Project version creation scopes
|
|
#[actix_rt::test]
|
|
pub async fn project_version_create_scopes_v3() {
|
|
with_test_environment(
|
|
None,
|
|
|test_env: TestEnvironment<ApiV3>| 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;
|
|
}
|
|
|
|
// Project management scopes
|
|
#[actix_rt::test]
|
|
pub async fn project_version_reads_scopes() {
|
|
with_test_environment_all(None, |test_env| async move {
|
|
let api = &test_env.api;
|
|
let DummyProjectAlpha {
|
|
team_id: alpha_team_id,
|
|
..
|
|
} = &test_env.dummy.project_alpha;
|
|
let DummyProjectBeta {
|
|
project_id: beta_project_id,
|
|
version_id: beta_version_id,
|
|
file_hash: beta_file_hash,
|
|
..
|
|
} = &test_env.dummy.project_beta;
|
|
|
|
// Project reading
|
|
// Uses 404 as the expected failure code (or 200 and an empty list for mass reads)
|
|
let read_project = Scopes::PROJECT_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_project(beta_project_id, pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_code(404)
|
|
.test(req_gen, read_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_project_dependencies(beta_project_id, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_code(404)
|
|
.test(req_gen, read_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_projects(&[beta_project_id.as_str()], pat.as_deref())
|
|
.await
|
|
};
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.test(req_gen, read_project)
|
|
.await
|
|
.unwrap();
|
|
assert!(failure.as_array().unwrap().is_empty());
|
|
assert!(!success.as_array().unwrap().is_empty());
|
|
|
|
// Team project reading
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_project_members(beta_project_id, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_code(404)
|
|
.test(req_gen, read_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Get team members
|
|
// In this case, as these are public endpoints, logging in only is relevant to showing permissions
|
|
// So for our test project (with 1 user, 'user') we will check the permissions before and after having the scope.
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_team_members(alpha_team_id, pat.as_deref()).await
|
|
};
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.test(req_gen, read_project)
|
|
.await
|
|
.unwrap();
|
|
assert!(!failure[0]["permissions"].is_number());
|
|
assert!(success[0]["permissions"].is_number());
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_teams_members(&[alpha_team_id.as_str()], pat.as_deref())
|
|
.await
|
|
};
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.test(req_gen, read_project)
|
|
.await
|
|
.unwrap();
|
|
assert!(!failure[0][0]["permissions"].is_number());
|
|
assert!(success[0][0]["permissions"].is_number());
|
|
|
|
// User project reading
|
|
// Test user has two projects, one public and one private
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_user_projects(USER_USER_ID, pat.as_deref()).await
|
|
};
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.test(req_gen, read_project)
|
|
.await
|
|
.unwrap();
|
|
assert!(
|
|
!failure
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|x| x["status"] == "processing")
|
|
);
|
|
assert!(
|
|
success
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|x| x["status"] == "processing")
|
|
);
|
|
|
|
// Project metadata reading
|
|
let req_gen = |pat: Option<String>| async move {
|
|
let req = test::TestRequest::get()
|
|
.uri(&format!(
|
|
"/maven/maven/modrinth/{beta_project_id}/maven-metadata.xml"
|
|
))
|
|
.append_pat(pat.as_deref())
|
|
.to_request();
|
|
api.call(req).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_code(404)
|
|
.test(req_gen, read_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Version reading
|
|
// First, set version to hidden (which is when the scope is required to read it)
|
|
let read_version = Scopes::VERSION_READ;
|
|
let resp = test_env
|
|
.api
|
|
.edit_version(
|
|
beta_version_id,
|
|
json!({ "status": "draft" }),
|
|
USER_USER_PAT,
|
|
)
|
|
.await;
|
|
assert_status!(&resp, StatusCode::NO_CONTENT);
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_version_from_hash(beta_file_hash, "sha1", pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_code(404)
|
|
.test(req_gen, read_version)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.download_version_redirect(
|
|
beta_file_hash,
|
|
"sha1",
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_code(404)
|
|
.test(req_gen, read_version)
|
|
.await
|
|
.unwrap();
|
|
|
|
// 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.get_update_from_hash(beta_file_hash, "sha1", None, None, None, pat.as_deref())
|
|
// .await
|
|
// };
|
|
// ScopeTest::new(&test_env).with_failure_code(404).test(req_gen, read_version).await.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_versions_from_hashes(
|
|
&[beta_file_hash],
|
|
"sha1",
|
|
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));
|
|
|
|
// Update 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_files("sha1", vec![beta_file_hash.clone()], None, None, 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));
|
|
|
|
// Both project and version reading
|
|
let read_project_and_version =
|
|
Scopes::PROJECT_READ | Scopes::VERSION_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_project_versions(
|
|
beta_project_id,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_code(404)
|
|
.test(req_gen, read_project_and_version)
|
|
.await
|
|
.unwrap();
|
|
|
|
// TODO: fails for the same reason as above
|
|
// let req_gen = || {
|
|
// test::TestRequest::get()
|
|
// .uri(&format!("/v3/project/{beta_project_id}/version/{beta_version_id}"))
|
|
// };
|
|
// ScopeTest::new(&test_env).with_failure_code(404).test(req_gen, read_project_and_version).await.unwrap();
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Project writing
|
|
#[actix_rt::test]
|
|
pub async fn project_write_scopes() {
|
|
// Test setup and dummy data
|
|
with_test_environment_all(None, |test_env| async move {
|
|
let api = &test_env.api;
|
|
let beta_project_id = &test_env.dummy.project_beta.project_id;
|
|
let alpha_team_id = &test_env.dummy.project_alpha.team_id;
|
|
|
|
// Projects writing
|
|
let write_project = Scopes::PROJECT_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_project(
|
|
beta_project_id,
|
|
json!(
|
|
{
|
|
"name": "test_project_version_write_scopes Title",
|
|
}
|
|
),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_project_bulk(
|
|
&[beta_project_id.as_str()],
|
|
json!(
|
|
{
|
|
"description": "test_project_version_write_scopes Description"
|
|
}),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Icons and gallery images
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_project_icon(
|
|
beta_project_id,
|
|
Some(DummyImage::SmallIcon.get_icon_data()),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_project_icon(beta_project_id, None, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.add_gallery_item(
|
|
beta_project_id,
|
|
DummyImage::SmallIcon.get_icon_data(),
|
|
true,
|
|
None,
|
|
None,
|
|
None,
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Get project, as we need the gallery image url
|
|
let resp = api.get_project(beta_project_id, USER_USER_PAT).await;
|
|
let project: serde_json::Value = test::read_body_json(resp).await;
|
|
let gallery_url = project["gallery"][0]["url"].as_str().unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_gallery_item(beta_project_id, gallery_url, HashMap::new(), pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.remove_gallery_item(beta_project_id, gallery_url, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Team scopes - add user 'friend'
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.add_user_to_team(alpha_team_id, FRIEND_USER_ID, None, None, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Accept team invite as 'friend'
|
|
let req_gen =
|
|
|pat: Option<String>| async move { api.join_team(alpha_team_id, pat.as_deref()).await };
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(FRIEND_USER_ID_PARSED)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Patch 'friend' user
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_team_member(
|
|
alpha_team_id,
|
|
FRIEND_USER_ID,
|
|
json!({
|
|
"permissions": 1
|
|
}),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Transfer ownership to 'friend'
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.transfer_team_ownership(alpha_team_id, FRIEND_USER_ID, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Now as 'friend', delete 'user'
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.remove_from_team(alpha_team_id, USER_USER_ID, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(FRIEND_USER_ID_PARSED)
|
|
.test(req_gen, write_project)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Delete project
|
|
// TODO: this route is currently broken,
|
|
// because the Project::get_id contained within Project::remove doesnt include hidden versions, meaning that if there
|
|
// is a hidden version, it will fail to delete the project (with a 500 error, as the versions of a project are not all deleted)
|
|
// let delete_version = Scopes::PROJECT_DELETE;
|
|
// let req_gen = || {
|
|
// test::TestRequest::delete()
|
|
// .uri(&format!("/v3/project/{beta_project_id}"))
|
|
// };
|
|
// ScopeTest::new(&test_env).test(req_gen, delete_version).await.unwrap();
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Version write
|
|
#[actix_rt::test]
|
|
pub async fn version_write_scopes() {
|
|
// Test setup and dummy data
|
|
with_test_environment_all(None, |test_env| async move {
|
|
let api = &test_env.api;
|
|
let DummyProjectAlpha {
|
|
version_id: alpha_version_id,
|
|
file_hash: alpha_file_hash,
|
|
..
|
|
} = &test_env.dummy.project_alpha;
|
|
|
|
let write_version = Scopes::VERSION_WRITE;
|
|
|
|
// Patch version
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_version(
|
|
alpha_version_id,
|
|
json!(
|
|
{
|
|
"name": "test_version_write_scopes Title",
|
|
}
|
|
),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_version)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Upload version file
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.upload_file_to_version(
|
|
alpha_version_id,
|
|
&TestFile::BasicZip,
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_version)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Delete version file. Notably, this uses 'VERSION_WRITE' instead of 'VERSION_DELETE' as it is writing to the version
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.remove_version_file(alpha_file_hash, pat.as_deref())
|
|
.await
|
|
// Delete from alpha_version_id, as we uploaded to alpha_version_id and it needs another file
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, write_version)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Delete version
|
|
let delete_version = Scopes::VERSION_DELETE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.remove_version(alpha_version_id, pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, delete_version)
|
|
.await
|
|
.unwrap();
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Report scopes
|
|
#[actix_rt::test]
|
|
pub async fn report_scopes() {
|
|
// Test setup and dummy data
|
|
with_test_environment_all(None, |test_env| async move {
|
|
let api = &test_env.api;
|
|
let beta_project_id = &test_env.dummy.project_beta.project_id;
|
|
|
|
// Create report
|
|
let report_create = Scopes::REPORT_CREATE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.create_report(
|
|
"copyright",
|
|
beta_project_id,
|
|
CommonItemType::Project,
|
|
"This is a reupload of my mod",
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, report_create)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Get reports
|
|
let report_read = Scopes::REPORT_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_user_reports(pat.as_deref()).await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.test(req_gen, report_read)
|
|
.await
|
|
.unwrap();
|
|
let report_id = success[0]["id"].as_str().unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_report(report_id, pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, report_read)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_reports(&[report_id], pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, report_read)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Edit report
|
|
let report_edit = Scopes::REPORT_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_report(
|
|
report_id,
|
|
json!({ "body": "This is a reupload of my mod, G8!" }),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, report_edit)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Delete report
|
|
// We use a moderator PAT here, as only moderators can delete reports
|
|
let report_delete = Scopes::REPORT_DELETE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.delete_report(report_id, pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(MOD_USER_ID_PARSED)
|
|
.test(req_gen, report_delete)
|
|
.await
|
|
.unwrap();
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Thread scopes
|
|
#[actix_rt::test]
|
|
pub async fn thread_scopes() {
|
|
// Test setup and dummy data
|
|
with_test_environment_all(None, |test_env| async move {
|
|
let api = &test_env.api;
|
|
let alpha_thread_id = &test_env.dummy.project_alpha.thread_id;
|
|
let beta_thread_id = &test_env.dummy.project_beta.thread_id;
|
|
|
|
// Thread read
|
|
let thread_read = Scopes::THREAD_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_thread(alpha_thread_id, pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, thread_read)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_threads(&[alpha_thread_id.as_str()], pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, thread_read)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Thread write (to also push to moderator inbox)
|
|
let thread_write = Scopes::THREAD_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.write_to_thread(
|
|
beta_thread_id,
|
|
"text",
|
|
"test_thread_scopes Body",
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_user_id(USER_USER_ID_PARSED)
|
|
.test(req_gen, thread_write)
|
|
.await
|
|
.unwrap();
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Pat scopes
|
|
#[actix_rt::test]
|
|
pub async fn pat_scopes() {
|
|
with_test_environment_all(None, |test_env| async move {
|
|
let api = &test_env.api;
|
|
// Pat create
|
|
let pat_create = Scopes::PAT_CREATE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
let req = test::TestRequest::post()
|
|
.uri("/_internal/pat")
|
|
.set_json(json!({
|
|
"scopes": 1,
|
|
"name": "test_pat_scopes Name",
|
|
"expires": Utc::now() + Duration::days(1),
|
|
}))
|
|
.append_pat(pat.as_deref())
|
|
.to_request();
|
|
api.call(req).await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.test(req_gen, pat_create)
|
|
.await
|
|
.unwrap();
|
|
let pat_id = success["id"].as_str().unwrap();
|
|
|
|
// Pat write
|
|
let pat_write = Scopes::PAT_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/_internal/pat/{pat_id}"))
|
|
.set_json(json!({}))
|
|
.append_pat(pat.as_deref())
|
|
.to_request();
|
|
api.call(req).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, pat_write)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Pat read
|
|
let pat_read = Scopes::PAT_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
let req = test::TestRequest::get()
|
|
.uri("/_internal/pat")
|
|
.append_pat(pat.as_deref())
|
|
.to_request();
|
|
api.call(req).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, pat_read)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Pat delete
|
|
let pat_delete = Scopes::PAT_DELETE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
let req = test::TestRequest::delete()
|
|
.uri(&format!("/_internal/pat/{pat_id}"))
|
|
.append_pat(pat.as_deref())
|
|
.to_request();
|
|
api.call(req).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, pat_delete)
|
|
.await
|
|
.unwrap();
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Collection scopes
|
|
#[actix_rt::test]
|
|
pub async fn collections_scopes() {
|
|
// Test setup and dummy data
|
|
with_test_environment(
|
|
None,
|
|
|test_env: TestEnvironment<ApiV3>| async move {
|
|
let api = &test_env.api;
|
|
let alpha_project_id = &test_env.dummy.project_alpha.project_id;
|
|
|
|
// Create collection
|
|
let collection_create = Scopes::COLLECTION_CREATE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.create_collection(
|
|
"Test Collection",
|
|
"Test Collection Description",
|
|
&[alpha_project_id.as_str()],
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.test(req_gen, collection_create)
|
|
.await
|
|
.unwrap();
|
|
let collection_id = success["id"].as_str().unwrap();
|
|
|
|
// Patch collection
|
|
// Collections always initialize to public, so we do patch before Get testing
|
|
let collection_write = Scopes::COLLECTION_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_collection(
|
|
collection_id,
|
|
json!({
|
|
"name": "Test Collection patch",
|
|
"status": "private",
|
|
}),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, collection_write)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Read collection
|
|
let collection_read = Scopes::COLLECTION_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_collection(collection_id, pat.as_deref()).await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_code(404)
|
|
.test(req_gen, collection_read)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_collections(&[collection_id], pat.as_deref()).await
|
|
};
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.test(req_gen, collection_read)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(failure.as_array().unwrap().len(), 0);
|
|
assert_eq!(success.as_array().unwrap().len(), 1);
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_user_collections(USER_USER_ID, pat.as_deref()).await
|
|
};
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.test(req_gen, collection_read)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(failure.as_array().unwrap().len(), 0);
|
|
assert_eq!(success.as_array().unwrap().len(), 1);
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_collection_icon(
|
|
collection_id,
|
|
Some(DummyImage::SmallIcon.get_icon_data()),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, collection_write)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_collection_icon(collection_id, None, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, collection_write)
|
|
.await
|
|
.unwrap();
|
|
},
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// Organization scopes (and a couple PROJECT_WRITE scopes that are only allowed for orgs)
|
|
#[actix_rt::test]
|
|
pub async fn organization_scopes() {
|
|
// Test setup and dummy data
|
|
with_test_environment(
|
|
None,
|
|
|test_env: TestEnvironment<ApiV3>| async move {
|
|
let api = &test_env.api;
|
|
let beta_project_id = &test_env.dummy.project_beta.project_id;
|
|
|
|
// Create organization
|
|
let organization_create = Scopes::ORGANIZATION_CREATE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.create_organization(
|
|
"Test Org",
|
|
"TestOrg",
|
|
"TestOrg Description",
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
let (_, success) = ScopeTest::new(&test_env)
|
|
.test(req_gen, organization_create)
|
|
.await
|
|
.unwrap();
|
|
let organization_id = success["id"].as_str().unwrap();
|
|
|
|
// Patch organization
|
|
let organization_edit = Scopes::ORGANIZATION_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_organization(
|
|
organization_id,
|
|
json!({
|
|
"description": "TestOrg Patch Description",
|
|
}),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, organization_edit)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_organization_icon(
|
|
organization_id,
|
|
Some(DummyImage::SmallIcon.get_icon_data()),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, organization_edit)
|
|
.await
|
|
.unwrap();
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.edit_organization_icon(
|
|
organization_id,
|
|
None,
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, organization_edit)
|
|
.await
|
|
.unwrap();
|
|
|
|
// add project
|
|
let organization_project_edit =
|
|
Scopes::PROJECT_WRITE | Scopes::ORGANIZATION_WRITE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.organization_add_project(
|
|
organization_id,
|
|
beta_project_id,
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_scopes(Scopes::all() ^ Scopes::ORGANIZATION_WRITE)
|
|
.test(req_gen, organization_project_edit)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Organization reads
|
|
let organization_read = Scopes::ORGANIZATION_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_organization(organization_id, pat.as_deref()).await
|
|
};
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.test(req_gen, organization_read)
|
|
.await
|
|
.unwrap();
|
|
assert!(failure["members"][0]["permissions"].is_null());
|
|
assert!(!success["members"][0]["permissions"].is_null());
|
|
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_organizations(&[organization_id], pat.as_deref())
|
|
.await
|
|
};
|
|
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.test(req_gen, organization_read)
|
|
.await
|
|
.unwrap();
|
|
assert!(failure[0]["members"][0]["permissions"].is_null());
|
|
assert!(!success[0]["members"][0]["permissions"].is_null());
|
|
|
|
let organization_project_read =
|
|
Scopes::PROJECT_READ | Scopes::ORGANIZATION_READ;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.get_organization_projects(organization_id, pat.as_deref())
|
|
.await
|
|
};
|
|
let (failure, success) = ScopeTest::new(&test_env)
|
|
.with_failure_code(200)
|
|
.with_failure_scopes(Scopes::all() ^ Scopes::ORGANIZATION_READ)
|
|
.test(req_gen, organization_project_read)
|
|
.await
|
|
.unwrap();
|
|
assert!(failure.as_array().unwrap().is_empty());
|
|
assert!(!success.as_array().unwrap().is_empty());
|
|
|
|
// remove project (now that we've checked)
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.organization_remove_project(
|
|
organization_id,
|
|
beta_project_id,
|
|
UserId(USER_USER_ID_PARSED as u64),
|
|
pat.as_deref(),
|
|
)
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.with_failure_scopes(Scopes::all() ^ Scopes::ORGANIZATION_WRITE)
|
|
.test(req_gen, organization_project_edit)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Delete organization
|
|
let organization_delete = Scopes::ORGANIZATION_DELETE;
|
|
let req_gen = |pat: Option<String>| async move {
|
|
api.delete_organization(organization_id, pat.as_deref())
|
|
.await
|
|
};
|
|
ScopeTest::new(&test_env)
|
|
.test(req_gen, organization_delete)
|
|
.await
|
|
.unwrap();
|
|
},
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// TODO: Analytics scopes
|
|
|
|
// TODO: User authentication, and Session scopes
|
|
|
|
// TODO: Some hash/version files functions
|
|
|
|
// TODO: Image scopes
|