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." 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..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') @@ -204,8 +204,7 @@ 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 + $(rust_docker_run) cargo test --package wordpress_org_api -- --nocapture test-kotlin-integration: @# Help: Run Kotlin integration tests in test server. 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} 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/src/plugin_directory.rs b/wordpress_org_api/src/plugin_directory.rs index b11eed2a9..da1e249de 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,23 +35,32 @@ 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, + #[serde(default)] pub preview_link: String, } @@ -82,6 +93,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, @@ -105,3 +122,204 @@ pub struct Icons { pub svg: Option, 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::*; + use rstest::*; + + 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_with_variant_types() -> Plugin { + let json_string = include_str!("../tests/plugin-with-different-types-of-values.json"); + Plugin::parse(json_string) + } + + #[fixture] + 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_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, + ) { + 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()); + assert!(!plugin_with_expected_types.parsed.author_profile.is_empty()); + } + + #[rstest] + fn test_plugin_with_different_types_of_contributors( + 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!(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_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, + ) { + 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_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, + ) { + 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_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, + ) { + 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_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!(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_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.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_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.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_with_variant_types: Plugin, + plugin_with_expected_types: Plugin, + ) { + 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_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.parsed.banners, Banners::default()); + + 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); + 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/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": [] + } 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 + } + } diff --git a/wordpress_org_api/tests/test_plugin_directory.rs b/wordpress_org_api/tests/test_plugin_directory.rs index 0037efc9d..d0fe9b77c 100644 --- a/wordpress_org_api/tests/test_plugin_directory.rs +++ b/wordpress_org_api/tests/test_plugin_directory.rs @@ -1,246 +1,103 @@ -use rstest::*; -use std::collections::HashMap; -use std::path::PathBuf; -use wordpress_org_api::plugin_directory::*; +use std::{ + env, + io::{self, Write}, +}; -#[fixture] -fn plugins_dir() -> PathBuf { - std::fs::canonicalize("../target/wordpress-org-plugin-directory").unwrap() -} +use wordpress_org_api::plugin_directory::*; -#[fixture] -fn plugin_info_files(plugins_dir: PathBuf) -> Vec { - println!( - "Reading plugin information files from {:?}...", - &plugins_dir +async fn query_plugins_slugs(url: &str) -> Result, reqwest::Error> { + reqwest::get(url) + .await? + .json::() + .await? + .plugins + .into_iter() + .map(|p| Ok(p.slug)) + .collect() +} + +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 ); + reqwest::get(&url) + .await? + .json::() + .await + .map_err(Into::into) +} + +#[tokio::test] +async fn test_parsing_full_plugin_directory() { + 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 response = response.json::().await.unwrap(); + total_pages = response.info.pages; + } else { + println!("Only a small amount of plugins will be fetched."); + page_size = 10; + total_pages = 2; + } - 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()); + let mut query_plugins_failures = Vec::new(); + let mut all_slugs: Vec = Vec::new(); + for page in 1..=total_pages { + 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) => { + print!("."); + all_slugs.extend(slugs); + } + Err(e) => { + print!("F({})", &url); + query_plugins_failures.push((url, e)); + } } + _ = io::stdout().flush(); } - 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 - }); - - 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()); + println!(); + + println!("Fetching and parsing {} plugins...", all_slugs.len()); + + let mut plugin_information_failures = Vec::new(); + for slug in all_slugs { + let info = plugin_information(&slug).await; + if let Err(e) = info { + print!("F({})", slug); + plugin_information_failures.push((slug.to_string(), e)); + } else { + print!("."); } + _ = io::stdout().flush(); } + println!(); - 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()); + for (slug, e) in &plugin_information_failures { + println!(" - {:?} : {:?}", slug, e); + } - 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); + assert!(query_plugins_failures.is_empty()); + assert!(plugin_information_failures.is_empty()) }