diff --git a/tests/avatar.jpg b/tests/avatar.jpg new file mode 100644 index 0000000..708d879 Binary files /dev/null and b/tests/avatar.jpg differ diff --git a/tests/folly.jpg b/tests/folly.jpg new file mode 100644 index 0000000..39ea1c7 Binary files /dev/null and b/tests/folly.jpg differ diff --git a/tests/folly1.jpg b/tests/folly1.jpg new file mode 100644 index 0000000..ff83e59 Binary files /dev/null and b/tests/folly1.jpg differ diff --git a/tests/folly2.jpg b/tests/folly2.jpg new file mode 100644 index 0000000..c01f08d Binary files /dev/null and b/tests/folly2.jpg differ diff --git a/tests/folly3.jpg b/tests/folly3.jpg new file mode 100644 index 0000000..a5d0609 Binary files /dev/null and b/tests/folly3.jpg differ diff --git a/tests/folly3_thumb.jpg b/tests/folly3_thumb.jpg new file mode 100644 index 0000000..96e52bf Binary files /dev/null and b/tests/folly3_thumb.jpg differ diff --git a/tests/folly4.jpg b/tests/folly4.jpg new file mode 100644 index 0000000..08ca156 Binary files /dev/null and b/tests/folly4.jpg differ diff --git a/tests/rust-test/Cargo.toml b/tests/rust-test/Cargo.toml new file mode 100644 index 0000000..b96d1dd --- /dev/null +++ b/tests/rust-test/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rust-test" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = "0.4.38" +sha1 = "0.10.6" +szurubooru-client = { path = "../../szurubooru-client" } +tempfile = "3.12.0" +tokio = { version = "1.39.2", features = ["full", "test-util", "tracing"] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/tests/rust-test/config.yaml b/tests/rust-test/config.yaml new file mode 100644 index 0000000..aab75cf --- /dev/null +++ b/tests/rust-test/config.yaml @@ -0,0 +1,179 @@ +# rather than editing this file, it is strongly suggested to create config.yaml +# and override only what you need. + +# shown in the website title and on the front page +name: integrationland +# full url to the homepage of this szurubooru site, with no trailing slash +domain: https://localhost:9801 +# used to salt the users' password hashes and generate filenames for static content +secret: awdopk1mdqsapdoawd21!!!#asw + +# Delete thumbnails and source files on post delete +# Original functionality is no, to mitigate the impacts of admins going +# on unchecked post purges. +delete_source_files: no + +thumbnails: + avatar_width: 300 + avatar_height: 300 + post_width: 300 + post_height: 300 + +# settings used to download files from the web on behalf of the api users +user_agent: +max_dl_filesize: 25.0E+6 # maximum filesize limit in bytes + +# automatically convert animated GIF uploads to video formats +convert: + gif: + to_webm: false + to_mp4: false + +# allow posts to be uploaded even if some image processing errors occur +allow_broken_uploads: false + +# used to send password reset e-mails +smtp: + host: # example: localhost + port: # example: 25 + user: # example: bot + pass: # example: groovy123 + from: # example: noreply@example.com + # if host is left empty the password reset feature will be disabled, + # in which case it is recommended to fill contactEmail so that users + # know who to contact when they want to reset their password + +contact_email: # example: bob@example.com. Meant for manual password reset procedures + +enable_safety: yes + +tag_name_regex: ^\S+$ +tag_category_name_regex: ^[^\s%+#/]+$ + +pool_name_regex: ^\S+$ +pool_category_name_regex: ^[^\s%+#/]+$ + +# don't make these more restrictive unless you want to annoy people; if you do +# customize them, make sure to update the instructions in the registration form +# template as well. +password_regex: '^.{5,}$' +user_name_regex: '^[a-zA-Z0-9_-]{1,32}$' + +# webhooks to call when events occur (such as post/tag/user/etc. changes) +# the listed urls will be called with a HTTP POST request with a payload +# containing a snapshot resource as JSON. See doc/API.md for details +webhooks: +# - https://api.example.com/webhooks/ + +default_rank: regular + +privileges: + 'users:create:self': anonymous # Registration permission + 'users:create:any': administrator + 'users:list': regular + 'users:view': regular + 'users:edit:any:name': moderator + 'users:edit:any:pass': moderator + 'users:edit:any:email': moderator + 'users:edit:any:avatar': moderator + 'users:edit:any:rank': moderator + 'users:edit:self:name': regular + 'users:edit:self:pass': regular + 'users:edit:self:email': regular + 'users:edit:self:avatar': regular + 'users:edit:self:rank': moderator # one can't promote themselves or anyone to upper rank than their own. + 'users:delete:any': administrator + 'users:delete:self': regular + + 'user_tokens:list:any': administrator + 'user_tokens:list:self': regular + 'user_tokens:create:any': administrator + 'user_tokens:create:self': regular + 'user_tokens:edit:any': administrator + 'user_tokens:edit:self': regular + 'user_tokens:delete:any': administrator + 'user_tokens:delete:self': regular + + 'posts:create:anonymous': regular + 'posts:create:identified': regular + 'posts:list': anonymous + 'posts:reverse_search': regular + 'posts:view': anonymous + 'posts:view:featured': anonymous + 'posts:edit:content': power + 'posts:edit:flags': regular + 'posts:edit:notes': regular + 'posts:edit:relations': regular + 'posts:edit:safety': power + 'posts:edit:source': regular + 'posts:edit:tags': regular + 'posts:edit:thumbnail': power + 'posts:feature': moderator + 'posts:delete': moderator + 'posts:score': regular + 'posts:merge': moderator + 'posts:favorite': regular + 'posts:bulk-edit:tags': power + 'posts:bulk-edit:safety': power + 'posts:bulk-edit:delete': power + + 'tags:create': regular + 'tags:edit:names': power + 'tags:edit:category': power + 'tags:edit:description': power + 'tags:edit:implications': power + 'tags:edit:suggestions': power + 'tags:list': regular + 'tags:view': anonymous + 'tags:merge': moderator + 'tags:delete': moderator + + 'tag_categories:create': moderator + 'tag_categories:edit:name': moderator + 'tag_categories:edit:color': moderator + 'tag_categories:edit:order': moderator + 'tag_categories:list': anonymous + 'tag_categories:view': anonymous + 'tag_categories:delete': moderator + 'tag_categories:set_default': moderator + + 'pools:create': regular + 'pools:edit:names': power + 'pools:edit:category': power + 'pools:edit:description': power + 'pools:edit:posts': power + 'pools:list': regular + 'pools:view': anonymous + 'pools:merge': moderator + 'pools:delete': moderator + + 'pool_categories:create': moderator + 'pool_categories:edit:name': moderator + 'pool_categories:edit:color': moderator + 'pool_categories:list': anonymous + 'pool_categories:view': anonymous + 'pool_categories:delete': moderator + 'pool_categories:set_default': moderator + + 'comments:create': regular + 'comments:delete:any': moderator + 'comments:delete:own': regular + 'comments:edit:any': moderator + 'comments:edit:own': regular + 'comments:list': regular + 'comments:view': regular + 'comments:score': regular + + 'snapshots:list': power + + 'uploads:create': regular + 'uploads:use_downloader': power + +## ONLY SET THESE IF DEPLOYING OUTSIDE OF DOCKER +#debug: 0 # generate server logs? +#show_sql: 0 # show sql in server logs? +#data_url: /data/ +#data_dir: /var/www/data +## usage: schema://user:password@host:port/database_name +## example: postgres://szuru:dog@localhost:5432/szuru_test +#database: diff --git a/tests/rust-test/docker-compose.yml b/tests/rust-test/docker-compose.yml new file mode 100644 index 0000000..9ff2e9d --- /dev/null +++ b/tests/rust-test/docker-compose.yml @@ -0,0 +1,51 @@ +## Example Docker Compose configuration +## +## Use this as a template to set up docker-compose, or as guide to set up other +## orchestration services +version: '2' + +services: + + server: + image: szurubooru/server:latest + depends_on: + - sql + environment: + ## These should be the names of the dependent containers listed below, + ## or FQDNs/IP addresses if these services are running outside of Docker + POSTGRES_HOST: sql + ## Credentials for database: + POSTGRES_USER: pguser + POSTGRES_PASSWORD: pgpassword + ## Commented Values are Default: + #POSTGRES_DB: defaults to same as POSTGRES_USER + #POSTGRES_PORT: 5432 + #LOG_SQL: 0 (1 for verbose SQL logs) + THREADS: + volumes: + - "sz-data:/data" + - "./config.yaml:/opt/app/config.yaml" + + client: + image: szurubooru/client:latest + depends_on: + - server + environment: + BACKEND_HOST: server + BASE_URL: http://localhost:9801 + volumes: + - "sz-data:/data:ro" + ports: + - "9801:80" + + sql: + image: postgres:11-alpine + restart: unless-stopped + environment: + POSTGRES_USER: pguser + POSTGRES_PASSWORD: pgpassword + #volumes: + # - "${MOUNT_SQL}/sqldata:/var/lib/postgresql/data" + +volumes: + sz-data: diff --git a/tests/rust-test/src/main.rs b/tests/rust-test/src/main.rs new file mode 100644 index 0000000..812ead2 --- /dev/null +++ b/tests/rust-test/src/main.rs @@ -0,0 +1,934 @@ +use chrono::Months; +use sha1::{Digest, Sha1}; +use std::error::Error; +use std::fs::File; +use std::io::{Seek, SeekFrom}; +use std::path::Path; +use std::process::Stdio; +use std::time::Duration; +use szurubooru_client::models::*; +use szurubooru_client::tokens::{CommentNamedToken, QueryToken}; +use szurubooru_client::*; +use tempfile::tempfile; +use tokio::process::Command; +use tracing::level_filters::LevelFilter; +use tracing::{info, instrument}; +use tracing_subscriber::filter::EnvFilter; + +#[tokio::main] +#[tracing::instrument] +async fn main() -> Result<(), Box> { + let ev = EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(); + tracing_subscriber::fmt() + .compact() + //.with_span_events(FmtSpan::ACTIVE) + .with_target(false) + .with_level(false) + .with_env_filter(ev) + .init(); + + //info!("Starting Szurubooru instance..."); + let anon_client = start_instance().await; + + let create_user = CreateUpdateUserBuilder::default() + .name("integration_user".to_string()) + .password("integration_password".to_string()) + .rank(UserRank::Administrator) + .avatar_style(UserAvatarStyle::Gravatar) + .build() + .expect("Unable to create CreateUser object"); + let _user = anon_client + .request() + .create_user(&create_user) + .await + .expect("Error creating user"); + + let auth_client = SzurubooruClient::new_with_basic_auth( + "http://localhost:9801", + "integration_user", + "integration_password", + true, + )?; + + test_tag_categories(&auth_client).await; + test_tags(&auth_client).await; + test_creating_posts(&auth_client).await; + test_pool_categories(&auth_client).await; + test_pools(&auth_client).await; + test_comments(&auth_client).await; + test_users(&auth_client).await; + test_snapshots(&auth_client).await; + test_downloads(&auth_client).await; + + Command::new("sh") + .current_dir(env!("CARGO_MANIFEST_DIR")) + .arg("-c") + .arg("./stop_szurubooru.sh") + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .status() + .await + .expect("Failed to stop szurubooru"); + Ok(()) +} + +#[tracing::instrument] +async fn start_instance() -> SzurubooruClient { + info!("Connecting to Szurubooru instance..."); + let anon_client = SzurubooruClient::new_anonymous("http://localhost:9801", true) + .expect("Can't create anonymous client"); + + let mut connected = false; + let mut error = None; + + for _ in 0..5 { + let info = anon_client.request().get_global_info().await; + if info.is_ok() { + connected = true; + break; + } else if let Err(e) = info { + error = Some(e); + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + if !connected { + panic!( + "{}", + format!("Unable to connect to instance. Last error: {error:?}") + ); + } + + anon_client +} + +#[tracing::instrument(skip(client), level = "INFO")] +async fn test_tag_categories(client: &SzurubooruClient) { + info!("Testing tag categories"); + let tag_cats = client + .request() + .list_tag_categories() + .await + .expect("Could not list tag categories"); + assert_eq!(tag_cats.results.len(), 1); + + info!("Creating tag category"); + let create_tag_cat = CreateUpdateTagCategoryBuilder::default() + .name("my_tag_cat".to_string()) + .color("purple".to_string()) + .order(1) + .build() + .expect("Unable to build create_tag_cat object"); + let result_tag_cat = client + .request() + .create_tag_category(&create_tag_cat) + .await + .expect("Unable to create tag category"); + assert_eq!(result_tag_cat.name, Some("my_tag_cat".to_string())); + let tag_cats = client + .request() + .list_tag_categories() + .await + .expect("Could not list tag categories again"); + assert_eq!(tag_cats.results.len(), 2); + + info!("GETting tag category"); + let tag_res = client + .request() + .get_tag_category("my_tag_cat") + .await + .expect("Could not fetch tag category"); + assert_eq!(result_tag_cat.color, tag_res.color); + + info!("Updating tag category"); + let update_tag_cat = CreateUpdateTagCategoryBuilder::default() + .version(tag_res.version) + .color("red".to_string()) + .build() + .expect("Could not create update for tag category"); + let update_res = client + .request() + .update_tag_category("my_tag_cat", &update_tag_cat) + .await + .expect("Unable to update tag category"); + assert_eq!(update_res.color, Some("red".to_string())); + + info!("Deleting tag category"); + client + .request() + .delete_tag_category("my_tag_cat", update_res.version) + .await + .expect("Could not delete tag category"); + let tag_cats = client + .request() + .list_tag_categories() + .await + .expect("Could not list tag categories again"); + assert_eq!(tag_cats.results.len(), 1); +} + +#[instrument(skip(client))] +async fn test_tags(client: &SzurubooruClient) { + info!("Testing tag functions"); + + info!("Listing tags"); + let tag_list = client + .request() + .list_tags(None) + .await + .expect("Could not list tags"); + assert_eq!(tag_list.total, 0); + + info!("Creating tag"); + let cutag = CreateUpdateTagBuilder::default() + .names(vec!["foo".to_string(), "foo2".to_string()]) + .category("default".to_string()) + .description("The foo tag".to_string()) + .build() + .expect("Could not build the CreateUpdateTag"); + let tag_res = client + .request() + .create_tag(&cutag) + .await + .expect("Could not create tag"); + + assert_eq!( + tag_res.names, + Some(vec!["foo".to_string(), "foo2".to_string()]) + ); + assert!(tag_res.description.is_some()); + + let tag_list = client + .request() + .list_tags(None) + .await + .expect("Could not list tags"); + assert_eq!(tag_list.total, 1); + + info!("Testing field selection"); + let tag_list = client + .with_fields(vec![ + "version".to_string(), + "names".to_string(), + "category".to_string(), + ]) + .list_tags(None) + .await + .expect("Could not list tags"); + assert_eq!(tag_list.total, 1); + assert!(tag_list.results.first().unwrap().description.is_none()); + + info!("Updating tag"); + let utag = CreateUpdateTagBuilder::default() + .version(tag_res.version) + .description("The foo2 tag".to_string()) + .build() + .expect("Could not build the CreateUpdateTag"); + let tag_res2 = client + .request() + .update_tag("foo", &utag) + .await + .expect("Could not update tag"); + assert_ne!(tag_res.description, tag_res2.description); + + info!("Getting tag"); + let tag_res3 = client + .request() + .get_tag("foo") + .await + .expect("Could not fetch tag"); + assert_eq!(tag_res2.description, tag_res3.description); + + info!("Creating a second tag"); + let cutag = CreateUpdateTagBuilder::default() + .names(vec!["bar".to_string()]) + .category("default".to_string()) + .description("The foo tag".to_string()) + .build() + .expect("Could not build the CreateUpdateTag"); + let bar_tag = client + .request() + .create_tag(&cutag) + .await + .expect("Unable to create second tag"); + let tag_list = client + .request() + .list_tags(None) + .await + .expect("Could not list tags"); + assert_eq!(tag_list.total, 2); + + info!("Merging tags"); + let merge_tag = MergeTagsBuilder::default() + .remove_tag_version(bar_tag.version) + .remove_tag(bar_tag.names.as_ref().unwrap().first().unwrap().clone()) + .merge_to_version(tag_res3.version) + .merge_to_tag(tag_res3.names.as_ref().unwrap().first().unwrap().clone()) + .build() + .expect("Could not create merge tags object"); + let merged_tag = client + .request() + .merge_tags(&merge_tag) + .await + .expect("Could not merge tags"); + assert_eq!(tag_res3.names, merged_tag.names); + let tag_list = client + .request() + .list_tags(None) + .await + .expect("Could not list tags"); + assert_eq!(tag_list.total, 1); + + info!("Deleting tag"); + client + .request() + .delete_tag("foo", merged_tag.version) + .await + .expect("Could not delete tag"); +} + +#[instrument(skip(client))] +async fn test_creating_posts(client: &SzurubooruClient) { + info!("Testing creating posts"); + + info!("Listing posts (should be empty)"); + let post_list = client + .request() + .list_posts(None) + .await + .expect("Could not list posts"); + assert_eq!(post_list.total, 0); + + info!("Testing upload by URL"); + let wiki_post_obj = CreateUpdatePostBuilder::default() + .content_url("https://upload.wikimedia.org/wikipedia/commons/thumb/5/5a/Maine_Coon_cat_by_Tomitheos.JPG/225px-Maine_Coon_cat_by_Tomitheos.JPG".to_string()) + .tags(vec!["maine_coon".to_string(), "cat".to_string()]) + .safety(PostSafety::Safe) + .build() + .expect("Could not build wiki post object"); + let wiki_post = client + .request() + .create_post_from_url(&wiki_post_obj) + .await + .expect("Unable to create wiki post object"); + + info!("Updating existing post"); + let wiki_post_update = CreateUpdatePostBuilder::default() + .version(wiki_post.version.unwrap()) + .safety(wiki_post.safety.unwrap()) + .source("Wikipedia".to_string()) + .build() + .expect("Could not build wiki post update object"); + let wiki_post = client + .request() + .update_post(wiki_post.id.unwrap(), &wiki_post_update) + .await + .expect("Unable to up wiki post object"); + let post_list = client + .request() + .list_posts(None) + .await + .expect("Could not list posts"); + assert_eq!(post_list.total, 1); + + info!("Deleting wikipedia image"); + client + .request() + .delete_post(wiki_post.id.unwrap(), wiki_post.version.unwrap()) + .await + .expect("Could not delete wiki post"); + + info!("Test upload by File type"); + let folly1_obj = CreateUpdatePostBuilder::default() + .tags(vec![ + "maine_coon".to_string(), + "cat".to_string(), + "folly1".to_string(), + ]) + .safety(PostSafety::Safe) + .build() + .expect("Could not build first upload object"); + let folly1_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly1.jpg"); + let mut folly1_file = + File::open(&folly1_path).expect(&format!("Could not open file {folly1_path:?}")); + let _folly1_post = client + .request() + .create_post_from_file(&mut folly1_file, None, "folly1.jpg", &folly1_obj) + .await + .expect("Could not create post from folly1 file"); + + info!("Test upload by file path"); + let folly2_obj = CreateUpdatePostBuilder::default() + .tags(vec![ + "maine_coon".to_string(), + "cat".to_string(), + "folly2".to_string(), + ]) + .safety(PostSafety::Safe) + .build() + .expect("Could not build second upload object"); + let folly2_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly2.jpg"); + let _folly2_post = client + .request() + .create_post_from_file_path(folly2_path, None::, &folly2_obj) + .await + .expect("Could not create post from folly2 path"); + + info!("Test upload by file path with thumbnail"); + let folly3_obj = CreateUpdatePostBuilder::default() + .tags(vec![ + "maine_coon".to_string(), + "cat".to_string(), + "folly3".to_string(), + ]) + .safety(PostSafety::Safe) + .build() + .expect("Could not build third upload object"); + let folly3_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly3.jpg"); + 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) + .await + .expect("Could not create post with thumbnail"); + + info!("Searching for post by image"); + let matching_posts = client + .request() + .post_for_file_path(&folly3_path) + .await + .expect("Could not search for post by file path"); + assert_eq!(&matching_posts.unwrap(), &folly3_post); + + info!("Reverse searching"); + let matching_posts = client + .request() + .reverse_search_file_path(&folly3_path) + .await + .expect("Could not reverse image search"); + assert!(matching_posts.exact_post.is_some()); + + info!("Testing temporary upload"); + let folly4_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly4.jpg"); + let folly4_temp_upload = client + .request() + .upload_temporary_file_from_path(folly4_path) + .await + .expect("Could not create temporary upload for folly4"); + let folly4_obj = CreateUpdatePostBuilder::default() + .tags(vec![ + "maine_coon".to_string(), + "cat".to_string(), + "folly4".to_string(), + ]) + .content_token(folly4_temp_upload.token) + .safety(PostSafety::Safe) + .build() + .expect("Could not build fourth upload object"); + let folly4_post = client + .request() + .create_post_from_token(&folly4_obj) + .await + .expect("Could not create upload from temporary token"); + + info!("Querying by tag"); + let f4_results = client + .request() + .list_posts(Some(&vec![QueryToken::anonymous("cat")])) + .await + .expect("Could not list posts by tag cat"); + assert_eq!(f4_results.total, 4); + + info!("Testing pagination"); + let post_list = client + .request() + .with_limit(1) + .list_posts(None) + .await + .expect("Could not list posts page 1"); + assert_eq!(post_list.results.len(), 1); + let post_list2 = client + .request() + .with_limit(1) + .with_offset(1) + .list_posts(None) + .await + .expect("Could not list posts page 2"); + assert_ne!(post_list.results, post_list2.results); + + info!("Testing tag siblings"); + let tag_occurrences = client + .request() + .get_tag_siblings("maine_coon") + .await + .expect("Could not fetch tag siblings"); + let occurrences_filtered = tag_occurrences + .results + .iter() + .filter(|oc| { + oc.tag + .names + .as_ref() + .map(|names| names.contains(&"cat".to_string())) + .unwrap_or(false) + }) + .count(); + assert_eq!(occurrences_filtered, 1); + + info!("Rating post"); + let folly3_post = client + .request() + .rate_post(folly3_post.id.unwrap(), 1) + .await + .expect("Could not rate post"); + assert_eq!(folly3_post.own_score, Some(1)); + + info!("Favoriting post"); + let folly3_post = client + .request() + .favorite_post(folly3_post.id.unwrap()) + .await + .expect("Could not favorite post"); + assert_eq!(folly3_post.own_favorite, Some(true)); + + info!("Unfavorite post"); + let folly3_post = client + .request() + .unfavorite_post(folly3_post.id.unwrap()) + .await + .expect("Could not unfavorite post"); + assert_eq!(folly3_post.own_favorite, Some(false)); + + info!("Featuring post"); + let featured_post = client + .request() + .get_featured_post() + .await + .expect("Could not get featured post"); + assert!(featured_post.is_none()); + + client + .request() + .set_featured_post(folly4_post.id.unwrap()) + .await + .expect("Could not set featured post"); + + let featured_post = client + .request() + .get_featured_post() + .await + .expect("Could not get featured post"); + + assert!(featured_post.is_some()); +} + +#[instrument(skip(client))] +async fn test_pool_categories(client: &SzurubooruClient) { + info!("Testing pool categories"); + + info!("Listing pool categories"); + let pool_cats = client + .request() + .list_pool_categories() + .await + .expect("Could not list pool categories"); + assert!(!pool_cats.results.is_empty()); + + info!("Creating pool category"); + let create_cat = CreateUpdatePoolCategoryBuilder::default() + .name("cat_pool_category".to_string()) + .color("purple".to_string()) + .build() + .expect("Could not build pool category object"); + let pool_cat = client + .request() + .create_pool_category(&create_cat) + .await + .expect("Could not create pool category"); + + let create_dog_cat = CreateUpdatePoolCategoryBuilder::default() + .name("dog_category".to_string()) + .color("orange".to_string()) + .build() + .expect("Could not build pool category object"); + let dog_pool_cat = client + .request() + .create_pool_category(&create_dog_cat) + .await + .expect("Could not create pool category"); + + info!("Updating pool category"); + let update_cat = CreateUpdatePoolCategoryBuilder::default() + .version(pool_cat.version.unwrap()) + .color("white".to_string()) + .build() + .expect("Could not build pool category update"); + let pool_cat = client + .request() + .update_pool_category(pool_cat.name.unwrap(), &update_cat) + .await + .expect("Could not update pool category"); + + info!("Getting pool category"); + let pool_cat = client + .request() + .get_pool_category(pool_cat.name.unwrap()) + .await + .expect("Could not get pool category"); + + info!("Deleting pool category"); + client + .request() + .delete_pool_category(dog_pool_cat.name.unwrap(), dog_pool_cat.version.unwrap()) + .await + .expect("Could not delete pool category"); + + info!("Setting default pool category"); + let _pool_cat = client + .request() + .set_default_pool_category(pool_cat.name.unwrap()) + .await + .expect("Could not set default pool category"); +} + +#[instrument(skip(client))] +async fn test_pools(client: &SzurubooruClient) { + info!("Testing post pools"); + let pools = client + .request() + .list_pools(None) + .await + .expect("Could not list pools"); + assert_eq!(pools.total, 0); + + info!("Creating pools"); + let create_pool = CreateUpdatePoolBuilder::default() + .names(vec!["cats_pool".to_string()]) + .category("cat_pool_category".to_string()) + .build() + .expect("Could not build pool creation object"); + let cat_pool = client + .request() + .create_pool(&create_pool) + .await + .expect("Could not create pool"); + let create_pool2 = CreateUpdatePoolBuilder::default() + .names(vec!["catz_pool".to_string()]) + .category("cat_pool_category".to_string()) + .build() + .expect("Could not build pool creation object"); + let catz_pool = client + .request() + .create_pool(&create_pool2) + .await + .expect("Could not create pool"); + let create_pool3 = CreateUpdatePoolBuilder::default() + .names(vec!["dogs_pool".to_string()]) + .category("cat_pool_category".to_string()) + .build() + .expect("Could not build pool creation object"); + let dogs_pool = client + .request() + .create_pool(&create_pool3) + .await + .expect("Could not create pool"); + + info!("Getting pool"); + let cat_pool = client + .request() + .get_pool(cat_pool.id.unwrap()) + .await + .expect("Could not fetch pool"); + + info!("Deleting pool"); + client + .request() + .delete_pool(dogs_pool.id.unwrap(), dogs_pool.version.unwrap()) + .await + .expect("Could not delete pool"); + + info!("Updating pool"); + let f4_results = client + .request() + .list_posts(Some(&vec![QueryToken::anonymous("cat")])) + .await + .expect("Could not list posts by tag cat"); + let post_ids = f4_results + .results + .into_iter() + .map(|p| p.id.unwrap()) + .collect::>(); + let update_pool = CreateUpdatePoolBuilder::default() + .version(cat_pool.version.unwrap()) + .posts(post_ids) + .description("All cat pictures all the time".to_string()) + .build() + .expect("Could not build update object"); + let cat_pool = client + .request() + .update_pool(cat_pool.id.unwrap(), &update_pool) + .await + .expect("Unable to update pool"); + + info!("Merging pools"); + let merge_pool_obj = MergePoolBuilder::default() + .remove_pool_version(catz_pool.version.unwrap()) + .remove_pool(catz_pool.id.unwrap()) + .merge_to_version(cat_pool.version.unwrap()) + .merge_to_pool(cat_pool.id.unwrap()) + .build() + .expect("Unable to build merge object"); + let _cat_pool = client + .request() + .merge_pools(&merge_pool_obj) + .await + .expect("Unable to merge pools"); +} + +#[instrument(skip(client))] +async fn test_comments(client: &SzurubooruClient) { + info!("Testing comments"); + + info!("Listing comments"); + let _comment_list = client + .request() + .list_comments(None) + .await + .expect("Unable to list comments"); + + let f4_results = client + .request() + .list_posts(Some(&vec![QueryToken::anonymous("cat")])) + .await + .expect("Could not list posts by tag cat"); + let post_ids = f4_results + .results + .into_iter() + .map(|p| p.id.unwrap()) + .collect::>(); + let post_id = *post_ids.first().unwrap(); + + info!("Creating comment"); + let create_comment = CreateUpdateCommentBuilder::default() + .text("Excellent cat!".to_string()) + .post_id(post_id) + .build() + .expect("Unable to create comment object"); + let comment = client + .request() + .create_comment(&create_comment) + .await + .expect("Unable to create comment"); + + info!("Updating comment"); + let update_comment = CreateUpdateCommentBuilder::default() + .text("Beautiful cat!".to_string()) + .version(comment.version.unwrap()) + .build() + .expect("Unable to create comment update object"); + let comment = client + .request() + .update_comment(comment.id.unwrap(), &update_comment) + .await + .expect("Unable to update comment"); + + info!("Getting comment"); + let comment = client + .request() + .get_comment(comment.id.unwrap()) + .await + .expect("Unable to fetch comment"); + + info!("Getting all comments for post"); + let comment_list = client + .request() + //.list_comments(None) + .list_comments(Some(&vec![QueryToken::token( + CommentNamedToken::Post, + post_id.to_string(), + )])) + .await + .expect("Unable to fetch comments for post"); + assert_ne!(comment_list.total, 0); + + info!("Rating comment"); + let comment = client + .request() + .rate_comment(comment.id.unwrap(), -1) + .await + .expect("Could not rate comment"); + assert_eq!(comment.own_score, Some(-1)); + + info!("Deleting comment"); + client + .request() + .delete_comment(comment.id.unwrap(), comment.version.unwrap()) + .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"); +} + +#[instrument(skip(client))] +async fn test_snapshots(client: &SzurubooruClient) { + info!("Testing snapshots"); + + info!("Listing snapshots"); + let snap_list = client + .request() + .list_snapshots(None) + .await + .expect("Could not list snapshots"); + assert!(snap_list.total > 0); +} + +#[instrument(skip(client))] +async fn test_downloads(client: &SzurubooruClient) { + info!("Testing image download"); + + let folly3_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly3.jpg"); + + let mut f3_hasher = Sha1::new(); + + let mut f3_file = File::open(&folly3_path).expect("Unable to open folly3.jpg"); + + std::io::copy(&mut f3_file, &mut f3_hasher).expect("Unable to hash folly3.jpg"); + + let f3_hash = f3_hasher.finalize(); + + let f3post = client + .request() + .reverse_search_file_path(folly3_path) + .await + .expect("Unable to reverse search for file") + .exact_post + .expect("Unable to find exact post for folly3.jpg"); + + let mut dl_file = tempfile().expect("Could not create temporary file"); + //let mut dl_thumb_file = tempfile().expect("Could not create temporary thumbnail file"); + + //let dlfolly3_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("dlfolly3.jpg"); + /*let mut dl_file = File::options() + .create(true) + .truncate(true) + .read(true) + .write(true) + .open(dlfolly3_path) + .expect("unable to open download file");*/ + + client + .request() + .download_image_to_file(f3post.id.unwrap(), &mut dl_file) + .await + .expect("Could not download to temporary file"); + dl_file + .seek(SeekFrom::Start(0)) + .expect("Could not rewind file"); + + let mut dl_f3_hasher = Sha1::new(); + + std::io::copy(&mut dl_file, &mut dl_f3_hasher).expect("Unable to hash folly3.jpg"); + + let dl_f3_hash = dl_f3_hasher.finalize(); + + assert_eq!(f3_hash, dl_f3_hash); +} diff --git a/tests/rust-test/test.sh b/tests/rust-test/test.sh new file mode 100755 index 0000000..b8ecbbc --- /dev/null +++ b/tests/rust-test/test.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +cargo check +docker compose down +docker compose up -d +cargo run && docker compose down