use crate::assert_status; use crate::common::api_common::{ApiProject, ApiVersion}; use crate::common::api_v2::ApiV2; use actix_http::StatusCode; use actix_web::test; use futures::StreamExt; use labrinth::models::ids::VersionId; use labrinth::{ models::projects::{Loader, VersionStatus, VersionType}, routes::v2::version_file::FileUpdateData, }; use serde_json::json; use crate::common::api_v2::request_data::get_public_project_creation_data; use crate::common::dummy_data::{DummyProjectAlpha, DummyProjectBeta}; use crate::common::environment::{TestEnvironment, with_test_environment}; use crate::common::{ database::{ENEMY_USER_PAT, USER_USER_PAT}, dummy_data::TestFile, }; #[actix_rt::test] pub async fn test_patch_version() { with_test_environment( None, |test_env: TestEnvironment| async move { let api = &test_env.api; let alpha_version_id = &test_env.dummy.project_alpha.version_id; // // First, we do some patch requests that should fail. // // Failure because the user is not authorized. let resp = api .edit_version( alpha_version_id, json!({ "name": "test 1", }), ENEMY_USER_PAT, ) .await; assert_status!(&resp, StatusCode::UNAUTHORIZED); // Failure because these are illegal requested statuses for a normal user. for req in ["unknown", "scheduled"] { let resp = api .edit_version( alpha_version_id, json!({ "status": req, // requested status it not set here, but in /schedule }), USER_USER_PAT, ) .await; assert_status!(&resp, StatusCode::BAD_REQUEST); } // Sucessful request to patch many fields. let resp = api .edit_version( alpha_version_id, json!({ "name": "new version name", "version_number": "1.3.0", "changelog": "new changelog", "version_type": "beta", // // "dependencies": [], TODO: test this "game_versions": ["1.20.5"], "loaders": ["forge"], "featured": false, // "primary_file": [], TODO: test this // // "downloads": 0, TODO: moderator exclusive "status": "draft", // // "filetypes": ["jar"], TODO: test this }), USER_USER_PAT, ) .await; assert_status!(&resp, StatusCode::NO_CONTENT); let version = api .get_version_deserialized(alpha_version_id, USER_USER_PAT) .await; assert_eq!(version.name, "new version name"); assert_eq!(version.version_number, "1.3.0"); assert_eq!(version.changelog, "new changelog"); assert_eq!( version.version_type, serde_json::from_str::("\"beta\"").unwrap() ); assert_eq!(version.game_versions, vec!["1.20.5"]); assert_eq!(version.loaders, vec![Loader("forge".to_string())]); assert!(!version.featured); assert_eq!(version.status, VersionStatus::from_string("draft")); // These ones are checking the v2-v3 rerouting, we eneusre that only 'game_versions' // works as expected, as well as only 'loaders' let resp = api .edit_version( alpha_version_id, json!({ "game_versions": ["1.20.1", "1.20.2", "1.20.4"], }), USER_USER_PAT, ) .await; assert_status!(&resp, StatusCode::NO_CONTENT); let version = api .get_version_deserialized(alpha_version_id, USER_USER_PAT) .await; assert_eq!( version.game_versions, vec!["1.20.1", "1.20.2", "1.20.4"] ); assert_eq!(version.loaders, vec![Loader("forge".to_string())]); // From last patch let resp = api .edit_version( alpha_version_id, json!({ "loaders": ["fabric"], }), USER_USER_PAT, ) .await; assert_status!(&resp, StatusCode::NO_CONTENT); let version = api .get_version_deserialized(alpha_version_id, USER_USER_PAT) .await; assert_eq!( version.game_versions, vec!["1.20.1", "1.20.2", "1.20.4"] ); // From last patch assert_eq!(version.loaders, vec![Loader("fabric".to_string())]); }, ) .await; } #[actix_rt::test] async fn version_updates() { // Test setup and dummy data with_test_environment( None, |test_env: TestEnvironment| async move { let api = &test_env.api; let DummyProjectAlpha { project_id: alpha_project_id, project_id_parsed: alpha_project_id_parsed, version_id: alpha_version_id, file_hash: alpha_version_hash, .. } = &test_env.dummy.project_alpha; let DummyProjectBeta { version_id: beta_version_id, file_hash: beta_version_hash, .. } = &test_env.dummy.project_beta; // Quick test, using get version from hash let version = api .get_version_from_hash_deserialized( alpha_version_hash, "sha1", USER_USER_PAT, ) .await; assert_eq!(&version.id.to_string(), alpha_version_id); // Get versions from hash let versions = api .get_versions_from_hashes_deserialized( &[alpha_version_hash.as_str(), beta_version_hash.as_str()], "sha1", USER_USER_PAT, ) .await; assert_eq!(versions.len(), 2); assert_eq!( &versions[alpha_version_hash].id.to_string(), alpha_version_id ); assert_eq!( &versions[beta_version_hash].id.to_string(), beta_version_id ); // When there is only the one version, there should be no updates let version = api .get_update_from_hash_deserialized_common( alpha_version_hash, "sha1", None, None, None, USER_USER_PAT, ) .await; assert_eq!(&version.id.to_string(), alpha_version_id); let versions = api .update_files_deserialized_common( "sha1", vec![alpha_version_hash.to_string()], None, None, None, USER_USER_PAT, ) .await; assert_eq!(versions.len(), 1); assert_eq!( &versions[alpha_version_hash].id.to_string(), alpha_version_id ); // Add 3 new versions, 1 before, and 2 after, with differing game_version/version_types/loaders let mut update_ids = vec![]; for (version_number, patch_value) in &[ ( "0.9.9", json!({ "game_versions": ["1.20.1"], }), ), ( "1.5.0", json!({ "game_versions": ["1.20.3"], "loaders": ["fabric"], }), ), ( "1.5.1", json!({ "game_versions": ["1.20.4"], "loaders": ["forge"], "version_type": "beta" }), ), ] { let version = api .add_public_version_deserialized_common( *alpha_project_id_parsed, version_number, TestFile::build_random_jar(), None, None, USER_USER_PAT, ) .await; update_ids.push(version.id); // Patch using json api.edit_version( &version.id.to_string(), patch_value.clone(), USER_USER_PAT, ) .await; } let check_expected = |game_versions: Option>, loaders: Option>, version_types: Option>, result_id: Option| async move { let (success, result_id) = match result_id { Some(id) => (true, id), None => (false, VersionId(0)), }; // get_update_from_hash let resp = api .get_update_from_hash( alpha_version_hash, "sha1", loaders.clone(), game_versions.clone(), version_types.clone(), USER_USER_PAT, ) .await; if success { assert_status!(&resp, StatusCode::OK); let body: serde_json::Value = test::read_body_json(resp).await; let id = body["id"].as_str().unwrap(); assert_eq!(id, &result_id.to_string()); } else { assert_status!(&resp, StatusCode::NOT_FOUND); } // update_files let versions = api .update_files_deserialized_common( "sha1", vec![alpha_version_hash.to_string()], loaders.clone(), game_versions.clone(), version_types.clone(), USER_USER_PAT, ) .await; if success { assert_eq!(versions.len(), 1); let first = versions.iter().next().unwrap(); assert_eq!(first.1.id, result_id); } else { assert_eq!(versions.len(), 0); } // update_individual_files let hashes = vec![FileUpdateData { hash: alpha_version_hash.to_string(), loaders, game_versions, version_types: version_types.map(|v| { v.into_iter() .map(|v| { serde_json::from_str(&format!("\"{v}\"")) .unwrap() }) .collect() }), }]; let versions = api .update_individual_files_deserialized( "sha1", hashes, USER_USER_PAT, ) .await; if success { assert_eq!(versions.len(), 1); let first = versions.iter().next().unwrap(); assert_eq!(first.1.id, result_id); } else { assert_eq!(versions.len(), 0); } }; let tests = vec![ check_expected( Some(vec!["1.20.1".to_string()]), None, None, Some(update_ids[0]), ), check_expected( Some(vec!["1.20.3".to_string()]), None, None, Some(update_ids[1]), ), check_expected( Some(vec!["1.20.4".to_string()]), None, None, Some(update_ids[2]), ), // Loader restrictions check_expected( None, Some(vec!["fabric".to_string()]), None, Some(update_ids[1]), ), check_expected( None, Some(vec!["forge".to_string()]), None, Some(update_ids[2]), ), // Version type restrictions check_expected( None, None, Some(vec!["release".to_string()]), Some(update_ids[1]), ), check_expected( None, None, Some(vec!["beta".to_string()]), Some(update_ids[2]), ), // Specific combination check_expected( None, Some(vec!["fabric".to_string()]), Some(vec!["release".to_string()]), Some(update_ids[1]), ), // Impossible combination check_expected( None, Some(vec!["fabric".to_string()]), Some(vec!["beta".to_string()]), None, ), // No restrictions, should do the last one check_expected(None, None, None, Some(update_ids[2])), ]; // Wait on all tests, 4 at a time futures::stream::iter(tests) .buffer_unordered(4) .collect::>() .await; // We do a couple small tests for get_project_versions_deserialized as well // TODO: expand this more. let versions = api .get_project_versions_deserialized_common( alpha_project_id, None, None, None, None, None, None, USER_USER_PAT, ) .await; assert_eq!(versions.len(), 4); let versions = api .get_project_versions_deserialized_common( alpha_project_id, None, Some(vec!["forge".to_string()]), None, None, None, None, USER_USER_PAT, ) .await; assert_eq!(versions.len(), 1); }, ) .await; } #[actix_rt::test] async fn add_version_project_types_v2() { with_test_environment( None, |test_env: TestEnvironment| async move { // Since v2 no longer keeps project_type at the project level but the version level, // we have to test that the project_type is set correctly when adding a version, if its done in separate requests. let api = &test_env.api; // Create a project in v2 with project_type = modpack, and no initial version set. let (test_project, test_versions) = api .add_public_project("test-modpack", None, None, USER_USER_PAT) .await; assert_eq!(test_versions.len(), 0); // No initial version set // Get as v2 project let test_project = api .get_project_deserialized( &test_project.slug.unwrap(), USER_USER_PAT, ) .await; assert_eq!(test_project.project_type, "project"); // No project_type set, as no versions are set // Default to 'project' if none are found // This is a known difference between older v2 ,but is acceptable. // This would be the appropriate test on older v2: // assert_eq!(test_project.project_type, "modpack"); // Create a version with a modpack file attached let test_version = api .add_public_version_deserialized_common( test_project.id, "1.0.0", TestFile::build_random_mrpack(), None, None, USER_USER_PAT, ) .await; // When we get the version as v2, it should display 'fabric' as the loader (and no project_type) let test_version = api .get_version_deserialized( &test_version.id.to_string(), USER_USER_PAT, ) .await; assert_eq!( test_version.loaders, vec![Loader("fabric".to_string())] ); // When we get the project as v2, it should display 'modpack' as the project_type, and 'fabric' as the loader let test_project = api .get_project_deserialized( &test_project.slug.unwrap(), USER_USER_PAT, ) .await; assert_eq!(test_project.project_type, "modpack"); assert_eq!(test_project.loaders, vec!["fabric"]); // When we get the version as v3, it should display 'mrpack' as the loader, and 'modpack' as the project_type // When we get the project as v3, it should display 'modpack' as the project_type, and 'mrpack' as the loader // The project should be a modpack project }, ) .await; } #[actix_rt::test] async fn test_incorrect_file_parts() { // Ensures that a version get that 'should' have mrpack_loaders does still display them // if the file is 'mrpack' but the file_parts are incorrect with_test_environment( None, |test_env: TestEnvironment| async move { let api = &test_env.api; // Patch to set the file_parts to something incorrect let patch = json!([{ "op": "add", "path": "/file_parts", "value": ["invalid.zip"] // one file, wrong non-mrpack extension }]); // Create an empty project let slug = "test-project"; let creation_data = get_public_project_creation_data(slug, None, None); let resp = api.create_project(creation_data, USER_USER_PAT).await; assert_status!(&resp, StatusCode::OK); // Get the project let project = api.get_project_deserialized(slug, USER_USER_PAT).await; assert_eq!(project.project_type, "project"); // Create a version with a mrpack file, but incorrect file_parts let resp = api .add_public_version( project.id, "1.0.0", TestFile::build_random_mrpack(), None, Some(serde_json::from_value(patch).unwrap()), USER_USER_PAT, ) .await; assert_status!(&resp, StatusCode::OK); // Get the project now, which should be now correctly identified as a modpack let project = api.get_project_deserialized(slug, USER_USER_PAT).await; assert_eq!(project.project_type, "modpack"); assert_eq!(project.loaders, vec!["fabric"]); }, ) .await; }