Alejandro González f84f8c1c2b
chore(clippy): enable and fix many stricter lints (#3783)
* chore(clippy): enable and fix many stricter lints

These ensure that the codebase uses more idiomatic, performant, and
concise language constructions.

* chore: make non-Clippy compiler warnings also deny by default
2025-06-14 00:10:12 +00: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;
pub 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;
}