Tests 3 restructure (#754)

* moved files

* moved files

* initial v3 additions

* moves req data

* tests passing, restructuring, remove v2

* fmt; clippy; prepare

* merge conflicts + issues

* merge conflict, fmt, clippy, prepare

* revs

* fixed failing test

* fixed tests
This commit is contained in:
Wyatt Verchere
2023-11-16 10:36:03 -08:00
committed by GitHub
parent f4880d0519
commit 74973e73e6
66 changed files with 4282 additions and 1033 deletions

View File

@@ -4,8 +4,8 @@ use super::environment::LocalService;
use actix_web::dev::ServiceResponse;
use std::rc::Rc;
pub mod organization;
pub mod project;
pub mod request_data;
pub mod tags;
pub mod team;
pub mod version;

View File

@@ -1,5 +1,4 @@
use std::collections::HashMap;
use crate::common::api_v2::request_data::ProjectCreationRequestData;
use actix_http::StatusCode;
use actix_web::{
dev::ServiceResponse,
@@ -14,14 +13,11 @@ use labrinth::{
};
use rust_decimal::Decimal;
use serde_json::json;
use std::collections::HashMap;
use crate::common::{
asserts::assert_status,
database::MOD_USER_PAT,
request_data::{ImageData, ProjectCreationRequestData},
};
use crate::common::{asserts::assert_status, database::MOD_USER_PAT};
use super::ApiV2;
use super::{request_data::ImageData, ApiV2};
impl ApiV2 {
pub async fn add_public_project(

View File

@@ -1,7 +1,7 @@
#![allow(dead_code)]
use serde_json::json;
use super::dummy_data::{DummyImage, TestFile};
use crate::common::dummy_data::{DummyImage, TestFile};
use labrinth::{
models::projects::ProjectId,
util::actix::{MultipartSegment, MultipartSegmentData},

View File

@@ -12,9 +12,9 @@ use labrinth::{
};
use serde_json::json;
use crate::common::{asserts::assert_status, request_data::VersionCreationRequestData};
use crate::common::asserts::assert_status;
use super::ApiV2;
use super::{request_data::VersionCreationRequestData, ApiV2};
pub fn url_encode_json_serialized_vec(elements: &[String]) -> String {
let serialized = serde_json::to_string(&elements).unwrap();
@@ -327,8 +327,7 @@ impl ApiV2 {
test::read_body_json(resp).await
}
// TODO: remove redundancy in these functions
// TODO: remove redundancy in these functions- some are essentially repeats
pub async fn create_default_version(
&self,
project_id: &str,

View File

@@ -6,7 +6,12 @@ use std::rc::Rc;
pub mod oauth;
pub mod oauth_clients;
pub mod organization;
pub mod project;
pub mod request_data;
pub mod tags;
pub mod team;
pub mod version;
#[derive(Clone)]
pub struct ApiV3 {
@@ -17,4 +22,15 @@ impl ApiV3 {
pub async fn call(&self, req: actix_http::Request) -> ServiceResponse {
self.test_app.call(req).await.unwrap()
}
pub async fn reset_search_index(&self) -> ServiceResponse {
let req = actix_web::test::TestRequest::post()
.uri("/v3/admin/_force_reindex")
.append_header((
"Modrinth-Admin",
dotenvy::var("LABRINTH_ADMIN_KEY").unwrap(),
))
.to_request();
self.call(req).await
}
}

View File

@@ -55,7 +55,7 @@ impl ApiV3 {
pub async fn oauth_accept(&self, flow: &str, pat: &str) -> ServiceResponse {
self.call(
TestRequest::post()
.uri("/v3/auth/oauth/accept")
.uri("/v3/oauth/accept")
.append_header((AUTHORIZATION, pat))
.set_json(RespondToOAuthClientScopes {
flow: flow.to_string(),
@@ -68,7 +68,7 @@ impl ApiV3 {
pub async fn oauth_reject(&self, flow: &str, pat: &str) -> ServiceResponse {
self.call(
TestRequest::post()
.uri("/v3/auth/oauth/reject")
.uri("/v3/oauth/reject")
.append_header((AUTHORIZATION, pat))
.set_json(RespondToOAuthClientScopes {
flow: flow.to_string(),
@@ -87,7 +87,7 @@ impl ApiV3 {
) -> ServiceResponse {
self.call(
TestRequest::post()
.uri("/v3/auth/oauth/token")
.uri("/v3/oauth/token")
.append_header((AUTHORIZATION, client_secret))
.set_form(TokenRequest {
grant_type: "authorization_code".to_string(),
@@ -108,7 +108,7 @@ pub fn generate_authorize_uri(
state: Option<&str>,
) -> String {
format!(
"/v3/auth/oauth/authorize?client_id={}{}{}{}",
"/v3/oauth/authorize?client_id={}{}{}{}",
urlencoding::encode(client_id),
optional_query_param("redirect_uri", redirect_uri),
optional_query_param("scope", scope),

View File

@@ -3,14 +3,12 @@ use actix_web::{
test::{self, TestRequest},
};
use bytes::Bytes;
use labrinth::models::{organizations::Organization, v2::projects::LegacyProject};
use labrinth::models::{organizations::Organization, v3::projects::Project};
use serde_json::json;
use crate::common::request_data::ImageData;
use super::{request_data::ImageData, ApiV3};
use super::ApiV2;
impl ApiV2 {
impl ApiV3 {
pub async fn create_organization(
&self,
organization_title: &str,
@@ -18,7 +16,7 @@ impl ApiV2 {
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::post()
.uri("/v2/organization")
.uri("/v3/organization")
.append_header(("Authorization", pat))
.set_json(json!({
"title": organization_title,
@@ -30,7 +28,7 @@ impl ApiV2 {
pub async fn get_organization(&self, id_or_title: &str, pat: &str) -> ServiceResponse {
let req = TestRequest::get()
.uri(&format!("/v2/organization/{id_or_title}"))
.uri(&format!("/v3/organization/{id_or_title}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
@@ -48,7 +46,7 @@ impl ApiV2 {
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"))
.uri(&format!("/v3/organization/{id_or_title}/projects"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
@@ -58,7 +56,7 @@ impl ApiV2 {
&self,
id_or_title: &str,
pat: &str,
) -> Vec<LegacyProject> {
) -> Vec<Project> {
let resp = self.get_organization_projects(id_or_title, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
@@ -71,7 +69,7 @@ impl ApiV2 {
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::patch()
.uri(&format!("/v2/organization/{id_or_title}"))
.uri(&format!("/v3/organization/{id_or_title}"))
.append_header(("Authorization", pat))
.set_json(patch)
.to_request();
@@ -89,7 +87,7 @@ impl ApiV2 {
// If an icon is provided, upload it
let req = test::TestRequest::patch()
.uri(&format!(
"/v2/organization/{id_or_title}/icon?ext={ext}",
"/v3/organization/{id_or_title}/icon?ext={ext}",
ext = icon.extension
))
.append_header(("Authorization", pat))
@@ -100,7 +98,7 @@ impl ApiV2 {
} else {
// If no icon is provided, delete the icon
let req = test::TestRequest::delete()
.uri(&format!("/v2/organization/{id_or_title}/icon"))
.uri(&format!("/v3/organization/{id_or_title}/icon"))
.append_header(("Authorization", pat))
.to_request();
@@ -110,7 +108,7 @@ impl ApiV2 {
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}"))
.uri(&format!("/v3/organization/{id_or_title}"))
.append_header(("Authorization", pat))
.to_request();
@@ -124,7 +122,7 @@ impl ApiV2 {
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::post()
.uri(&format!("/v2/organization/{id_or_title}/projects"))
.uri(&format!("/v3/organization/{id_or_title}/projects"))
.append_header(("Authorization", pat))
.set_json(json!({
"project_id": project_id_or_slug,
@@ -142,7 +140,7 @@ impl ApiV2 {
) -> ServiceResponse {
let req = test::TestRequest::delete()
.uri(&format!(
"/v2/organization/{id_or_title}/projects/{project_id_or_slug}"
"/v3/organization/{id_or_title}/projects/{project_id_or_slug}"
))
.append_header(("Authorization", pat))
.to_request();

View File

@@ -0,0 +1,256 @@
use std::collections::HashMap;
use actix_http::StatusCode;
use actix_web::{
dev::ServiceResponse,
test::{self, TestRequest},
};
use bytes::Bytes;
use chrono::{DateTime, Utc};
use labrinth::{
models::v3::projects::{Project, Version},
search::SearchResults,
util::actix::AppendsMultipart,
};
use rust_decimal::Decimal;
use serde_json::json;
use crate::common::{asserts::assert_status, database::MOD_USER_PAT};
use super::{
request_data::{ImageData, ProjectCreationRequestData},
ApiV3,
};
impl ApiV3 {
pub async fn add_public_project(
&self,
creation_data: ProjectCreationRequestData,
pat: &str,
) -> (Project, Vec<Version>) {
// Add a project.
let req = TestRequest::post()
.uri("/v3/project")
.append_header(("Authorization", pat))
.set_multipart(creation_data.segment_data)
.to_request();
let resp = self.call(req).await;
assert_status(&resp, StatusCode::OK);
// Approve as a moderator.
let req = TestRequest::patch()
.uri(&format!("/v3/project/{}", creation_data.slug))
.append_header(("Authorization", MOD_USER_PAT))
.set_json(json!(
{
"status": "approved"
}
))
.to_request();
let resp = self.call(req).await;
assert_status(&resp, StatusCode::NO_CONTENT);
let project = self
.get_project_deserialized(&creation_data.slug, pat)
.await;
// Get project's versions
let req = TestRequest::get()
.uri(&format!("/v3/project/{}/version", creation_data.slug))
.append_header(("Authorization", pat))
.to_request();
let resp = self.call(req).await;
let versions: Vec<Version> = test::read_body_json(resp).await;
(project, versions)
}
pub async fn remove_project(&self, project_slug_or_id: &str, pat: &str) -> ServiceResponse {
let req = test::TestRequest::delete()
.uri(&format!("/v3/project/{project_slug_or_id}"))
.append_header(("Authorization", pat))
.to_request();
let resp = self.call(req).await;
assert_eq!(resp.status(), 204);
resp
}
pub async fn get_project(&self, id_or_slug: &str, pat: &str) -> ServiceResponse {
let req = TestRequest::get()
.uri(&format!("/v3/project/{id_or_slug}"))
.append_header(("Authorization", pat))
.to_request();
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
}
pub async fn get_user_projects(&self, user_id_or_username: &str, pat: &str) -> ServiceResponse {
let req = test::TestRequest::get()
.uri(&format!("/v3/user/{}/projects", user_id_or_username))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
pub async fn get_user_projects_deserialized(
&self,
user_id_or_username: &str,
pat: &str,
) -> Vec<Project> {
let resp = self.get_user_projects(user_id_or_username, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn edit_project(
&self,
id_or_slug: &str,
patch: serde_json::Value,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::patch()
.uri(&format!("/v3/project/{id_or_slug}"))
.append_header(("Authorization", pat))
.set_json(patch)
.to_request();
self.call(req).await
}
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!(
"/v3/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!(
"/v3/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!("/v3/project/{id_or_slug}/icon"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
}
pub async fn search_deserialized(
&self,
query: Option<&str>,
facets: Option<serde_json::Value>,
pat: &str,
) -> SearchResults {
let query_field = if let Some(query) = query {
format!("&query={}", urlencoding::encode(query))
} else {
"".to_string()
};
let facets_field = if let Some(facets) = facets {
format!("&facets={}", urlencoding::encode(&facets.to_string()))
} else {
"".to_string()
};
let req = test::TestRequest::get()
.uri(&format!("/v3/search?{}{}", query_field, facets_field))
.append_header(("Authorization", pat))
.to_request();
let resp = self.call(req).await;
let status = resp.status();
assert_eq!(status, 200);
test::read_body_json(resp).await
}
pub async fn get_analytics_revenue(
&self,
id_or_slugs: Vec<&str>,
start_date: Option<DateTime<Utc>>,
end_date: Option<DateTime<Utc>>,
resolution_minutes: Option<u32>,
pat: &str,
) -> ServiceResponse {
let projects_string = serde_json::to_string(&id_or_slugs).unwrap();
let projects_string = urlencoding::encode(&projects_string);
let mut extra_args = String::new();
if let Some(start_date) = start_date {
let start_date = start_date.to_rfc3339();
// let start_date = serde_json::to_string(&start_date).unwrap();
let start_date = urlencoding::encode(&start_date);
extra_args.push_str(&format!("&start_date={start_date}"));
}
if let Some(end_date) = end_date {
let end_date = end_date.to_rfc3339();
// let end_date = serde_json::to_string(&end_date).unwrap();
let end_date = urlencoding::encode(&end_date);
extra_args.push_str(&format!("&end_date={end_date}"));
}
if let Some(resolution_minutes) = resolution_minutes {
extra_args.push_str(&format!("&resolution_minutes={}", resolution_minutes));
}
let req = test::TestRequest::get()
.uri(&format!(
"/v3/analytics/revenue?{projects_string}{extra_args}",
))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
pub async fn get_analytics_revenue_deserialized(
&self,
id_or_slugs: Vec<&str>,
start_date: Option<DateTime<Utc>>,
end_date: Option<DateTime<Utc>>,
resolution_minutes: Option<u32>,
pat: &str,
) -> HashMap<String, HashMap<i64, Decimal>> {
let resp = self
.get_analytics_revenue(id_or_slugs, start_date, end_date, resolution_minutes, pat)
.await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
}

View File

@@ -0,0 +1,146 @@
#![allow(dead_code)]
use serde_json::json;
use crate::common::dummy_data::{DummyImage, TestFile};
use labrinth::{
models::projects::ProjectId,
util::actix::{MultipartSegment, MultipartSegmentData},
};
pub struct ProjectCreationRequestData {
pub slug: String,
pub jar: Option<TestFile>,
pub segment_data: Vec<MultipartSegment>,
}
pub struct VersionCreationRequestData {
pub version: String,
pub jar: Option<TestFile>,
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,
version_jar: Option<TestFile>,
) -> ProjectCreationRequestData {
let json_data = get_public_project_creation_data_json(slug, version_jar.as_ref());
let multipart_data = get_public_creation_data_multipart(&json_data, version_jar.as_ref());
ProjectCreationRequestData {
slug: slug.to_string(),
jar: version_jar,
segment_data: multipart_data,
}
}
pub fn get_public_version_creation_data(
project_id: ProjectId,
version_number: &str,
version_jar: TestFile,
// closure that takes in a &mut serde_json::Value
// and modifies it before it is serialized and sent
modify_json: Option<impl FnOnce(&mut serde_json::Value)>,
) -> VersionCreationRequestData {
let mut json_data = get_public_version_creation_data_json(version_number, &version_jar);
json_data["project_id"] = json!(project_id);
if let Some(modify_json) = modify_json {
modify_json(&mut json_data);
}
let multipart_data = get_public_creation_data_multipart(&json_data, Some(&version_jar));
VersionCreationRequestData {
version: version_number.to_string(),
jar: Some(version_jar),
segment_data: multipart_data,
}
}
pub fn get_public_version_creation_data_json(
version_number: &str,
version_jar: &TestFile,
) -> serde_json::Value {
let is_modpack = version_jar.project_type() == "modpack";
let mut j = json!({
"file_parts": [version_jar.filename()],
"version_number": version_number,
"version_title": "start",
"dependencies": [],
"release_channel": "release",
"loaders": [if is_modpack { "mrpack" } else { "fabric" }],
"featured": true,
// Loader fields
"game_versions": ["1.20.1"],
"client_side": "required",
"server_side": "optional"
});
if is_modpack {
j["mrpack_loaders"] = json!(["fabric"]);
}
j
}
pub fn get_public_project_creation_data_json(
slug: &str,
version_jar: Option<&TestFile>,
) -> serde_json::Value {
let initial_versions = if let Some(jar) = version_jar {
json!([get_public_version_creation_data_json("1.2.3", jar)])
} else {
json!([])
};
let is_draft = version_jar.is_none();
json!(
{
"title": format!("Test Project {slug}"),
"slug": slug,
"description": "A dummy project for testing with.",
"body": "This project is approved, and versions are listed.",
"initial_versions": initial_versions,
"is_draft": is_draft,
"categories": [],
"license_id": "MIT",
}
)
}
pub fn get_public_creation_data_multipart(
json_data: &serde_json::Value,
version_jar: Option<&TestFile>,
) -> Vec<MultipartSegment> {
// Basic json
let json_segment = MultipartSegment {
name: "data".to_string(),
filename: None,
content_type: Some("application/json".to_string()),
data: MultipartSegmentData::Text(serde_json::to_string(json_data).unwrap()),
};
if let Some(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, file_segment]
} else {
vec![json_segment]
}
}
pub fn get_icon_data(dummy_icon: DummyImage) -> ImageData {
ImageData {
filename: dummy_icon.filename(),
extension: dummy_icon.extension(),
icon: dummy_icon.bytes(),
}
}

View File

@@ -3,12 +3,61 @@ use actix_web::{
test::{self, TestRequest},
};
use labrinth::routes::v3::tags::GameData;
use labrinth::{
database::models::loader_fields::LoaderFieldEnumValue,
routes::v3::tags::{CategoryData, LoaderData},
};
use crate::common::database::ADMIN_USER_PAT;
use super::ApiV3;
impl ApiV3 {
pub async fn get_loaders(&self) -> ServiceResponse {
let req = TestRequest::get()
.uri("/v3/tag/loader")
.append_header(("Authorization", ADMIN_USER_PAT))
.to_request();
self.call(req).await
}
pub async fn get_loaders_deserialized(&self) -> Vec<LoaderData> {
let resp = self.get_loaders().await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_categories(&self) -> ServiceResponse {
let req = TestRequest::get()
.uri("/v3/tag/category")
.append_header(("Authorization", ADMIN_USER_PAT))
.to_request();
self.call(req).await
}
pub async fn get_categories_deserialized(&self) -> Vec<CategoryData> {
let resp = self.get_categories().await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_loader_field_variants(&self, loader_field: &str) -> ServiceResponse {
let req = TestRequest::get()
.uri(&format!("/v3/loader_field?loader_field={}", loader_field))
.append_header(("Authorization", ADMIN_USER_PAT))
.to_request();
self.call(req).await
}
pub async fn get_loader_field_variants_deserialized(
&self,
loader_field: &str,
) -> Vec<LoaderFieldEnumValue> {
let resp = self.get_loader_field_variants(loader_field).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
// TODO: fold this into v3 API of other v3 testing PR
pub async fn get_games(&self) -> ServiceResponse {
let req = TestRequest::get()

176
tests/common/api_v3/team.rs Normal file
View File

@@ -0,0 +1,176 @@
use actix_http::StatusCode;
use actix_web::{dev::ServiceResponse, test};
use labrinth::models::{
notifications::Notification,
teams::{OrganizationPermissions, ProjectPermissions, TeamMember},
};
use serde_json::json;
use crate::common::asserts::assert_status;
use super::ApiV3;
impl ApiV3 {
pub async fn get_team_members(&self, id_or_title: &str, pat: &str) -> ServiceResponse {
let req = test::TestRequest::get()
.uri(&format!("/v3/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!("/v3/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!("/v3/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!("/v3/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!("/v3/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!("/v3/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!("/v3/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(&self, user_id: &str, pat: &str) -> ServiceResponse {
let req = test::TestRequest::get()
.uri(&format!("/v3/user/{user_id}/notifications"))
.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 resp = self.get_user_notifications(user_id, pat).await;
assert_status(&resp, StatusCode::OK);
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!("/v3/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!("/v3/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!("/v3/notification/{notification_id}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
}

View File

@@ -0,0 +1,422 @@
use std::collections::HashMap;
use actix_http::{header::AUTHORIZATION, StatusCode};
use actix_web::{
dev::ServiceResponse,
test::{self, TestRequest},
};
use labrinth::{
models::{projects::VersionType, v3::projects::Version},
routes::v3::version_file::FileUpdateData,
util::actix::AppendsMultipart,
};
use serde_json::json;
use crate::common::asserts::assert_status;
use super::{request_data::VersionCreationRequestData, ApiV3};
pub fn url_encode_json_serialized_vec(elements: &[String]) -> String {
let serialized = serde_json::to_string(&elements).unwrap();
urlencoding::encode(&serialized).to_string()
}
impl ApiV3 {
pub async fn add_public_version(
&self,
creation_data: VersionCreationRequestData,
pat: &str,
) -> ServiceResponse {
// Add a project.
let req = TestRequest::post()
.uri("/v3/version")
.append_header(("Authorization", pat))
.set_multipart(creation_data.segment_data)
.to_request();
self.call(req).await
}
pub async fn add_public_version_deserialized(
&self,
creation_data: VersionCreationRequestData,
pat: &str,
) -> Version {
let resp = self.add_public_version(creation_data, pat).await;
assert_status(&resp, StatusCode::OK);
let value: serde_json::Value = test::read_body_json(resp).await;
let version_id = value["id"].as_str().unwrap();
self.get_version_deserialized(version_id, pat).await
}
pub async fn get_version(&self, id: &str, pat: &str) -> ServiceResponse {
let req = TestRequest::get()
.uri(&format!("/v3/version/{id}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
pub async fn get_version_deserialized(&self, id: &str, pat: &str) -> Version {
let resp = self.get_version(id, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn edit_version(
&self,
version_id: &str,
patch: serde_json::Value,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::patch()
.uri(&format!("/v3/version/{version_id}"))
.append_header(("Authorization", pat))
.set_json(patch)
.to_request();
self.call(req).await
}
pub async fn get_version_from_hash(
&self,
hash: &str,
algorithm: &str,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::get()
.uri(&format!("/v3/version_file/{hash}?algorithm={algorithm}"))
.append_header(("Authorization", pat))
.to_request();
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 get_versions_from_hashes(
&self,
hashes: &[&str],
algorithm: &str,
pat: &str,
) -> ServiceResponse {
let req = TestRequest::post()
.uri("/v3/version_files")
.append_header(("Authorization", pat))
.set_json(json!({
"hashes": hashes,
"algorithm": algorithm,
}))
.to_request();
self.call(req).await
}
pub async fn get_versions_from_hashes_deserialized(
&self,
hashes: &[&str],
algorithm: &str,
pat: &str,
) -> HashMap<String, Version> {
let resp = self.get_versions_from_hashes(hashes, algorithm, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_update_from_hash(
&self,
hash: &str,
algorithm: &str,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> ServiceResponse {
let mut json = json!({});
if let Some(loaders) = loaders {
json["loaders"] = serde_json::to_value(loaders).unwrap();
}
if let Some(game_versions) = game_versions {
json["loader_fields"] = json!({
"game_versions": game_versions,
});
}
if let Some(version_types) = version_types {
json["version_types"] = serde_json::to_value(version_types).unwrap();
}
let req = test::TestRequest::post()
.uri(&format!(
"/v3/version_file/{hash}/update?algorithm={algorithm}"
))
.append_header(("Authorization", pat))
.set_json(json)
.to_request();
self.call(req).await
}
pub async fn get_update_from_hash_deserialized(
&self,
hash: &str,
algorithm: &str,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> Version {
let resp = self
.get_update_from_hash(hash, algorithm, loaders, game_versions, version_types, pat)
.await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn update_files(
&self,
algorithm: &str,
hashes: Vec<String>,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> ServiceResponse {
let mut json = json!({
"algorithm": algorithm,
"hashes": hashes,
});
if let Some(loaders) = loaders {
json["loaders"] = serde_json::to_value(loaders).unwrap();
}
if let Some(game_versions) = game_versions {
json["loader_fields"] = json!({
"game_versions": game_versions,
});
}
if let Some(version_types) = version_types {
json["version_types"] = serde_json::to_value(version_types).unwrap();
}
let req = test::TestRequest::post()
.uri("/v3/version_files/update")
.append_header(("Authorization", pat))
.set_json(json)
.to_request();
self.call(req).await
}
pub async fn update_files_deserialized(
&self,
algorithm: &str,
hashes: Vec<String>,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> HashMap<String, Version> {
let resp = self
.update_files(
algorithm,
hashes,
loaders,
game_versions,
version_types,
pat,
)
.await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn update_individual_files(
&self,
algorithm: &str,
hashes: Vec<FileUpdateData>,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::post()
.uri("/v3/version_files/update_individual")
.append_header(("Authorization", pat))
.set_json(json!({
"algorithm": algorithm,
"hashes": hashes
}))
.to_request();
self.call(req).await
}
pub async fn update_individual_files_deserialized(
&self,
algorithm: &str,
hashes: Vec<FileUpdateData>,
pat: &str,
) -> HashMap<String, Version> {
let resp = self.update_individual_files(algorithm, hashes, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
// TODO: Not all fields are tested currently in the v3 tests, only the v2-v3 relevant ones are
#[allow(clippy::too_many_arguments)]
pub async fn get_project_versions(
&self,
project_id_slug: &str,
game_versions: Option<Vec<String>>,
loaders: Option<Vec<String>>,
featured: Option<bool>,
version_type: Option<VersionType>,
limit: Option<usize>,
offset: Option<usize>,
pat: &str,
) -> ServiceResponse {
let mut query_string = String::new();
if let Some(game_versions) = game_versions {
query_string.push_str(&format!(
"&game_versions={}",
urlencoding::encode(&serde_json::to_string(&game_versions).unwrap())
));
}
if let Some(loaders) = loaders {
query_string.push_str(&format!(
"&loaders={}",
urlencoding::encode(&serde_json::to_string(&loaders).unwrap())
));
}
if let Some(featured) = featured {
query_string.push_str(&format!("&featured={}", featured));
}
if let Some(version_type) = version_type {
query_string.push_str(&format!("&version_type={}", version_type));
}
if let Some(limit) = limit {
let limit = limit.to_string();
query_string.push_str(&format!("&limit={}", limit));
}
if let Some(offset) = offset {
let offset = offset.to_string();
query_string.push_str(&format!("&offset={}", offset));
}
let req = test::TestRequest::get()
.uri(&format!(
"/v3/project/{project_id_slug}/version?{}",
query_string.trim_start_matches('&')
))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
#[allow(clippy::too_many_arguments)]
pub async fn get_project_versions_deserialized(
&self,
slug: &str,
game_versions: Option<Vec<String>>,
loaders: Option<Vec<String>>,
featured: Option<bool>,
version_type: Option<VersionType>,
limit: Option<usize>,
offset: Option<usize>,
pat: &str,
) -> Vec<Version> {
let resp = self
.get_project_versions(
slug,
game_versions,
loaders,
featured,
version_type,
limit,
offset,
pat,
)
.await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
// TODO: remove redundancy in these functions
pub async fn create_default_version(
&self,
project_id: &str,
ordering: Option<i32>,
pat: &str,
) -> Version {
let json_data = json!(
{
"project_id": project_id,
"file_parts": ["basic-mod-different.jar"],
"version_number": "1.2.3.4",
"version_title": "start",
"dependencies": [],
"game_versions": ["1.20.1"] ,
"client_side": "required",
"server_side": "optional",
"release_channel": "release",
"loaders": ["fabric"],
"featured": true,
"ordering": ordering,
}
);
let json_segment = labrinth::util::actix::MultipartSegment {
name: "data".to_string(),
filename: None,
content_type: Some("application/json".to_string()),
data: labrinth::util::actix::MultipartSegmentData::Text(
serde_json::to_string(&json_data).unwrap(),
),
};
let file_segment = labrinth::util::actix::MultipartSegment {
name: "basic-mod-different.jar".to_string(),
filename: Some("basic-mod.jar".to_string()),
content_type: Some("application/java-archive".to_string()),
data: labrinth::util::actix::MultipartSegmentData::Binary(
include_bytes!("../../../tests/files/basic-mod-different.jar").to_vec(),
),
};
let request = test::TestRequest::post()
.uri("/v3/version")
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
.append_header((AUTHORIZATION, pat))
.to_request();
let resp = self.call(request).await;
assert_status(&resp, StatusCode::OK);
test::read_body_json(resp).await
}
pub async fn get_versions(&self, version_ids: Vec<String>, pat: &str) -> Vec<Version> {
let ids = url_encode_json_serialized_vec(&version_ids);
let request = test::TestRequest::get()
.uri(&format!("/v3/versions?ids={}", ids))
.append_header((AUTHORIZATION, pat))
.to_request();
let resp = self.call(request).await;
assert_status(&resp, StatusCode::OK);
test::read_body_json(resp).await
}
pub async fn edit_version_ordering(
&self,
version_id: &str,
ordering: Option<i32>,
pat: &str,
) -> ServiceResponse {
let request = test::TestRequest::patch()
.uri(&format!("/v3/version/{version_id}"))
.set_json(json!(
{
"ordering": ordering
}
))
.append_header((AUTHORIZATION, pat))
.to_request();
self.call(request).await
}
}

View File

@@ -2,13 +2,13 @@
use crate::common::get_json_val_str;
use itertools::Itertools;
use labrinth::models::v2::projects::LegacyVersion;
use labrinth::models::v3::projects::Version;
pub fn assert_status(response: &actix_web::dev::ServiceResponse, status: actix_http::StatusCode) {
assert_eq!(response.status(), status, "{:#?}", response.response());
}
pub fn assert_version_ids(versions: &[LegacyVersion], expected_ids: Vec<String>) {
pub fn assert_version_ids(versions: &[Version], expected_ids: Vec<String>) {
let version_ids = versions
.iter()
.map(|v| get_json_val_str(v.id))

View File

@@ -7,7 +7,7 @@ use labrinth::models::{
oauth_clients::OAuthClient,
organizations::Organization,
pats::Scopes,
v2::projects::{LegacyProject, LegacyVersion},
v3::projects::{Project, Version},
};
use serde_json::json;
use sqlx::Executor;
@@ -16,7 +16,7 @@ use zip::{write::FileOptions, CompressionMethod, ZipWriter};
use crate::common::database::USER_USER_PAT;
use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData};
use super::{environment::TestEnvironment, request_data::get_public_project_creation_data};
use super::{api_v3::request_data::get_public_project_creation_data, environment::TestEnvironment};
use super::{asserts::assert_status, database::USER_USER_ID, get_json_val_str};
@@ -170,10 +170,10 @@ pub struct DummyData {
impl DummyData {
pub fn new(
project_alpha: LegacyProject,
project_alpha_version: LegacyVersion,
project_beta: LegacyProject,
project_beta_version: LegacyVersion,
project_alpha: Project,
project_alpha_version: Version,
project_beta: Project,
project_beta_version: Version,
organization_zeta: Organization,
oauth_client_alpha: OAuthClient,
) -> Self {
@@ -303,9 +303,9 @@ pub async fn get_dummy_data(test_env: &TestEnvironment) -> DummyData {
)
}
pub async fn add_project_alpha(test_env: &TestEnvironment) -> (LegacyProject, LegacyVersion) {
pub async fn add_project_alpha(test_env: &TestEnvironment) -> (Project, Version) {
let (project, versions) = test_env
.v2
.v3
.add_public_project(
get_public_project_creation_data("alpha", Some(TestFile::DummyProjectAlpha)),
USER_USER_PAT,
@@ -314,25 +314,25 @@ pub async fn add_project_alpha(test_env: &TestEnvironment) -> (LegacyProject, Le
(project, versions.into_iter().next().unwrap())
}
pub async fn add_project_beta(test_env: &TestEnvironment) -> (LegacyProject, LegacyVersion) {
pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version) {
// Adds dummy data to the database with sqlx (projects, versions, threads)
// Generate test project data.
let jar = TestFile::DummyProjectBeta;
// TODO: this shouldnt be hardcoded (nor should other similar ones be)
let json_data = json!(
{
"title": "Test Project Beta",
"slug": "beta",
"description": "A dummy project for testing with.",
"body": "This project is not-yet-approved, and versions are draft.",
"client_side": "required",
"server_side": "optional",
"initial_versions": [{
"file_parts": [jar.filename()],
"version_number": "1.2.3",
"version_title": "start",
"status": "unlisted",
"requested_status": "unlisted",
"dependencies": [],
"client_side": "required",
"server_side": "optional",
"game_versions": ["1.20.1"] ,
"release_channel": "release",
"loaders": ["fabric"],
@@ -363,12 +363,11 @@ pub async fn add_project_beta(test_env: &TestEnvironment) -> (LegacyProject, Leg
// Add a project.
let req = TestRequest::post()
.uri("/v2/project")
.uri("/v3/project")
.append_header(("Authorization", USER_USER_PAT))
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
.to_request();
let resp = test_env.call(req).await;
assert_eq!(resp.status(), 200);
get_project_beta(test_env).await
@@ -377,7 +376,7 @@ pub async fn add_project_beta(test_env: &TestEnvironment) -> (LegacyProject, Leg
pub async fn add_organization_zeta(test_env: &TestEnvironment) -> Organization {
// Add an organzation.
let req = TestRequest::post()
.uri("/v2/organization")
.uri("/v3/organization")
.append_header(("Authorization", USER_USER_PAT))
.set_json(json!({
"title": "zeta",
@@ -391,46 +390,46 @@ pub async fn add_organization_zeta(test_env: &TestEnvironment) -> Organization {
get_organization_zeta(test_env).await
}
pub async fn get_project_alpha(test_env: &TestEnvironment) -> (LegacyProject, LegacyVersion) {
pub async fn get_project_alpha(test_env: &TestEnvironment) -> (Project, Version) {
// Get project
let req = TestRequest::get()
.uri("/v2/project/alpha")
.uri("/v3/project/alpha")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
let project: LegacyProject = test::read_body_json(resp).await;
let project: Project = test::read_body_json(resp).await;
// Get project's versions
let req = TestRequest::get()
.uri("/v2/project/alpha/version")
.uri("/v3/project/alpha/version")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
let versions: Vec<LegacyVersion> = test::read_body_json(resp).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) -> (LegacyProject, LegacyVersion) {
pub async fn get_project_beta(test_env: &TestEnvironment) -> (Project, Version) {
// Get project
let req = TestRequest::get()
.uri("/v2/project/beta")
.uri("/v3/project/beta")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
assert_status(&resp, StatusCode::OK);
let project: serde_json::Value = test::read_body_json(resp).await;
let project: LegacyProject = serde_json::from_value(project).unwrap();
let project: Project = serde_json::from_value(project).unwrap();
// Get project's versions
let req = TestRequest::get()
.uri("/v2/project/beta/version")
.uri("/v3/project/beta/version")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
assert_status(&resp, StatusCode::OK);
let versions: Vec<LegacyVersion> = test::read_body_json(resp).await;
let versions: Vec<Version> = test::read_body_json(resp).await;
let version = versions.into_iter().next().unwrap();
(project, version)
@@ -439,7 +438,7 @@ pub async fn get_project_beta(test_env: &TestEnvironment) -> (LegacyProject, Leg
pub async fn get_organization_zeta(test_env: &TestEnvironment) -> Organization {
// Get organization
let req = TestRequest::get()
.uri("/v2/organization/zeta")
.uri("/v3/organization/zeta")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;

View File

@@ -25,6 +25,9 @@ where
db.cleanup().await;
}
// TODO: This needs to be slightly redesigned in order to do both V2 and v3 tests.
// TODO: Most tests, since they use API functions, can be applied to both. The ones that weren't are in v2/, but
// all tests that can be applied to both should use both v2 and v3 (extract api to a trait with all the API functions and call both).
// A complete test environment, with a test actix app and a database.
// Must be called in an #[actix_rt::test] context. It also simulates a
@@ -77,7 +80,7 @@ impl TestEnvironment {
pub async fn generate_friend_user_notification(&self) {
let resp = self
.v2
.v3
.add_user_to_team(
&self.dummy.as_ref().unwrap().project_alpha.team_id,
FRIEND_USER_ID,
@@ -95,7 +98,7 @@ impl TestEnvironment {
pat: &str,
status_code: StatusCode,
) {
let resp = self.v2.get_user_notifications(user_id, pat).await;
let resp = self.v3.get_user_notifications(user_id, pat).await;
assert_status(&resp, status_code);
}
@@ -105,7 +108,7 @@ impl TestEnvironment {
pat: &str,
status_code: StatusCode,
) {
let resp = self.v2.get_user_projects(user_id, pat).await;
let resp = self.v3.get_user_projects(user_id, pat).await;
assert_status(&resp, status_code);
}
}

View File

@@ -10,7 +10,6 @@ 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

View File

@@ -4,13 +4,11 @@ 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 crate::common::database::{generate_random_name, ADMIN_USER_PAT};
use super::{
database::{USER_USER_ID, USER_USER_PAT},
api_v3::request_data,
database::{ENEMY_USER_PAT, USER_USER_ID, USER_USER_PAT},
environment::TestEnvironment,
};
@@ -164,7 +162,48 @@ impl<'a> PermissionsTest<'a> {
)
.await;
// Failure test
// Failure test- not logged in
let request = req_gen(&PermissionsTestContext {
project_id: Some(&project_id),
team_id: Some(&team_id),
..test_context
})
.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()
));
}
// Failure test- logged in on a non-team user
let request = req_gen(&PermissionsTestContext {
project_id: Some(&project_id),
team_id: Some(&team_id),
..test_context
})
.append_header(("Authorization", ENEMY_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()
));
}
// Failure test- logged in with EVERY non-relevant permission
let request = req_gen(&PermissionsTestContext {
project_id: Some(&project_id),
team_id: Some(&team_id),
@@ -175,7 +214,6 @@ impl<'a> PermissionsTest<'a> {
let resp = test_env.call(request).await;
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
println!("Body: {:?}", resp.response().body());
return Err(format!(
"Failure permissions test failed. Expected failure codes {} got {}",
self.allowed_failure_codes
@@ -207,7 +245,6 @@ impl<'a> PermissionsTest<'a> {
let resp = test_env.call(request).await;
if !resp.status().is_success() {
println!("Body: {:?}", resp.response().body());
return Err(format!(
"Success permissions test failed. Expected success, got {}",
resp.status().as_u16()
@@ -342,8 +379,9 @@ impl<'a> PermissionsTest<'a> {
organization_team_id: None,
};
// TEST 1: Failure
// Random user, unaffiliated with the project, with no permissions
// TEST 1: User not logged in - no PAT.
// This should always fail, regardless of permissions
// (As we are testing permissions-based failures)
let test_1 = async {
let (project_id, team_id) = create_dummy_project(test_env).await;
@@ -379,18 +417,9 @@ impl<'a> PermissionsTest<'a> {
};
// TEST 2: Failure
// User affiliated with the project, with failure permissions
// Random user, unaffiliated with the project, with no 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),
@@ -399,7 +428,6 @@ impl<'a> PermissionsTest<'a> {
})
.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!(
@@ -412,11 +440,57 @@ impl<'a> PermissionsTest<'a> {
));
}
let p =
get_project_permissions(self.user_id, self.user_pat, &project_id, test_env).await;
if p != ProjectPermissions::empty() {
return Err(format!(
"Test 2 failed. Expected no permissions, got {:?}",
p
));
}
Ok(())
};
// TEST 3: Failure
// User affiliated with the project, with failure 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(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 3 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 {:?}",
"Test 3 failed. Expected {:?}, got {:?}",
failure_project_permissions, p
));
}
@@ -424,9 +498,9 @@ impl<'a> PermissionsTest<'a> {
Ok(())
};
// TEST 3: Success
// TEST 4: Success
// User affiliated with the project, with the given permissions
let test_3 = async {
let test_4 = async {
let (project_id, team_id) = create_dummy_project(test_env).await;
add_user_to_team(
self.user_id,
@@ -449,7 +523,7 @@ impl<'a> PermissionsTest<'a> {
let resp = test_env.call(request).await;
if !resp.status().is_success() {
return Err(format!(
"Test 3 failed. Expected success, got {}",
"Test 4 failed. Expected success, got {}",
resp.status().as_u16()
));
}
@@ -458,7 +532,7 @@ impl<'a> PermissionsTest<'a> {
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 {:?}",
"Test 4 failed. Expected {:?}, got {:?}",
success_permissions, p
));
}
@@ -466,10 +540,10 @@ impl<'a> PermissionsTest<'a> {
Ok(())
};
// TEST 4: Failure
// TEST 5: Failure
// Project has an organization
// User affiliated with the project's org, with default failure permissions
let test_4 = async {
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;
@@ -494,7 +568,7 @@ impl<'a> PermissionsTest<'a> {
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 {}",
"Test 5 failed. Expected failure codes {} got {}",
self.allowed_failure_codes
.iter()
.map(|code| code.to_string())
@@ -507,7 +581,7 @@ impl<'a> PermissionsTest<'a> {
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 {:?}",
"Test 5 failed. Expected {:?}, got {:?}",
failure_project_permissions, p
));
}
@@ -515,55 +589,9 @@ impl<'a> PermissionsTest<'a> {
Ok(())
};
// TEST 5: Success
// TEST 6: 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;
@@ -577,6 +605,52 @@ impl<'a> PermissionsTest<'a> {
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 6 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 6 failed. Expected {:?}, got {:?}",
success_permissions, p
));
}
Ok(())
};
// TEST 7: 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_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(success_permissions),
None,
test_env,
)
.await;
add_user_to_team(
self.user_id,
self.user_pat,
@@ -598,7 +672,7 @@ impl<'a> PermissionsTest<'a> {
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 {}",
"Test 7 failed. Expected failure codes {} got {}",
self.allowed_failure_codes
.iter()
.map(|code| code.to_string())
@@ -611,7 +685,7 @@ impl<'a> PermissionsTest<'a> {
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 {:?}",
"Test 7 failed. Expected {:?}, got {:?}",
failure_project_permissions, p
));
}
@@ -619,11 +693,11 @@ impl<'a> PermissionsTest<'a> {
Ok(())
};
// TEST 7: Success
// TEST 8: 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 test_8 = 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;
@@ -658,7 +732,7 @@ impl<'a> PermissionsTest<'a> {
if !resp.status().is_success() {
return Err(format!(
"Test 7 failed. Expected success, got {}",
"Test 8 failed. Expected success, got {}",
resp.status().as_u16()
));
}
@@ -667,7 +741,7 @@ impl<'a> PermissionsTest<'a> {
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 {:?}",
"Test 8 failed. Expected {:?}, got {:?}",
success_permissions, p
));
}
@@ -675,7 +749,8 @@ impl<'a> PermissionsTest<'a> {
Ok(())
};
tokio::try_join!(test_1, test_2, test_3, test_4, test_5, test_6, test_7,).map_err(|e| e)?;
tokio::try_join!(test_1, test_2, test_3, test_4, test_5, test_6, test_7, test_8)
.map_err(|e| e)?;
Ok(())
}
@@ -845,7 +920,7 @@ impl<'a> PermissionsTest<'a> {
}
async fn create_dummy_project(test_env: &TestEnvironment) -> (String, String) {
let api = &test_env.v2;
let api = &test_env.v3;
// Create a very simple project
let slug = generate_random_name("test_project");
@@ -861,7 +936,7 @@ async fn create_dummy_project(test_env: &TestEnvironment) -> (String, String) {
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 api = &test_env.v3;
let resp = api
.create_organization(&name, "Example description.", ADMIN_USER_PAT)
@@ -878,7 +953,7 @@ async fn create_dummy_org(test_env: &TestEnvironment) -> (String, String) {
}
async fn add_project_to_org(test_env: &TestEnvironment, project_id: &str, organization_id: &str) {
let api = &test_env.v2;
let api = &test_env.v3;
let resp = api
.organization_add_project(organization_id, project_id, ADMIN_USER_PAT)
.await;
@@ -893,7 +968,7 @@ async fn add_user_to_team(
organization_permissions: Option<OrganizationPermissions>,
test_env: &TestEnvironment,
) {
let api = &test_env.v2;
let api = &test_env.v3;
// Invite user
let resp = api
@@ -919,7 +994,7 @@ async fn modify_user_team_permissions(
organization_permissions: Option<OrganizationPermissions>,
test_env: &TestEnvironment,
) {
let api = &test_env.v2;
let api = &test_env.v3;
// Send invitation to user
let resp = api
@@ -938,7 +1013,7 @@ async fn modify_user_team_permissions(
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 api = &test_env.v3;
let resp = api.remove_from_team(team_id, user_id, ADMIN_USER_PAT).await;
assert!(resp.status().is_success());
}
@@ -949,7 +1024,7 @@ async fn get_project_permissions(
project_id: &str,
test_env: &TestEnvironment,
) -> ProjectPermissions {
let resp = test_env.v2.get_project_members(project_id, user_pat).await;
let resp = test_env.v3.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
@@ -972,7 +1047,7 @@ async fn get_organization_permissions(
organization_id: &str,
test_env: &TestEnvironment,
) -> OrganizationPermissions {
let api = &test_env.v2;
let api = &test_env.v3;
let resp = api
.get_organization_members(organization_id, user_pat)
.await;