Cleans up and removes TODOs, adds tests (#844)

* removes version ordering from v2; simplifies now-unecessary three-level faceting

* resolved some todos

* test for game version updating

* merge fixes; display_categories fix
This commit is contained in:
Wyatt Verchere
2024-01-11 18:11:27 -08:00
committed by GitHub
parent 76c885f080
commit ef31c0c0da
24 changed files with 243 additions and 241 deletions

View File

@@ -77,9 +77,6 @@ pub struct CommonVersion {
pub requested_status: Option<VersionStatus>,
pub files: Vec<VersionFile>,
pub dependencies: Vec<Dependency>,
// TODO: should ordering be in v2?
pub ordering: Option<i32>,
}
#[derive(Deserialize)]

View File

@@ -21,8 +21,6 @@ use crate::{
use super::ApiV2;
// TODO: Tag gets do not include PAT, as they are public.
impl ApiV2 {
async fn get_side_types(&self) -> ServiceResponse {
let req = TestRequest::get()

View File

@@ -66,10 +66,11 @@ impl ApiTeams for ApiV2 {
) -> Vec<CommonTeamMember> {
let resp = self.get_team_members(id_or_title, pat).await;
assert_status!(&resp, StatusCode::OK);
// TODO: Note, this does NOT deserialize to any other struct first, as currently TeamMember is the same in v2 and v3.
// CommonTeamMember = TeamMember (v3)
// This may yet change, so we should keep common struct.
test::read_body_json(resp).await
// First, deserialize to the non-common format (to test the response is valid for this api version)
let v: Vec<LegacyTeamMember> = test::read_body_json(resp).await;
// Then, deserialize to the common format
let value = serde_json::to_value(v).unwrap();
serde_json::from_value(value).unwrap()
}
async fn get_teams_members(
@@ -103,10 +104,11 @@ impl ApiTeams for ApiV2 {
) -> Vec<CommonTeamMember> {
let resp = self.get_project_members(id_or_title, pat).await;
assert_status!(&resp, StatusCode::OK);
// TODO: Note, this does NOT deserialize to any other struct first, as currently TeamMember is the same in v2 and v3.
// CommonTeamMember = TeamMember (v3)
// This may yet change, so we should keep common struct.
test::read_body_json(resp).await
// First, deserialize to the non-common format (to test the response is valid for this api version)
let v: Vec<LegacyTeamMember> = test::read_body_json(resp).await;
// Then, deserialize to the common format
let value = serde_json::to_value(v).unwrap();
serde_json::from_value(value).unwrap()
}
async fn get_organization_members(
@@ -128,10 +130,11 @@ impl ApiTeams for ApiV2 {
) -> Vec<CommonTeamMember> {
let resp = self.get_organization_members(id_or_title, pat).await;
assert_status!(&resp, StatusCode::OK);
// TODO: Note, this does NOT deserialize to any other struct first, as currently TeamMember is the same in v2 and v3.
// CommonTeamMember = TeamMember (v3)
// This may yet change, so we should keep common struct.
test::read_body_json(resp).await
// First, deserialize to the non-common format (to test the response is valid for this api version)
let v: Vec<LegacyTeamMember> = test::read_body_json(resp).await;
// Then, deserialize to the common format
let value = serde_json::to_value(v).unwrap();
serde_json::from_value(value).unwrap()
}
async fn join_team(&self, team_id: &str, pat: Option<&str>) -> ServiceResponse {

View File

@@ -85,7 +85,6 @@ impl ApiV3 {
test::read_body_json(resp).await
}
// TODO: fold this into v3 API of other v3 testing PR
async fn get_games(&self) -> ServiceResponse {
let req = TestRequest::get()
.uri("/v3/games")

View File

@@ -66,6 +66,16 @@ impl ApiV3 {
test::read_body_json(resp).await
}
pub async fn get_versions_deserialized(
&self,
version_ids: Vec<String>,
pat: Option<&str>,
) -> Vec<Version> {
let resp = self.get_versions(version_ids, pat).await;
assert_status!(&resp, StatusCode::OK);
test::read_body_json(resp).await
}
pub async fn update_individual_files(
&self,
algorithm: &str,
@@ -454,7 +464,6 @@ impl ApiVersion for ApiV3 {
serde_json::from_value(value).unwrap()
}
// TODO: remove redundancy in these functions
async fn edit_version_ordering(
&self,
version_id: &str,

View File

@@ -15,9 +15,8 @@ use zip::{write::FileOptions, CompressionMethod, ZipWriter};
use crate::{
assert_status,
common::{api_common::Api, database::USER_USER_PAT},
common::{api_common::Api, api_v3, database::USER_USER_PAT},
};
use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData};
use super::{
api_common::{request_data::ImageData, ApiProject, AppendsOptionalPat},
@@ -342,58 +341,22 @@ pub async fn add_project_beta(api: &ApiV3) -> (Project, Version) {
// Generate test project data.
let jar = TestFile::DummyProjectBeta;
// TODO: this shouldnt be hardcoded (nor should other similar ones be)
let json_data = json!(
{
"name": "Test Project Beta",
"slug": "beta",
"summary": "A dummy project for testing with.",
"description": "This project is not-yet-approved, and versions are draft.",
"initial_versions": [{
"file_parts": [jar.filename()],
"version_number": "1.2.3",
"version_title": "start",
"status": "unlisted",
"dependencies": [],
"singleplayer": true,
"client_and_server": true,
"client_only": true,
"server_only": false,
"game_versions": ["1.20.1"] ,
"release_channel": "release",
"loaders": ["fabric"],
"featured": true
}],
"status": "private",
"requested_status": "private",
"categories": [],
"license_id": "MIT"
}
let modify_json = serde_json::from_value(json!([
{ "op": "add", "path": "/summary", "value": "A dummy project for testing with." },
{ "op": "add", "path": "/description", "value": "This project is not-yet-approved, and versions are draft." },
{ "op": "add", "path": "/initial_versions/0/status", "value": "unlisted" },
{ "op": "add", "path": "/status", "value": "private" },
{ "op": "add", "path": "/requested_status", "value": "private" },
]))
.unwrap();
let creation_data = api_v3::request_data::get_public_project_creation_data(
"beta",
Some(jar),
Some(modify_json),
);
// Basic json
let json_segment = MultipartSegment {
name: "data".to_string(),
filename: None,
content_type: Some("application/json".to_string()),
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
};
// Basic 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()),
};
// Add a project.
let req = TestRequest::post()
.uri("/v3/project")
.append_pat(USER_USER_PAT)
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
.to_request();
let resp = api.call(req).await;
assert_status!(&resp, StatusCode::OK);
api.create_project(creation_data, USER_USER_PAT).await;
get_project_beta(api).await
}

View File

@@ -25,9 +25,6 @@ pub async fn with_test_environment<Fut, A>(
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).
pub async fn with_test_environment_all<Fut, F>(max_connections: Option<u32>, f: F)
where
Fut: Future<Output = ()>,

View File

@@ -5,6 +5,7 @@ use actix_web::test;
use common::api_v3::ApiV3;
use common::environment::{with_test_environment, TestEnvironment};
use itertools::Itertools;
use labrinth::database::models::legacy_loader_fields::MinecraftGameVersion;
use labrinth::models::v3;
use serde_json::json;
@@ -556,3 +557,79 @@ async fn test_multi_get_redis_cache() {
})
.await;
}
#[actix_rt::test]
async fn minecraft_game_version_update() {
// We simulate adding a Minecraft game version, to ensure other data doesn't get overwritten
// This is basically a test for the insertion/concatenation query
// This doesn't use a route (as this behaviour isn't exposed via a route, but a scheduled URL call)
// We just interact with the labrinth functions directly
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
let api = &test_env.api;
// First, get a list of all gameversions
let game_versions = api
.get_loader_field_variants_deserialized("game_versions")
.await;
// A couple specific checks- in the dummy data, all game versions are marked as major=false except 1.20.5
let name_to_major = game_versions
.iter()
.map(|x| {
(
x.value.clone(),
x.metadata.get("major").unwrap().as_bool().unwrap(),
)
})
.collect::<std::collections::HashMap<_, _>>();
for (name, major) in name_to_major {
if name == "1.20.5" {
assert!(major);
} else {
assert!(!major);
}
}
// Now, we add a new game version, directly to the db
let pool = test_env.db.pool.clone();
let redis = test_env.db.redis_pool.clone();
MinecraftGameVersion::builder()
.version("1.20.6")
.unwrap()
.version_type("release")
.unwrap()
.created(
// now
&chrono::Utc::now(),
)
.insert(&pool, &redis)
.await
.unwrap();
// Check again
let game_versions = api
.get_loader_field_variants_deserialized("game_versions")
.await;
let name_to_major = game_versions
.iter()
.map(|x| {
(
x.value.clone(),
x.metadata.get("major").unwrap().as_bool().unwrap(),
)
})
.collect::<std::collections::HashMap<_, _>>();
// Confirm that the new version is there
assert!(name_to_major.contains_key("1.20.6"));
// Confirm metadata is unaltered
for (name, major) in name_to_major {
if name == "1.20.5" {
assert!(major);
} else {
assert!(!major);
}
}
})
.await
}

View File

@@ -218,7 +218,6 @@ pub async fn notifications_scopes() {
#[actix_rt::test]
pub async fn project_version_create_scopes_v3() {
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
// TODO: If possible, find a way to use generic api functions with the Permissions/Scopes test, then this can be recombined with the V2 version of this test
let api = &test_env.api;
// Create project
@@ -409,16 +408,14 @@ pub async fn project_version_reads_scopes() {
.await
.unwrap();
// TODO: Should this be /POST? Looks like /GET
// TODO: this scope doesn't actually affect anything, because the Project::get_id contained within disallows hidden versions, which is the point of this scope
// let req_gen = || {
// test::TestRequest::post()
// .uri(&format!("/v3/version_file/{beta_file_hash}/update"))
// .set_json(json!({}))
// TODO: This scope currently fails still as the 'version' field of QueryProject only allows public versions.
// TODO: This will be fixed when the 'extracts_versions' PR is merged.
// let req_gen = |pat: Option<String>| async move {
// api.get_update_from_hash(beta_file_hash, "sha1", None, None, None, pat.as_deref())
// .await
// };
// ScopeTest::new(&test_env).with_failure_code(404).test(req_gen, read_version).await.unwrap();
// TODO: Should this be /POST? Looks like /GET
let req_gen = |pat: Option<String>| async move {
api.get_versions_from_hashes(&[beta_file_hash], "sha1", pat.as_deref())
.await
@@ -432,30 +429,10 @@ pub async fn project_version_reads_scopes() {
assert!(success.as_object().unwrap().contains_key(beta_file_hash));
// Update version file
// TODO: Should this be /POST? Looks like /GET
// TODO: this scope doesn't actually affect anything, because the Project::get_id contained within disallows hidden versions, which is the point of this scope
// let req_gen = || {
// test::TestRequest::post()
// .uri(&format!("/v3/version_files/update_individual"))
// .set_json(json!({
// "hashes": [{
// "hash": beta_file_hash,
// }]
// }))
// };
// let (failure, success) = ScopeTest::new(&test_env).with_failure_code(200).test(req_gen, read_version).await.unwrap();
// assert!(!failure.as_object().unwrap().contains_key(beta_file_hash));
// assert!(success.as_object().unwrap().contains_key(beta_file_hash));
// Update version file
// TODO: this scope doesn't actually affect anything, because the Project::get_id contained within disallows hidden versions, which is the point of this scope
// let req_gen = || {
// test::TestRequest::post()
// .uri(&format!("/v3/version_files/update"))
// .set_json(json!({
// "hashes": [beta_file_hash]
// }))
// TODO: This scope currently fails still as the 'version' field of QueryProject only allows public versions.
// TODO: This will be fixed when the 'extracts_versions' PR is merged.
// let req_gen = |pat : Option<String>| async move {
// api.update_files("sha1", vec![beta_file_hash.clone()], None, None, None, pat.as_deref()).await
// };
// let (failure, success) = ScopeTest::new(&test_env).with_failure_code(200).test(req_gen, read_version).await.unwrap();
// assert!(!failure.as_object().unwrap().contains_key(beta_file_hash));
@@ -712,8 +689,7 @@ pub async fn version_write_scopes() {
.await
.unwrap();
// Delete version file
// TODO: Should this scope be VERSION_DELETE?
// Delete version file. Notably, this uses 'VERSION_WRITE' instead of 'VERSION_DELETE' as it is writing to the version
let req_gen = |pat: Option<String>| async move {
api.remove_version_file(alpha_file_hash, pat.as_deref())
.await

View File

@@ -90,7 +90,7 @@ async fn test_project_type_sanity() {
);
}
// TODO: as we get more complicated strucures with v3 testing, and alpha/beta get more complicated, we should add more tests here,
// As we get more complicated strucures with as v3 continues to expand, and alpha/beta get more complicated, we should add more tests here,
// to ensure that projects created with v3 routes are still valid and work with v3 routes.
})
.await;
@@ -397,7 +397,6 @@ async fn permissions_patch_project_v2() {
with_test_environment(Some(8), |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
// TODO: This only includes v2 ones (as it should. See v3)
// For each permission covered by EDIT_DETAILS, ensure the permission is required
let edit_details = ProjectPermissions::EDIT_DETAILS;
let test_pairs = [

View File

@@ -50,3 +50,32 @@ pub async fn project_version_create_scopes() {
})
.await;
}
#[actix_rt::test]
pub async fn project_version_reads_scopes() {
with_test_environment(None, |_test_env: TestEnvironment<ApiV2>| async move {
// let api = &test_env.api;
// let beta_file_hash = &test_env.dummy.project_beta.file_hash;
// let read_version = Scopes::VERSION_READ;
// Update individual version file
// TODO: This scope currently fails still as the 'version' field of QueryProject only allows public versions.
// TODO: This will be fixed when the 'extracts_versions' PR is merged.
// let req_gen = |pat : Option<String>| async move {
// api.update_individual_files("sha1", vec![
// FileUpdateData {
// hash: beta_file_hash.clone(),
// loaders: None,
// game_versions: None,
// version_types: None
// }
// ], pat.as_deref())
// .await
// };
// let (failure, success) = ScopeTest::new(&test_env).with_failure_code(200).test(req_gen, read_version).await.unwrap();
// assert!(!failure.as_object().unwrap().contains_key(beta_file_hash));
// assert!(success.as_object().unwrap().contains_key(beta_file_hash));
})
.await;
}

View File

@@ -18,9 +18,6 @@ use std::sync::Arc;
#[actix_rt::test]
async fn search_projects() {
// TODO: ("Match changes in the 2 version of thee add_public_version_creation_data to those made in v3
// It should drastically simplify this function
// Test setup and dummy data
with_test_environment(Some(10), |test_env: TestEnvironment<ApiV2>| async move {
let api = &test_env.api;
@@ -399,6 +396,8 @@ async fn search_projects() {
);
assert!(hit.categories.contains(&"forge".to_string()));
assert!(hit.categories.contains(&"fabric".to_string()));
assert!(hit.display_categories.contains(&"forge".to_string()));
assert!(hit.display_categories.contains(&"fabric".to_string()));
// Also, ensure author is correctly capitalized
assert_eq!(hit.author, "User".to_string());

View File

@@ -536,49 +536,55 @@ pub async fn test_project_versions() {
#[actix_rt::test]
async fn can_create_version_with_ordering() {
with_test_environment_all(None, |env| async move {
let alpha_project_id_parsed = env.dummy.project_alpha.project_id_parsed;
with_test_environment(
None,
|env: common::environment::TestEnvironment<ApiV3>| async move {
let alpha_project_id_parsed = env.dummy.project_alpha.project_id_parsed;
let new_version_id = get_json_val_str(
env.api
.add_public_version_deserialized_common(
alpha_project_id_parsed,
"1.2.3.4",
TestFile::BasicMod,
Some(1),
None,
USER_USER_PAT,
)
.await
.id,
);
let new_version_id = get_json_val_str(
env.api
.add_public_version_deserialized_common(
alpha_project_id_parsed,
"1.2.3.4",
TestFile::BasicMod,
Some(1),
None,
USER_USER_PAT,
)
.await
.id,
);
let versions = env
.api
.get_versions_deserialized_common(vec![new_version_id.clone()], USER_USER_PAT)
.await;
assert_eq!(versions[0].ordering, Some(1));
})
let versions = env
.api
.get_versions_deserialized(vec![new_version_id.clone()], USER_USER_PAT)
.await;
assert_eq!(versions[0].ordering, Some(1));
},
)
.await;
}
#[actix_rt::test]
async fn edit_version_ordering_works() {
with_test_environment_all(None, |env| async move {
let alpha_version_id = env.dummy.project_alpha.version_id.clone();
with_test_environment(
None,
|env: common::environment::TestEnvironment<ApiV3>| async move {
let alpha_version_id = env.dummy.project_alpha.version_id.clone();
let resp = env
.api
.edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let resp = env
.api
.edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT)
.await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let versions = env
.api
.get_versions_deserialized_common(vec![alpha_version_id.clone()], USER_USER_PAT)
.await;
assert_eq!(versions[0].ordering, Some(10));
})
let versions = env
.api
.get_versions_deserialized(vec![alpha_version_id.clone()], USER_USER_PAT)
.await;
assert_eq!(versions[0].ordering, Some(10));
},
)
.await;
}