use std::collections::HashMap; use crate::common::api_common::ApiVersion; use crate::common::database::*; use crate::common::dummy_data::{ DummyProjectAlpha, DummyProjectBeta, TestFile, }; use crate::common::get_json_val_str; use actix_http::StatusCode; use actix_web::test; use ariadne::ids::base62_impl::parse_base62; use common::api_v3::ApiV3; use common::asserts::assert_common_version_ids; use common::database::USER_USER_PAT; use common::environment::{with_test_environment, with_test_environment_all}; use futures::StreamExt; use labrinth::database::models::version_item::VERSIONS_NAMESPACE; use labrinth::models::ids::VersionId; use labrinth::models::projects::{ Dependency, DependencyType, VersionStatus, VersionType, }; use labrinth::routes::v3::version_file::FileUpdateData; use serde_json::json; pub mod common; #[actix_rt::test] async fn test_get_version() { // Test setup and dummy data with_test_environment_all(None, |test_env| async move { let api = &test_env.api; let DummyProjectAlpha { project_id: alpha_project_id, version_id: alpha_version_id, .. } = &test_env.dummy.project_alpha; let DummyProjectBeta { version_id: beta_version_id, .. } = &test_env.dummy.project_beta; // Perform request on dummy data let version = api .get_version_deserialized_common(alpha_version_id, USER_USER_PAT) .await; assert_eq!(&version.project_id.to_string(), alpha_project_id); assert_eq!(&version.id.to_string(), alpha_version_id); let mut redis_conn = test_env.db.redis_pool.connect().await.unwrap(); let cached_project = redis_conn .get( VERSIONS_NAMESPACE, &parse_base62(alpha_version_id).unwrap().to_string(), ) .await .unwrap() .unwrap(); let cached_project: serde_json::Value = serde_json::from_str(&cached_project).unwrap(); assert_eq!( cached_project["val"]["inner"]["project_id"], json!(parse_base62(alpha_project_id).unwrap()) ); // Request should fail on non-existent version let resp = api.get_version("false", USER_USER_PAT).await; assert_status!(&resp, StatusCode::NOT_FOUND); // Similarly, request should fail on non-authorized user, on a yet-to-be-approved or hidden project, with a 404 (hiding the existence of the project) // TODO: beta version should already be draft in dummy data, but theres a bug in finding it that api.edit_version( beta_version_id, json!({ "status": "draft" }), USER_USER_PAT, ) .await; let resp = api.get_version(beta_version_id, USER_USER_PAT).await; assert_status!(&resp, StatusCode::OK); let resp = api.get_version(beta_version_id, ENEMY_USER_PAT).await; assert_status!(&resp, StatusCode::NOT_FOUND); }) .await; } #[actix_rt::test] async fn version_updates() { // Test setup and dummy data with_test_environment( None, |test_env: common::environment::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_common( 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_common( &[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( *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 mut loader_fields = HashMap::new(); if let Some(game_versions) = game_versions { loader_fields.insert( "game_versions".to_string(), game_versions .into_iter() .map(|v| json!(v)) .collect::>(), ); } let hashes = vec![FileUpdateData { hash: alpha_version_hash.to_string(), loaders, loader_fields: Some(loader_fields), 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] pub async fn test_patch_version() { with_test_environment_all(None, |test_env| async move { let api = &test_env.api; let alpha_version_id = &test_env.dummy.project_alpha.version_id; let DummyProjectBeta { project_id: beta_project_id, project_id_parsed: beta_project_id_parsed, .. } = &test_env.dummy.project_beta; // 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": [{ "project_id": beta_project_id, "dependency_type": "required", "file_name": "dummy_file_name" }], "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_common(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.dependencies, vec![Dependency { project_id: Some(*beta_project_id_parsed), version_id: None, file_name: Some("dummy_file_name".to_string()), dependency_type: DependencyType::Required }] ); assert_eq!(version.loaders, vec!["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_common(alpha_version_id, USER_USER_PAT) .await; assert_eq!(version.loaders, vec!["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_common(alpha_version_id, USER_USER_PAT) .await; assert_eq!(version.loaders, vec!["fabric".to_string()]); }) .await; } #[actix_rt::test] pub async fn test_project_versions() { with_test_environment_all(None, |test_env| async move { let api = &test_env.api; let alpha_project_id: &String = &test_env.dummy.project_alpha.project_id; let alpha_version_id = &test_env.dummy.project_alpha.version_id; 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(), 1); assert_eq!(&versions[0].id.to_string(), alpha_version_id); }) .await; } #[actix_rt::test] async fn can_create_version_with_ordering() { with_test_environment( None, |env: common::environment::TestEnvironment| 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 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( None, |env: common::environment::TestEnvironment| 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 versions = env .api .get_versions_deserialized( vec![alpha_version_id.clone()], USER_USER_PAT, ) .await; assert_eq!(versions[0].ordering, Some(10)); }, ) .await; } #[actix_rt::test] async fn version_ordering_for_specified_orderings_orders_lower_order_first() { with_test_environment_all(None, |env| async move { let alpha_project_id_parsed = env.dummy.project_alpha.project_id_parsed; let alpha_version_id = env.dummy.project_alpha.version_id.clone(); 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, ); env.api .edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT) .await; let versions = env .api .get_versions_deserialized_common( vec![alpha_version_id.clone(), new_version_id.clone()], USER_USER_PAT, ) .await; assert_common_version_ids( &versions, vec![new_version_id, alpha_version_id], ); }) .await; } #[actix_rt::test] async fn version_ordering_when_unspecified_orders_oldest_first() { with_test_environment_all(None, |env| async move { let alpha_project_id_parsed = env.dummy.project_alpha.project_id_parsed; let alpha_version_id: String = env.dummy.project_alpha.version_id.clone(); 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, None, None, USER_USER_PAT, ) .await .id, ); let versions = env .api .get_versions_deserialized_common( vec![alpha_version_id.clone(), new_version_id.clone()], USER_USER_PAT, ) .await; assert_common_version_ids( &versions, vec![alpha_version_id, new_version_id], ); }) .await } #[actix_rt::test] async fn version_ordering_when_specified_orders_specified_before_unspecified() { with_test_environment_all(None, |env| async move { let alpha_project_id_parsed = env.dummy.project_alpha.project_id_parsed; let alpha_version_id = env.dummy.project_alpha.version_id.clone(); 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(1000), None, USER_USER_PAT, ) .await .id, ); env.api .edit_version_ordering(&alpha_version_id, None, USER_USER_PAT) .await; let versions = env .api .get_versions_deserialized_common( vec![alpha_version_id.clone(), new_version_id.clone()], USER_USER_PAT, ) .await; assert_common_version_ids( &versions, vec![new_version_id, alpha_version_id], ); }) .await; }