You've already forked AstralRinth
forked from didirus/AstralRinth
More tests (#729)
* permissions tests * finished permissions; organization tests * clippy, fmt * post-merge fixes * teams changes * refactored to use new api * fmt, clippy * sqlx prepare * revs * revs * re-tested * re-added name * reverted to matrix
This commit is contained in:
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
rust: [beta, nightly, stable]
|
||||
rust: [stable]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -2247,7 +2247,11 @@ pub async fn link_trolley(
|
||||
}
|
||||
|
||||
if let Some(email) = user.email {
|
||||
let id = payouts_queue.lock().await.register_recipient(&email, body.0).await?;
|
||||
let id = payouts_queue
|
||||
.lock()
|
||||
.await
|
||||
.register_recipient(&email, body.0)
|
||||
.await?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ impl User {
|
||||
redis
|
||||
.delete_many(
|
||||
user_ids
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|id| (USERS_PROJECTS_NAMESPACE, Some(id.0.to_string()))),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct Team {
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ProjectPermissions: u64 {
|
||||
const UPLOAD_VERSION = 1 << 0;
|
||||
const DELETE_VERSION = 1 << 1;
|
||||
@@ -35,8 +35,6 @@ bitflags::bitflags! {
|
||||
const DELETE_PROJECT = 1 << 7;
|
||||
const VIEW_ANALYTICS = 1 << 8;
|
||||
const VIEW_PAYOUTS = 1 << 9;
|
||||
|
||||
const ALL = 0b1111111111;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,15 +53,19 @@ impl ProjectPermissions {
|
||||
organization_team_member: &Option<crate::database::models::TeamMember>, // team member of the user in the organization
|
||||
) -> Option<Self> {
|
||||
if role.is_admin() {
|
||||
return Some(ProjectPermissions::ALL);
|
||||
return Some(ProjectPermissions::all());
|
||||
}
|
||||
|
||||
if let Some(member) = project_team_member {
|
||||
return Some(member.permissions);
|
||||
if member.accepted {
|
||||
return Some(member.permissions);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(member) = organization_team_member {
|
||||
return Some(member.permissions); // Use default project permissions for the organization team member
|
||||
if member.accepted {
|
||||
return Some(member.permissions);
|
||||
}
|
||||
}
|
||||
|
||||
if role.is_mod() {
|
||||
@@ -79,18 +81,16 @@ impl ProjectPermissions {
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct OrganizationPermissions: u64 {
|
||||
const EDIT_DETAILS = 1 << 0;
|
||||
const EDIT_BODY = 1 << 1;
|
||||
const MANAGE_INVITES = 1 << 2;
|
||||
const REMOVE_MEMBER = 1 << 3;
|
||||
const EDIT_MEMBER = 1 << 4;
|
||||
const ADD_PROJECT = 1 << 5;
|
||||
const REMOVE_PROJECT = 1 << 6;
|
||||
const DELETE_ORGANIZATION = 1 << 8;
|
||||
const EDIT_MEMBER_DEFAULT_PERMISSIONS = 1 << 9; // Separate from EDIT_MEMBER
|
||||
const ALL = 0b1111111111;
|
||||
const MANAGE_INVITES = 1 << 1;
|
||||
const REMOVE_MEMBER = 1 << 2;
|
||||
const EDIT_MEMBER = 1 << 3;
|
||||
const ADD_PROJECT = 1 << 4;
|
||||
const REMOVE_PROJECT = 1 << 5;
|
||||
const DELETE_ORGANIZATION = 1 << 6;
|
||||
const EDIT_MEMBER_DEFAULT_PERMISSIONS = 1 << 7; // Separate from EDIT_MEMBER
|
||||
const NONE = 0b0;
|
||||
}
|
||||
}
|
||||
@@ -109,17 +109,17 @@ impl OrganizationPermissions {
|
||||
team_member: &Option<crate::database::models::TeamMember>,
|
||||
) -> Option<Self> {
|
||||
if role.is_admin() {
|
||||
return Some(OrganizationPermissions::ALL);
|
||||
return Some(OrganizationPermissions::all());
|
||||
}
|
||||
|
||||
if let Some(member) = team_member {
|
||||
return member.organization_permissions;
|
||||
if member.accepted {
|
||||
return member.organization_permissions;
|
||||
}
|
||||
}
|
||||
if role.is_mod() {
|
||||
return Some(
|
||||
OrganizationPermissions::EDIT_DETAILS
|
||||
| OrganizationPermissions::EDIT_BODY
|
||||
| OrganizationPermissions::ADD_PROJECT,
|
||||
OrganizationPermissions::EDIT_DETAILS | OrganizationPermissions::ADD_PROJECT,
|
||||
);
|
||||
}
|
||||
None
|
||||
|
||||
@@ -206,7 +206,11 @@ async fn find_version(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(matched.get(0).or_else(|| exact_matches.get(0)).copied().cloned())
|
||||
Ok(matched
|
||||
.get(0)
|
||||
.or_else(|| exact_matches.get(0))
|
||||
.copied()
|
||||
.cloned())
|
||||
}
|
||||
|
||||
fn find_file<'a>(
|
||||
|
||||
@@ -120,7 +120,7 @@ pub async fn count_download(
|
||||
|
||||
analytics_queue.add_download(Download {
|
||||
id: Uuid::new_v4(),
|
||||
recorded: get_current_tenths_of_ms(),
|
||||
recorded: get_current_tenths_of_ms(),
|
||||
domain: url.host_str().unwrap_or_default().to_string(),
|
||||
site_path: url.path().to_string(),
|
||||
user_id: user
|
||||
|
||||
@@ -94,8 +94,8 @@ pub async fn organization_create(
|
||||
members: vec![team_item::TeamMemberBuilder {
|
||||
user_id: current_user.id.into(),
|
||||
role: crate::models::teams::OWNER_ROLE.to_owned(),
|
||||
permissions: ProjectPermissions::ALL,
|
||||
organization_permissions: Some(OrganizationPermissions::ALL),
|
||||
permissions: ProjectPermissions::all(),
|
||||
organization_permissions: Some(OrganizationPermissions::all()),
|
||||
accepted: true,
|
||||
payouts_split: Decimal::ONE_HUNDRED,
|
||||
ordering: 0,
|
||||
|
||||
@@ -407,12 +407,10 @@ pub async fn add_team_member(
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let team_association = Team::get_association(team_id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::InvalidInput("The team specified does not exist".to_string()))?;
|
||||
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool).await?;
|
||||
|
||||
match team_association {
|
||||
// If team is associated with a project, check if they have permissions to invite users to that project
|
||||
TeamAssociationId::Project(pid) => {
|
||||
@@ -470,8 +468,8 @@ pub async fn add_team_member(
|
||||
.contains(OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS)
|
||||
&& !new_member.permissions.is_empty()
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You do not have permission to give this user default project permissions."
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to give this user default project permissions. Ensure 'permissions' is set if it is not, and empty (0)."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
@@ -654,8 +652,8 @@ pub async fn edit_team_member(
|
||||
.unwrap_or_default();
|
||||
|
||||
if !organization_permissions.contains(OrganizationPermissions::EDIT_MEMBER) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You don't have permission to edit organization permissions".to_string(),
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -672,7 +670,7 @@ pub async fn edit_team_member(
|
||||
&& !organization_permissions
|
||||
.contains(OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS)
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to give this user default project permissions."
|
||||
.to_string(),
|
||||
));
|
||||
@@ -884,7 +882,6 @@ pub async fn remove_team_member(
|
||||
// removed by a member with the REMOVE_MEMBER permission.
|
||||
if Some(delete_member.user_id) == member.as_ref().map(|m| m.user_id)
|
||||
|| permissions.contains(ProjectPermissions::REMOVE_MEMBER)
|
||||
&& member.as_ref().map(|m| m.accepted).unwrap_or(true)
|
||||
// true as if the permission exists, but the member does not, they are part of an org
|
||||
{
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
@@ -896,7 +893,6 @@ pub async fn remove_team_member(
|
||||
}
|
||||
} else if Some(delete_member.user_id) == member.as_ref().map(|m| m.user_id)
|
||||
|| permissions.contains(ProjectPermissions::MANAGE_INVITES)
|
||||
&& member.as_ref().map(|m| m.accepted).unwrap_or(true)
|
||||
// true as if the permission exists, but the member does not, they are part of an org
|
||||
{
|
||||
// This is a pending invite rather than a member, so the
|
||||
@@ -913,49 +909,37 @@ pub async fn remove_team_member(
|
||||
let organization_permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(¤t_user.role, &member)
|
||||
.unwrap_or_default();
|
||||
if let Some(member) = member {
|
||||
// Organization teams requires a TeamMember, so we can 'unwrap'
|
||||
if delete_member.accepted {
|
||||
// Members other than the owner can either leave the team, or be
|
||||
// removed by a member with the REMOVE_MEMBER permission.
|
||||
if delete_member.user_id == member.user_id
|
||||
|| organization_permissions
|
||||
.contains(OrganizationPermissions::REMOVE_MEMBER)
|
||||
&& member.accepted
|
||||
{
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to remove a member from this organization"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
} else if delete_member.user_id == member.user_id
|
||||
|| organization_permissions
|
||||
.contains(OrganizationPermissions::MANAGE_INVITES)
|
||||
&& member.accepted
|
||||
// Organization teams requires a TeamMember, so we can 'unwrap'
|
||||
if delete_member.accepted {
|
||||
// Members other than the owner can either leave the team, or be
|
||||
// removed by a member with the REMOVE_MEMBER permission.
|
||||
if Some(delete_member.user_id) == member.map(|m| m.user_id)
|
||||
|| organization_permissions.contains(OrganizationPermissions::REMOVE_MEMBER)
|
||||
{
|
||||
// This is a pending invite rather than a member, so the
|
||||
// user being invited or team members with the MANAGE_INVITES
|
||||
// permission can remove it.
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to cancel an organization invite"
|
||||
"You do not have permission to remove a member from this organization"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
} else if Some(delete_member.user_id) == member.map(|m| m.user_id)
|
||||
|| organization_permissions.contains(OrganizationPermissions::MANAGE_INVITES)
|
||||
{
|
||||
// This is a pending invite rather than a member, so the
|
||||
// user being invited or team members with the MANAGE_INVITES
|
||||
// permission can remove it.
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to remove a member from this organization"
|
||||
.to_string(),
|
||||
"You do not have permission to cancel an organization invite".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TeamMember::clear_cache(id, &redis).await?;
|
||||
User::clear_project_cache(&[delete_member.user_id.into()], &redis).await?;
|
||||
User::clear_project_cache(&[delete_member.user_id], &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
|
||||
@@ -18,11 +18,11 @@ pub enum MultipartSegmentData {
|
||||
}
|
||||
|
||||
pub trait AppendsMultipart {
|
||||
fn set_multipart(self, data: Vec<MultipartSegment>) -> Self;
|
||||
fn set_multipart(self, data: impl IntoIterator<Item = MultipartSegment>) -> Self;
|
||||
}
|
||||
|
||||
impl AppendsMultipart for TestRequest {
|
||||
fn set_multipart(self, data: Vec<MultipartSegment>) -> Self {
|
||||
fn set_multipart(self, data: impl IntoIterator<Item = MultipartSegment>) -> Self {
|
||||
let (boundary, payload) = generate_multipart(data);
|
||||
self.append_header((
|
||||
"Content-Type",
|
||||
@@ -32,7 +32,7 @@ impl AppendsMultipart for TestRequest {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_multipart(data: Vec<MultipartSegment>) -> (String, Bytes) {
|
||||
fn generate_multipart(data: impl IntoIterator<Item = MultipartSegment>) -> (String, Bytes) {
|
||||
let mut boundary = String::from("----WebKitFormBoundary");
|
||||
boundary.push_str(&rand::random::<u64>().to_string());
|
||||
boundary.push_str(&rand::random::<u64>().to_string());
|
||||
|
||||
20
tests/common/api_v2/mod.rs
Normal file
20
tests/common/api_v2/mod.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::environment::LocalService;
|
||||
use actix_web::dev::ServiceResponse;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub mod organization;
|
||||
pub mod project;
|
||||
pub mod team;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ApiV2 {
|
||||
pub test_app: Rc<dyn LocalService>,
|
||||
}
|
||||
|
||||
impl ApiV2 {
|
||||
pub async fn call(&self, req: actix_http::Request) -> ServiceResponse {
|
||||
self.test_app.call(req).await.unwrap()
|
||||
}
|
||||
}
|
||||
152
tests/common/api_v2/organization.rs
Normal file
152
tests/common/api_v2/organization.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use actix_web::{
|
||||
dev::ServiceResponse,
|
||||
test::{self, TestRequest},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use labrinth::models::{organizations::Organization, projects::Project};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::request_data::ImageData;
|
||||
|
||||
use super::ApiV2;
|
||||
|
||||
impl ApiV2 {
|
||||
pub async fn create_organization(
|
||||
&self,
|
||||
organization_title: &str,
|
||||
description: &str,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/v2/organization")
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(json!({
|
||||
"title": organization_title,
|
||||
"description": description,
|
||||
}))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_organization(&self, id_or_title: &str, pat: &str) -> ServiceResponse {
|
||||
let req = TestRequest::get()
|
||||
.uri(&format!("/v2/organization/{id_or_title}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_organization_deserialized(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
pat: &str,
|
||||
) -> Organization {
|
||||
let resp = self.get_organization(id_or_title, pat).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn get_organization_projects(&self, id_or_title: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/organization/{id_or_title}/projects"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_organization_projects_deserialized(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
pat: &str,
|
||||
) -> Vec<Project> {
|
||||
let resp = self.get_organization_projects(id_or_title, pat).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn edit_organization(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
patch: serde_json::Value,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/organization/{id_or_title}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(patch)
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn edit_organization_icon(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
icon: Option<ImageData>,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
if let Some(icon) = icon {
|
||||
// If an icon is provided, upload it
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/organization/{id_or_title}/icon?ext={ext}",
|
||||
ext = icon.extension
|
||||
))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_payload(Bytes::from(icon.icon))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
} else {
|
||||
// If no icon is provided, delete the icon
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/v2/organization/{id_or_title}/icon"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_organization(&self, id_or_title: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/v2/organization/{id_or_title}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn organization_add_project(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
project_id_or_slug: &str,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/organization/{id_or_title}/projects"))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(json!({
|
||||
"project_id": project_id_or_slug,
|
||||
}))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn organization_remove_project(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
project_id_or_slug: &str,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!(
|
||||
"/v2/organization/{id_or_title}/projects/{project_id_or_slug}"
|
||||
))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,31 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::{
|
||||
actix::AppendsMultipart,
|
||||
asserts::assert_status,
|
||||
database::{MOD_USER_PAT, USER_USER_PAT},
|
||||
environment::LocalService,
|
||||
request_data::ProjectCreationRequestData,
|
||||
};
|
||||
use actix_http::StatusCode;
|
||||
use actix_web::{
|
||||
dev::ServiceResponse,
|
||||
test::{self, TestRequest},
|
||||
};
|
||||
use labrinth::models::{
|
||||
notifications::Notification,
|
||||
projects::{Project, Version},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use labrinth::models::projects::{Project, Version};
|
||||
use serde_json::json;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct ApiV2 {
|
||||
pub test_app: Rc<Box<dyn LocalService>>,
|
||||
}
|
||||
use crate::common::{
|
||||
actix::AppendsMultipart,
|
||||
asserts::assert_status,
|
||||
database::MOD_USER_PAT,
|
||||
request_data::{ImageData, ProjectCreationRequestData},
|
||||
};
|
||||
|
||||
use super::ApiV2;
|
||||
|
||||
impl ApiV2 {
|
||||
pub async fn call(&self, req: actix_http::Request) -> ServiceResponse {
|
||||
self.test_app.call(req).await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn add_public_project(
|
||||
&self,
|
||||
creation_data: ProjectCreationRequestData,
|
||||
) -> (Project, Version) {
|
||||
pat: &str,
|
||||
) -> (Project, Vec<Version>) {
|
||||
// Add a project.
|
||||
let req = TestRequest::post()
|
||||
.uri("/v2/project")
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_multipart(creation_data.segment_data)
|
||||
.to_request();
|
||||
let resp = self.call(req).await;
|
||||
@@ -55,19 +45,18 @@ impl ApiV2 {
|
||||
assert_status(resp, StatusCode::NO_CONTENT);
|
||||
|
||||
let project = self
|
||||
.get_project_deserialized(&creation_data.slug, USER_USER_PAT)
|
||||
.get_project_deserialized(&creation_data.slug, pat)
|
||||
.await;
|
||||
|
||||
// Get project's versions
|
||||
let req = TestRequest::get()
|
||||
.uri(&format!("/v2/project/{}/version", creation_data.slug))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
let resp = self.call(req).await;
|
||||
let versions: Vec<Version> = test::read_body_json(resp).await;
|
||||
let version = versions.into_iter().next().unwrap();
|
||||
|
||||
(project, version)
|
||||
(project, versions)
|
||||
}
|
||||
|
||||
pub async fn remove_project(&self, project_slug_or_id: &str, pat: &str) -> ServiceResponse {
|
||||
@@ -80,12 +69,16 @@ impl ApiV2 {
|
||||
resp
|
||||
}
|
||||
|
||||
pub async fn get_project_deserialized(&self, slug: &str, pat: &str) -> Project {
|
||||
pub async fn get_project(&self, id_or_slug: &str, pat: &str) -> ServiceResponse {
|
||||
let req = TestRequest::get()
|
||||
.uri(&format!("/v2/project/{slug}"))
|
||||
.uri(&format!("/v2/project/{id_or_slug}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
let resp = self.call(req).await;
|
||||
self.call(req).await
|
||||
}
|
||||
pub async fn get_project_deserialized(&self, id_or_slug: &str, pat: &str) -> Project {
|
||||
let resp = self.get_project(id_or_slug, pat).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
@@ -103,73 +96,94 @@ impl ApiV2 {
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn add_user_to_team(
|
||||
pub async fn get_version_from_hash(
|
||||
&self,
|
||||
team_id: &str,
|
||||
user_id: &str,
|
||||
hash: &str,
|
||||
algorithm: &str,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{team_id}/members"))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(json!( {
|
||||
"user_id": user_id
|
||||
}))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn join_team(&self, team_id: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{team_id}/join"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn remove_from_team(
|
||||
&self,
|
||||
team_id: &str,
|
||||
user_id: &str,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/v2/team/{team_id}/members/{user_id}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_user_notifications_deserialized(
|
||||
&self,
|
||||
user_id: &str,
|
||||
pat: &str,
|
||||
) -> Vec<Notification> {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/user/{user_id}/notifications"))
|
||||
.uri(&format!("/v2/version_file/{hash}?algorithm={algorithm}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
let resp = self.call(req).await;
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_version_from_hash_deserialized(
|
||||
&self,
|
||||
hash: &str,
|
||||
algorithm: &str,
|
||||
pat: &str,
|
||||
) -> Version {
|
||||
let resp = self.get_version_from_hash(hash, algorithm, pat).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn mark_notification_read(
|
||||
pub async fn edit_project(
|
||||
&self,
|
||||
notification_id: &str,
|
||||
id_or_slug: &str,
|
||||
patch: serde_json::Value,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/notification/{notification_id}"))
|
||||
.uri(&format!("/v2/project/{id_or_slug}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(patch)
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn delete_notification(&self, notification_id: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/v2/notification/{notification_id}"))
|
||||
pub async fn edit_project_bulk(
|
||||
&self,
|
||||
ids_or_slugs: impl IntoIterator<Item = &str>,
|
||||
patch: serde_json::Value,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let projects_str = ids_or_slugs
|
||||
.into_iter()
|
||||
.map(|s| format!("\"{}\"", s))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/projects?ids={encoded}",
|
||||
encoded = urlencoding::encode(&format!("[{projects_str}]"))
|
||||
))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(patch)
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn edit_project_icon(
|
||||
&self,
|
||||
id_or_slug: &str,
|
||||
icon: Option<ImageData>,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
if let Some(icon) = icon {
|
||||
// If an icon is provided, upload it
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/project/{id_or_slug}/icon?ext={ext}",
|
||||
ext = icon.extension
|
||||
))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_payload(Bytes::from(icon.icon))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
} else {
|
||||
// If no icon is provided, delete the icon
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/v2/project/{id_or_slug}/icon"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
}
|
||||
}
|
||||
168
tests/common/api_v2/team.rs
Normal file
168
tests/common/api_v2/team.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
use actix_web::{dev::ServiceResponse, test};
|
||||
use labrinth::models::{
|
||||
notifications::Notification,
|
||||
teams::{OrganizationPermissions, ProjectPermissions, TeamMember},
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::ApiV2;
|
||||
|
||||
impl ApiV2 {
|
||||
pub async fn get_team_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/team/{id_or_title}/members"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_team_members_deserialized(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
pat: &str,
|
||||
) -> Vec<TeamMember> {
|
||||
let resp = self.get_team_members(id_or_title, pat).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn get_project_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/project/{id_or_title}/members"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_project_members_deserialized(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
pat: &str,
|
||||
) -> Vec<TeamMember> {
|
||||
let resp = self.get_project_members(id_or_title, pat).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn get_organization_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/organization/{id_or_title}/members"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_organization_members_deserialized(
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
pat: &str,
|
||||
) -> Vec<TeamMember> {
|
||||
let resp = self.get_organization_members(id_or_title, pat).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn join_team(&self, team_id: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{team_id}/join"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn remove_from_team(
|
||||
&self,
|
||||
team_id: &str,
|
||||
user_id: &str,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/v2/team/{team_id}/members/{user_id}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn edit_team_member(
|
||||
&self,
|
||||
team_id: &str,
|
||||
user_id: &str,
|
||||
patch: serde_json::Value,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{team_id}/members/{user_id}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(patch)
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn transfer_team_ownership(
|
||||
&self,
|
||||
team_id: &str,
|
||||
user_id: &str,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{team_id}/owner"))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(json!({
|
||||
"user_id": user_id,
|
||||
}))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_user_notifications_deserialized(
|
||||
&self,
|
||||
user_id: &str,
|
||||
pat: &str,
|
||||
) -> Vec<Notification> {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/user/{user_id}/notifications"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
let resp = self.call(req).await;
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn mark_notification_read(
|
||||
&self,
|
||||
notification_id: &str,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/notification/{notification_id}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
pub async fn add_user_to_team(
|
||||
&self,
|
||||
team_id: &str,
|
||||
user_id: &str,
|
||||
project_permissions: Option<ProjectPermissions>,
|
||||
organization_permissions: Option<OrganizationPermissions>,
|
||||
pat: &str,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{team_id}/members"))
|
||||
.append_header(("Authorization", pat))
|
||||
.set_json(json!( {
|
||||
"user_id": user_id,
|
||||
"permissions" : project_permissions.map(|p| p.bits()).unwrap_or_default(),
|
||||
"organization_permissions" : organization_permissions.map(|p| p.bits()),
|
||||
}))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn delete_notification(&self, notification_id: &str, pat: &str) -> ServiceResponse {
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/v2/notification/{notification_id}"))
|
||||
.append_header(("Authorization", pat))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ use sqlx::{postgres::PgPoolOptions, PgPool};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use crate::common::{dummy_data, environment::TestEnvironment};
|
||||
|
||||
// The dummy test database adds a fair bit of 'dummy' data to test with.
|
||||
// Some constants are used to refer to that data, and are described here.
|
||||
// The rest can be accessed in the TestEnvironment 'dummy' field.
|
||||
@@ -29,6 +31,8 @@ pub const USER_USER_PAT: &str = "mrp_patuser";
|
||||
pub const FRIEND_USER_PAT: &str = "mrp_patfriend";
|
||||
pub const ENEMY_USER_PAT: &str = "mrp_patenemy";
|
||||
|
||||
const TEMPLATE_DATABASE_NAME: &str = "labrinth_tests_template";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TemporaryDatabase {
|
||||
pub pool: PgPool,
|
||||
@@ -37,41 +41,32 @@ pub struct TemporaryDatabase {
|
||||
}
|
||||
|
||||
impl TemporaryDatabase {
|
||||
// Creates a temporary database like sqlx::test does
|
||||
// Creates a temporary database like sqlx::test does (panics)
|
||||
// 1. Logs into the main database
|
||||
// 2. Creates a new randomly generated database
|
||||
// 3. Runs migrations on the new database
|
||||
// 4. (Optionally, by using create_with_dummy) adds dummy data to the database
|
||||
// If a db is created with create_with_dummy, it must be cleaned up with cleanup.
|
||||
// This means that dbs will only 'remain' if a test fails (for examination of the db), and will be cleaned up otherwise.
|
||||
pub async fn create() -> Self {
|
||||
let temp_database_name = generate_random_database_name();
|
||||
pub async fn create(max_connections: Option<u32>) -> Self {
|
||||
let temp_database_name = generate_random_name("labrinth_tests_db_");
|
||||
println!("Creating temporary database: {}", &temp_database_name);
|
||||
|
||||
let database_url = dotenvy::var("DATABASE_URL").expect("No database URL");
|
||||
let mut url = Url::parse(&database_url).expect("Invalid database URL");
|
||||
let pool = PgPool::connect(&database_url)
|
||||
.await
|
||||
.expect("Connection to database failed");
|
||||
|
||||
// Create the temporary database
|
||||
let create_db_query = format!("CREATE DATABASE {}", &temp_database_name);
|
||||
// Create the temporary (and template datbase, if needed)
|
||||
Self::create_temporary(&database_url, &temp_database_name).await;
|
||||
|
||||
sqlx::query(&create_db_query)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.expect("Database creation failed");
|
||||
// Pool to the temporary database
|
||||
let mut temporary_url = Url::parse(&database_url).expect("Invalid database URL");
|
||||
|
||||
pool.close().await;
|
||||
|
||||
// Modify the URL to switch to the temporary database
|
||||
url.set_path(&format!("/{}", &temp_database_name));
|
||||
let temp_db_url = url.to_string();
|
||||
temporary_url.set_path(&format!("/{}", &temp_database_name));
|
||||
let temp_db_url = temporary_url.to_string();
|
||||
|
||||
let pool = PgPoolOptions::new()
|
||||
.min_connections(0)
|
||||
.max_connections(4)
|
||||
.max_lifetime(Some(Duration::from_secs(60 * 60)))
|
||||
.max_connections(max_connections.unwrap_or(4))
|
||||
.max_lifetime(Some(Duration::from_secs(60)))
|
||||
.connect(&temp_db_url)
|
||||
.await
|
||||
.expect("Connection to temporary database failed");
|
||||
@@ -94,7 +89,103 @@ impl TemporaryDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes the temporary database
|
||||
// Creates a template and temporary databse (panics)
|
||||
// 1. Waits to obtain a pg lock on the main database
|
||||
// 2. Creates a new template database called 'TEMPLATE_DATABASE_NAME', if needed
|
||||
// 3. Switches to the template database
|
||||
// 4. Runs migrations on the new database (for most tests, this should not take time)
|
||||
// 5. Creates dummy data on the new db
|
||||
// 6. Creates a temporary database at 'temp_database_name' from the template
|
||||
// 7. Drops lock and all created connections in the function
|
||||
async fn create_temporary(database_url: &str, temp_database_name: &str) {
|
||||
let main_pool = PgPool::connect(database_url)
|
||||
.await
|
||||
.expect("Connection to database failed");
|
||||
|
||||
loop {
|
||||
// Try to acquire an advisory lock
|
||||
let lock_acquired: bool = sqlx::query_scalar("SELECT pg_try_advisory_lock(1)")
|
||||
.fetch_one(&main_pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if lock_acquired {
|
||||
// Create the db template if it doesn't exist
|
||||
// Check if template_db already exists
|
||||
let db_exists: Option<i32> = sqlx::query_scalar(&format!(
|
||||
"SELECT 1 FROM pg_database WHERE datname = '{TEMPLATE_DATABASE_NAME}'"
|
||||
))
|
||||
.fetch_optional(&main_pool)
|
||||
.await
|
||||
.unwrap();
|
||||
if db_exists.is_none() {
|
||||
let create_db_query = format!("CREATE DATABASE {TEMPLATE_DATABASE_NAME}");
|
||||
sqlx::query(&create_db_query)
|
||||
.execute(&main_pool)
|
||||
.await
|
||||
.expect("Database creation failed");
|
||||
}
|
||||
|
||||
// Switch to template
|
||||
let url = dotenvy::var("DATABASE_URL").expect("No database URL");
|
||||
let mut template_url = Url::parse(&url).expect("Invalid database URL");
|
||||
template_url.set_path(&format!("/{}", TEMPLATE_DATABASE_NAME));
|
||||
|
||||
let pool = PgPool::connect(template_url.as_str())
|
||||
.await
|
||||
.expect("Connection to database failed");
|
||||
|
||||
// Run migrations on the template
|
||||
let migrations = sqlx::migrate!("./migrations");
|
||||
migrations.run(&pool).await.expect("Migrations failed");
|
||||
|
||||
// Check if dummy data exists- a fake 'dummy_data' table is created if it does
|
||||
let dummy_data_exists: bool =
|
||||
sqlx::query_scalar("SELECT to_regclass('dummy_data') IS NOT NULL")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
if !dummy_data_exists {
|
||||
// Add dummy data
|
||||
let temporary_test_env = TestEnvironment::build_with_db(TemporaryDatabase {
|
||||
pool: pool.clone(),
|
||||
database_name: TEMPLATE_DATABASE_NAME.to_string(),
|
||||
redis_pool: RedisPool::new(None),
|
||||
})
|
||||
.await;
|
||||
dummy_data::add_dummy_data(&temporary_test_env).await;
|
||||
}
|
||||
pool.close().await;
|
||||
|
||||
// Switch back to main database (as we cant create from template while connected to it)
|
||||
let pool = PgPool::connect(url.as_str()).await.unwrap();
|
||||
|
||||
// Create the temporary database from the template
|
||||
let create_db_query = format!(
|
||||
"CREATE DATABASE {} TEMPLATE {}",
|
||||
&temp_database_name, TEMPLATE_DATABASE_NAME
|
||||
);
|
||||
|
||||
sqlx::query(&create_db_query)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.expect("Database creation failed");
|
||||
|
||||
// Release the advisory lock
|
||||
sqlx::query("SELECT pg_advisory_unlock(1)")
|
||||
.execute(&main_pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
main_pool.close().await;
|
||||
break;
|
||||
}
|
||||
// Wait for the lock to be released
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes the temporary database (panics)
|
||||
// If a temporary db is created, it must be cleaned up with cleanup.
|
||||
// This means that dbs will only 'remain' if a test fails (for examination of the db), and will be cleaned up otherwise.
|
||||
pub async fn cleanup(mut self) {
|
||||
@@ -125,15 +216,9 @@ impl TemporaryDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_random_database_name() -> String {
|
||||
// Generate a random database name here
|
||||
// You can use your logic to create a unique name
|
||||
// For example, you can use a random string as you did before
|
||||
// or append a timestamp, etc.
|
||||
|
||||
// We will use a random string starting with "labrinth_tests_db_"
|
||||
// and append a 6-digit number to it.
|
||||
let mut database_name = String::from("labrinth_tests_db_");
|
||||
database_name.push_str(&rand::random::<u64>().to_string()[..6]);
|
||||
database_name
|
||||
// Appends a random 8-digit number to the end of the str
|
||||
pub fn generate_random_name(str: &str) -> String {
|
||||
let mut str = String::from(str);
|
||||
str.push_str(&rand::random::<u64>().to_string()[..8]);
|
||||
str
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#![allow(dead_code)]
|
||||
use actix_web::test::{self, TestRequest};
|
||||
use labrinth::{models::projects::Project, models::projects::Version};
|
||||
use labrinth::{
|
||||
models::projects::Project,
|
||||
models::{organizations::Organization, pats::Scopes, projects::Version},
|
||||
};
|
||||
use serde_json::json;
|
||||
use sqlx::Executor;
|
||||
|
||||
@@ -11,8 +15,10 @@ use super::{
|
||||
request_data::get_public_project_creation_data,
|
||||
};
|
||||
|
||||
pub const DUMMY_DATA_UPDATE: i64 = 1;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const DUMMY_CATEGORIES: &'static [&str] = &[
|
||||
pub const DUMMY_CATEGORIES: &[&str] = &[
|
||||
"combat",
|
||||
"decoration",
|
||||
"economy",
|
||||
@@ -30,65 +36,145 @@ pub enum DummyJarFile {
|
||||
BasicModDifferent,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum DummyImage {
|
||||
SmallIcon, // 200x200
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DummyData {
|
||||
pub alpha_team_id: String,
|
||||
pub beta_team_id: String,
|
||||
pub project_alpha: DummyProjectAlpha,
|
||||
pub project_beta: DummyProjectBeta,
|
||||
pub organization_zeta: DummyOrganizationZeta,
|
||||
}
|
||||
|
||||
pub alpha_project_id: String,
|
||||
pub beta_project_id: String,
|
||||
#[derive(Clone)]
|
||||
pub struct DummyProjectAlpha {
|
||||
// Alpha project:
|
||||
// This is a dummy project created by USER user.
|
||||
// It's approved, listed, and visible to the public.
|
||||
pub project_id: String,
|
||||
pub project_slug: String,
|
||||
pub version_id: String,
|
||||
pub thread_id: String,
|
||||
pub file_hash: String,
|
||||
pub team_id: String,
|
||||
}
|
||||
|
||||
pub alpha_project_slug: String,
|
||||
pub beta_project_slug: String,
|
||||
#[derive(Clone)]
|
||||
pub struct DummyProjectBeta {
|
||||
// Beta project:
|
||||
// This is a dummy project created by USER user.
|
||||
// It's not approved, unlisted, and not visible to the public.
|
||||
pub project_id: String,
|
||||
pub project_slug: String,
|
||||
pub version_id: String,
|
||||
pub thread_id: String,
|
||||
pub file_hash: String,
|
||||
pub team_id: String,
|
||||
}
|
||||
|
||||
pub alpha_version_id: String,
|
||||
pub beta_version_id: String,
|
||||
|
||||
pub alpha_thread_id: String,
|
||||
pub beta_thread_id: String,
|
||||
|
||||
pub alpha_file_hash: String,
|
||||
pub beta_file_hash: String,
|
||||
#[derive(Clone)]
|
||||
pub struct DummyOrganizationZeta {
|
||||
// Zeta organization:
|
||||
// This is a dummy organization created by USER user.
|
||||
// There are no projects in it.
|
||||
pub organization_id: String,
|
||||
pub organization_title: String,
|
||||
pub team_id: String,
|
||||
}
|
||||
|
||||
pub async fn add_dummy_data(test_env: &TestEnvironment) -> DummyData {
|
||||
// Adds basic dummy data to the database directly with sql (user, pats)
|
||||
let pool = &test_env.db.pool.clone();
|
||||
pool.execute(include_str!("../files/dummy_data.sql"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pool.execute(
|
||||
include_str!("../files/dummy_data.sql")
|
||||
.replace("$1", &Scopes::all().bits().to_string())
|
||||
.as_str(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (alpha_project, alpha_version) = add_project_alpha(test_env).await;
|
||||
let (beta_project, beta_version) = add_project_beta(test_env).await;
|
||||
|
||||
let zeta_organization = add_organization_zeta(test_env).await;
|
||||
|
||||
sqlx::query("INSERT INTO dummy_data (update_id) VALUES ($1)")
|
||||
.bind(DUMMY_DATA_UPDATE)
|
||||
.execute(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
DummyData {
|
||||
alpha_team_id: alpha_project.team.to_string(),
|
||||
beta_team_id: beta_project.team.to_string(),
|
||||
project_alpha: DummyProjectAlpha {
|
||||
team_id: alpha_project.team.to_string(),
|
||||
project_id: alpha_project.id.to_string(),
|
||||
project_slug: alpha_project.slug.unwrap(),
|
||||
version_id: alpha_version.id.to_string(),
|
||||
thread_id: alpha_project.thread_id.to_string(),
|
||||
file_hash: alpha_version.files[0].hashes["sha1"].clone(),
|
||||
},
|
||||
|
||||
alpha_project_id: alpha_project.id.to_string(),
|
||||
beta_project_id: beta_project.id.to_string(),
|
||||
project_beta: DummyProjectBeta {
|
||||
team_id: beta_project.team.to_string(),
|
||||
project_id: beta_project.id.to_string(),
|
||||
project_slug: beta_project.slug.unwrap(),
|
||||
version_id: beta_version.id.to_string(),
|
||||
thread_id: beta_project.thread_id.to_string(),
|
||||
file_hash: beta_version.files[0].hashes["sha1"].clone(),
|
||||
},
|
||||
|
||||
alpha_project_slug: alpha_project.slug.unwrap(),
|
||||
beta_project_slug: beta_project.slug.unwrap(),
|
||||
organization_zeta: DummyOrganizationZeta {
|
||||
organization_id: zeta_organization.id.to_string(),
|
||||
team_id: zeta_organization.team_id.to_string(),
|
||||
organization_title: zeta_organization.title,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
alpha_version_id: alpha_version.id.to_string(),
|
||||
beta_version_id: beta_version.id.to_string(),
|
||||
pub async fn get_dummy_data(test_env: &TestEnvironment) -> DummyData {
|
||||
let (alpha_project, alpha_version) = get_project_alpha(test_env).await;
|
||||
let (beta_project, beta_version) = get_project_beta(test_env).await;
|
||||
|
||||
alpha_thread_id: alpha_project.thread_id.to_string(),
|
||||
beta_thread_id: beta_project.thread_id.to_string(),
|
||||
let zeta_organization = get_organization_zeta(test_env).await;
|
||||
DummyData {
|
||||
project_alpha: DummyProjectAlpha {
|
||||
team_id: alpha_project.team.to_string(),
|
||||
project_id: alpha_project.id.to_string(),
|
||||
project_slug: alpha_project.slug.unwrap(),
|
||||
version_id: alpha_version.id.to_string(),
|
||||
thread_id: alpha_project.thread_id.to_string(),
|
||||
file_hash: alpha_version.files[0].hashes["sha1"].clone(),
|
||||
},
|
||||
|
||||
alpha_file_hash: alpha_version.files[0].hashes["sha1"].clone(),
|
||||
beta_file_hash: beta_version.files[0].hashes["sha1"].clone(),
|
||||
project_beta: DummyProjectBeta {
|
||||
team_id: beta_project.team.to_string(),
|
||||
project_id: beta_project.id.to_string(),
|
||||
project_slug: beta_project.slug.unwrap(),
|
||||
version_id: beta_version.id.to_string(),
|
||||
thread_id: beta_project.thread_id.to_string(),
|
||||
file_hash: beta_version.files[0].hashes["sha1"].clone(),
|
||||
},
|
||||
|
||||
organization_zeta: DummyOrganizationZeta {
|
||||
organization_id: zeta_organization.id.to_string(),
|
||||
team_id: zeta_organization.team_id.to_string(),
|
||||
organization_title: zeta_organization.title,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_project_alpha(test_env: &TestEnvironment) -> (Project, Version) {
|
||||
test_env
|
||||
let (project, versions) = test_env
|
||||
.v2
|
||||
.add_public_project(get_public_project_creation_data(
|
||||
"alpha",
|
||||
DummyJarFile::DummyProjectAlpha,
|
||||
))
|
||||
.await
|
||||
.add_public_project(
|
||||
get_public_project_creation_data("alpha", Some(DummyJarFile::DummyProjectAlpha)),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
(project, versions.into_iter().next().unwrap())
|
||||
}
|
||||
|
||||
pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version) {
|
||||
@@ -148,6 +234,48 @@ pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version)
|
||||
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
get_project_beta(test_env).await
|
||||
}
|
||||
|
||||
pub async fn add_organization_zeta(test_env: &TestEnvironment) -> Organization {
|
||||
// Add an organzation.
|
||||
let req = TestRequest::post()
|
||||
.uri("/v2/organization")
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"title": "zeta",
|
||||
"description": "A dummy organization for testing with."
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
get_organization_zeta(test_env).await
|
||||
}
|
||||
|
||||
pub async fn get_project_alpha(test_env: &TestEnvironment) -> (Project, Version) {
|
||||
// Get project
|
||||
let req = TestRequest::get()
|
||||
.uri("/v2/project/alpha")
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
let project: Project = test::read_body_json(resp).await;
|
||||
|
||||
// Get project's versions
|
||||
let req = TestRequest::get()
|
||||
.uri("/v2/project/alpha/version")
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
let versions: Vec<Version> = test::read_body_json(resp).await;
|
||||
let version = versions.into_iter().next().unwrap();
|
||||
|
||||
(project, version)
|
||||
}
|
||||
|
||||
pub async fn get_project_beta(test_env: &TestEnvironment) -> (Project, Version) {
|
||||
// Get project
|
||||
let req = TestRequest::get()
|
||||
.uri("/v2/project/beta")
|
||||
@@ -168,6 +296,18 @@ pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version)
|
||||
(project, version)
|
||||
}
|
||||
|
||||
pub async fn get_organization_zeta(test_env: &TestEnvironment) -> Organization {
|
||||
// Get organization
|
||||
let req = TestRequest::get()
|
||||
.uri("/v2/organization/zeta")
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
let organization: Organization = test::read_body_json(resp).await;
|
||||
|
||||
organization
|
||||
}
|
||||
|
||||
impl DummyJarFile {
|
||||
pub fn filename(&self) -> String {
|
||||
match self {
|
||||
@@ -194,3 +334,25 @@ impl DummyJarFile {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DummyImage {
|
||||
pub fn filename(&self) -> String {
|
||||
match self {
|
||||
DummyImage::SmallIcon => "200x200.png",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn extension(&self) -> String {
|
||||
match self {
|
||||
DummyImage::SmallIcon => "png",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
DummyImage::SmallIcon => include_bytes!("../../tests/files/200x200.png").to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use super::{
|
||||
api_v2::ApiV2,
|
||||
@@ -17,7 +17,7 @@ pub async fn with_test_environment<Fut>(f: impl FnOnce(TestEnvironment) -> Fut)
|
||||
where
|
||||
Fut: Future<Output = ()>,
|
||||
{
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let db = test_env.db.clone();
|
||||
|
||||
f(test_env).await;
|
||||
@@ -29,27 +29,29 @@ where
|
||||
// Must be called in an #[actix_rt::test] context. It also simulates a
|
||||
// temporary sqlx db like #[sqlx::test] would.
|
||||
// Use .call(req) on it directly to make a test call as if test::call_service(req) were being used.
|
||||
#[derive(Clone)]
|
||||
pub struct TestEnvironment {
|
||||
test_app: Rc<Box<dyn LocalService>>,
|
||||
test_app: Rc<dyn LocalService>, // Rc as it's not Send
|
||||
pub db: TemporaryDatabase,
|
||||
pub v2: ApiV2,
|
||||
|
||||
pub dummy: Option<dummy_data::DummyData>,
|
||||
pub dummy: Option<Arc<dummy_data::DummyData>>,
|
||||
}
|
||||
|
||||
impl TestEnvironment {
|
||||
pub async fn build_with_dummy() -> Self {
|
||||
let mut test_env = Self::build().await;
|
||||
let dummy = dummy_data::add_dummy_data(&test_env).await;
|
||||
test_env.dummy = Some(dummy);
|
||||
pub async fn build(max_connections: Option<u32>) -> Self {
|
||||
let db = TemporaryDatabase::create(max_connections).await;
|
||||
let mut test_env = Self::build_with_db(db).await;
|
||||
|
||||
let dummy = dummy_data::get_dummy_data(&test_env).await;
|
||||
test_env.dummy = Some(Arc::new(dummy));
|
||||
test_env
|
||||
}
|
||||
|
||||
pub async fn build() -> Self {
|
||||
let db = TemporaryDatabase::create().await;
|
||||
pub async fn build_with_db(db: TemporaryDatabase) -> Self {
|
||||
let labrinth_config = setup(&db).await;
|
||||
let app = App::new().configure(|cfg| labrinth::app_config(cfg, labrinth_config.clone()));
|
||||
let test_app: Rc<Box<dyn LocalService>> = Rc::new(Box::new(test::init_service(app).await));
|
||||
let test_app: Rc<dyn LocalService> = Rc::new(test::init_service(app).await);
|
||||
Self {
|
||||
v2: ApiV2 {
|
||||
test_app: test_app.clone(),
|
||||
@@ -59,6 +61,7 @@ impl TestEnvironment {
|
||||
dummy: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cleanup(self) {
|
||||
self.db.cleanup().await;
|
||||
}
|
||||
@@ -71,8 +74,10 @@ impl TestEnvironment {
|
||||
let resp = self
|
||||
.v2
|
||||
.add_user_to_team(
|
||||
&self.dummy.as_ref().unwrap().alpha_team_id,
|
||||
&self.dummy.as_ref().unwrap().project_alpha.team_id,
|
||||
FRIEND_USER_ID,
|
||||
None,
|
||||
None,
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -11,11 +11,12 @@ pub mod database;
|
||||
pub mod dummy_data;
|
||||
pub mod environment;
|
||||
pub mod pats;
|
||||
pub mod permissions;
|
||||
pub mod request_data;
|
||||
pub mod scopes;
|
||||
|
||||
// Testing equivalent to 'setup' function, producing a LabrinthConfig
|
||||
// If making a test, you should probably use environment::TestEnvironment::build_with_dummy() (which calls this)
|
||||
// If making a test, you should probably use environment::TestEnvironment::build() (which calls this)
|
||||
pub async fn setup(db: &TemporaryDatabase) -> LabrinthConfig {
|
||||
println!("Setting up labrinth config");
|
||||
|
||||
|
||||
992
tests/common/permissions.rs
Normal file
992
tests/common/permissions.rs
Normal file
@@ -0,0 +1,992 @@
|
||||
#![allow(dead_code)]
|
||||
use actix_web::test::{self, TestRequest};
|
||||
use itertools::Itertools;
|
||||
use labrinth::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::{
|
||||
database::{generate_random_name, ADMIN_USER_PAT},
|
||||
request_data,
|
||||
};
|
||||
|
||||
use super::{
|
||||
database::{USER_USER_ID, USER_USER_PAT},
|
||||
environment::TestEnvironment,
|
||||
};
|
||||
|
||||
// A reusable test type that works for any permissions test testing an endpoint that:
|
||||
// - returns a known 'expected_failure_code' if the scope is not present (defaults to 401)
|
||||
// - returns a 200-299 if the scope is present
|
||||
// - returns failure and success JSON bodies for requests that are 200 (for performing non-simple follow-up tests on)
|
||||
// This uses a builder format, so you can chain methods to set the parameters to non-defaults (most will probably be not need to be set).
|
||||
pub struct PermissionsTest<'a> {
|
||||
test_env: &'a TestEnvironment,
|
||||
// Permissions expected to fail on this test. By default, this is all permissions except the success permissions.
|
||||
// (To ensure we have isolated the permissions we are testing)
|
||||
failure_project_permissions: Option<ProjectPermissions>,
|
||||
failure_organization_permissions: Option<OrganizationPermissions>,
|
||||
|
||||
// User ID to use for the test user, and their PAT
|
||||
user_id: &'a str,
|
||||
user_pat: &'a str,
|
||||
|
||||
// Whether or not the user ID should be removed from the project/organization team after the test
|
||||
// (This is mostly reelvant if you are also using an existing project/organization, and want to do
|
||||
// multiple tests with the same user.
|
||||
remove_user: bool,
|
||||
|
||||
// ID to use for the test project (project, organization)
|
||||
// By default, create a new project or organization to test upon.
|
||||
// However, if we want, we can use an existing project or organization.
|
||||
// (eg: if we want to test a specific project, or a project with a specific state)
|
||||
project_id: Option<String>,
|
||||
project_team_id: Option<String>,
|
||||
organization_id: Option<String>,
|
||||
organization_team_id: Option<String>,
|
||||
|
||||
// The codes that is allow to be returned if the scope is not present.
|
||||
// (for instance, we might expect a 401, but not a 400)
|
||||
allowed_failure_codes: Vec<u16>,
|
||||
}
|
||||
|
||||
pub struct PermissionsTestContext<'a> {
|
||||
pub test_env: &'a TestEnvironment,
|
||||
pub user_id: &'a str,
|
||||
pub user_pat: &'a str,
|
||||
pub project_id: Option<&'a str>,
|
||||
pub team_id: Option<&'a str>,
|
||||
pub organization_id: Option<&'a str>,
|
||||
pub organization_team_id: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> PermissionsTest<'a> {
|
||||
pub fn new(test_env: &'a TestEnvironment) -> Self {
|
||||
Self {
|
||||
test_env,
|
||||
failure_project_permissions: None,
|
||||
failure_organization_permissions: None,
|
||||
user_id: USER_USER_ID,
|
||||
user_pat: USER_USER_PAT,
|
||||
remove_user: false,
|
||||
project_id: None,
|
||||
organization_id: None,
|
||||
project_team_id: None,
|
||||
organization_team_id: None,
|
||||
allowed_failure_codes: vec![401, 404],
|
||||
}
|
||||
}
|
||||
|
||||
// Set non-standard failure permissions
|
||||
// If not set, it will be set to all permissions except the success permissions
|
||||
// (eg: if a combination of permissions is needed, but you want to make sure that the endpoint does not work with all-but-one of them)
|
||||
pub fn with_failure_permissions(
|
||||
mut self,
|
||||
failure_project_permissions: Option<ProjectPermissions>,
|
||||
failure_organization_permissions: Option<OrganizationPermissions>,
|
||||
) -> Self {
|
||||
self.failure_project_permissions = failure_project_permissions;
|
||||
self.failure_organization_permissions = failure_organization_permissions;
|
||||
self
|
||||
}
|
||||
|
||||
// Set the user ID to use
|
||||
// (eg: a moderator, or friend)
|
||||
// remove_user: Whether or not the user ID should be removed from the project/organization team after the test
|
||||
pub fn with_user(mut self, user_id: &'a str, user_pat: &'a str, remove_user: bool) -> Self {
|
||||
self.user_id = user_id;
|
||||
self.user_pat = user_pat;
|
||||
self.remove_user = remove_user;
|
||||
self
|
||||
}
|
||||
|
||||
// If a non-standard code is expected.
|
||||
// (eg: perhaps 200 for a resource with hidden values deeper in)
|
||||
pub fn with_failure_codes(
|
||||
mut self,
|
||||
allowed_failure_codes: impl IntoIterator<Item = u16>,
|
||||
) -> Self {
|
||||
self.allowed_failure_codes = allowed_failure_codes.into_iter().collect();
|
||||
self
|
||||
}
|
||||
|
||||
// If an existing project or organization is intended to be used
|
||||
// We will not create a new project, and will use the given project ID
|
||||
// (But will still add the user to the project's team)
|
||||
pub fn with_existing_project(mut self, project_id: &str, team_id: &str) -> Self {
|
||||
self.project_id = Some(project_id.to_string());
|
||||
self.project_team_id = Some(team_id.to_string());
|
||||
self
|
||||
}
|
||||
pub fn with_existing_organization(mut self, organization_id: &str, team_id: &str) -> Self {
|
||||
self.organization_id = Some(organization_id.to_string());
|
||||
self.organization_team_id = Some(team_id.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn simple_project_permissions_test<T>(
|
||||
&self,
|
||||
success_permissions: ProjectPermissions,
|
||||
req_gen: T,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
T: Fn(&PermissionsTestContext) -> TestRequest,
|
||||
{
|
||||
let test_env = self.test_env;
|
||||
let failure_project_permissions = self
|
||||
.failure_project_permissions
|
||||
.unwrap_or(ProjectPermissions::all() ^ success_permissions);
|
||||
let test_context = PermissionsTestContext {
|
||||
test_env,
|
||||
user_id: self.user_id,
|
||||
user_pat: self.user_pat,
|
||||
project_id: None,
|
||||
team_id: None,
|
||||
organization_id: None,
|
||||
organization_team_id: None,
|
||||
};
|
||||
|
||||
let (project_id, team_id) = if self.project_id.is_some() && self.project_team_id.is_some() {
|
||||
(
|
||||
self.project_id.clone().unwrap(),
|
||||
self.project_team_id.clone().unwrap(),
|
||||
)
|
||||
} else {
|
||||
create_dummy_project(test_env).await
|
||||
};
|
||||
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&team_id,
|
||||
Some(failure_project_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Failure test
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
|
||||
return Err(format!(
|
||||
"Failure permissions test failed. Expected failure codes {} got {}",
|
||||
self.allowed_failure_codes
|
||||
.iter()
|
||||
.map(|code| code.to_string())
|
||||
.join(","),
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
// Patch user's permissions to success permissions
|
||||
modify_user_team_permissions(
|
||||
self.user_id,
|
||||
&team_id,
|
||||
Some(success_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Successful test
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!(
|
||||
"Success permissions test failed. Expected success, got {}",
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
// If the remove_user flag is set, remove the user from the project
|
||||
// Relevant for existing projects/users
|
||||
if self.remove_user {
|
||||
remove_user_from_team(self.user_id, &team_id, test_env).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn simple_organization_permissions_test<T>(
|
||||
&self,
|
||||
success_permissions: OrganizationPermissions,
|
||||
req_gen: T,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
T: Fn(&PermissionsTestContext) -> TestRequest,
|
||||
{
|
||||
let test_env = self.test_env;
|
||||
let failure_organization_permissions = self
|
||||
.failure_organization_permissions
|
||||
.unwrap_or(OrganizationPermissions::all() ^ success_permissions);
|
||||
let test_context = PermissionsTestContext {
|
||||
test_env,
|
||||
user_id: self.user_id,
|
||||
user_pat: self.user_pat,
|
||||
project_id: None,
|
||||
team_id: None,
|
||||
organization_id: None,
|
||||
organization_team_id: None,
|
||||
};
|
||||
|
||||
let (organization_id, team_id) =
|
||||
if self.organization_id.is_some() && self.organization_team_id.is_some() {
|
||||
(
|
||||
self.organization_id.clone().unwrap(),
|
||||
self.organization_team_id.clone().unwrap(),
|
||||
)
|
||||
} else {
|
||||
create_dummy_org(test_env).await
|
||||
};
|
||||
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&team_id,
|
||||
None,
|
||||
Some(failure_organization_permissions),
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Failure test
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
organization_id: Some(&organization_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
|
||||
return Err(format!(
|
||||
"Failure permissions test failed. Expected failure codes {} got {}",
|
||||
self.allowed_failure_codes
|
||||
.iter()
|
||||
.map(|code| code.to_string())
|
||||
.join(","),
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
// Patch user's permissions to success permissions
|
||||
modify_user_team_permissions(
|
||||
self.user_id,
|
||||
&team_id,
|
||||
None,
|
||||
Some(success_permissions),
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Successful test
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
organization_id: Some(&organization_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!(
|
||||
"Success permissions test failed. Expected success, got {}",
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
// If the remove_user flag is set, remove the user from the organization
|
||||
// Relevant for existing projects/users
|
||||
if self.remove_user {
|
||||
remove_user_from_team(self.user_id, &team_id, test_env).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn full_project_permissions_test<T>(
|
||||
&self,
|
||||
success_permissions: ProjectPermissions,
|
||||
req_gen: T,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
T: Fn(&PermissionsTestContext) -> TestRequest,
|
||||
{
|
||||
let test_env = self.test_env;
|
||||
let failure_project_permissions = self
|
||||
.failure_project_permissions
|
||||
.unwrap_or(ProjectPermissions::all() ^ success_permissions);
|
||||
let test_context = PermissionsTestContext {
|
||||
test_env,
|
||||
user_id: self.user_id,
|
||||
user_pat: self.user_pat,
|
||||
project_id: None,
|
||||
team_id: None,
|
||||
organization_id: None,
|
||||
organization_team_id: None,
|
||||
};
|
||||
|
||||
// TEST 1: Failure
|
||||
// Random user, unaffiliated with the project, with no permissions
|
||||
let test_1 = async {
|
||||
let (project_id, team_id) = create_dummy_project(test_env).await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
let resp = test_env.call(request).await;
|
||||
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
|
||||
return Err(format!(
|
||||
"Test 1 failed. Expected failure codes {} got {}",
|
||||
self.allowed_failure_codes
|
||||
.iter()
|
||||
.map(|code| code.to_string())
|
||||
.join(","),
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p =
|
||||
get_project_permissions(self.user_id, self.user_pat, &project_id, test_env).await;
|
||||
if p != ProjectPermissions::empty() {
|
||||
return Err(format!(
|
||||
"Test 1 failed. Expected no permissions, got {:?}",
|
||||
p
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// TEST 2: Failure
|
||||
// User affiliated with the project, with failure permissions
|
||||
let test_2 = async {
|
||||
let (project_id, team_id) = create_dummy_project(test_env).await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&team_id,
|
||||
Some(failure_project_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
|
||||
return Err(format!(
|
||||
"Test 2 failed. Expected failure codes {} got {}",
|
||||
self.allowed_failure_codes
|
||||
.iter()
|
||||
.map(|code| code.to_string())
|
||||
.join(","),
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p =
|
||||
get_project_permissions(self.user_id, self.user_pat, &project_id, test_env).await;
|
||||
if p != failure_project_permissions {
|
||||
return Err(format!(
|
||||
"Test 2 failed. Expected {:?}, got {:?}",
|
||||
failure_project_permissions, p
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// TEST 3: Success
|
||||
// User affiliated with the project, with the given permissions
|
||||
let test_3 = async {
|
||||
let (project_id, team_id) = create_dummy_project(test_env).await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&team_id,
|
||||
Some(success_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!(
|
||||
"Test 3 failed. Expected success, got {}",
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p =
|
||||
get_project_permissions(self.user_id, self.user_pat, &project_id, test_env).await;
|
||||
if p != success_permissions {
|
||||
return Err(format!(
|
||||
"Test 3 failed. Expected {:?}, got {:?}",
|
||||
success_permissions, p
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// TEST 4: Failure
|
||||
// Project has an organization
|
||||
// User affiliated with the project's org, with default failure permissions
|
||||
let test_4 = async {
|
||||
let (project_id, team_id) = create_dummy_project(test_env).await;
|
||||
let (organization_id, organization_team_id) = create_dummy_org(test_env).await;
|
||||
add_project_to_org(test_env, &project_id, &organization_id).await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_team_id,
|
||||
Some(failure_project_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
|
||||
return Err(format!(
|
||||
"Test 4 failed. Expected failure codes {} got {}",
|
||||
self.allowed_failure_codes
|
||||
.iter()
|
||||
.map(|code| code.to_string())
|
||||
.join(","),
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p =
|
||||
get_project_permissions(self.user_id, self.user_pat, &project_id, test_env).await;
|
||||
if p != failure_project_permissions {
|
||||
return Err(format!(
|
||||
"Test 4 failed. Expected {:?}, got {:?}",
|
||||
failure_project_permissions, p
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// TEST 5: Success
|
||||
// Project has an organization
|
||||
// User affiliated with the project's org, with the default success
|
||||
let test_5 = async {
|
||||
let (project_id, team_id) = create_dummy_project(test_env).await;
|
||||
let (organization_id, organization_team_id) = create_dummy_org(test_env).await;
|
||||
add_project_to_org(test_env, &project_id, &organization_id).await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_team_id,
|
||||
Some(success_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!(
|
||||
"Test 5 failed. Expected success, got {}",
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p =
|
||||
get_project_permissions(self.user_id, self.user_pat, &project_id, test_env).await;
|
||||
if p != success_permissions {
|
||||
return Err(format!(
|
||||
"Test 5 failed. Expected {:?}, got {:?}",
|
||||
success_permissions, p
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// TEST 6: Failure
|
||||
// Project has an organization
|
||||
// User affiliated with the project's org (even can have successful permissions!)
|
||||
// User overwritten on the project team with failure permissions
|
||||
let test_6 = async {
|
||||
let (project_id, team_id) = create_dummy_project(test_env).await;
|
||||
let (organization_id, organization_team_id) = create_dummy_org(test_env).await;
|
||||
add_project_to_org(test_env, &project_id, &organization_id).await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_team_id,
|
||||
Some(success_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&team_id,
|
||||
Some(failure_project_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
|
||||
return Err(format!(
|
||||
"Test 6 failed. Expected failure codes {} got {}",
|
||||
self.allowed_failure_codes
|
||||
.iter()
|
||||
.map(|code| code.to_string())
|
||||
.join(","),
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p =
|
||||
get_project_permissions(self.user_id, self.user_pat, &project_id, test_env).await;
|
||||
if p != failure_project_permissions {
|
||||
return Err(format!(
|
||||
"Test 6 failed. Expected {:?}, got {:?}",
|
||||
failure_project_permissions, p
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// TEST 7: Success
|
||||
// Project has an organization
|
||||
// User affiliated with the project's org with default failure permissions
|
||||
// User overwritten to the project with the success permissions
|
||||
let test_7 = async {
|
||||
let (project_id, team_id) = create_dummy_project(test_env).await;
|
||||
let (organization_id, organization_team_id) = create_dummy_org(test_env).await;
|
||||
add_project_to_org(test_env, &project_id, &organization_id).await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_team_id,
|
||||
Some(failure_project_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&team_id,
|
||||
Some(success_permissions),
|
||||
None,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
project_id: Some(&project_id),
|
||||
team_id: Some(&team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!(
|
||||
"Test 7 failed. Expected success, got {}",
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p =
|
||||
get_project_permissions(self.user_id, self.user_pat, &project_id, test_env).await;
|
||||
if p != success_permissions {
|
||||
return Err(format!(
|
||||
"Test 7 failed. Expected {:?}, got {:?}",
|
||||
success_permissions, p
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
tokio::try_join!(test_1, test_2, test_3, test_4, test_5, test_6, test_7,)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn full_organization_permissions_tests<T>(
|
||||
&self,
|
||||
success_permissions: OrganizationPermissions,
|
||||
req_gen: T,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
T: Fn(&PermissionsTestContext) -> TestRequest,
|
||||
{
|
||||
let test_env = self.test_env;
|
||||
let failure_organization_permissions = self
|
||||
.failure_organization_permissions
|
||||
.unwrap_or(OrganizationPermissions::all() ^ success_permissions);
|
||||
let test_context = PermissionsTestContext {
|
||||
test_env,
|
||||
user_id: self.user_id,
|
||||
user_pat: self.user_pat,
|
||||
project_id: None, // Will be overwritten on each test
|
||||
team_id: None, // Will be overwritten on each test
|
||||
organization_id: None,
|
||||
organization_team_id: None,
|
||||
};
|
||||
|
||||
// TEST 1: Failure
|
||||
// Random user, entirely unaffliaited with the organization
|
||||
let test_1 = async {
|
||||
let (organization_id, organization_team_id) = create_dummy_org(test_env).await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
organization_id: Some(&organization_id),
|
||||
organization_team_id: Some(&organization_team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
let resp = test_env.call(request).await;
|
||||
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
|
||||
return Err(format!(
|
||||
"Test 1 failed. Expected failure codes {} got {}",
|
||||
self.allowed_failure_codes
|
||||
.iter()
|
||||
.map(|code| code.to_string())
|
||||
.join(","),
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p = get_organization_permissions(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_id,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
if p != OrganizationPermissions::empty() {
|
||||
return Err(format!(
|
||||
"Test 1 failed. Expected no permissions, got {:?}",
|
||||
p
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// TEST 2: Failure
|
||||
// User affiliated with the organization, with failure permissions
|
||||
let test_2 = async {
|
||||
let (organization_id, organization_team_id) = create_dummy_org(test_env).await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_team_id,
|
||||
None,
|
||||
Some(failure_organization_permissions),
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
organization_id: Some(&organization_id),
|
||||
organization_team_id: Some(&organization_team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
|
||||
return Err(format!(
|
||||
"Test 2 failed. Expected failure codes {} got {}",
|
||||
self.allowed_failure_codes
|
||||
.iter()
|
||||
.map(|code| code.to_string())
|
||||
.join(","),
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p = get_organization_permissions(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_id,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
if p != failure_organization_permissions {
|
||||
return Err(format!(
|
||||
"Test 2 failed. Expected {:?}, got {:?}",
|
||||
failure_organization_permissions, p
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// TEST 3: Success
|
||||
// User affiliated with the organization, with the given permissions
|
||||
let test_3 = async {
|
||||
let (organization_id, organization_team_id) = create_dummy_org(test_env).await;
|
||||
add_user_to_team(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_team_id,
|
||||
None,
|
||||
Some(success_permissions),
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = req_gen(&PermissionsTestContext {
|
||||
organization_id: Some(&organization_id),
|
||||
organization_team_id: Some(&organization_team_id),
|
||||
..test_context
|
||||
})
|
||||
.append_header(("Authorization", self.user_pat))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(request).await;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!(
|
||||
"Test 3 failed. Expected success, got {}",
|
||||
resp.status().as_u16()
|
||||
));
|
||||
}
|
||||
|
||||
let p = get_organization_permissions(
|
||||
self.user_id,
|
||||
self.user_pat,
|
||||
&organization_id,
|
||||
test_env,
|
||||
)
|
||||
.await;
|
||||
if p != success_permissions {
|
||||
return Err(format!(
|
||||
"Test 3 failed. Expected {:?}, got {:?}",
|
||||
success_permissions, p
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
tokio::try_join!(test_1, test_2, test_3,).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_dummy_project(test_env: &TestEnvironment) -> (String, String) {
|
||||
let api = &test_env.v2;
|
||||
|
||||
// Create a very simple project
|
||||
let slug = generate_random_name("test_project");
|
||||
|
||||
let creation_data = request_data::get_public_project_creation_data(&slug, None);
|
||||
let (project, _) = api.add_public_project(creation_data, ADMIN_USER_PAT).await;
|
||||
let project_id = project.id.to_string();
|
||||
let team_id = project.team.to_string();
|
||||
|
||||
(project_id, team_id)
|
||||
}
|
||||
|
||||
async fn create_dummy_org(test_env: &TestEnvironment) -> (String, String) {
|
||||
// Create a very simple organization
|
||||
let name = generate_random_name("test_org");
|
||||
let api = &test_env.v2;
|
||||
|
||||
let resp = api
|
||||
.create_organization(&name, "Example description.", ADMIN_USER_PAT)
|
||||
.await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let organization = api
|
||||
.get_organization_deserialized(&name, ADMIN_USER_PAT)
|
||||
.await;
|
||||
let organizaion_id = organization.id.to_string();
|
||||
let team_id = organization.team_id.to_string();
|
||||
|
||||
(organizaion_id, team_id)
|
||||
}
|
||||
|
||||
async fn add_project_to_org(test_env: &TestEnvironment, project_id: &str, organization_id: &str) {
|
||||
let api = &test_env.v2;
|
||||
let resp = api
|
||||
.organization_add_project(organization_id, project_id, ADMIN_USER_PAT)
|
||||
.await;
|
||||
assert!(resp.status().is_success());
|
||||
}
|
||||
|
||||
async fn add_user_to_team(
|
||||
user_id: &str,
|
||||
user_pat: &str,
|
||||
team_id: &str,
|
||||
project_permissions: Option<ProjectPermissions>,
|
||||
organization_permissions: Option<OrganizationPermissions>,
|
||||
test_env: &TestEnvironment,
|
||||
) {
|
||||
let api = &test_env.v2;
|
||||
|
||||
// Invite user
|
||||
let resp = api
|
||||
.add_user_to_team(
|
||||
team_id,
|
||||
user_id,
|
||||
project_permissions,
|
||||
organization_permissions,
|
||||
ADMIN_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
// Accept invitation
|
||||
let resp = api.join_team(team_id, user_pat).await;
|
||||
assert!(resp.status().is_success());
|
||||
}
|
||||
|
||||
async fn modify_user_team_permissions(
|
||||
user_id: &str,
|
||||
team_id: &str,
|
||||
permissions: Option<ProjectPermissions>,
|
||||
organization_permissions: Option<OrganizationPermissions>,
|
||||
test_env: &TestEnvironment,
|
||||
) {
|
||||
let api = &test_env.v2;
|
||||
|
||||
// Send invitation to user
|
||||
let resp = api
|
||||
.edit_team_member(
|
||||
team_id,
|
||||
user_id,
|
||||
json!({
|
||||
"permissions" : permissions.map(|p| p.bits()),
|
||||
"organization_permissions" : organization_permissions.map(|p| p.bits()),
|
||||
}),
|
||||
ADMIN_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert!(resp.status().is_success());
|
||||
}
|
||||
|
||||
async fn remove_user_from_team(user_id: &str, team_id: &str, test_env: &TestEnvironment) {
|
||||
// Send invitation to user
|
||||
let api = &test_env.v2;
|
||||
let resp = api.remove_from_team(team_id, user_id, ADMIN_USER_PAT).await;
|
||||
assert!(resp.status().is_success());
|
||||
}
|
||||
|
||||
async fn get_project_permissions(
|
||||
user_id: &str,
|
||||
user_pat: &str,
|
||||
project_id: &str,
|
||||
test_env: &TestEnvironment,
|
||||
) -> ProjectPermissions {
|
||||
let resp = test_env.v2.get_project_members(project_id, user_pat).await;
|
||||
let permissions = if resp.status().as_u16() == 200 {
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
value
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|member| member["user"]["id"].as_str().unwrap() == user_id)
|
||||
.map(|member| member["permissions"].as_u64().unwrap())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
ProjectPermissions::from_bits_truncate(permissions)
|
||||
}
|
||||
|
||||
async fn get_organization_permissions(
|
||||
user_id: &str,
|
||||
user_pat: &str,
|
||||
organization_id: &str,
|
||||
test_env: &TestEnvironment,
|
||||
) -> OrganizationPermissions {
|
||||
let api = &test_env.v2;
|
||||
let resp = api
|
||||
.get_organization_members(organization_id, user_pat)
|
||||
.await;
|
||||
let permissions = if resp.status().as_u16() == 200 {
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
value
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|member| member["user"]["id"].as_str().unwrap() == user_id)
|
||||
.map(|member| member["organization_permissions"].as_u64().unwrap())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
OrganizationPermissions::from_bits_truncate(permissions)
|
||||
}
|
||||
@@ -1,18 +1,45 @@
|
||||
#![allow(dead_code)]
|
||||
use serde_json::json;
|
||||
|
||||
use super::{actix::MultipartSegment, dummy_data::DummyJarFile};
|
||||
use super::{
|
||||
actix::MultipartSegment,
|
||||
dummy_data::{DummyImage, DummyJarFile},
|
||||
};
|
||||
use crate::common::actix::MultipartSegmentData;
|
||||
|
||||
pub struct ProjectCreationRequestData {
|
||||
pub slug: String,
|
||||
pub jar: DummyJarFile,
|
||||
pub jar: Option<DummyJarFile>,
|
||||
pub segment_data: Vec<MultipartSegment>,
|
||||
}
|
||||
|
||||
pub struct ImageData {
|
||||
pub filename: String,
|
||||
pub extension: String,
|
||||
pub icon: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn get_public_project_creation_data(
|
||||
slug: &str,
|
||||
jar: DummyJarFile,
|
||||
version_jar: Option<DummyJarFile>,
|
||||
) -> ProjectCreationRequestData {
|
||||
let initial_versions = if let Some(ref jar) = version_jar {
|
||||
json!([{
|
||||
"file_parts": [jar.filename()],
|
||||
"version_number": "1.2.3",
|
||||
"version_title": "start",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
"featured": true
|
||||
}])
|
||||
} else {
|
||||
json!([])
|
||||
};
|
||||
|
||||
let is_draft = version_jar.is_none();
|
||||
|
||||
let json_data = json!(
|
||||
{
|
||||
"title": format!("Test Project {slug}"),
|
||||
@@ -21,16 +48,8 @@ pub fn get_public_project_creation_data(
|
||||
"body": "This project is approved, and versions are listed.",
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"initial_versions": [{
|
||||
"file_parts": [jar.filename()],
|
||||
"version_number": "1.2.3",
|
||||
"version_title": "start",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
"featured": true
|
||||
}],
|
||||
"initial_versions": initial_versions,
|
||||
"is_draft": is_draft,
|
||||
"categories": [],
|
||||
"license_id": "MIT"
|
||||
}
|
||||
@@ -44,17 +63,31 @@ pub fn get_public_project_creation_data(
|
||||
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
|
||||
};
|
||||
|
||||
// Basic file
|
||||
let file_segment = MultipartSegment {
|
||||
name: jar.filename(),
|
||||
filename: Some(jar.filename()),
|
||||
content_type: Some("application/java-archive".to_string()),
|
||||
data: MultipartSegmentData::Binary(jar.bytes()),
|
||||
let segment_data = if let Some(ref jar) = version_jar {
|
||||
// Basic file
|
||||
let file_segment = MultipartSegment {
|
||||
name: jar.filename(),
|
||||
filename: Some(jar.filename()),
|
||||
content_type: Some("application/java-archive".to_string()),
|
||||
data: MultipartSegmentData::Binary(jar.bytes()),
|
||||
};
|
||||
|
||||
vec![json_segment.clone(), file_segment]
|
||||
} else {
|
||||
vec![json_segment.clone()]
|
||||
};
|
||||
|
||||
ProjectCreationRequestData {
|
||||
slug: slug.to_string(),
|
||||
jar,
|
||||
segment_data: vec![json_segment.clone(), file_segment.clone()],
|
||||
jar: version_jar,
|
||||
segment_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_icon_data(dummy_icon: DummyImage) -> ImageData {
|
||||
ImageData {
|
||||
filename: dummy_icon.filename(),
|
||||
extension: dummy_icon.extension(),
|
||||
icon: dummy_icon.bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ INSERT INTO users (id, username, name, email, role) VALUES (5, 'enemy', 'Enemy T
|
||||
-- Full PATs for each user, with different scopes
|
||||
-- These are not legal PATs, as they contain all scopes- they mimic permissions of a logged in user
|
||||
-- IDs: 50-54, o p q r s
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (50, 1, 'admin-pat', 'mrp_patadmin', B'11111111111111111111111111111111111'::BIGINT, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (51, 2, 'moderator-pat', 'mrp_patmoderator', B'11111111111111111111111111111111111'::BIGINT, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (52, 3, 'user-pat', 'mrp_patuser', B'11111111111111111111111111111111111'::BIGINT, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (53, 4, 'friend-pat', 'mrp_patfriend', B'11111111111111111111111111111111111'::BIGINT, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (54, 5, 'enemy-pat', 'mrp_patenemy', B'11111111111111111111111111111111111'::BIGINT, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (50, 1, 'admin-pat', 'mrp_patadmin', $1, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (51, 2, 'moderator-pat', 'mrp_patmoderator', $1, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (52, 3, 'user-pat', 'mrp_patuser', $1, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (53, 4, 'friend-pat', 'mrp_patfriend', $1, '2030-08-18 15:48:58.435729+00');
|
||||
INSERT INTO pats (id, user_id, name, access_token, scopes, expires) VALUES (54, 5, 'enemy-pat', 'mrp_patenemy', $1, '2030-08-18 15:48:58.435729+00');
|
||||
|
||||
-- -- Sample game versions, loaders, categories
|
||||
INSERT INTO game_versions (id, version, type, created)
|
||||
@@ -43,4 +43,9 @@ INSERT INTO categories (id, category, project_type) VALUES
|
||||
(104, 'food', 2),
|
||||
(105, 'magic', 2),
|
||||
(106, 'mobs', 2),
|
||||
(107, 'optimization', 2);
|
||||
(107, 'optimization', 2);
|
||||
|
||||
-- Create dummy data table to mark that this file has been run
|
||||
CREATE TABLE dummy_data (
|
||||
update_id bigint PRIMARY KEY
|
||||
);
|
||||
|
||||
@@ -8,12 +8,18 @@ mod common;
|
||||
#[actix_rt::test]
|
||||
pub async fn get_user_notifications_after_team_invitation_returns_notification() {
|
||||
with_test_environment(|test_env| async move {
|
||||
let alpha_team_id = test_env.dummy.as_ref().unwrap().alpha_team_id.clone();
|
||||
let alpha_team_id = test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_alpha
|
||||
.team_id
|
||||
.clone();
|
||||
let api = test_env.v2;
|
||||
api.get_user_notifications_deserialized(FRIEND_USER_ID, FRIEND_USER_PAT)
|
||||
.await;
|
||||
|
||||
api.add_user_to_team(&alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
api.add_user_to_team(&alpha_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
|
||||
let notifications = api
|
||||
|
||||
649
tests/organizations.rs
Normal file
649
tests/organizations.rs
Normal file
@@ -0,0 +1,649 @@
|
||||
use crate::common::{
|
||||
database::{generate_random_name, ADMIN_USER_PAT, MOD_USER_ID, MOD_USER_PAT, USER_USER_ID},
|
||||
dummy_data::DummyImage,
|
||||
environment::TestEnvironment,
|
||||
request_data::get_icon_data,
|
||||
};
|
||||
use actix_web::test;
|
||||
use bytes::Bytes;
|
||||
use common::{
|
||||
database::{FRIEND_USER_ID, FRIEND_USER_PAT, USER_USER_PAT},
|
||||
permissions::{PermissionsTest, PermissionsTestContext},
|
||||
};
|
||||
use labrinth::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||
use serde_json::json;
|
||||
|
||||
mod common;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn create_organization() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let api = &test_env.v2;
|
||||
let zeta_organization_slug = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
|
||||
// Failed creations title:
|
||||
// - slug collision with zeta
|
||||
// - too short slug
|
||||
// - too long slug
|
||||
// - not url safe slug
|
||||
for title in [
|
||||
zeta_organization_slug,
|
||||
"a",
|
||||
&"a".repeat(100),
|
||||
"not url safe%&^!#$##!@#$%^&*()",
|
||||
] {
|
||||
let resp = api
|
||||
.create_organization(title, "theta_description", USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
}
|
||||
|
||||
// Failed creations description:
|
||||
// - too short slug
|
||||
// - too long slug
|
||||
for description in ["a", &"a".repeat(300)] {
|
||||
let resp = api
|
||||
.create_organization("theta", description, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
}
|
||||
|
||||
// Create 'theta' organization
|
||||
let resp = api
|
||||
.create_organization("theta", "not url safe%&^!#$##!@#$%^&", USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Get organization using slug
|
||||
let theta = api
|
||||
.get_organization_deserialized("theta", USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(theta.title, "theta");
|
||||
assert_eq!(theta.description, "not url safe%&^!#$##!@#$%^&");
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Get created team
|
||||
let members = api
|
||||
.get_organization_members_deserialized("theta", USER_USER_PAT)
|
||||
.await;
|
||||
|
||||
// Should only be one member, which is USER_USER_ID, and is the owner with full permissions
|
||||
assert_eq!(members[0].user.id.to_string(), USER_USER_ID);
|
||||
assert_eq!(
|
||||
members[0].organization_permissions,
|
||||
Some(OrganizationPermissions::all())
|
||||
);
|
||||
assert_eq!(members[0].role, "Owner");
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn patch_organization() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let api = &test_env.v2;
|
||||
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
|
||||
// Create 'theta' organization
|
||||
let resp = api
|
||||
.create_organization("theta", "theta_description", USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Failed patch to zeta slug:
|
||||
// - slug collision with theta
|
||||
// - too short slug
|
||||
// - too long slug
|
||||
// - not url safe slug
|
||||
for title in [
|
||||
"theta",
|
||||
"a",
|
||||
&"a".repeat(100),
|
||||
"not url safe%&^!#$##!@#$%^&*()",
|
||||
] {
|
||||
let resp = api
|
||||
.edit_organization(
|
||||
zeta_organization_id,
|
||||
json!({
|
||||
"title": title,
|
||||
"description": "theta_description"
|
||||
}),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
}
|
||||
|
||||
// Failed patch to zeta description:
|
||||
// - too short description
|
||||
// - too long description
|
||||
for description in ["a", &"a".repeat(300)] {
|
||||
let resp = api
|
||||
.edit_organization(
|
||||
zeta_organization_id,
|
||||
json!({
|
||||
"description": description
|
||||
}),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
}
|
||||
|
||||
// Successful patch to many fields
|
||||
let resp = api
|
||||
.edit_organization(
|
||||
zeta_organization_id,
|
||||
json!({
|
||||
"title": "new_title",
|
||||
"description": "not url safe%&^!#$##!@#$%^&" // not-URL-safe description should still work
|
||||
}),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Get project using new slug
|
||||
let new_title = api
|
||||
.get_organization_deserialized("new_title", USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(new_title.title, "new_title");
|
||||
assert_eq!(new_title.description, "not url safe%&^!#$##!@#$%^&");
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
// add/remove icon
|
||||
#[actix_rt::test]
|
||||
async fn add_remove_icon() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let api = &test_env.v2;
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
|
||||
// Get project
|
||||
let resp = test_env
|
||||
.v2
|
||||
.get_organization_deserialized(zeta_organization_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.icon_url, None);
|
||||
|
||||
// Icon edit
|
||||
// Uses alpha organization to delete this icon
|
||||
let resp = api
|
||||
.edit_organization_icon(
|
||||
zeta_organization_id,
|
||||
Some(get_icon_data(DummyImage::SmallIcon)),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Get project
|
||||
let zeta_org = api
|
||||
.get_organization_deserialized(zeta_organization_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert!(zeta_org.icon_url.is_some());
|
||||
|
||||
// Icon delete
|
||||
// Uses alpha organization to delete added icon
|
||||
let resp = api
|
||||
.edit_organization_icon(zeta_organization_id, None, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Get project
|
||||
let zeta_org = api
|
||||
.get_organization_deserialized(zeta_organization_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert!(zeta_org.icon_url.is_none());
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
// delete org
|
||||
#[actix_rt::test]
|
||||
async fn delete_org() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let api = &test_env.v2;
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
|
||||
let resp = api
|
||||
.delete_organization(zeta_organization_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Get organization, which should no longer exist
|
||||
let resp = api
|
||||
.get_organization(zeta_organization_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 404);
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
// add/remove organization projects
|
||||
#[actix_rt::test]
|
||||
async fn add_remove_organization_projects() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_project_id: &str = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
let alpha_project_slug: &str = &test_env.dummy.as_ref().unwrap().project_alpha.project_slug;
|
||||
let zeta_organization_id: &str = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
|
||||
// Add/remove project to organization, first by ID, then by slug
|
||||
for alpha in [alpha_project_id, alpha_project_slug] {
|
||||
let resp = test_env
|
||||
.v2
|
||||
.organization_add_project(zeta_organization_id, alpha, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Get organization projects
|
||||
let projects = test_env
|
||||
.v2
|
||||
.get_organization_projects_deserialized(zeta_organization_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(projects[0].id.to_string(), alpha_project_id);
|
||||
assert_eq!(projects[0].slug, Some(alpha_project_slug.to_string()));
|
||||
|
||||
// Remove project from organization
|
||||
let resp = test_env
|
||||
.v2
|
||||
.organization_remove_project(zeta_organization_id, alpha, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Get organization projects
|
||||
let projects = test_env
|
||||
.v2
|
||||
.get_organization_projects_deserialized(zeta_organization_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert!(projects.is_empty());
|
||||
}
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_patch_organization() {
|
||||
let test_env = TestEnvironment::build(Some(8)).await;
|
||||
|
||||
// For each permission covered by EDIT_DETAILS, ensure the permission is required
|
||||
let edit_details = OrganizationPermissions::EDIT_DETAILS;
|
||||
let test_pairs = [
|
||||
("title", json!("")), // generated in the test to not collide slugs
|
||||
("description", json!("New description")),
|
||||
];
|
||||
|
||||
for (key, value) in test_pairs {
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/organization/{}",
|
||||
ctx.organization_id.unwrap()
|
||||
))
|
||||
.set_json(json!({
|
||||
key: if key == "title" {
|
||||
json!(generate_random_name("randomslug"))
|
||||
} else {
|
||||
value.clone()
|
||||
},
|
||||
}))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.simple_organization_permissions_test(edit_details, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
// Not covered by PATCH /organization
|
||||
#[actix_rt::test]
|
||||
async fn permissions_edit_details() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id;
|
||||
|
||||
let edit_details = OrganizationPermissions::EDIT_DETAILS;
|
||||
|
||||
// Icon edit
|
||||
// Uses alpha organization to delete this icon
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/organization/{}/icon?ext=png",
|
||||
ctx.organization_id.unwrap()
|
||||
))
|
||||
.set_payload(Bytes::from(
|
||||
include_bytes!("../tests/files/200x200.png") as &[u8]
|
||||
))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.simple_organization_permissions_test(edit_details, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Icon delete
|
||||
// Uses alpha project to delete added icon
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::delete().uri(&format!(
|
||||
"/v2/organization/{}/icon?ext=png",
|
||||
ctx.organization_id.unwrap()
|
||||
))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.simple_organization_permissions_test(edit_details, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_manage_invites() {
|
||||
// Add member, remove member, edit member
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let api = &test_env.v2;
|
||||
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id;
|
||||
|
||||
let manage_invites = OrganizationPermissions::MANAGE_INVITES;
|
||||
|
||||
// Add member
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{}/members", ctx.team_id.unwrap()))
|
||||
.set_json(json!({
|
||||
"user_id": MOD_USER_ID,
|
||||
"permissions": 0,
|
||||
"organization_permissions": 0,
|
||||
}))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.simple_organization_permissions_test(manage_invites, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Edit member
|
||||
let edit_member = OrganizationPermissions::EDIT_MEMBER;
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/team/{}/members/{MOD_USER_ID}",
|
||||
ctx.team_id.unwrap()
|
||||
))
|
||||
.set_json(json!({
|
||||
"organization_permissions": 0,
|
||||
}))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.simple_organization_permissions_test(edit_member, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// remove member
|
||||
// requires manage_invites if they have not yet accepted the invite
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::delete().uri(&format!(
|
||||
"/v2/team/{}/members/{MOD_USER_ID}",
|
||||
ctx.team_id.unwrap()
|
||||
))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.simple_organization_permissions_test(manage_invites, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// re-add member for testing
|
||||
let resp = api
|
||||
.add_user_to_team(zeta_team_id, MOD_USER_ID, None, None, ADMIN_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
let resp = api.join_team(zeta_team_id, MOD_USER_PAT).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// remove existing member (requires remove_member)
|
||||
let remove_member = OrganizationPermissions::REMOVE_MEMBER;
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::delete().uri(&format!(
|
||||
"/v2/team/{}/members/{MOD_USER_ID}",
|
||||
ctx.team_id.unwrap()
|
||||
))
|
||||
};
|
||||
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.simple_organization_permissions_test(remove_member, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_add_remove_project() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let api = &test_env.v2;
|
||||
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id;
|
||||
|
||||
let add_project = OrganizationPermissions::ADD_PROJECT;
|
||||
|
||||
// First, we add FRIEND_USER_ID to the alpha project and transfer ownership to them
|
||||
// This is because the ownership of a project is needed to add it to an organization
|
||||
let resp = api
|
||||
.add_user_to_team(alpha_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
let resp = api.join_team(alpha_team_id, FRIEND_USER_PAT).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
let resp = api
|
||||
.transfer_team_ownership(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Now, FRIEND_USER_ID owns the alpha project
|
||||
// Add alpha project to zeta organization
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::post()
|
||||
.uri(&format!(
|
||||
"/v2/organization/{}/projects",
|
||||
ctx.organization_id.unwrap()
|
||||
))
|
||||
.set_json(json!({
|
||||
"project_id": alpha_project_id,
|
||||
}))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.simple_organization_permissions_test(add_project, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Remove alpha project from zeta organization
|
||||
let remove_project = OrganizationPermissions::REMOVE_PROJECT;
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::delete().uri(&format!(
|
||||
"/v2/organization/{}/projects/{alpha_project_id}",
|
||||
ctx.organization_id.unwrap()
|
||||
))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.simple_organization_permissions_test(remove_project, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_delete_organization() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let delete_organization = OrganizationPermissions::DELETE_ORGANIZATION;
|
||||
|
||||
// Now, FRIEND_USER_ID owns the alpha project
|
||||
// Add alpha project to zeta organization
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::delete().uri(&format!(
|
||||
"/v2/organization/{}",
|
||||
ctx.organization_id.unwrap()
|
||||
))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.simple_organization_permissions_test(delete_organization, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_add_default_project_permissions() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id;
|
||||
|
||||
// Add member
|
||||
let add_member_default_permissions = OrganizationPermissions::MANAGE_INVITES
|
||||
| OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS;
|
||||
|
||||
// Failure test should include MANAGE_INVITES, as it is required to add
|
||||
// default permissions on an invited user, but should still fail without EDIT_MEMBER_DEFAULT_PERMISSIONS
|
||||
let failure_with_add_member = (OrganizationPermissions::all() ^ add_member_default_permissions)
|
||||
| OrganizationPermissions::MANAGE_INVITES;
|
||||
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{}/members", ctx.team_id.unwrap()))
|
||||
.set_json(json!({
|
||||
"user_id": MOD_USER_ID,
|
||||
// do not set permissions as it will be set to default, which is non-zero
|
||||
"organization_permissions": 0,
|
||||
}))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.with_failure_permissions(None, Some(failure_with_add_member))
|
||||
.simple_organization_permissions_test(add_member_default_permissions, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Now that member is added, modify default permissions
|
||||
let modify_member_default_permission = OrganizationPermissions::EDIT_MEMBER
|
||||
| OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS;
|
||||
|
||||
// Failure test should include MANAGE_INVITES, as it is required to add
|
||||
// default permissions on an invited user, but should still fail without EDIT_MEMBER_DEFAULT_PERMISSIONS
|
||||
let failure_with_modify_member = (OrganizationPermissions::all()
|
||||
^ add_member_default_permissions)
|
||||
| OrganizationPermissions::EDIT_MEMBER;
|
||||
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/team/{}/members/{MOD_USER_ID}",
|
||||
ctx.team_id.unwrap()
|
||||
))
|
||||
.set_json(json!({
|
||||
"permissions": ProjectPermissions::EDIT_DETAILS.bits(),
|
||||
}))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.with_existing_organization(zeta_organization_id, zeta_team_id)
|
||||
.with_user(FRIEND_USER_ID, FRIEND_USER_PAT, true)
|
||||
.with_failure_permissions(None, Some(failure_with_modify_member))
|
||||
.simple_organization_permissions_test(modify_member_default_permission, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_organization_permissions_consistency_test() {
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
// Ensuring that permission are as we expect them to be
|
||||
// Full organization permissions test
|
||||
let success_permissions = OrganizationPermissions::EDIT_DETAILS;
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/organization/{}",
|
||||
ctx.organization_id.unwrap()
|
||||
))
|
||||
.set_json(json!({
|
||||
"description": "Example description - changed.",
|
||||
}))
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.full_organization_permissions_tests(success_permissions, req_gen)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ mod common;
|
||||
// - ensure PATs can be deleted
|
||||
#[actix_rt::test]
|
||||
pub async fn pat_full_test() {
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
|
||||
// Create a PAT for a full test
|
||||
let req = test::TestRequest::post()
|
||||
@@ -163,7 +163,7 @@ pub async fn pat_full_test() {
|
||||
// Test illegal PAT setting, both in POST and PATCH
|
||||
#[actix_rt::test]
|
||||
pub async fn bad_pats() {
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
|
||||
// Creating a PAT with no name should fail
|
||||
let req = test::TestRequest::post()
|
||||
|
||||
900
tests/project.rs
900
tests/project.rs
File diff suppressed because it is too large
Load Diff
209
tests/scopes.rs
209
tests/scopes.rs
@@ -20,7 +20,7 @@ mod common;
|
||||
#[actix_rt::test]
|
||||
async fn user_scopes() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
|
||||
// User reading
|
||||
let read_user = Scopes::USER_READ;
|
||||
@@ -87,14 +87,20 @@ async fn user_scopes() {
|
||||
// Notifications
|
||||
#[actix_rt::test]
|
||||
pub async fn notifications_scopes() {
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().alpha_team_id.clone();
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_team_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_alpha
|
||||
.team_id
|
||||
.clone();
|
||||
|
||||
// We will invite user 'friend' to project team, and use that as a notification
|
||||
// Get notifications
|
||||
let resp = test_env
|
||||
.v2
|
||||
.add_user_to_team(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
.add_user_to_team(alpha_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
@@ -107,7 +113,7 @@ pub async fn notifications_scopes() {
|
||||
.test(req_gen, read_notifications)
|
||||
.await
|
||||
.unwrap();
|
||||
let notification_id = success.as_array().unwrap()[0]["id"].as_str().unwrap();
|
||||
let notification_id = success[0]["id"].as_str().unwrap();
|
||||
|
||||
let req_gen = || {
|
||||
test::TestRequest::get().uri(&format!(
|
||||
@@ -162,7 +168,7 @@ pub async fn notifications_scopes() {
|
||||
// We invite mod, get the notification ID, and do mass delete using that
|
||||
let resp = test_env
|
||||
.v2
|
||||
.add_user_to_team(alpha_team_id, MOD_USER_ID, USER_USER_PAT)
|
||||
.add_user_to_team(alpha_team_id, MOD_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
let read_notifications = Scopes::NOTIFICATION_READ;
|
||||
@@ -172,7 +178,7 @@ pub async fn notifications_scopes() {
|
||||
.test(req_gen, read_notifications)
|
||||
.await
|
||||
.unwrap();
|
||||
let notification_id = success.as_array().unwrap()[0]["id"].as_str().unwrap();
|
||||
let notification_id = success[0]["id"].as_str().unwrap();
|
||||
|
||||
let req_gen = || {
|
||||
test::TestRequest::delete().uri(&format!(
|
||||
@@ -193,7 +199,7 @@ pub async fn notifications_scopes() {
|
||||
// Project version creation scopes
|
||||
#[actix_rt::test]
|
||||
pub async fn project_version_create_scopes() {
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
|
||||
// Create project
|
||||
let create_project = Scopes::PROJECT_CREATE;
|
||||
@@ -292,11 +298,35 @@ pub async fn project_version_create_scopes() {
|
||||
// Project management scopes
|
||||
#[actix_rt::test]
|
||||
pub async fn project_version_reads_scopes() {
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let beta_project_id = &test_env.dummy.as_ref().unwrap().beta_project_id.clone();
|
||||
let beta_version_id = &test_env.dummy.as_ref().unwrap().beta_version_id.clone();
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().alpha_team_id.clone();
|
||||
let beta_file_hash = &test_env.dummy.as_ref().unwrap().beta_file_hash.clone();
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let beta_project_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_beta
|
||||
.project_id
|
||||
.clone();
|
||||
let beta_version_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_beta
|
||||
.version_id
|
||||
.clone();
|
||||
let alpha_team_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_alpha
|
||||
.team_id
|
||||
.clone();
|
||||
let beta_file_hash = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_beta
|
||||
.file_hash
|
||||
.clone();
|
||||
|
||||
// Project reading
|
||||
// Uses 404 as the expected failure code (or 200 and an empty list for mass reads)
|
||||
@@ -348,8 +378,8 @@ pub async fn project_version_reads_scopes() {
|
||||
.test(req_gen, read_project)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!failure.as_array().unwrap()[0].as_object().unwrap()["permissions"].is_number());
|
||||
assert!(success.as_array().unwrap()[0].as_object().unwrap()["permissions"].is_number());
|
||||
assert!(!failure[0]["permissions"].is_number());
|
||||
assert!(success[0]["permissions"].is_number());
|
||||
|
||||
let req_gen = || {
|
||||
test::TestRequest::get().uri(&format!(
|
||||
@@ -362,14 +392,8 @@ pub async fn project_version_reads_scopes() {
|
||||
.test(req_gen, read_project)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!failure.as_array().unwrap()[0].as_array().unwrap()[0]
|
||||
.as_object()
|
||||
.unwrap()["permissions"]
|
||||
.is_number());
|
||||
assert!(success.as_array().unwrap()[0].as_array().unwrap()[0]
|
||||
.as_object()
|
||||
.unwrap()["permissions"]
|
||||
.is_number());
|
||||
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
|
||||
@@ -510,9 +534,21 @@ pub async fn project_version_reads_scopes() {
|
||||
#[actix_rt::test]
|
||||
pub async fn project_write_scopes() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let beta_project_id = &test_env.dummy.as_ref().unwrap().beta_project_id.clone();
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().alpha_team_id.clone();
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let beta_project_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_beta
|
||||
.project_id
|
||||
.clone();
|
||||
let alpha_team_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_alpha
|
||||
.team_id
|
||||
.clone();
|
||||
|
||||
// Projects writing
|
||||
let write_project = Scopes::PROJECT_WRITE;
|
||||
@@ -714,10 +750,28 @@ pub async fn project_write_scopes() {
|
||||
#[actix_rt::test]
|
||||
pub async fn version_write_scopes() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let alpha_version_id = &test_env.dummy.as_ref().unwrap().beta_version_id.clone();
|
||||
let beta_version_id = &test_env.dummy.as_ref().unwrap().beta_version_id.clone();
|
||||
let alpha_file_hash = &test_env.dummy.as_ref().unwrap().beta_file_hash.clone();
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_version_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_alpha
|
||||
.version_id
|
||||
.clone();
|
||||
let beta_version_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_beta
|
||||
.version_id
|
||||
.clone();
|
||||
let alpha_file_hash = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_alpha
|
||||
.file_hash
|
||||
.clone();
|
||||
|
||||
let write_version = Scopes::VERSION_WRITE;
|
||||
|
||||
@@ -829,8 +883,14 @@ pub async fn version_write_scopes() {
|
||||
#[actix_rt::test]
|
||||
pub async fn report_scopes() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let beta_project_id = &test_env.dummy.as_ref().unwrap().beta_project_id.clone();
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let beta_project_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_beta
|
||||
.project_id
|
||||
.clone();
|
||||
|
||||
// Create report
|
||||
let report_create = Scopes::REPORT_CREATE;
|
||||
@@ -854,7 +914,7 @@ pub async fn report_scopes() {
|
||||
.test(req_gen, report_read)
|
||||
.await
|
||||
.unwrap();
|
||||
let report_id = success.as_array().unwrap()[0]["id"].as_str().unwrap();
|
||||
let report_id = success[0]["id"].as_str().unwrap();
|
||||
|
||||
let req_gen = || test::TestRequest::get().uri(&format!("/v2/report/{}", report_id));
|
||||
ScopeTest::new(&test_env)
|
||||
@@ -905,9 +965,21 @@ pub async fn report_scopes() {
|
||||
#[actix_rt::test]
|
||||
pub async fn thread_scopes() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let alpha_thread_id = &test_env.dummy.as_ref().unwrap().alpha_thread_id.clone();
|
||||
let beta_thread_id = &test_env.dummy.as_ref().unwrap().beta_thread_id.clone();
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_thread_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_alpha
|
||||
.thread_id
|
||||
.clone();
|
||||
let beta_thread_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_beta
|
||||
.thread_id
|
||||
.clone();
|
||||
|
||||
// Thread read
|
||||
let thread_read = Scopes::THREAD_READ;
|
||||
@@ -954,8 +1026,7 @@ pub async fn thread_scopes() {
|
||||
.test(req_gen, thread_read)
|
||||
.await
|
||||
.unwrap();
|
||||
let thread = success.as_array().unwrap()[0].as_object().unwrap();
|
||||
let thread_id = thread["id"].as_str().unwrap();
|
||||
let thread_id = success[0]["id"].as_str().unwrap();
|
||||
|
||||
// Moderator 'read' thread
|
||||
// Uses moderator PAT, as only moderators can see the moderation inbox
|
||||
@@ -974,10 +1045,8 @@ pub async fn thread_scopes() {
|
||||
.to_request();
|
||||
let resp = test_env.call(req_gen).await;
|
||||
let success: serde_json::Value = test::read_body_json(resp).await;
|
||||
let thread_messages = success.as_object().unwrap()["messages"].as_array().unwrap();
|
||||
let thread_message_id = thread_messages[0].as_object().unwrap()["id"]
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let thread_message_id = success["messages"][0]["id"].as_str().unwrap();
|
||||
|
||||
let req_gen = || test::TestRequest::delete().uri(&format!("/v2/message/{thread_message_id}"));
|
||||
ScopeTest::new(&test_env)
|
||||
.with_user_id(MOD_USER_ID_PARSED)
|
||||
@@ -992,7 +1061,7 @@ pub async fn thread_scopes() {
|
||||
// Pat scopes
|
||||
#[actix_rt::test]
|
||||
pub async fn pat_scopes() {
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
|
||||
// Pat create
|
||||
let pat_create = Scopes::PAT_CREATE;
|
||||
@@ -1045,8 +1114,14 @@ pub async fn pat_scopes() {
|
||||
#[actix_rt::test]
|
||||
pub async fn collections_scopes() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().alpha_project_id.clone();
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_project_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_alpha
|
||||
.project_id
|
||||
.clone();
|
||||
|
||||
// Create collection
|
||||
let collection_create = Scopes::COLLECTION_CREATE;
|
||||
@@ -1140,8 +1215,14 @@ pub async fn collections_scopes() {
|
||||
#[actix_rt::test]
|
||||
pub async fn organization_scopes() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build_with_dummy().await;
|
||||
let beta_project_id = &test_env.dummy.as_ref().unwrap().beta_project_id.clone();
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let beta_project_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.project_beta
|
||||
.project_id
|
||||
.clone();
|
||||
|
||||
// Create organization
|
||||
let organization_create = Scopes::ORGANIZATION_CREATE;
|
||||
@@ -1215,18 +1296,8 @@ pub async fn organization_scopes() {
|
||||
.test(req_gen, organization_read)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
failure.as_object().unwrap()["members"].as_array().unwrap()[0]
|
||||
.as_object()
|
||||
.unwrap()["permissions"]
|
||||
.is_null()
|
||||
);
|
||||
assert!(
|
||||
!success.as_object().unwrap()["members"].as_array().unwrap()[0]
|
||||
.as_object()
|
||||
.unwrap()["permissions"]
|
||||
.is_null()
|
||||
);
|
||||
assert!(failure["members"][0]["permissions"].is_null());
|
||||
assert!(!success["members"][0]["permissions"].is_null());
|
||||
|
||||
let req_gen = || {
|
||||
test::TestRequest::get().uri(&format!(
|
||||
@@ -1240,22 +1311,8 @@ pub async fn organization_scopes() {
|
||||
.test(req_gen, organization_read)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
failure.as_array().unwrap()[0].as_object().unwrap()["members"]
|
||||
.as_array()
|
||||
.unwrap()[0]
|
||||
.as_object()
|
||||
.unwrap()["permissions"]
|
||||
.is_null()
|
||||
);
|
||||
assert!(
|
||||
!success.as_array().unwrap()[0].as_object().unwrap()["members"]
|
||||
.as_array()
|
||||
.unwrap()[0]
|
||||
.as_object()
|
||||
.unwrap()["permissions"]
|
||||
.is_null()
|
||||
);
|
||||
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 =
|
||||
@@ -1300,6 +1357,4 @@ pub async fn organization_scopes() {
|
||||
|
||||
// TODO: Some hash/version files functions
|
||||
|
||||
// TODO: Meta pat stuff
|
||||
|
||||
// TODO: Image scopes
|
||||
|
||||
661
tests/teams.rs
Normal file
661
tests/teams.rs
Normal file
@@ -0,0 +1,661 @@
|
||||
use actix_web::test;
|
||||
use labrinth::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::database::*;
|
||||
|
||||
use crate::common::environment::TestEnvironment;
|
||||
|
||||
// importing common module.
|
||||
mod common;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_get_team() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id;
|
||||
|
||||
// Perform tests for an organization team and a project team
|
||||
for (team_association_id, team_association, team_id) in [
|
||||
(alpha_project_id, "project", alpha_team_id),
|
||||
(zeta_organization_id, "organization", zeta_team_id),
|
||||
] {
|
||||
// A non-member of the team should get basic info but not be able to see private data
|
||||
for uri in [
|
||||
format!("/v2/team/{team_id}/members"),
|
||||
format!("/v2/{team_association}/{team_association_id}/members"),
|
||||
] {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&uri)
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
assert_eq!(value[0]["user"]["id"], USER_USER_ID);
|
||||
assert!(value[0]["permissions"].is_null());
|
||||
}
|
||||
|
||||
// A non-accepted member of the team should:
|
||||
// - not be able to see private data about the team, but see all members including themselves
|
||||
// - should not appear in the team members list to enemy users
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{team_id}/members"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(&json!({
|
||||
"user_id": FRIEND_USER_ID,
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
for uri in [
|
||||
format!("/v2/team/{team_id}/members"),
|
||||
format!("/v2/{team_association}/{team_association_id}/members"),
|
||||
] {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&uri)
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
let members = value.as_array().unwrap();
|
||||
assert!(members.len() == 2); // USER_USER_ID and FRIEND_USER_ID should be in the team
|
||||
let user_user = members
|
||||
.iter()
|
||||
.find(|x| x["user"]["id"] == USER_USER_ID)
|
||||
.unwrap();
|
||||
let friend_user = members
|
||||
.iter()
|
||||
.find(|x| x["user"]["id"] == FRIEND_USER_ID)
|
||||
.unwrap();
|
||||
assert_eq!(user_user["user"]["id"], USER_USER_ID);
|
||||
assert!(user_user["permissions"].is_null()); // Should not see private data of the team
|
||||
assert_eq!(friend_user["user"]["id"], FRIEND_USER_ID);
|
||||
assert!(friend_user["permissions"].is_null());
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&uri)
|
||||
.append_header(("Authorization", ENEMY_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
let members = value.as_array().unwrap();
|
||||
assert_eq!(members.len(), 1); // Only USER_USER_ID should be in the team
|
||||
assert_eq!(members[0]["user"]["id"], USER_USER_ID);
|
||||
assert!(members[0]["permissions"].is_null());
|
||||
}
|
||||
// An accepted member of the team should appear in the team members list
|
||||
// and should be able to see private data about the team
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{team_id}/join"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
for uri in [
|
||||
format!("/v2/team/{team_id}/members"),
|
||||
format!("/v2/{team_association}/{team_association_id}/members"),
|
||||
] {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&uri)
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
let members = value.as_array().unwrap();
|
||||
assert!(members.len() == 2); // USER_USER_ID and FRIEND_USER_ID should be in the team
|
||||
let user_user = members
|
||||
.iter()
|
||||
.find(|x| x["user"]["id"] == USER_USER_ID)
|
||||
.unwrap();
|
||||
let friend_user = members
|
||||
.iter()
|
||||
.find(|x| x["user"]["id"] == FRIEND_USER_ID)
|
||||
.unwrap();
|
||||
assert_eq!(user_user["user"]["id"], USER_USER_ID);
|
||||
assert!(!user_user["permissions"].is_null()); // SHOULD see private data of the team
|
||||
assert_eq!(friend_user["user"]["id"], FRIEND_USER_ID);
|
||||
assert!(!friend_user["permissions"].is_null());
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup test db
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_get_team_project_orgs() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
let zeta_organization_id = &test_env
|
||||
.dummy
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.organization_zeta
|
||||
.organization_id;
|
||||
let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id;
|
||||
|
||||
// Attach alpha to zeta
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/organization/{zeta_organization_id}/projects"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"project_id": alpha_project_id,
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Invite and add friend to zeta
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"user_id": FRIEND_USER_ID,
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/join"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// The team members route from teams (on a project's team):
|
||||
// - the members of the project team specifically
|
||||
// - not the ones from the organization
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
let members = value.as_array().unwrap();
|
||||
assert_eq!(members.len(), 1);
|
||||
|
||||
// The team members route from project should show:
|
||||
// - the members of the project team including the ones from the organization
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/project/{alpha_project_id}/members"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
let members = value.as_array().unwrap();
|
||||
assert_eq!(members.len(), 2);
|
||||
|
||||
// Cleanup test db
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
// edit team member (Varying permissions, varying roles)
|
||||
#[actix_rt::test]
|
||||
async fn test_patch_project_team_member() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
|
||||
// Edit team as admin/mod but not a part of the team should be OK
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members/{USER_USER_ID}"))
|
||||
.set_json(json!({}))
|
||||
.append_header(("Authorization", ADMIN_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// As a non-owner with full permissions, attempt to edit the owner's permissions/roles
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members/{USER_USER_ID}"))
|
||||
.append_header(("Authorization", ADMIN_USER_PAT))
|
||||
.set_json(json!({
|
||||
"role": "member"
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members/{USER_USER_ID}"))
|
||||
.append_header(("Authorization", ADMIN_USER_PAT))
|
||||
.set_json(json!({
|
||||
"permissions": 0
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Should not be able to edit organization permissions of a project team
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members/{USER_USER_ID}"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"organization_permissions": 0
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Should not be able to add permissions to a user that the adding-user does not have
|
||||
// (true for both project and org)
|
||||
|
||||
// first, invite friend
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(&json!({
|
||||
"user_id": FRIEND_USER_ID,
|
||||
"permissions": (ProjectPermissions::EDIT_MEMBER | ProjectPermissions::EDIT_BODY).bits(),
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// accept
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/join"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// try to add permissions
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members/{FRIEND_USER_ID}"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.set_json(json!({
|
||||
"permissions": (ProjectPermissions::EDIT_MEMBER | ProjectPermissions::EDIT_DETAILS).bits()
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Cannot set a user to Owner
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/team/{alpha_team_id}/members/{FRIEND_USER_ID}"
|
||||
))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"role": "Owner"
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Cannot set payouts outside of 0 and 5000
|
||||
for payout in [-1, 5001] {
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/team/{alpha_team_id}/members/{FRIEND_USER_ID}"
|
||||
))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"payouts_split": payout
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
|
||||
assert_eq!(resp.status(), 400);
|
||||
}
|
||||
|
||||
// Successful patch
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!(
|
||||
"/v2/team/{alpha_team_id}/members/{FRIEND_USER_ID}"
|
||||
))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.set_json(json!({
|
||||
"payouts_split": 51,
|
||||
"permissions": ProjectPermissions::EDIT_MEMBER.bits(), // reduces permissions
|
||||
"role": "member",
|
||||
"ordering": 5
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Check results
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
let member = value
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|x| x["user"]["id"] == FRIEND_USER_ID)
|
||||
.unwrap();
|
||||
assert_eq!(member["payouts_split"], 51.0);
|
||||
assert_eq!(
|
||||
member["permissions"],
|
||||
ProjectPermissions::EDIT_MEMBER.bits()
|
||||
);
|
||||
assert_eq!(member["role"], "member");
|
||||
assert_eq!(member["ordering"], 5);
|
||||
|
||||
// Cleanup test db
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
// edit team member (Varying permissions, varying roles)
|
||||
#[actix_rt::test]
|
||||
async fn test_patch_organization_team_member() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let zeta_team_id = &test_env.dummy.as_ref().unwrap().organization_zeta.team_id;
|
||||
|
||||
// Edit team as admin/mod but not a part of the team should be OK
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members/{USER_USER_ID}"))
|
||||
.set_json(json!({}))
|
||||
.append_header(("Authorization", ADMIN_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// As a non-owner with full permissions, attempt to edit the owner's permissions/roles
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members/{USER_USER_ID}"))
|
||||
.append_header(("Authorization", ADMIN_USER_PAT))
|
||||
.set_json(json!({
|
||||
"role": "member"
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members/{USER_USER_ID}"))
|
||||
.append_header(("Authorization", ADMIN_USER_PAT))
|
||||
.set_json(json!({
|
||||
"permissions": 0
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Should not be able to add permissions to a user that the adding-user does not have
|
||||
// (true for both project and org)
|
||||
|
||||
// first, invite friend
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(&json!({
|
||||
"user_id": FRIEND_USER_ID,
|
||||
"organization_permissions": (OrganizationPermissions::EDIT_MEMBER | OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS).bits(),
|
||||
})).to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// accept
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/join"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// try to add permissions- fails, as we do not have EDIT_DETAILS
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members/{FRIEND_USER_ID}"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.set_json(json!({
|
||||
"organization_permissions": (OrganizationPermissions::EDIT_MEMBER | OrganizationPermissions::EDIT_DETAILS).bits()
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Cannot set a user to Owner
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members/{FRIEND_USER_ID}"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"role": "Owner"
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Cannot set payouts outside of 0 and 5000
|
||||
for payout in [-1, 5001] {
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members/{FRIEND_USER_ID}"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"payouts_split": payout
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
}
|
||||
|
||||
// Successful patch
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members/{FRIEND_USER_ID}"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.set_json(json!({
|
||||
"payouts_split": 51,
|
||||
"organization_permissions": (OrganizationPermissions::EDIT_MEMBER).bits(), // reduces permissions
|
||||
"permissions": (ProjectPermissions::EDIT_MEMBER).bits(),
|
||||
"role": "member",
|
||||
"ordering": 5
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Check results
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/team/{zeta_team_id}/members"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
let member = value
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|x| x["user"]["id"] == FRIEND_USER_ID)
|
||||
.unwrap();
|
||||
assert_eq!(member["payouts_split"], 51.0);
|
||||
assert_eq!(
|
||||
member["organization_permissions"],
|
||||
OrganizationPermissions::EDIT_MEMBER.bits()
|
||||
);
|
||||
assert_eq!(
|
||||
member["permissions"],
|
||||
ProjectPermissions::EDIT_MEMBER.bits()
|
||||
);
|
||||
assert_eq!(member["role"], "member");
|
||||
assert_eq!(member["ordering"], 5);
|
||||
|
||||
// Cleanup test db
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
// trasnfer ownership (requires being owner, etc)
|
||||
#[actix_rt::test]
|
||||
async fn transfer_ownership() {
|
||||
// Test setup and dummy data
|
||||
let test_env = TestEnvironment::build(None).await;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
|
||||
// Cannot set friend as owner (not a member)
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/owner"))
|
||||
.set_json(json!({
|
||||
"user_id": FRIEND_USER_ID
|
||||
}))
|
||||
.append_header(("Authorization", USER_USER_ID))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 401);
|
||||
|
||||
// first, invite friend
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members"))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.set_json(json!({
|
||||
"user_id": FRIEND_USER_ID,
|
||||
}))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// accept
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/join"))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Cannot set ourselves as owner
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/owner"))
|
||||
.set_json(json!({
|
||||
"user_id": FRIEND_USER_ID
|
||||
}))
|
||||
.append_header(("Authorization", FRIEND_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 401);
|
||||
|
||||
// Can set friend as owner
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/owner"))
|
||||
.set_json(json!({
|
||||
"user_id": FRIEND_USER_ID
|
||||
}))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Check
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v2/team/{alpha_team_id}/members"))
|
||||
.set_json(json!({
|
||||
"user_id": FRIEND_USER_ID
|
||||
}))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.to_request();
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let value: serde_json::Value = test::read_body_json(resp).await;
|
||||
let friend_member = value
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|x| x["user"]["id"] == FRIEND_USER_ID)
|
||||
.unwrap();
|
||||
assert_eq!(friend_member["role"], "Owner");
|
||||
assert_eq!(
|
||||
friend_member["permissions"],
|
||||
ProjectPermissions::all().bits()
|
||||
);
|
||||
let user_member = value
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|x| x["user"]["id"] == USER_USER_ID)
|
||||
.unwrap();
|
||||
assert_eq!(user_member["role"], "Member");
|
||||
assert_eq!(user_member["permissions"], ProjectPermissions::all().bits());
|
||||
|
||||
// Confirm that user, a user who still has full permissions, cannot then remove the owner
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!(
|
||||
"/v2/team/{alpha_team_id}/members/{FRIEND_USER_ID}"
|
||||
))
|
||||
.append_header(("Authorization", USER_USER_PAT))
|
||||
.to_request();
|
||||
|
||||
let resp = test_env.call(req).await;
|
||||
assert_eq!(resp.status(), 401);
|
||||
|
||||
// Cleanup test db
|
||||
test_env.cleanup().await;
|
||||
}
|
||||
|
||||
// This test is currently not working.
|
||||
// #[actix_rt::test]
|
||||
// pub async fn no_acceptance_permissions() {
|
||||
// // Adding a user to a project team in an organization, when that user is in the organization but not the team,
|
||||
// // should have those permissions apply regardless of whether the user has accepted the invite or not.
|
||||
|
||||
// // This is because project-team permission overrriding must be possible, and this overriding can decrease the number of permissions a user has.
|
||||
|
||||
// let test_env = TestEnvironment::build(None).await;
|
||||
// let api = &test_env.v2;
|
||||
|
||||
// let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
// let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
// let zeta_organization_id = &test_env.dummy.as_ref().unwrap().zeta_organization_id;
|
||||
// let zeta_team_id = &test_env.dummy.as_ref().unwrap().zeta_team_id;
|
||||
|
||||
// // Link alpha team to zeta org
|
||||
// let resp = api.organization_add_project(zeta_organization_id, alpha_project_id, USER_USER_PAT).await;
|
||||
// assert_eq!(resp.status(), 200);
|
||||
|
||||
// // Invite friend to zeta team with all project default permissions
|
||||
// let resp = api.add_user_to_team(&zeta_team_id, FRIEND_USER_ID, Some(ProjectPermissions::all()), Some(OrganizationPermissions::all()), USER_USER_PAT).await;
|
||||
// assert_eq!(resp.status(), 204);
|
||||
|
||||
// // Accept invite to zeta team
|
||||
// let resp = api.join_team(&zeta_team_id, FRIEND_USER_PAT).await;
|
||||
// assert_eq!(resp.status(), 204);
|
||||
|
||||
// // Attempt, as friend, to edit details of alpha project (should succeed, org invite accepted)
|
||||
// let resp = api.edit_project(alpha_project_id, json!({
|
||||
// "title": "new name"
|
||||
// }), FRIEND_USER_PAT).await;
|
||||
// assert_eq!(resp.status(), 204);
|
||||
|
||||
// // Invite friend to alpha team with *no* project permissions
|
||||
// let resp = api.add_user_to_team(&alpha_team_id, FRIEND_USER_ID, Some(ProjectPermissions::empty()), None, USER_USER_PAT).await;
|
||||
// assert_eq!(resp.status(), 204);
|
||||
|
||||
// // Do not accept invite to alpha team
|
||||
|
||||
// // Attempt, as friend, to edit details of alpha project (should fail now, even though user has not accepted invite)
|
||||
// let resp = api.edit_project(alpha_project_id, json!({
|
||||
// "title": "new name"
|
||||
// }), FRIEND_USER_PAT).await;
|
||||
// assert_eq!(resp.status(), 401);
|
||||
|
||||
// test_env.cleanup().await;
|
||||
// }
|
||||
@@ -7,6 +7,15 @@ use crate::common::{dummy_data::DummyJarFile, request_data::get_public_project_c
|
||||
|
||||
mod common;
|
||||
|
||||
// user GET (permissions, different users)
|
||||
// users GET
|
||||
// user auth
|
||||
// user projects get
|
||||
// user collections get
|
||||
// patch user
|
||||
// patch user icon
|
||||
// user follows
|
||||
|
||||
#[actix_rt::test]
|
||||
pub async fn get_user_projects_after_creating_project_returns_new_project() {
|
||||
with_test_environment(|test_env| async move {
|
||||
@@ -15,10 +24,10 @@ pub async fn get_user_projects_after_creating_project_returns_new_project() {
|
||||
.await;
|
||||
|
||||
let (project, _) = api
|
||||
.add_public_project(get_public_project_creation_data(
|
||||
"slug",
|
||||
DummyJarFile::BasicMod,
|
||||
))
|
||||
.add_public_project(
|
||||
get_public_project_creation_data("slug", Some(DummyJarFile::BasicMod)),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
|
||||
let resp_projects = api
|
||||
@@ -34,15 +43,15 @@ pub async fn get_user_projects_after_deleting_project_shows_removal() {
|
||||
with_test_environment(|test_env| async move {
|
||||
let api = test_env.v2;
|
||||
let (project, _) = api
|
||||
.add_public_project(get_public_project_creation_data(
|
||||
"iota",
|
||||
DummyJarFile::BasicMod,
|
||||
))
|
||||
.add_public_project(
|
||||
get_public_project_creation_data("iota", Some(DummyJarFile::BasicMod)),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
api.get_user_projects_deserialized(USER_USER_ID, USER_USER_PAT)
|
||||
.await;
|
||||
|
||||
api.remove_project(&project.slug.as_ref().unwrap(), USER_USER_PAT)
|
||||
api.remove_project(project.slug.as_ref().unwrap(), USER_USER_PAT)
|
||||
.await;
|
||||
|
||||
let resp_projects = api
|
||||
@@ -56,15 +65,15 @@ pub async fn get_user_projects_after_deleting_project_shows_removal() {
|
||||
#[actix_rt::test]
|
||||
pub async fn get_user_projects_after_joining_team_shows_team_projects() {
|
||||
with_test_environment(|test_env| async move {
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().alpha_team_id;
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().alpha_project_id;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
let api = test_env.v2;
|
||||
api.get_user_projects_deserialized(FRIEND_USER_ID, FRIEND_USER_PAT)
|
||||
.await;
|
||||
|
||||
api.add_user_to_team(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
api.add_user_to_team(alpha_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
api.join_team(&alpha_team_id, FRIEND_USER_PAT).await;
|
||||
api.join_team(alpha_team_id, FRIEND_USER_PAT).await;
|
||||
|
||||
let projects = api
|
||||
.get_user_projects_deserialized(FRIEND_USER_ID, FRIEND_USER_PAT)
|
||||
@@ -79,16 +88,16 @@ pub async fn get_user_projects_after_joining_team_shows_team_projects() {
|
||||
#[actix_rt::test]
|
||||
pub async fn get_user_projects_after_leaving_team_shows_no_team_projects() {
|
||||
with_test_environment(|test_env| async move {
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().alpha_team_id;
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().alpha_project_id;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
let api = test_env.v2;
|
||||
api.add_user_to_team(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
api.add_user_to_team(alpha_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
api.join_team(&alpha_team_id, FRIEND_USER_PAT).await;
|
||||
api.join_team(alpha_team_id, FRIEND_USER_PAT).await;
|
||||
api.get_user_projects_deserialized(FRIEND_USER_ID, FRIEND_USER_PAT)
|
||||
.await;
|
||||
|
||||
api.remove_from_team(&alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
api.remove_from_team(alpha_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
.await;
|
||||
|
||||
let projects = api
|
||||
|
||||
Reference in New Issue
Block a user