fix(client): fixes for user and token CRUD operations
This commit is contained in:
parent
713c2c0542
commit
1a438119a3
6 changed files with 180 additions and 55 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1421,6 +1421,7 @@ dependencies = [
|
|||
name = "szurubooru-integration-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"szurubooru-client",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ use serde_json::Value;
|
|||
use sha1::{Digest, Sha1};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, Read},
|
||||
};
|
||||
use std::{fs::File, io::Read};
|
||||
use url::Url;
|
||||
|
||||
///
|
||||
|
|
@ -324,7 +321,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
// This doesn't detect the required `mut` for some reason
|
||||
#[allow(unused_mut)]
|
||||
let mut req = self.client.client.request(method, req_url);
|
||||
let req = match &self.client.auth {
|
||||
match &self.client.auth {
|
||||
SzurubooruAuth::TokenAuth(t) => {
|
||||
let mut header_map = HeaderMap::new();
|
||||
header_map.append(AUTHORIZATION, t.parse().unwrap());
|
||||
|
|
@ -333,8 +330,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
SzurubooruAuth::BasicAuth(u, p) => req.basic_auth(u, Some(p)),
|
||||
SzurubooruAuth::None => req,
|
||||
};
|
||||
req
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self), fields(base_url=self.client.base_url.to_string()))]
|
||||
|
|
@ -767,7 +763,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
};
|
||||
|
||||
let mut thumbnail_file = if let Some(t) = thumbnail {
|
||||
if let None = filename {
|
||||
if filename.is_none() {
|
||||
filename = Some(
|
||||
t.as_ref()
|
||||
.file_name()
|
||||
|
|
@ -832,7 +828,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
content_response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| SzurubooruClientError::RequestError(e))
|
||||
.map_err(SzurubooruClientError::RequestError)
|
||||
}
|
||||
|
||||
/// Retrieves posts that look like the input image
|
||||
|
|
@ -861,24 +857,12 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
/// Searches for an exact match of a file based on the SHA1 checksum
|
||||
pub async fn posts_for_file(
|
||||
&self,
|
||||
file: &mut File,
|
||||
mut file: &mut File,
|
||||
) -> SzurubooruResult<PagedSearchResult<PostResource>> {
|
||||
let mut buffer = [0; 1024];
|
||||
let mut hasher = Sha1::new();
|
||||
let mut reader = BufReader::new(file);
|
||||
|
||||
loop {
|
||||
let n = reader
|
||||
.read(&mut buffer)
|
||||
.map_err(SzurubooruClientError::IOError)?;
|
||||
hasher.update(buffer);
|
||||
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let result = hasher.finalize();
|
||||
let hex_string = hex::encode(result);
|
||||
std::io::copy(&mut file, &mut hasher).map_err(SzurubooruClientError::IOError)?;
|
||||
let hash = hasher.finalize();
|
||||
let hex_string = hex::encode(hash);
|
||||
|
||||
let qt = QueryToken::token(PostNamedToken::ContentChecksum, hex_string);
|
||||
self.list_posts(Some(&vec![qt])).await
|
||||
|
|
@ -1188,6 +1172,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
path: &str,
|
||||
new_user: &CreateUpdateUser,
|
||||
file: Option<&mut File>,
|
||||
file_name: Option<impl AsRef<str>>,
|
||||
) -> SzurubooruResult<UserResource> {
|
||||
match file {
|
||||
None => self.do_request(method, path, None, Some(new_user)).await,
|
||||
|
|
@ -1198,7 +1183,9 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map_err(SzurubooruClientError::JSONSerializationError)?;
|
||||
let metadata_part = Part::text(metadata_str);
|
||||
|
||||
let content_part = self.part_from_file(file)?;
|
||||
let content_part = self
|
||||
.part_from_file(file)?
|
||||
.file_name(file_name.unwrap().as_ref().to_string());
|
||||
|
||||
let form = Form::new()
|
||||
.part("avatar", content_part)
|
||||
|
|
@ -1228,10 +1215,17 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
pub async fn create_user_with_avatar_file(
|
||||
&self,
|
||||
avatar: &mut File,
|
||||
file_name: impl AsRef<str>,
|
||||
new_user: &CreateUpdateUser,
|
||||
) -> SzurubooruResult<UserResource> {
|
||||
self.create_update_user(Method::POST, "/users", new_user, Some(avatar))
|
||||
.await
|
||||
self.create_update_user(
|
||||
Method::POST,
|
||||
"/users",
|
||||
new_user,
|
||||
Some(avatar),
|
||||
Some(file_name),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Create a [UserResource](models::UserResource) with the included Avatar file path
|
||||
|
|
@ -1242,9 +1236,16 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
avatar_path: impl AsRef<Path>,
|
||||
new_user: &CreateUpdateUser,
|
||||
) -> SzurubooruResult<UserResource> {
|
||||
let mut file = File::open(avatar_path).map_err(SzurubooruClientError::IOError)?;
|
||||
self.create_update_user(Method::POST, "/users", new_user, Some(&mut file))
|
||||
.await
|
||||
let mut file = File::open(&avatar_path).map_err(SzurubooruClientError::IOError)?;
|
||||
let filename = avatar_path.as_ref().file_name().unwrap().to_str().unwrap();
|
||||
self.create_update_user(
|
||||
Method::POST,
|
||||
"/users",
|
||||
new_user,
|
||||
Some(&mut file),
|
||||
Some(filename),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Updates user using specified parameters. Names and passwords must match
|
||||
|
|
@ -1262,8 +1263,8 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/users/{name}");
|
||||
self.do_request(Method::POST, path, None, Some(update_user))
|
||||
let path = format!("/user/{name}");
|
||||
self.do_request(Method::PUT, path, None, Some(update_user))
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -1274,14 +1275,21 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
&self,
|
||||
name: T,
|
||||
avatar: &mut File,
|
||||
file_name: impl AsRef<str>,
|
||||
update_user: &CreateUpdateUser,
|
||||
) -> SzurubooruResult<UserResource>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/users/{name}");
|
||||
self.create_update_user(Method::POST, &path, update_user, Some(avatar))
|
||||
.await
|
||||
let path = format!("/user/{name}");
|
||||
self.create_update_user(
|
||||
Method::PUT,
|
||||
&path,
|
||||
update_user,
|
||||
Some(avatar),
|
||||
Some(file_name),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Update a [UserResource](models::UserResource) with the included Avatar file path
|
||||
|
|
@ -1296,10 +1304,17 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/users/{name}");
|
||||
let mut file = File::open(avatar_path).map_err(SzurubooruClientError::IOError)?;
|
||||
self.create_update_user(Method::POST, &path, new_user, Some(&mut file))
|
||||
.await
|
||||
let path = format!("/user/{name}");
|
||||
let mut file = File::open(&avatar_path).map_err(SzurubooruClientError::IOError)?;
|
||||
let filename = avatar_path.as_ref().file_name().unwrap().to_str().unwrap();
|
||||
self.create_update_user(
|
||||
Method::PUT,
|
||||
&path,
|
||||
new_user,
|
||||
Some(&mut file),
|
||||
Some(filename),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieves information about an existing user
|
||||
|
|
@ -1307,7 +1322,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/users/{name}");
|
||||
let path = format!("/user/{name}");
|
||||
self.do_request(Method::GET, &path, None, None::<&String>)
|
||||
.await
|
||||
}
|
||||
|
|
@ -1317,10 +1332,11 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/users/{name}");
|
||||
let path = format!("/user/{name}");
|
||||
let version_obj = ResourceVersion { version };
|
||||
self.do_request(Method::DELETE, &path, None, Some(&version_obj))
|
||||
self.do_request::<Value, _, _>(Method::DELETE, &path, None, Some(&version_obj))
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Listing user tokens for the given user.
|
||||
|
|
@ -1346,7 +1362,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/user-tokens/{name}");
|
||||
let path = format!("/user-token/{name}");
|
||||
self.do_request(Method::POST, &path, None, Some(create_token))
|
||||
.await
|
||||
}
|
||||
|
|
@ -1363,7 +1379,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/user-tokens/{name}/{token}");
|
||||
let path = format!("/user-token/{name}/{token}");
|
||||
self.do_request(Method::PUT, &path, None, Some(update_token))
|
||||
.await
|
||||
}
|
||||
|
|
@ -1380,10 +1396,11 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/user-tokens/{name}/{token}");
|
||||
let path = format!("/user-token/{name}/{token}");
|
||||
let version_obj = ResourceVersion { version };
|
||||
self.do_request(Method::DELETE, &path, None, Some(&version_obj))
|
||||
self.do_request::<Value, _, _>(Method::DELETE, &path, None, Some(&version_obj))
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Sends a confirmation email to given user. The email contains link containing a token. The
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@ pub struct UserResource {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, Builder)]
|
||||
#[builder(setter(into, strip_option))]
|
||||
#[builder(setter(strip_option))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// `struct` used to create or update a user resource. The version field is only used when
|
||||
/// updating an existing resource
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.38"
|
||||
szurubooru-client = { path = "../szurubooru-client" }
|
||||
tokio = { version = "1.39.2", features = ["full", "test-util", "tracing"] }
|
||||
tracing = "0.1.40"
|
||||
|
|
|
|||
BIN
szurubooru-integration-test/avatar.jpg
Normal file
BIN
szurubooru-integration-test/avatar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -1,3 +1,4 @@
|
|||
use chrono::Months;
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
|
@ -29,8 +30,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||
let anon_client = start_instance().await;
|
||||
|
||||
let create_user = CreateUpdateUserBuilder::default()
|
||||
.name("integration_user")
|
||||
.password("integration_password")
|
||||
.name("integration_user".to_string())
|
||||
.password("integration_password".to_string())
|
||||
.rank(UserRank::Administrator)
|
||||
.avatar_style(UserAvatarStyle::Gravatar)
|
||||
.build()
|
||||
|
|
@ -54,6 +55,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||
test_pool_categories(&auth_client).await;
|
||||
test_pools(&auth_client).await;
|
||||
test_comments(&auth_client).await;
|
||||
test_users(&auth_client).await;
|
||||
|
||||
Command::new("sh")
|
||||
.current_dir(env!("CARGO_MANIFEST_DIR"))
|
||||
|
|
@ -390,10 +392,18 @@ async fn test_creating_posts(client: &SzurubooruClient) {
|
|||
let folly3_thumbnail = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly3_thumb.jpg");
|
||||
let folly3_post = client
|
||||
.request()
|
||||
.create_post_from_file_path(folly3_path, Some(folly3_thumbnail), &folly3_obj)
|
||||
.create_post_from_file_path(&folly3_path, Some(folly3_thumbnail), &folly3_obj)
|
||||
.await
|
||||
.expect("Could not create post with thumbnail");
|
||||
|
||||
info!("Searching for post by image");
|
||||
let matching_posts = client
|
||||
.request()
|
||||
.posts_for_file_path(&folly3_path)
|
||||
.await
|
||||
.expect("Could not search for post by file path");
|
||||
assert_eq!(matching_posts.results.first().unwrap(), &folly3_post);
|
||||
|
||||
info!("Testing temporary upload");
|
||||
let folly4_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly4.jpg");
|
||||
let folly4_temp_upload = client
|
||||
|
|
@ -504,6 +514,7 @@ async fn test_creating_posts(client: &SzurubooruClient) {
|
|||
.get_featured_post()
|
||||
.await
|
||||
.expect("Could not get featured post");
|
||||
|
||||
assert!(featured_post.is_some());
|
||||
}
|
||||
|
||||
|
|
@ -725,10 +736,6 @@ async fn test_comments(client: &SzurubooruClient) {
|
|||
.expect("Unable to fetch comment");
|
||||
|
||||
info!("Getting all comments for post");
|
||||
let query_vec = vec![QueryToken::token(
|
||||
CommentNamedToken::Post,
|
||||
post_id.to_string(),
|
||||
)];
|
||||
let comment_list = client
|
||||
.request()
|
||||
//.list_comments(None)
|
||||
|
|
@ -755,3 +762,102 @@ async fn test_comments(client: &SzurubooruClient) {
|
|||
.await
|
||||
.expect("Could not delete comment");
|
||||
}
|
||||
|
||||
#[instrument(skip(client))]
|
||||
async fn test_users(client: &SzurubooruClient) {
|
||||
info!("Testing users");
|
||||
|
||||
info!("Listing users");
|
||||
let user_list = client
|
||||
.request()
|
||||
.list_users(None)
|
||||
.await
|
||||
.expect("Could not list users");
|
||||
assert_eq!(user_list.total, 1);
|
||||
|
||||
// Create user is already tested above
|
||||
info!("Creating user with avatar");
|
||||
let avatar_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("avatar.jpg");
|
||||
let create_user = CreateUpdateUserBuilder::default()
|
||||
.name("iu2".to_string())
|
||||
.password("ipass2".to_string())
|
||||
.rank(UserRank::Regular)
|
||||
.avatar_style(UserAvatarStyle::Manual)
|
||||
.build()
|
||||
.expect("Could not create user creation object");
|
||||
let user_obj = client
|
||||
.request()
|
||||
.create_user_with_avatar_path(avatar_path, &create_user)
|
||||
.await
|
||||
.expect("Could not create user");
|
||||
|
||||
info!("Updating user");
|
||||
let update_user = CreateUpdateUserBuilder::default()
|
||||
.version(user_obj.version.unwrap())
|
||||
.rank(UserRank::Restricted)
|
||||
.build()
|
||||
.expect("Could not create user update object");
|
||||
let user_obj = client
|
||||
.request()
|
||||
.update_user(user_obj.name.unwrap(), &update_user)
|
||||
.await
|
||||
.expect("Could not update user");
|
||||
|
||||
info!("Getting user");
|
||||
let user_obj = client
|
||||
.request()
|
||||
.get_user(user_obj.name.unwrap())
|
||||
.await
|
||||
.expect("Could not get user");
|
||||
|
||||
info!("Deleting user");
|
||||
client
|
||||
.request()
|
||||
.delete_user(user_obj.name.unwrap(), user_obj.version.unwrap())
|
||||
.await
|
||||
.expect("Could not delete user");
|
||||
|
||||
let username = "integration_user".to_string();
|
||||
info!("Listing user tokens");
|
||||
let tokens = client
|
||||
.request()
|
||||
.list_user_tokens(&username)
|
||||
.await
|
||||
.expect("Could not list user tokens");
|
||||
assert!(tokens.results.is_empty());
|
||||
|
||||
info!("Creating user token");
|
||||
let create_token = CreateUpdateUserAuthTokenBuilder::default()
|
||||
.note("My token")
|
||||
.enabled(true)
|
||||
.build()
|
||||
.expect("Could not create token creation object");
|
||||
let token = client
|
||||
.request()
|
||||
.create_user_token(&username, &create_token)
|
||||
.await
|
||||
.expect("Could not create auth token");
|
||||
|
||||
info!("Updating user token");
|
||||
let new_expiration = chrono::offset::Utc::now()
|
||||
.checked_add_months(Months::new(1))
|
||||
.unwrap();
|
||||
let update_token = CreateUpdateUserAuthTokenBuilder::default()
|
||||
.version(token.version.unwrap())
|
||||
.expiration_time(new_expiration)
|
||||
.build()
|
||||
.expect("Could not create token update object");
|
||||
|
||||
let token = client
|
||||
.request()
|
||||
.update_user_token(&username, &token.token.unwrap(), &update_token)
|
||||
.await
|
||||
.expect("Could not update token");
|
||||
|
||||
info!("Deleting user token");
|
||||
client
|
||||
.request()
|
||||
.delete_user_token(username, token.token.unwrap(), token.version.unwrap())
|
||||
.await
|
||||
.expect("Could not delete token");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue