feat: user search (#6302)

* feat: user search

* refactor: use sqlx macro

* refactor: better name for escaped query

* style: cleanup unused import

* chore: update sqlx query cache

* fix: tests

---------

Co-authored-by: sychic <47618543+Sychic@users.noreply.github.com>
This commit is contained in:
Calum H.
2026-06-03 19:38:24 +00:00
committed by GitHub
co-authored by sychic
parent d907083d83
commit 58ad58f958
6 changed files with 237 additions and 2 deletions
+103 -1
View File
@@ -1,9 +1,13 @@
use crate::common::api_common::{ApiProject, ApiTeams};
use actix_web::test;
use common::dummy_data::TestFile;
use common::{
database::{FRIEND_USER_ID, FRIEND_USER_PAT, USER_USER_ID, USER_USER_PAT},
environment::with_test_environment_all,
environment::{
TestEnvironment, with_test_environment, with_test_environment_all,
},
};
use labrinth::test::api_v3::ApiV3;
pub mod common;
@@ -16,6 +20,104 @@ pub mod common;
// patch user icon
// user follows
#[actix_rt::test]
pub async fn search_users_returns_compact_prefix_matches_with_exact_first() {
with_test_environment(
None,
|test_env: TestEnvironment<ApiV3>| async move {
sqlx::query(
"
INSERT INTO users (id, username, email, role)
VALUES
(1000, 'userland', 'userland@modrinth.com', 'developer'),
(1001, 'useful', 'useful@modrinth.com', 'developer'),
(1002, 'Useless', 'useless@modrinth.com', 'developer')
",
)
.execute(&*test_env.db.pool)
.await
.unwrap();
let req = test::TestRequest::get()
.uri("/v3/users/search?query=user")
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, actix_http::StatusCode::OK);
let users: Vec<serde_json::Value> =
test::read_body_json(resp).await;
assert_eq!(users.len(), 2);
assert_eq!(users[0]["username"], "User");
assert_eq!(users[1]["username"], "userland");
assert!(users.iter().all(|user| {
user.as_object().is_some_and(|object| {
object.len() == 3 && object.contains_key("avatar_url")
})
}));
},
)
.await;
}
#[actix_rt::test]
pub async fn search_users_escapes_wildcards_and_limits_results() {
with_test_environment(
None,
|test_env: TestEnvironment<ApiV3>| async move {
for i in 0..30 {
sqlx::query(
"
INSERT INTO users (id, username, email, role)
VALUES ($1, $2, $3, 'developer')
",
)
.bind(2000 + i)
.bind(format!("prefix{i:02}"))
.bind(format!("prefix{i:02}@modrinth.com"))
.execute(&*test_env.db.pool)
.await
.unwrap();
}
let req = test::TestRequest::get()
.uri("/v3/users/search?query=prefix")
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, actix_http::StatusCode::OK);
let users: Vec<serde_json::Value> =
test::read_body_json(resp).await;
assert_eq!(users.len(), 25);
assert!(users.iter().all(|user| {
user["username"]
.as_str()
.is_some_and(|username| username.starts_with("prefix"))
}));
let req = test::TestRequest::get()
.uri("/v3/users/search?query=%25")
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, actix_http::StatusCode::OK);
let users: Vec<serde_json::Value> =
test::read_body_json(resp).await;
assert!(users.is_empty());
let req = test::TestRequest::get()
.uri("/v3/users/search?query=%20%20")
.to_request();
let resp = test_env.call(req).await;
assert_status!(&resp, actix_http::StatusCode::OK);
let users: Vec<serde_json::Value> =
test::read_body_json(resp).await;
assert!(users.is_empty());
},
)
.await;
}
#[actix_rt::test]
pub async fn get_user_projects_after_creating_project_returns_new_project() {
with_test_environment_all(None, |test_env| async move {