From f992d785b932abf8ab4176e5d983b3b5f354b7c2 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 12:27:50 +1300 Subject: [PATCH 01/17] Run tests against wordpress.org plugin directory --- Cargo.lock | 2 + Makefile | 2 +- wordpress_org_api/Cargo.toml | 2 + .../tests/test_plugin_directory.rs | 310 +++++------------- 4 files changed, 84 insertions(+), 232 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2664abe77..d127de67a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3205,9 +3205,11 @@ dependencies = [ name = "wordpress_org_api" version = "0.1.0" dependencies = [ + "reqwest", "rstest", "serde", "serde_json", + "tokio", ] [[package]] diff --git a/Makefile b/Makefile index 39376b151..f3e821f41 100644 --- a/Makefile +++ b/Makefile @@ -205,7 +205,7 @@ test-rust-integration: test-rust-integration-wordpress-org-api: @test -d target/wordpress-org-plugin-directory || ./scripts/plugin-directory.sh download_from_s3 - $(rust_docker_run) cargo test --package wordpress_org_api + $(rust_docker_run) cargo test --package wordpress_org_api -- --nocapture test-kotlin-integration: @# Help: Run Kotlin integration tests in test server. diff --git a/wordpress_org_api/Cargo.toml b/wordpress_org_api/Cargo.toml index 38adf001c..1da4d943c 100644 --- a/wordpress_org_api/Cargo.toml +++ b/wordpress_org_api/Cargo.toml @@ -8,5 +8,7 @@ serde = { workspace = true, features = [ "derive" ] } serde_json = { workspace = true } [dev-dependencies] +reqwest = { workspace = true, features = [ "json" ] } rstest = { workspace = true } serde_json = { workspace = true } +tokio = { workspace = true, features = [ "full" ] } diff --git a/wordpress_org_api/tests/test_plugin_directory.rs b/wordpress_org_api/tests/test_plugin_directory.rs index 0037efc9d..16cb9f862 100644 --- a/wordpress_org_api/tests/test_plugin_directory.rs +++ b/wordpress_org_api/tests/test_plugin_directory.rs @@ -1,246 +1,94 @@ -use rstest::*; -use std::collections::HashMap; -use std::path::PathBuf; +use serde::Deserialize; use wordpress_org_api::plugin_directory::*; -#[fixture] -fn plugins_dir() -> PathBuf { - std::fs::canonicalize("../target/wordpress-org-plugin-directory").unwrap() +async fn query_plugins_slugs(url: &str) -> Result, reqwest::Error> { + #[derive(Deserialize, Debug)] + struct Response { + plugins: Vec, + } + #[derive(Deserialize, Debug)] + struct Plugin { + slug: String, + } + + reqwest::get(url) + .await? + .json::() + .await? + .plugins + .iter() + .map(|p| Ok(p.slug.clone())) + .collect() } -#[fixture] -fn plugin_info_files(plugins_dir: PathBuf) -> Vec { - println!( - "Reading plugin information files from {:?}...", - &plugins_dir +async fn plugin_information(slug: &str) -> Result { + let url = format!( + "https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]={}&fields=icons", + slug ); - - let mut files = vec![]; - for entry in std::fs::read_dir(plugins_dir).unwrap() { - let entry = entry.unwrap(); - if entry.file_type().unwrap().is_file() - && entry.path().extension().and_then(|f| f.to_str()) == Some("json") - { - files.push(entry.path()); + reqwest::get(&url) + .await? + .json::() + .await + .map_err(Into::into) +} + +#[tokio::test] +async fn test_parsing_full_plugin_directory() { + println!("Checking how many pages to fetch..."); + let page_size = 200; + let url = format!( + "https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[per_page]={}", + page_size + ); + let response = reqwest::get(&url).await.unwrap(); + let json = response.json::().await.unwrap(); + let total_pages = json["info"]["pages"].as_u64().unwrap(); + println!("Total pages: {}", total_pages); + + let mut query_plugins_failures = Vec::new(); + let mut all_slugs: Vec = Vec::new(); + for page in 1..=total_pages { + println!("Processing page {}...", page); + + let url = format!( + "https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[per_page]={}&request[page]={}", + page_size, page + ); + let slugs = query_plugins_slugs(&url).await; + match slugs { + Ok(slugs) => { + all_slugs.extend(slugs); + } + Err(e) => { + println!("Failed to fetch page: {:?}", url); + query_plugins_failures.push((url, e)); + } } } - files -} - -fn parse_plugin(slug: &str) -> PluginInformation { - let file = plugins_dir().join(format!("{}.json", slug)); - let content = std::fs::read_to_string(file).unwrap(); - let result = serde_json::from_str::(&content); - - assert!( - result.is_ok(), - "Failed to parse plugin {:?}: {:?}", - slug, - result.err() - ); - - result.unwrap() -} -#[rstest] -fn parse_plugin_info(plugin_info_files: Vec) { - let results: HashMap<&PathBuf, _> = - plugin_info_files - .iter() - .fold(HashMap::new(), |mut results, file| { - let info = std::fs::read_to_string(file).unwrap(); - let result = serde_json::from_str::(&info); - results.insert(file, result); - results - }); + println!("Fetching and parsing {} plugins...", all_slugs.len()); - let success: Vec<_> = results - .iter() - .filter(|(_, result)| result.is_ok()) - .collect(); - - let failed: Vec<_> = results - .iter() - .filter(|(_, result)| result.is_err()) - .map(|(file, _)| file) - .collect(); - if !failed.is_empty() { - println!("Failed to parse the following files:"); - for file in &failed { - println!("- {:?}", file.file_name().unwrap()); + let mut plugin_information_failures = Vec::new(); + for slug in all_slugs { + println!("Fetching plugin information for: {}", slug); + let info = plugin_information(&slug).await; + if let Err(e) = info { + println!("Failed to fetch plugin information: {:?}", slug); + plugin_information_failures.push((slug.to_string(), e)); } } - assert_eq!( - success.len(), - results.len(), - "{} out of {} files parsed successfully", - success.len(), - results.len() - ); - assert!( - failed.is_empty(), - "Failed to parse {} out of {} files", - failed.len(), - results.len() - ); -} - -#[rstest] -#[case("jetpack", "https://profiles.wordpress.org/automattic/")] -#[case("appmysite", "https://profiles.wordpress.org/appmysite/")] -#[case("superlinks", "")] -fn test_property_author_profile(#[case] slug: &str, #[case] value: &str) { - let result = parse_plugin(slug); - assert_eq!(result.author_profile, value); -} + println!("{} query plugins failures:", query_plugins_failures.len()); + for (url, e) in query_plugins_failures { + println!(" - {:?} : {:?}", url, e); + } -#[rstest] -#[case("jetpack", "automattic", "https://profiles.wordpress.org/automattic/")] -#[case("appmysite", "appmysite", "https://profiles.wordpress.org/appmysite/")] -fn test_property_contributors_profile( - #[case] slug: &str, - #[case] username: &str, - #[case] profile: &str, -) { - let result = parse_plugin(slug); - let contributors = result.contributors; - assert_eq!( - contributors.get(username).map(|f| f.profile.as_str()), - Some(profile) + println!( + "{} plugin information failures:", + plugin_information_failures.len() ); -} - -#[rstest] -#[case("superlinks")] -fn test_property_no_contributors(#[case] slug: &str) { - let result = parse_plugin(slug); - assert!(result.contributors.is_empty()); -} - -#[rstest] -#[case("timeline-express-no-icons-add-on", "")] -#[case("appmysite", "6.4")] -#[case("superlinks", "2.5")] -fn test_property_requires(#[case] slug: &str, #[case] value: &str) { - let result = parse_plugin(slug); - assert_eq!(result.requires, value); -} - -#[rstest] -#[case("jetpack", "6.7.1")] -#[case("add-rss", "")] -fn test_property_tested(#[case] slug: &str, #[case] value: &str) { - let result = parse_plugin(slug); - assert_eq!(result.tested, value); -} - -#[rstest] -#[case("about-author", "")] -#[case("accessibility-toolbar", "7.4")] -fn test_property_requires_php(#[case] slug: &str, #[case] value: &str) { - let result = parse_plugin(slug); - assert_eq!(result.requires_php, value); -} - -#[rstest] -#[case("accordion-archive-widget", "")] -#[case("abc-pricing-table", "commercial")] -fn test_property_business_model(#[case] slug: &str, #[case] value: &str) { - let result = parse_plugin(slug); - assert_eq!(result.business_model, value); -} - -#[rstest] -fn test_property_empty_upgrade_notice(#[values("1-click-close-store", "2em")] slug: &str) { - let result = parse_plugin(slug); - assert!(result.upgrade_notice.is_empty()); -} - -#[rstest] -fn test_property_nonempty_upgrade_notice( - #[values("ab-wp-security", "absolute-addons")] slug: &str, -) { - let result = parse_plugin(slug); - assert!(!result.upgrade_notice.is_empty()); -} - -#[rstest] -fn test_property_empty_tags(#[values("acf-rest", "add-rss")] slug: &str) { - let result = parse_plugin(slug); - assert!(result.tags.is_empty()); -} - -#[rstest] -fn test_property_nonempty_tags(#[values("appbanners", "seo-assistant")] slug: &str) { - let result = parse_plugin(slug); - assert!(!result.tags.is_empty()); -} - -#[rstest] -fn test_property_empty_versions(#[values("mos-faqs", "adjustly-collapse")] slug: &str) { - let result = parse_plugin(slug); - assert!(result.versions.is_empty()); -} - -#[rstest] -fn test_property_nonempty_versions(#[values("abcsubmit", "acf-views")] slug: &str) { - let result = parse_plugin(slug); - assert!(!result.versions.is_empty()); -} - -#[rstest] -#[case( - "appmysite", - "https://ps.w.org/appmysite/assets/banner-772x250.png?rev=2829272", - "https://ps.w.org/appmysite/assets/banner-1544x500.png?rev=2829272" -)] -#[case( - "jetpack", - "https://ps.w.org/jetpack/assets/banner-772x250.png?rev=2653649", - "https://ps.w.org/jetpack/assets/banner-1544x500.png?rev=2653649" -)] -#[case( - "1-click-migration", - "https://ps.w.org/1-click-migration/assets/banner-772x250.png?rev=2333853", - "" -)] -fn test_property_banners(#[case] slug: &str, #[case] low: String, #[case] high: String) { - let result = parse_plugin(slug); - let expected = Banners { low, high }; - let banners = result.banners; - assert_eq!(banners, expected); -} - -#[rstest] -#[case( - "contact-form-7", - Some("https://ps.w.org/contact-form-7/assets/icon.svg?rev=2339255"), - None, - Some("https://ps.w.org/contact-form-7/assets/icon.svg?rev=2339255"), - None -)] -#[case( - "adminimize", - None, - None, - None, - Some("https://s.w.org/plugins/geopattern-icon/adminimize_000000.svg") -)] -fn test_property_icons( - #[case] slug: &str, - #[case] low: Option<&str>, - #[case] high: Option<&str>, - #[case] svg: Option<&str>, - #[case] default: Option<&str>, -) { - let result = parse_plugin(slug); - let icons = result.icons; - assert!(icons.is_some()); - - let icons = icons.unwrap(); - assert_eq!(icons.low.as_deref(), low); - assert_eq!(icons.high.as_deref(), high); - assert_eq!(icons.svg.as_deref(), svg); - assert_eq!(icons.default.as_deref(), default); + for (slug, e) in plugin_information_failures { + println!(" - {:?} : {:?}", slug, e); + } } From e836db0c8b7c7a6bbbe9425b7bdfbdd5fbfbb192 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 13:30:14 +1300 Subject: [PATCH 02/17] Reduce console output --- .../tests/test_plugin_directory.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/wordpress_org_api/tests/test_plugin_directory.rs b/wordpress_org_api/tests/test_plugin_directory.rs index 16cb9f862..80634e4c8 100644 --- a/wordpress_org_api/tests/test_plugin_directory.rs +++ b/wordpress_org_api/tests/test_plugin_directory.rs @@ -1,3 +1,8 @@ +use std::{ + env, + io::{self, Write}, +}; + use serde::Deserialize; use wordpress_org_api::plugin_directory::*; @@ -49,8 +54,6 @@ async fn test_parsing_full_plugin_directory() { let mut query_plugins_failures = Vec::new(); let mut all_slugs: Vec = Vec::new(); for page in 1..=total_pages { - println!("Processing page {}...", page); - let url = format!( "https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[per_page]={}&request[page]={}", page_size, page @@ -58,26 +61,32 @@ async fn test_parsing_full_plugin_directory() { let slugs = query_plugins_slugs(&url).await; match slugs { Ok(slugs) => { + print!("."); all_slugs.extend(slugs); } Err(e) => { - println!("Failed to fetch page: {:?}", url); + print!("F({})", &url); query_plugins_failures.push((url, e)); } } + _ = io::stdout().flush(); } + println!(); println!("Fetching and parsing {} plugins...", all_slugs.len()); let mut plugin_information_failures = Vec::new(); for slug in all_slugs { - println!("Fetching plugin information for: {}", slug); let info = plugin_information(&slug).await; if let Err(e) = info { - println!("Failed to fetch plugin information: {:?}", slug); + print!("F({})", slug); plugin_information_failures.push((slug.to_string(), e)); + } else { + print!("."); } + _ = io::stdout().flush(); } + println!(); println!("{} query plugins failures:", query_plugins_failures.len()); for (url, e) in query_plugins_failures { From a4ed45669bdcb33af01f2347a8e8b065aa4501b2 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 13:30:33 +1300 Subject: [PATCH 03/17] Verify all plugins when 'TEST_ALL_PLUGINS' env is set --- .../tests/test_plugin_directory.rs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/wordpress_org_api/tests/test_plugin_directory.rs b/wordpress_org_api/tests/test_plugin_directory.rs index 80634e4c8..09414e2a9 100644 --- a/wordpress_org_api/tests/test_plugin_directory.rs +++ b/wordpress_org_api/tests/test_plugin_directory.rs @@ -40,16 +40,23 @@ async fn plugin_information(slug: &str) -> Result().await.unwrap(); - let total_pages = json["info"]["pages"].as_u64().unwrap(); - println!("Total pages: {}", total_pages); + let page_size: u64; + let total_pages: u64; + if env::var("TEST_ALL_PLUGINS").is_ok() { + println!("Checking how many pages to fetch..."); + page_size = 200; + let url = format!( + "https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[per_page]={}", + page_size + ); + let response = reqwest::get(&url).await.unwrap(); + let json = response.json::().await.unwrap(); + total_pages = json["info"]["pages"].as_u64().unwrap(); + } else { + println!("Only a small amount of plugins will be fetched."); + page_size = 10; + total_pages = 2; + } let mut query_plugins_failures = Vec::new(); let mut all_slugs: Vec = Vec::new(); From 7b69637552b7e21e99caab824626350c4ab7f242 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 13:14:50 +1300 Subject: [PATCH 04/17] No need to download plugin cache --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index f3e821f41..3c3ada93e 100644 --- a/Makefile +++ b/Makefile @@ -204,7 +204,6 @@ test-rust-integration: docker exec -i wordpress /bin/bash < ./scripts/run-rust-integration-tests.sh test-rust-integration-wordpress-org-api: - @test -d target/wordpress-org-plugin-directory || ./scripts/plugin-directory.sh download_from_s3 $(rust_docker_run) cargo test --package wordpress_org_api -- --nocapture test-kotlin-integration: From a74e489491d6d339529a850fb09d2e4ce6c838a2 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 13:28:22 +1300 Subject: [PATCH 05/17] Add nightly build to test verify all plugins in wordpress.org --- .buildkite/nightly.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .buildkite/nightly.yml diff --git a/.buildkite/nightly.yml b/.buildkite/nightly.yml new file mode 100644 index 000000000..8437f3a9a --- /dev/null +++ b/.buildkite/nightly.yml @@ -0,0 +1,13 @@ +steps: + - label: ":wordpress: :rust: WordPress.org API" + command: | + echo "--- :rust: Testing" + make test-rust-integration-wordpress-org-api + env: + TEST_ALL_PLUGINS: true + +notify: + - slack: + channels: + - "#wordpress-rs" + message: "Nightly build." From efec4fda060e681e0c889db77306b1b0c3fadc52 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 14:04:14 +1300 Subject: [PATCH 06/17] Remove plugin-directory.sh --- scripts/plugin-directory.sh | 71 ------------------------------------- 1 file changed, 71 deletions(-) delete mode 100755 scripts/plugin-directory.sh diff --git a/scripts/plugin-directory.sh b/scripts/plugin-directory.sh deleted file mode 100755 index bbd8c3232..000000000 --- a/scripts/plugin-directory.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -OUTPUT_DIR="target/wordpress-org-plugin-directory" - -fetch_plugin_data() { - slug=$1 - curl -s --output "$OUTPUT_DIR/$slug.json" "$API_URL?action=plugin_information&fields=icons%2Cbanners&slug=$slug" -} - -download_from_wp_org() { - echo "Downloading plugin data from WordPress.org." - echo "(Run this script within sandbox to get a much faster download speed.)" - - rm -rf "$OUTPUT_DIR" && mkdir -p "$OUTPUT_DIR" - - API_URL="https://api.wordpress.org/plugins/info/1.2/" - PARALLEL_JOBS=10 - - current_page=1 - - echo "Fetching the total number of pages..." - response=$(curl -s "$API_URL?action=query_plugins&request%5Bpage%5D=$current_page&request%5Bper_page%5D=100") - total_pages=$(echo "$response" | jq -r '.info.pages') - if [[ -z "$total_pages" || "$total_pages" -eq 0 ]]; then - echo "Failed to fetch pagination info or no pages available." - exit 1 - fi - - echo "Total pages to process: $total_pages" - - export -f fetch_plugin_data - export OUTPUT_DIR - export API_URL - - while [[ $current_page -le $total_pages ]]; do - echo "[$(date)] Processing page: $current_page" - - response=$(curl -s "$API_URL?action=query_plugins&request%5Bpage%5D=$current_page&request%5Bper_page%5D=100") - plugin_slugs=$(echo "$response" | jq -r '.plugins[].slug') - - echo "$plugin_slugs" | xargs -n 1 -P "$PARALLEL_JOBS" bash -c 'fetch_plugin_data "$@"' _ - - current_page=$((current_page + 1)) - done - - echo "All plugin data has been saved to the directory: $OUTPUT_DIR" -} - -S3_URI="s3://a8c-ci-cache/wordpress-rs-wordpress-org-plugin-directory.tar.gz" -S3_LOCAL_CACHE="target/wordpress-org-plugin-directory.tar.gz" - -upload_to_s3() { - echo "Uploading $(find "$OUTPUT_DIR" -type f | wc -l) plugins in $OUTPUT_DIR to S3..." - echo "Compressing ..." - tar -czf "$S3_LOCAL_CACHE" -C "$OUTPUT_DIR" . - aws s3 cp "$S3_LOCAL_CACHE" "$S3_URI" - rm "$S3_LOCAL_CACHE" -} - -download_from_s3() { - echo "Downloading plugin data from S3 cache..." - rm -rf "$OUTPUT_DIR" && mkdir -p "$OUTPUT_DIR" - aws s3 cp "$S3_URI" "$S3_LOCAL_CACHE" - echo "Unzip to $OUTPUT_DIR" - tar -xzf "$S3_LOCAL_CACHE" -C "$OUTPUT_DIR" - rm "$S3_LOCAL_CACHE" -} - -${1:-download_from_wp_org} From d4a07b690ead5266aa140e03f4eb79ae4614bbe4 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 14:09:39 +1300 Subject: [PATCH 07/17] Pass TEST_ALL_PLUGINS env over to docker --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3c3ada93e..798c59f69 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ docker_container_repo_dir=/app rust_docker_container := public.ecr.aws/docker/library/rust:1.80 docker_opts_shared := --rm -v "$(PWD)":$(docker_container_repo_dir) -w $(docker_container_repo_dir) -rust_docker_run := docker run -v $(PWD):/$(docker_container_repo_dir) -w $(docker_container_repo_dir) -it -e CARGO_HOME=/app/.cargo $(rust_docker_container) +rust_docker_run := docker run -v $(PWD):/$(docker_container_repo_dir) -w $(docker_container_repo_dir) -it -e TEST_ALL_PLUGINS -e CARGO_HOME=/app/.cargo $(rust_docker_container) docker_build_and_run := docker build -t foo . && docker run $(docker_opts_shared) -it foo swift_package_platform_version = $(shell swift package dump-package | jq -r '.platforms[] | select(.platformName=="$1") | .version') From e6be7bbadf1335ef406cb2881f2eb9dfaaf8e734 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 21:21:08 +1300 Subject: [PATCH 08/17] Add tests for plugin fields which have different types when set and un-set --- wordpress_org_api/src/plugin_directory.rs | 82 +++++++++++++++++++ ...plugin-with-different-types-of-values.json | 42 ++++++++++ 2 files changed, 124 insertions(+) create mode 100644 wordpress_org_api/tests/plugin-with-different-types-of-values.json diff --git a/wordpress_org_api/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index b11eed2a9..ed1f808ae 100644 --- a/wordpress_org_api/src/plugin_directory.rs +++ b/wordpress_org_api/src/plugin_directory.rs @@ -105,3 +105,85 @@ pub struct Icons { pub svg: Option, pub default: Option, } + +#[cfg(test)] +mod tests { + use super::*; + use rstest::*; + + const JSON_STRING: &str = include_str!("../tests/plugin-with-different-types-of-values.json"); + + #[fixture] + fn plugin_info() -> PluginInformation { + let result = serde_json::from_str::(JSON_STRING); + assert!(result.is_ok()); + result.unwrap() + } + + #[fixture] + fn raw_json() -> serde_json::Value { + let result = serde_json::from_str(JSON_STRING); + assert!(result.is_ok()); + result.unwrap() + } + + #[rstest] + fn test_plugin_with_different_types_of_author_profile(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["author_profile"], false); + assert_eq!(plugin_info.author_profile, ""); + } + + #[rstest] + fn test_plugin_with_different_types_of_contributors(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["contributors"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_info.contributors, HashMap::new()); + } + + #[rstest] + fn test_plugin_with_different_types_of_requires(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["requires"], false); + assert_eq!(plugin_info.requires, ""); + } + + #[rstest] + fn test_plugin_with_different_types_of_tested(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["tested"], false); + assert_eq!(plugin_info.tested, ""); + } + + #[rstest] + fn test_plugin_with_different_types_of_requires_php(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["requires_php"], false); + assert_eq!(plugin_info.requires_php, ""); + } + + #[rstest] + fn test_plugin_with_different_types_of_upgrade_notice(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["upgrade_notice"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_info.upgrade_notice, HashMap::new()); + } + + #[rstest] + fn test_plugin_with_different_types_of_tags(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["tags"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_info.tags, HashMap::new()); + } + + #[rstest] + fn test_plugin_with_different_types_of_versions(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["versions"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_info.versions, HashMap::new()); + } + + #[rstest] + fn test_plugin_with_different_types_of_business_model(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["business_model"], false); + assert_eq!(plugin_info.business_model, ""); + } + + #[rstest] + fn test_plugin_with_different_types_of_banners(plugin_info: PluginInformation, raw_json: serde_json::Value) { + assert_eq!(raw_json["banners"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_info.banners, Banners::default()); + } +} diff --git a/wordpress_org_api/tests/plugin-with-different-types-of-values.json b/wordpress_org_api/tests/plugin-with-different-types-of-values.json new file mode 100644 index 000000000..4ccfd9d0a --- /dev/null +++ b/wordpress_org_api/tests/plugin-with-different-types-of-values.json @@ -0,0 +1,42 @@ +{ + "name": "Plugin name", + "slug": "plugins-lug", + "version": "1.0", + "author": "author", + "requires_plugins": [], + "rating": 0, + "ratings": { "5": 0, "4": 0, "3": 0, "2": 0, "1": 0 }, + "num_ratings": 0, + "support_url": "https://wordpress.org/support/plugin/plugin-slug/", + "support_threads": 0, + "support_threads_resolved": 0, + "active_installs": 20, + "last_updated": "2007-12-24 3:00am GMT", + "added": "2007-12-23", + "homepage": "http://www.author.com/plugin-slug", + "sections": { + "description": "

Awesome plugin

\n", + "installation": "Install", + "reviews": "" + }, + "download_link": "https://downloads.wordpress.org/plugin/plugin-slug.1.0.zip", + "screenshots": [], + "repository_url": "", + "commercial_support_url": "", + "donate_link": "http://www.author.com/", + "icons": { + "default": "https://s.w.org/plugins/plugin-slug.svg" + }, + "preview_link": "", + "---- When not set by the plugin author, the fields below have different types compared to when values are set.": false, + "author_profile": false, + "contributors": [], + "requires": false, + "tested": false, + "requires_php": false, + "upgrade_notice": [], + "tags": [], + "versions": [], + "business_model": false, + "banners": [] + } From 7a443c047c09ed66827eba1433009ed66cbea9c3 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 4 Dec 2024 22:53:16 +1300 Subject: [PATCH 09/17] Fix code format issues --- wordpress_org_api/src/plugin_directory.rs | 50 ++++++++++++++++++----- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/wordpress_org_api/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index ed1f808ae..a0f06363b 100644 --- a/wordpress_org_api/src/plugin_directory.rs +++ b/wordpress_org_api/src/plugin_directory.rs @@ -128,61 +128,91 @@ mod tests { } #[rstest] - fn test_plugin_with_different_types_of_author_profile(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_author_profile( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["author_profile"], false); assert_eq!(plugin_info.author_profile, ""); } #[rstest] - fn test_plugin_with_different_types_of_contributors(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_contributors( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["contributors"].as_array(), Some(vec![]).as_ref()); assert_eq!(plugin_info.contributors, HashMap::new()); } #[rstest] - fn test_plugin_with_different_types_of_requires(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_requires( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["requires"], false); assert_eq!(plugin_info.requires, ""); } #[rstest] - fn test_plugin_with_different_types_of_tested(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_tested( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["tested"], false); assert_eq!(plugin_info.tested, ""); } #[rstest] - fn test_plugin_with_different_types_of_requires_php(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_requires_php( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["requires_php"], false); assert_eq!(plugin_info.requires_php, ""); } #[rstest] - fn test_plugin_with_different_types_of_upgrade_notice(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_upgrade_notice( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["upgrade_notice"].as_array(), Some(vec![]).as_ref()); assert_eq!(plugin_info.upgrade_notice, HashMap::new()); } #[rstest] - fn test_plugin_with_different_types_of_tags(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_tags( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["tags"].as_array(), Some(vec![]).as_ref()); assert_eq!(plugin_info.tags, HashMap::new()); } #[rstest] - fn test_plugin_with_different_types_of_versions(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_versions( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["versions"].as_array(), Some(vec![]).as_ref()); assert_eq!(plugin_info.versions, HashMap::new()); } #[rstest] - fn test_plugin_with_different_types_of_business_model(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_business_model( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["business_model"], false); assert_eq!(plugin_info.business_model, ""); } #[rstest] - fn test_plugin_with_different_types_of_banners(plugin_info: PluginInformation, raw_json: serde_json::Value) { + fn test_plugin_with_different_types_of_banners( + plugin_info: PluginInformation, + raw_json: serde_json::Value, + ) { assert_eq!(raw_json["banners"].as_array(), Some(vec![]).as_ref()); assert_eq!(plugin_info.banners, Banners::default()); } From 022fa1f4b22fcac33b6a2af4721c6e1746b7f6c8 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 5 Dec 2024 11:04:01 +1300 Subject: [PATCH 10/17] Add a plugin JSON file with expected types --- wordpress_org_api/src/plugin_directory.rs | 141 ++++++++++++------ .../tests/plugin-with-expected-types.json | 57 +++++++ 2 files changed, 149 insertions(+), 49 deletions(-) create mode 100644 wordpress_org_api/tests/plugin-with-expected-types.json diff --git a/wordpress_org_api/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index a0f06363b..4646d8ba2 100644 --- a/wordpress_org_api/src/plugin_directory.rs +++ b/wordpress_org_api/src/plugin_directory.rs @@ -111,109 +111,152 @@ mod tests { use super::*; use rstest::*; - const JSON_STRING: &str = include_str!("../tests/plugin-with-different-types-of-values.json"); + struct Plugin { + parsed: PluginInformation, + raw_json: serde_json::Value, + } + + impl Plugin { + fn parse(json_string: &str) -> Self { + let parsed = serde_json::from_str::(json_string); + let raw_json = serde_json::from_str(json_string); + assert!(parsed.is_ok(), "Failed to parse JSON: {:?}", parsed.err()); + assert!(raw_json.is_ok(), "Failed to parse JSON: {:?}", raw_json.err()); + + Self { + parsed: parsed.unwrap(), + raw_json: raw_json.unwrap(), + } + } + } #[fixture] - fn plugin_info() -> PluginInformation { - let result = serde_json::from_str::(JSON_STRING); - assert!(result.is_ok()); - result.unwrap() + fn plugin_with_variant_types() -> Plugin { + let json_string = include_str!("../tests/plugin-with-different-types-of-values.json"); + Plugin::parse(json_string) } #[fixture] - fn raw_json() -> serde_json::Value { - let result = serde_json::from_str(JSON_STRING); - assert!(result.is_ok()); - result.unwrap() + fn plugin_with_expected_types() -> Plugin { + let json_string = include_str!("../tests/plugin-with-expected-types.json"); + Plugin::parse(json_string) } #[rstest] fn test_plugin_with_different_types_of_author_profile( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["author_profile"], false); - assert_eq!(plugin_info.author_profile, ""); + assert_eq!(plugin_with_variant_types.raw_json["author_profile"], false); + assert_eq!(plugin_with_variant_types.parsed.author_profile, ""); + + assert!(plugin_with_expected_types.raw_json["author_profile"].is_string()); } #[rstest] fn test_plugin_with_different_types_of_contributors( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["contributors"].as_array(), Some(vec![]).as_ref()); - assert_eq!(plugin_info.contributors, HashMap::new()); + assert_eq!(plugin_with_variant_types.raw_json["contributors"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_with_variant_types.parsed.contributors, HashMap::new()); + + assert!(plugin_with_expected_types.raw_json["contributors"].is_object()); + assert!(!plugin_with_expected_types.parsed.contributors.is_empty()); } #[rstest] fn test_plugin_with_different_types_of_requires( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["requires"], false); - assert_eq!(plugin_info.requires, ""); + assert_eq!(plugin_with_variant_types.raw_json["requires"], false); + assert_eq!(plugin_with_variant_types.parsed.requires, ""); + + assert!(plugin_with_expected_types.raw_json["requires"].is_string()); + assert!(!plugin_with_expected_types.parsed.requires.is_empty()); } #[rstest] fn test_plugin_with_different_types_of_tested( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["tested"], false); - assert_eq!(plugin_info.tested, ""); + assert_eq!(plugin_with_variant_types.raw_json["tested"], false); + assert_eq!(plugin_with_variant_types.parsed.tested, ""); + + assert!(plugin_with_expected_types.raw_json["tested"].is_string()); + assert!(!plugin_with_expected_types.parsed.tested.is_empty()); } #[rstest] fn test_plugin_with_different_types_of_requires_php( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["requires_php"], false); - assert_eq!(plugin_info.requires_php, ""); + assert_eq!(plugin_with_variant_types.raw_json["requires_php"], false); + assert_eq!(plugin_with_variant_types.parsed.requires_php, ""); + + assert!(plugin_with_expected_types.raw_json["requires_php"].is_string()); + assert!(!plugin_with_expected_types.parsed.requires_php.is_empty()); } #[rstest] fn test_plugin_with_different_types_of_upgrade_notice( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["upgrade_notice"].as_array(), Some(vec![]).as_ref()); - assert_eq!(plugin_info.upgrade_notice, HashMap::new()); + assert_eq!(plugin_with_variant_types.raw_json["upgrade_notice"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_with_variant_types.parsed.upgrade_notice, HashMap::new()); + + assert!(plugin_with_expected_types.raw_json["upgrade_notice"].is_object()); + assert!(!plugin_with_expected_types.parsed.upgrade_notice.is_empty()); } #[rstest] fn test_plugin_with_different_types_of_tags( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["tags"].as_array(), Some(vec![]).as_ref()); - assert_eq!(plugin_info.tags, HashMap::new()); + assert_eq!(plugin_with_variant_types.raw_json["tags"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_with_variant_types.parsed.tags, HashMap::new()); + + assert!(plugin_with_expected_types.raw_json["tags"].is_object()); + assert!(!plugin_with_expected_types.parsed.tags.is_empty()); } #[rstest] fn test_plugin_with_different_types_of_versions( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["versions"].as_array(), Some(vec![]).as_ref()); - assert_eq!(plugin_info.versions, HashMap::new()); + assert_eq!(plugin_with_variant_types.raw_json["versions"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_with_variant_types.parsed.versions, HashMap::new()); + + assert!(plugin_with_expected_types.raw_json["versions"].is_object()); + assert!(!plugin_with_expected_types.parsed.versions.is_empty()); } #[rstest] fn test_plugin_with_different_types_of_business_model( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["business_model"], false); - assert_eq!(plugin_info.business_model, ""); + assert_eq!(plugin_with_variant_types.raw_json["business_model"], false); + assert_eq!(plugin_with_variant_types.parsed.business_model, ""); + + assert!(plugin_with_expected_types.raw_json["business_model"].is_string()); + assert!(!plugin_with_expected_types.parsed.business_model.is_empty()); } #[rstest] fn test_plugin_with_different_types_of_banners( - plugin_info: PluginInformation, - raw_json: serde_json::Value, + plugin_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, ) { - assert_eq!(raw_json["banners"].as_array(), Some(vec![]).as_ref()); - assert_eq!(plugin_info.banners, Banners::default()); + assert_eq!(plugin_with_variant_types.raw_json["banners"].as_array(), Some(vec![]).as_ref()); + assert_eq!(plugin_with_variant_types.parsed.banners, Banners::default()); + + assert!(plugin_with_expected_types.raw_json["banners"].is_object()); } } diff --git a/wordpress_org_api/tests/plugin-with-expected-types.json b/wordpress_org_api/tests/plugin-with-expected-types.json new file mode 100644 index 000000000..8ed657369 --- /dev/null +++ b/wordpress_org_api/tests/plugin-with-expected-types.json @@ -0,0 +1,57 @@ +{ + "name": "Plugin name", + "slug": "plugins-lug", + "version": "1.0", + "author": "author", + "requires_plugins": [], + "rating": 0, + "ratings": { "5": 0, "4": 0, "3": 0, "2": 0, "1": 0 }, + "num_ratings": 0, + "support_url": "https://wordpress.org/support/plugin/plugin-slug/", + "support_threads": 0, + "support_threads_resolved": 0, + "active_installs": 20, + "last_updated": "2007-12-24 3:00am GMT", + "added": "2007-12-23", + "homepage": "http://www.author.com/plugin-slug", + "sections": { + "description": "

Awesome plugin

\n", + "installation": "Install", + "reviews": "" + }, + "download_link": "https://downloads.wordpress.org/plugin/plugin-slug.1.0.zip", + "screenshots": [], + "repository_url": "", + "commercial_support_url": "", + "donate_link": "http://www.author.com/", + "icons": { + "default": "https://s.w.org/plugins/plugin-slug.svg" + }, + "preview_link": "", + "---- When not set by the plugin author, the fields below have different types compared to when values are set.": false, + "author_profile": "https://about.me/author", + "contributors": { + "me": { + "profile": "https://about.me/me", + "avatar": "https://secure.gravatar.com/avatar/me", + "display_name": "Author" + } + }, + "requires": "4.0", + "tested": "4.9", + "requires_php": "5.6", + "upgrade_notice": { "1.0.0": "Please upgrade" }, + "tags": { + "popular-posts": "popular posts", + "recent-posts": "recent posts" + }, + "versions": { + "1.0.0": "https://downloads.wordpress.org/plugin/plugin-slug.1.0.0.zip", + "trunk": "https://downloads.wordpress.org/plugin/plugin-slug.zip" + }, + "business_model": "commercial", + "banners": { + "low": "https://ps.w.org/assets/plugin-slug.png", + "high": false + } + } From cf672fe84755e1eef5a90f39d194882f8e836cd7 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 5 Dec 2024 11:13:14 +1300 Subject: [PATCH 11/17] Fix code format issues --- wordpress_org_api/src/plugin_directory.rs | 41 ++++++++++++++++++----- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/wordpress_org_api/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index 4646d8ba2..7775d062f 100644 --- a/wordpress_org_api/src/plugin_directory.rs +++ b/wordpress_org_api/src/plugin_directory.rs @@ -121,7 +121,11 @@ mod tests { let parsed = serde_json::from_str::(json_string); let raw_json = serde_json::from_str(json_string); assert!(parsed.is_ok(), "Failed to parse JSON: {:?}", parsed.err()); - assert!(raw_json.is_ok(), "Failed to parse JSON: {:?}", raw_json.err()); + assert!( + raw_json.is_ok(), + "Failed to parse JSON: {:?}", + raw_json.err() + ); Self { parsed: parsed.unwrap(), @@ -158,8 +162,14 @@ mod tests { plugin_with_variant_types: Plugin, plugin_with_expected_types: Plugin, ) { - assert_eq!(plugin_with_variant_types.raw_json["contributors"].as_array(), Some(vec![]).as_ref()); - assert_eq!(plugin_with_variant_types.parsed.contributors, HashMap::new()); + assert_eq!( + plugin_with_variant_types.raw_json["contributors"].as_array(), + Some(vec![]).as_ref() + ); + assert_eq!( + plugin_with_variant_types.parsed.contributors, + HashMap::new() + ); assert!(plugin_with_expected_types.raw_json["contributors"].is_object()); assert!(!plugin_with_expected_types.parsed.contributors.is_empty()); @@ -206,8 +216,14 @@ mod tests { plugin_with_variant_types: Plugin, plugin_with_expected_types: Plugin, ) { - assert_eq!(plugin_with_variant_types.raw_json["upgrade_notice"].as_array(), Some(vec![]).as_ref()); - assert_eq!(plugin_with_variant_types.parsed.upgrade_notice, HashMap::new()); + assert_eq!( + plugin_with_variant_types.raw_json["upgrade_notice"].as_array(), + Some(vec![]).as_ref() + ); + assert_eq!( + plugin_with_variant_types.parsed.upgrade_notice, + HashMap::new() + ); assert!(plugin_with_expected_types.raw_json["upgrade_notice"].is_object()); assert!(!plugin_with_expected_types.parsed.upgrade_notice.is_empty()); @@ -218,7 +234,10 @@ mod tests { plugin_with_variant_types: Plugin, plugin_with_expected_types: Plugin, ) { - assert_eq!(plugin_with_variant_types.raw_json["tags"].as_array(), Some(vec![]).as_ref()); + assert_eq!( + plugin_with_variant_types.raw_json["tags"].as_array(), + Some(vec![]).as_ref() + ); assert_eq!(plugin_with_variant_types.parsed.tags, HashMap::new()); assert!(plugin_with_expected_types.raw_json["tags"].is_object()); @@ -230,7 +249,10 @@ mod tests { plugin_with_variant_types: Plugin, plugin_with_expected_types: Plugin, ) { - assert_eq!(plugin_with_variant_types.raw_json["versions"].as_array(), Some(vec![]).as_ref()); + assert_eq!( + plugin_with_variant_types.raw_json["versions"].as_array(), + Some(vec![]).as_ref() + ); assert_eq!(plugin_with_variant_types.parsed.versions, HashMap::new()); assert!(plugin_with_expected_types.raw_json["versions"].is_object()); @@ -254,7 +276,10 @@ mod tests { plugin_with_variant_types: Plugin, plugin_with_expected_types: Plugin, ) { - assert_eq!(plugin_with_variant_types.raw_json["banners"].as_array(), Some(vec![]).as_ref()); + assert_eq!( + plugin_with_variant_types.raw_json["banners"].as_array(), + Some(vec![]).as_ref() + ); assert_eq!(plugin_with_variant_types.parsed.banners, Banners::default()); assert!(plugin_with_expected_types.raw_json["banners"].is_object()); From 2b8be020532e227b01b6125eab4c5fb28d1cbbd0 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 10 Dec 2024 12:07:40 +1300 Subject: [PATCH 12/17] Support query plugins result --- wordpress_org_api/src/plugin_directory.rs | 35 ++++++++++ .../tests/plugin-query-result.json | 70 +++++++++++++++++++ .../tests/test_plugin_directory.rs | 16 +---- 3 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 wordpress_org_api/tests/plugin-query-result.json diff --git a/wordpress_org_api/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index 7775d062f..a4e943fba 100644 --- a/wordpress_org_api/src/plugin_directory.rs +++ b/wordpress_org_api/src/plugin_directory.rs @@ -15,6 +15,7 @@ pub struct PluginInformation { #[serde(deserialize_with = "deserialize_default_values")] pub author_profile: String, #[serde(deserialize_with = "deserialize_default_values")] + #[serde(default)] pub contributors: HashMap, #[serde(deserialize_with = "deserialize_default_values")] pub requires: String, @@ -26,6 +27,7 @@ pub struct PluginInformation { pub rating: u32, pub ratings: Ratings, pub num_ratings: u32, + #[serde(default)] pub support_url: String, pub support_threads: u32, pub support_threads_resolved: u32, @@ -33,21 +35,29 @@ pub struct PluginInformation { pub last_updated: String, pub added: String, pub homepage: String, + #[serde(default)] pub sections: HashMap, pub download_link: String, #[serde(deserialize_with = "deserialize_default_values")] + #[serde(default)] pub upgrade_notice: HashMap, + #[serde(default)] pub screenshots: Screenshots, #[serde(deserialize_with = "deserialize_default_values")] pub tags: HashMap, #[serde(deserialize_with = "deserialize_default_values")] + #[serde(default)] pub versions: HashMap, #[serde(deserialize_with = "deserialize_default_values")] + #[serde(default)] pub business_model: String, + #[serde(default)] pub repository_url: String, + #[serde(default)] pub commercial_support_url: String, pub donate_link: String, #[serde(deserialize_with = "deserialize_default_values")] + #[serde(default)] pub banners: Banners, pub icons: Option, pub preview_link: String, @@ -82,6 +92,12 @@ pub enum Screenshots { List(Vec), } +impl Default for Screenshots { + fn default() -> Self { + Screenshots::List(vec![]) + } +} + #[derive(Deserialize, Debug)] pub struct Screenshot { pub src: String, @@ -106,6 +122,19 @@ pub struct Icons { pub default: Option, } +#[derive(Deserialize, Debug)] +pub struct QueryPluginResponse { + pub info: QueryPluginResponseInfo, + pub plugins: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct QueryPluginResponseInfo { + pub page: u64, + pub pages: u64, + pub results: u64, +} + #[cfg(test)] mod tests { use super::*; @@ -284,4 +313,10 @@ mod tests { assert!(plugin_with_expected_types.raw_json["banners"].is_object()); } + + fn test_plugin_query_result() { + let json_string = include_str!("../tests/plugin-query-result.json"); + let parsed = serde_json::from_str::(json_string); + assert!(parsed.is_ok(), "Failed to parse JSON: {:?}", parsed.err()); + } } diff --git a/wordpress_org_api/tests/plugin-query-result.json b/wordpress_org_api/tests/plugin-query-result.json new file mode 100644 index 000000000..712ab97a7 --- /dev/null +++ b/wordpress_org_api/tests/plugin-query-result.json @@ -0,0 +1,70 @@ +{ + "info": { "page": 1, "pages": 27333, "results": 54665 }, + "plugins": [ + { + "name": "Plugin 1", + "slug": "plugin-1", + "version": "3.2", + "author": "Author", + "author_profile": "https://profiles.wordpress.org/author/", + "requires": "6.3", + "tested": "6.7.1", + "requires_php": "7.4", + "requires_plugins": [], + "rating": 92, + "ratings": { "5": 5, "4": 4, "3": 3, "2": 2, "1": 1 }, + "num_ratings": 15, + "support_threads": 2, + "support_threads_resolved": 9, + "active_installs": 100, + "downloaded": 514, + "last_updated": "2023-11-30 3:00pm GMT", + "added": "2023-05-30", + "homepage": "https://author.com/plugin-1", + "short_description": "A short desc", + "description": "A long description", + "download_link": "https://downloads.wordpress.org/plugin/1.zip", + "tags": { + "editor": "editor", + "page-builder": "page builder" + }, + "donate_link": "", + "icons": { + "1x": "https://ps.w.org/plugin/assets/icon-128x128.gif", + "2x": "https://ps.w.org/plugin/assets/icon-256x256.gif" + } + }, + { + "name": "Plugin 2", + "slug": "plugin-2", + "version": "6.0.1", + "author": "Author", + "author_profile": "https://profiles.wordpress.org/author/", + "requires": "6.3", + "tested": "6.7.1", + "requires_php": "7.4", + "requires_plugins": [], + "rating": 92, + "ratings": { "5": 5, "4": 4, "3": 3, "2": 2, "1": 1 }, + "num_ratings": 15, + "support_threads": 2, + "support_threads_resolved": 9, + "active_installs": 100, + "downloaded": 54, + "last_updated": "2022-11-30 3:00pm GMT", + "added": "2022-05-30", + "homepage": "https://author.com/plugin-2", + "short_description": "short", + "description": "long", + "download_link": "https://downloads.wordpress.org/plugin/2.zip", + "tags": { + "validation": "validation" + }, + "donate_link": "https://author.com/donate/", + "icons": { + "1x": "https://ps.w.org/plugin-2/assets/icon.svg", + "svg": "https://ps.w.org/plugin-2/assets/icon.svg" + } + } + ] +} diff --git a/wordpress_org_api/tests/test_plugin_directory.rs b/wordpress_org_api/tests/test_plugin_directory.rs index 09414e2a9..f548b8ccf 100644 --- a/wordpress_org_api/tests/test_plugin_directory.rs +++ b/wordpress_org_api/tests/test_plugin_directory.rs @@ -3,22 +3,12 @@ use std::{ io::{self, Write}, }; -use serde::Deserialize; use wordpress_org_api::plugin_directory::*; async fn query_plugins_slugs(url: &str) -> Result, reqwest::Error> { - #[derive(Deserialize, Debug)] - struct Response { - plugins: Vec, - } - #[derive(Deserialize, Debug)] - struct Plugin { - slug: String, - } - reqwest::get(url) .await? - .json::() + .json::() .await? .plugins .iter() @@ -50,8 +40,8 @@ async fn test_parsing_full_plugin_directory() { page_size ); let response = reqwest::get(&url).await.unwrap(); - let json = response.json::().await.unwrap(); - total_pages = json["info"]["pages"].as_u64().unwrap(); + let response = response.json::().await.unwrap(); + total_pages = response.info.pages; } else { println!("Only a small amount of plugins will be fetched."); page_size = 10; From e2f2aed89f1ed6e7bfdfd7b6e6df40129455023c Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 11 Dec 2024 09:18:52 +1300 Subject: [PATCH 13/17] Add missing test attribute --- wordpress_org_api/src/plugin_directory.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wordpress_org_api/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index a4e943fba..6e1094e2a 100644 --- a/wordpress_org_api/src/plugin_directory.rs +++ b/wordpress_org_api/src/plugin_directory.rs @@ -314,6 +314,7 @@ mod tests { assert!(plugin_with_expected_types.raw_json["banners"].is_object()); } + #[test] fn test_plugin_query_result() { let json_string = include_str!("../tests/plugin-query-result.json"); let parsed = serde_json::from_str::(json_string); From 1e14536f74d63ecffe5ab859f6e678d9283117aa Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 11 Dec 2024 09:19:15 +1300 Subject: [PATCH 14/17] Add missing assertions --- wordpress_org_api/tests/test_plugin_directory.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wordpress_org_api/tests/test_plugin_directory.rs b/wordpress_org_api/tests/test_plugin_directory.rs index f548b8ccf..eeb942fdb 100644 --- a/wordpress_org_api/tests/test_plugin_directory.rs +++ b/wordpress_org_api/tests/test_plugin_directory.rs @@ -86,7 +86,7 @@ async fn test_parsing_full_plugin_directory() { println!(); println!("{} query plugins failures:", query_plugins_failures.len()); - for (url, e) in query_plugins_failures { + for (url, e) in &query_plugins_failures { println!(" - {:?} : {:?}", url, e); } @@ -94,7 +94,10 @@ async fn test_parsing_full_plugin_directory() { "{} plugin information failures:", plugin_information_failures.len() ); - for (slug, e) in plugin_information_failures { + for (slug, e) in &plugin_information_failures { println!(" - {:?} : {:?}", slug, e); } + + assert!(query_plugins_failures.is_empty()); + assert!(plugin_information_failures.is_empty()) } From 1834bdc329d27bbf83f1c1951ea74c305db87343 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 11 Dec 2024 09:21:29 +1300 Subject: [PATCH 15/17] Add missing serde(default) attribute to preview_link --- wordpress_org_api/src/plugin_directory.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wordpress_org_api/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index 6e1094e2a..ddebc9876 100644 --- a/wordpress_org_api/src/plugin_directory.rs +++ b/wordpress_org_api/src/plugin_directory.rs @@ -60,6 +60,7 @@ pub struct PluginInformation { #[serde(default)] pub banners: Banners, pub icons: Option, + #[serde(default)] pub preview_link: String, } From 53d585e5456e09cabeed3e7933b39d0509129b40 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 11 Dec 2024 09:22:28 +1300 Subject: [PATCH 16/17] Add another assertion --- wordpress_org_api/src/plugin_directory.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wordpress_org_api/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index ddebc9876..da1e249de 100644 --- a/wordpress_org_api/src/plugin_directory.rs +++ b/wordpress_org_api/src/plugin_directory.rs @@ -185,6 +185,7 @@ mod tests { assert_eq!(plugin_with_variant_types.parsed.author_profile, ""); assert!(plugin_with_expected_types.raw_json["author_profile"].is_string()); + assert!(!plugin_with_expected_types.parsed.author_profile.is_empty()); } #[rstest] From 8d5c6112df67f9c476a76d378b17c1ddb128ca69 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 11 Dec 2024 09:23:23 +1300 Subject: [PATCH 17/17] Use into_iter to avoid clone --- wordpress_org_api/tests/test_plugin_directory.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wordpress_org_api/tests/test_plugin_directory.rs b/wordpress_org_api/tests/test_plugin_directory.rs index eeb942fdb..d0fe9b77c 100644 --- a/wordpress_org_api/tests/test_plugin_directory.rs +++ b/wordpress_org_api/tests/test_plugin_directory.rs @@ -11,8 +11,8 @@ async fn query_plugins_slugs(url: &str) -> Result, reqwest::Error> { .json::() .await? .plugins - .iter() - .map(|p| Ok(p.slug.clone())) + .into_iter() + .map(|p| Ok(p.slug)) .collect() }