Prospector 3dad6b317f
MR App 0.9.5 - Big bugfix update (#3585)
* Add launcher_feature_version to Profile

* Misc fixes

- Add typing to theme and settings stuff
- Push instance route on creation from installing a modpack
- Fixed servers not reloading properly when first added

* Make old instances scan the logs folder for joined servers on launcher startup

* Create AttachedWorldData

* Change AttachedWorldData interface

* Rename WorldType::World to WorldType::Singleplayer

* Implement world display status system

* Fix Minecraft font

* Fix set_world_display_status Tauri error

* Add 'Play instance' option

* Add option to disable worlds showing in Home

* Fixes

- Fix available server filter only showing if there are some available
- Fixed server and singleplayer filters sometimes showing when there are only servers or singleplayer worlds
- Fixed new worlds not being automatically added when detected
- Rephrased Jump back into worlds option description

* Fixed sometimes more than 6 items showing up in Jump back in

* Fix servers.dat issue with instances you haven't played before

* Fix too large of bulk requests being made, limit max to 800 #3430

* Add hiding from home page, add types to Mods.vue

* Make recent worlds go into grid when display is huge

* Fix lint

* Remove redundant media query

* Fix protocol version on home page, and home page being blocked by pinging servers

* Clippy fix

* More Clippy fixes

* Fix Prettier lints

* Undo `from_string` changes

---------

Co-authored-by: Josiah Glosson <soujournme@gmail.com>
Co-authored-by: Alejandro González <me@alegon.dev>
2025-05-01 16:13:13 -07:00

295 lines
11 KiB
Rust

use actix_http::StatusCode;
use actix_web::test;
use chrono::{Duration, Utc};
use common::{database::*, environment::with_test_environment_all};
use labrinth::models::pats::Scopes;
use serde_json::json;
use crate::common::api_common::AppendsOptionalPat;
mod common;
// Full pat test:
// - create a PAT and ensure it can be used for the scope
// - ensure access token is not returned for any PAT in GET
// - ensure PAT can be patched to change scopes
// - ensure PAT can be patched to change expiry
// - ensure expired PATs cannot be used
// - ensure PATs can be deleted
#[actix_rt::test]
pub async fn pat_full_test() {
with_test_environment_all(None, |test_env| async move {
// Create a PAT for a full test
let req = test::TestRequest::post()
.uri("/_internal/pat")
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example
"name": "test_pat_scopes Test",
"expires": Utc::now() + Duration::days(1),
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::OK);
let success: serde_json::Value = test::read_body_json(resp).await;
let id = success["id"].as_str().unwrap();
// Has access token and correct scopes
assert!(success["access_token"].as_str().is_some());
assert_eq!(
success["scopes"].as_u64().unwrap(),
Scopes::COLLECTION_CREATE.bits()
);
let access_token = success["access_token"].as_str().unwrap();
// Get PAT again
let req = test::TestRequest::get()
.append_pat(USER_USER_PAT)
.uri("/_internal/pat")
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::OK);
let success: serde_json::Value = test::read_body_json(resp).await;
// Ensure access token is NOT returned for any PATs
for pat in success.as_array().unwrap() {
assert!(pat["access_token"].as_str().is_none());
}
// Create mock test for using PAT
let mock_pat_test = |token: &str| {
let token = token.to_string();
async {
// This uses a route directly instead of an api call because it doesn't relaly matter and we
// want it to succeed no matter what.
// This is an arbitrary request.
let req = test::TestRequest::post()
.uri("/v3/collection")
.append_header(("Authorization", token))
.set_json(json!({
"name": "Test Collection 1",
"description": "Test Collection Description"
}))
.to_request();
let resp = test_env.call(req).await;
resp.status().as_u16()
}
};
assert_eq!(mock_pat_test(access_token).await, 200);
// Change scopes and test again
let req = test::TestRequest::patch()
.uri(&format!("/_internal/pat/{id}"))
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": 0,
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::NO_CONTENT);
assert_eq!(mock_pat_test(access_token).await, 401); // No longer works
// Change scopes back, and set expiry to the past, and test again
let req = test::TestRequest::patch()
.uri(&format!("/_internal/pat/{id}"))
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": Scopes::COLLECTION_CREATE,
"expires": Utc::now() + Duration::seconds(1), // expires in 1 second
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::NO_CONTENT);
// Wait 1 second before testing again for expiry
tokio::time::sleep(Duration::seconds(1).to_std().unwrap()).await;
assert_eq!(mock_pat_test(access_token).await, 401); // No longer works
// Change everything back to normal and test again
let req = test::TestRequest::patch()
.uri(&format!("/_internal/pat/{id}"))
.append_pat(USER_USER_PAT)
.set_json(json!({
"expires": Utc::now() + Duration::days(1), // no longer expired!
}))
.to_request();
println!("PAT ID FOR TEST: {id}");
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::NO_CONTENT);
assert_eq!(mock_pat_test(access_token).await, 200); // Works again
// Patching to a bad expiry should fail
let req = test::TestRequest::patch()
.uri(&format!("/_internal/pat/{id}"))
.append_pat(USER_USER_PAT)
.set_json(json!({
"expires": Utc::now() - Duration::days(1), // Past
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
// Similar to above with PAT creation, patching to a bad scope should fail
for i in 0..64 {
let scope = Scopes::from_bits_truncate(1 << i);
if !Scopes::all().contains(scope) {
continue;
}
let req = test::TestRequest::patch()
.uri(&format!("/_internal/pat/{id}"))
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": scope.bits(),
}))
.to_request();
let resp = test_env.call(req).await;
assert_eq!(
resp.status().as_u16(),
if scope.is_restricted() { 400 } else { 204 }
);
}
// Delete PAT
let req = test::TestRequest::delete()
.append_pat(USER_USER_PAT)
.uri(&format!("/_internal/pat/{id}"))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::NO_CONTENT);
})
.await;
}
// Test illegal PAT setting, both in POST and PATCH
#[actix_rt::test]
pub async fn bad_pats() {
with_test_environment_all(None, |test_env| async move {
// Creating a PAT with no name should fail
let req = test::TestRequest::post()
.uri("/_internal/pat")
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example
"expires": Utc::now() + Duration::days(1),
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
// Name too short or too long should fail
for name in ["n", "this_name_is_too_long".repeat(16).as_str()] {
let req = test::TestRequest::post()
.uri("/_internal/pat")
.append_pat(USER_USER_PAT)
.set_json(json!({
"name": name,
"scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example
"expires": Utc::now() + Duration::days(1),
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
}
// Creating a PAT with an expiry in the past should fail
let req = test::TestRequest::post()
.uri("/_internal/pat")
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example
"name": "test_pat_scopes Test",
"expires": Utc::now() - Duration::days(1),
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
// Make a PAT with each scope, with the result varying by whether that scope is restricted
for i in 0..64 {
let scope = Scopes::from_bits_truncate(1 << i);
if !Scopes::all().contains(scope) {
continue;
}
let req = test::TestRequest::post()
.uri("/_internal/pat")
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": scope.bits(),
"name": format!("test_pat_scopes Name {}", i),
"expires": Utc::now() + Duration::days(1),
}))
.to_request();
let resp = test_env.call(req).await;
assert_eq!(
resp.status().as_u16(),
if scope.is_restricted() { 400 } else { 200 }
);
}
// Create a 'good' PAT for patching
let req = test::TestRequest::post()
.uri("/_internal/pat")
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": Scopes::COLLECTION_CREATE,
"name": "test_pat_scopes Test",
"expires": Utc::now() + Duration::days(1),
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::OK);
let success: serde_json::Value = test::read_body_json(resp).await;
let id = success["id"].as_str().unwrap();
// Patching to a bad name should fail
for name in ["n", "this_name_is_too_long".repeat(16).as_str()] {
let req = test::TestRequest::post()
.uri("/_internal/pat")
.append_pat(USER_USER_PAT)
.set_json(json!({
"name": name,
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
}
// Patching to a bad expiry should fail
let req = test::TestRequest::patch()
.uri(&format!("/_internal/pat/{id}"))
.append_pat(USER_USER_PAT)
.set_json(json!({
"expires": Utc::now() - Duration::days(1), // Past
}))
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, StatusCode::BAD_REQUEST);
// Similar to above with PAT creation, patching to a bad scope should fail
for i in 0..64 {
let scope = Scopes::from_bits_truncate(1 << i);
if !Scopes::all().contains(scope) {
continue;
}
let req = test::TestRequest::patch()
.uri(&format!("/_internal/pat/{id}"))
.append_pat(USER_USER_PAT)
.set_json(json!({
"scopes": scope.bits(),
}))
.to_request();
let resp = test_env.call(req).await;
assert_eq!(
resp.status().as_u16(),
if scope.is_restricted() { 400 } else { 204 }
);
}
})
.await;
}