You've already forked AstralRinth
forked from didirus/AstralRinth
Organization ownership (#796)
* organization changes * changes * fixes failing test * version changes * removed printlns * add_team_member comes pre-accepted * no notification on force accept * fixes tests * merge fixes
This commit is contained in:
@@ -3,7 +3,7 @@ use actix_web::{
|
||||
test::{self, TestRequest},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use labrinth::models::{organizations::Organization, v3::projects::Project};
|
||||
use labrinth::models::{organizations::Organization, users::UserId, v3::projects::Project};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::api_common::{request_data::ImageData, Api, AppendsOptionalPat};
|
||||
@@ -162,12 +162,16 @@ impl ApiV3 {
|
||||
&self,
|
||||
id_or_title: &str,
|
||||
project_id_or_slug: &str,
|
||||
new_owner_user_id: UserId,
|
||||
pat: Option<&str>,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!(
|
||||
"/v3/organization/{id_or_title}/projects/{project_id_or_slug}"
|
||||
))
|
||||
.set_json(json!({
|
||||
"new_owner": new_owner_user_id,
|
||||
}))
|
||||
.append_pat(pat)
|
||||
.to_request();
|
||||
|
||||
|
||||
@@ -37,6 +37,16 @@ impl ApiV3 {
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn get_project_members_deserialized(
|
||||
&self,
|
||||
project_id: &str,
|
||||
pat: Option<&str>,
|
||||
) -> Vec<TeamMember> {
|
||||
let resp = self.get_project_members(project_id, pat).await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
||||
@@ -1051,8 +1051,10 @@ async fn add_user_to_team(
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
// Accept invitation
|
||||
let resp = setup_api.join_team(team_id, user_pat).await;
|
||||
assert!(resp.status().is_success());
|
||||
setup_api.join_team(team_id, user_pat).await;
|
||||
// This does not check if the join request was successful,
|
||||
// as the join is not always needed- an org project + in-org invite
|
||||
// will automatically go through.
|
||||
}
|
||||
|
||||
async fn modify_user_team_permissions(
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::common::{
|
||||
api_common::ApiTeams,
|
||||
database::{generate_random_name, ADMIN_USER_PAT, MOD_USER_ID, MOD_USER_PAT, USER_USER_ID},
|
||||
dummy_data::DummyImage,
|
||||
api_common::{ApiProject, ApiTeams},
|
||||
database::{
|
||||
generate_random_name, ADMIN_USER_PAT, ENEMY_USER_ID_PARSED, ENEMY_USER_PAT,
|
||||
FRIEND_USER_ID_PARSED, MOD_USER_ID, MOD_USER_PAT, USER_USER_ID, USER_USER_ID_PARSED,
|
||||
},
|
||||
dummy_data::{DummyImage, DummyOrganizationZeta, DummyProjectAlpha, DummyProjectBeta},
|
||||
};
|
||||
use common::{
|
||||
api_v3::ApiV3,
|
||||
@@ -9,7 +12,10 @@ use common::{
|
||||
environment::{with_test_environment, with_test_environment_all, TestEnvironment},
|
||||
permissions::{PermissionsTest, PermissionsTestContext},
|
||||
};
|
||||
use labrinth::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||
use labrinth::models::{
|
||||
teams::{OrganizationPermissions, ProjectPermissions},
|
||||
users::UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
mod common;
|
||||
@@ -249,7 +255,12 @@ async fn add_remove_organization_projects() {
|
||||
// Remove project from organization
|
||||
let resp = test_env
|
||||
.api
|
||||
.organization_remove_project(zeta_organization_id, alpha, USER_USER_PAT)
|
||||
.organization_remove_project(
|
||||
zeta_organization_id,
|
||||
alpha,
|
||||
UserId(USER_USER_ID_PARSED as u64),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
@@ -264,6 +275,357 @@ async fn add_remove_organization_projects() {
|
||||
.await;
|
||||
}
|
||||
|
||||
// Like above, but specifically regarding ownership transferring
|
||||
#[actix_rt::test]
|
||||
async fn add_remove_organization_project_ownership_to_user() {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
let DummyProjectAlpha {
|
||||
project_id: alpha_project_id,
|
||||
team_id: alpha_team_id,
|
||||
..
|
||||
} = &test_env.dummy.project_alpha;
|
||||
let DummyProjectBeta {
|
||||
project_id: beta_project_id,
|
||||
team_id: beta_team_id,
|
||||
..
|
||||
} = &test_env.dummy.project_beta;
|
||||
let DummyOrganizationZeta {
|
||||
organization_id: zeta_organization_id,
|
||||
team_id: zeta_team_id,
|
||||
..
|
||||
} = &test_env.dummy.organization_zeta;
|
||||
|
||||
// Add friend to alpha, beta, and zeta
|
||||
for (team, organization) in [
|
||||
(alpha_team_id, false),
|
||||
(beta_team_id, false),
|
||||
(zeta_team_id, true),
|
||||
] {
|
||||
let org_permissions = if organization {
|
||||
Some(OrganizationPermissions::all())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let resp = test_env
|
||||
.api
|
||||
.add_user_to_team(
|
||||
team,
|
||||
FRIEND_USER_ID,
|
||||
Some(ProjectPermissions::all()),
|
||||
org_permissions,
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Accept invites
|
||||
let resp = test_env.api.join_team(team, FRIEND_USER_PAT).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
}
|
||||
|
||||
// For each team, confirm there are two members, but only one owner of the project, and it is USER_USER_ID
|
||||
for team in [alpha_team_id, beta_team_id, zeta_team_id] {
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(team, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(members.len(), 2);
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 1);
|
||||
assert_eq!(user_member[0].user.id.to_string(), USER_USER_ID);
|
||||
}
|
||||
|
||||
// Transfer ownership of beta project to FRIEND
|
||||
let resp = test_env
|
||||
.api
|
||||
.transfer_team_ownership(beta_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Confirm there are still two users, but now FRIEND_USER_ID is the owner
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(beta_team_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(members.len(), 2);
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 1);
|
||||
assert_eq!(user_member[0].user.id.to_string(), FRIEND_USER_ID);
|
||||
|
||||
// Add alpha, beta to zeta organization
|
||||
for (project_id, pat) in [
|
||||
(alpha_project_id, USER_USER_PAT),
|
||||
(beta_project_id, FRIEND_USER_PAT),
|
||||
] {
|
||||
let resp = test_env
|
||||
.api
|
||||
.organization_add_project(zeta_organization_id, project_id, pat)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Get and confirm it has been added
|
||||
let project = test_env.api.get_project_deserialized(project_id, pat).await;
|
||||
assert_eq!(
|
||||
&project.organization.unwrap().to_string(),
|
||||
zeta_organization_id
|
||||
);
|
||||
}
|
||||
|
||||
// Both alpha and beta project should have:
|
||||
// - 1 member, FRIEND_USER_ID
|
||||
// - No owner.
|
||||
// -> For alpha, user was removed as owner when it was added to the organization
|
||||
// -> For beta, user was removed as owner when ownership was transferred to friend
|
||||
// then friend was removed as owner when it was added to the organization
|
||||
// -> In both cases, user was removed entirely as a team_member as it is now the owner of the organization
|
||||
for team_id in [alpha_team_id, beta_team_id] {
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(team_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(members.len(), 1);
|
||||
assert_eq!(members[0].user.id.to_string(), FRIEND_USER_ID);
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 0);
|
||||
}
|
||||
|
||||
// Transfer ownership of zeta organization to FRIEND
|
||||
let resp = test_env
|
||||
.api
|
||||
.transfer_team_ownership(zeta_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Confirm there are no members of the alpha project OR the beta project
|
||||
// - Friend was removed as a member of these projects when ownership was transferred to them
|
||||
for team_id in [alpha_team_id, beta_team_id] {
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(team_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert!(members.is_empty());
|
||||
}
|
||||
|
||||
// As user, cannot add friend to alpha project, as they are the org owner
|
||||
let resp = test_env
|
||||
.api
|
||||
.add_user_to_team(alpha_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// As friend, can add user to alpha project, as they are not the org owner
|
||||
let resp = test_env
|
||||
.api
|
||||
.add_user_to_team(alpha_team_id, USER_USER_ID, None, None, FRIEND_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// At this point, friend owns the org
|
||||
// Alpha member has user as a member, but not as an owner
|
||||
// Neither project has an owner, as they are owned by the org
|
||||
|
||||
// Remove project from organization with a user that is not an organization member
|
||||
// This should fail as we cannot give a project to a user that is not a member of the organization
|
||||
let resp = test_env
|
||||
.api
|
||||
.organization_remove_project(
|
||||
zeta_organization_id,
|
||||
alpha_project_id,
|
||||
UserId(ENEMY_USER_ID_PARSED as u64),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Remove project from organization with a user that is an organization member, and a project member
|
||||
// This should succeed
|
||||
let resp = test_env
|
||||
.api
|
||||
.organization_remove_project(
|
||||
zeta_organization_id,
|
||||
alpha_project_id,
|
||||
UserId(USER_USER_ID_PARSED as u64),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Remove project from organization with a user that is an organization member, but not a project member
|
||||
// This should succeed
|
||||
let resp = test_env
|
||||
.api
|
||||
.organization_remove_project(
|
||||
zeta_organization_id,
|
||||
beta_project_id,
|
||||
UserId(USER_USER_ID_PARSED as u64),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// For each of alpha and beta, confirm:
|
||||
// - There is one member of each project, the owner, USER_USER_ID
|
||||
// - They no longer have an attached organization
|
||||
for team_id in [alpha_team_id, beta_team_id] {
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(team_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(members.len(), 1);
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 1);
|
||||
assert_eq!(user_member[0].user.id.to_string(), USER_USER_ID);
|
||||
}
|
||||
|
||||
for project_id in [alpha_project_id, beta_project_id] {
|
||||
let project = test_env
|
||||
.api
|
||||
.get_project_deserialized(project_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert!(project.organization.is_none());
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn delete_organization_means_all_projects_to_org_owner() {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
let DummyProjectAlpha {
|
||||
project_id: alpha_project_id,
|
||||
team_id: alpha_team_id,
|
||||
..
|
||||
} = &test_env.dummy.project_alpha;
|
||||
let DummyProjectBeta {
|
||||
project_id: beta_project_id,
|
||||
team_id: beta_team_id,
|
||||
..
|
||||
} = &test_env.dummy.project_beta;
|
||||
let DummyOrganizationZeta {
|
||||
organization_id: zeta_organization_id,
|
||||
team_id: zeta_team_id,
|
||||
..
|
||||
} = &test_env.dummy.organization_zeta;
|
||||
|
||||
// Create random project from enemy, ensure it wont get affected
|
||||
let (enemy_project, _) = test_env
|
||||
.api
|
||||
.add_public_project("enemy_project", None, None, ENEMY_USER_PAT)
|
||||
.await;
|
||||
|
||||
// Add FRIEND
|
||||
let resp = test_env
|
||||
.api
|
||||
.add_user_to_team(zeta_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Accept invite
|
||||
let resp = test_env.api.join_team(zeta_team_id, FRIEND_USER_PAT).await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Confirm there is only one owner of the project, and it is USER_USER_ID
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(alpha_team_id, USER_USER_PAT)
|
||||
.await;
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 1);
|
||||
assert_eq!(user_member[0].user.id.to_string(), USER_USER_ID);
|
||||
|
||||
// Add alpha to zeta organization
|
||||
let resp = test_env
|
||||
.api
|
||||
.organization_add_project(zeta_organization_id, alpha_project_id, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// Add beta to zeta organization
|
||||
test_env
|
||||
.api
|
||||
.organization_add_project(zeta_organization_id, beta_project_id, USER_USER_PAT)
|
||||
.await;
|
||||
|
||||
// Add friend as a member of the beta project
|
||||
let resp = test_env
|
||||
.api
|
||||
.add_user_to_team(beta_team_id, FRIEND_USER_ID, None, None, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Try to accept invite
|
||||
// This returns a failure, because since beta and FRIEND are in the organizations,
|
||||
// they can be added to the project without an invite
|
||||
let resp = test_env.api.join_team(beta_team_id, FRIEND_USER_PAT).await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
|
||||
// Confirm there is NO owner of the project, as it is owned by the organization
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(alpha_team_id, USER_USER_PAT)
|
||||
.await;
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 0);
|
||||
|
||||
// Transfer ownership of zeta organization to FRIEND
|
||||
let resp = test_env
|
||||
.api
|
||||
.transfer_team_ownership(zeta_team_id, FRIEND_USER_ID, USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Confirm there is NO owner of the project, as it is owned by the organization
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(alpha_team_id, USER_USER_PAT)
|
||||
.await;
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 0);
|
||||
|
||||
// Delete organization
|
||||
let resp = test_env
|
||||
.api
|
||||
.delete_organization(zeta_organization_id, FRIEND_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(resp.status(), 204);
|
||||
|
||||
// Confirm there is only one owner of the alpha project, and it is now FRIEND_USER_ID
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(alpha_team_id, USER_USER_PAT)
|
||||
.await;
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 1);
|
||||
assert_eq!(user_member[0].user.id.to_string(), FRIEND_USER_ID);
|
||||
|
||||
// Confirm there is only one owner of the beta project, and it is now FRIEND_USER_ID
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(beta_team_id, USER_USER_PAT)
|
||||
.await;
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 1);
|
||||
assert_eq!(user_member[0].user.id.to_string(), FRIEND_USER_ID);
|
||||
|
||||
// Confirm there is only one member of the enemy project, and it is STILL ENEMY_USER_ID
|
||||
let enemy_project = test_env
|
||||
.api
|
||||
.get_project_deserialized(&enemy_project.id.to_string(), ENEMY_USER_PAT)
|
||||
.await;
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized(&enemy_project.team_id.to_string(), ENEMY_USER_PAT)
|
||||
.await;
|
||||
let user_member = members.iter().filter(|m| m.is_owner).collect::<Vec<_>>();
|
||||
assert_eq!(user_member.len(), 1);
|
||||
assert_eq!(
|
||||
user_member[0].user.id.to_string(),
|
||||
ENEMY_USER_ID_PARSED.to_string()
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_patch_organization() {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
@@ -480,6 +842,7 @@ async fn permissions_add_remove_project() {
|
||||
api.organization_remove_project(
|
||||
&ctx.organization_id.unwrap(),
|
||||
alpha_project_id,
|
||||
UserId(FRIEND_USER_ID_PARSED as u64),
|
||||
ctx.test_pat.as_deref(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -14,6 +14,7 @@ use common::{database::*, scopes::ScopeTest};
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::pats::Scopes;
|
||||
use labrinth::models::projects::ProjectId;
|
||||
use labrinth::models::users::UserId;
|
||||
use serde_json::json;
|
||||
|
||||
// For each scope, we (using test_scope):
|
||||
@@ -1185,8 +1186,13 @@ pub async fn organization_scopes() {
|
||||
|
||||
// remove project (now that we've checked)
|
||||
let req_gen = |pat: Option<String>| async move {
|
||||
api.organization_remove_project(organization_id, beta_project_id, pat.as_deref())
|
||||
.await
|
||||
api.organization_remove_project(
|
||||
organization_id,
|
||||
beta_project_id,
|
||||
UserId(USER_USER_ID_PARSED as u64),
|
||||
pat.as_deref(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
ScopeTest::new(&test_env)
|
||||
.with_failure_scopes(Scopes::all() ^ Scopes::ORGANIZATION_WRITE)
|
||||
|
||||
@@ -287,11 +287,12 @@ async fn test_get_team_project_orgs() {
|
||||
// The team members route from teams (on a project's team):
|
||||
// - the members of the project team specifically
|
||||
// - not the ones from the organization
|
||||
// - Remember: the owner of an org will not be included in the org's team members list
|
||||
let members = test_env
|
||||
.api
|
||||
.get_team_members_deserialized_common(alpha_team_id, FRIEND_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(members.len(), 1);
|
||||
assert_eq!(members.len(), 0);
|
||||
|
||||
// The team members route from project should show:
|
||||
// - the members of the project team including the ones from the organization
|
||||
|
||||
Reference in New Issue
Block a user