diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/TagsEndpointTest.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/TagsEndpointTest.kt index 2d243db3..8af1095d 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/TagsEndpointTest.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/TagsEndpointTest.kt @@ -2,10 +2,12 @@ package rs.wordpress.api.kotlin import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test +import uniffi.wp_api.PostListParams import uniffi.wp_api.SparseTagFieldWithEditContext import uniffi.wp_api.TagCreateParams import uniffi.wp_api.TagListParams import uniffi.wp_api.TagUpdateParams +import uniffi.wp_api.WpErrorCode import uniffi.wp_api.wpAuthenticationFromUsernameAndPassword import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -104,4 +106,13 @@ class TagsEndpointTest { assertEquals("new_slug", updatedTag.slug) restoreTestServer() } + + @Test + fun testErrorTermInvalid() = runTest { + val result = + client.request { requestBuilder -> + requestBuilder.tags().retrieveWithEditContext(9999999) + } + assert(result.wpErrorCode() is WpErrorCode.TermInvalid) + } } diff --git a/wp_api/src/api_error.rs b/wp_api/src/api_error.rs index f37e2995..e9bfea4c 100644 --- a/wp_api/src/api_error.rs +++ b/wp_api/src/api_error.rs @@ -147,6 +147,8 @@ pub enum WpErrorCode { CannotRead, #[serde(rename = "rest_cannot_read_post")] CannotReadPost, + #[serde(rename = "rest_cannot_update")] + CannotUpdate, #[serde(rename = "rest_cannot_view")] CannotView, #[serde(rename = "rest_cannot_view_plugin")] @@ -213,6 +215,8 @@ pub enum WpErrorCode { PostInvalidPageNumber, #[serde(rename = "rest_taxonomy_invalid")] TaxonomyInvalid, + #[serde(rename = "rest_term_invalid")] + TermInvalid, #[serde(rename = "rest_type_invalid")] TypeInvalid, #[serde(rename = "rest_not_logged_in")] diff --git a/wp_api/src/request/endpoint/tags_endpoint.rs b/wp_api/src/request/endpoint/tags_endpoint.rs index 4bc80353..61ce82d3 100644 --- a/wp_api/src/request/endpoint/tags_endpoint.rs +++ b/wp_api/src/request/endpoint/tags_endpoint.rs @@ -40,3 +40,225 @@ impl DerivedRequest for TagsRequest { super::macros::default_sparse_field_implementation_from_field_name!(SparseTagFieldWithEditContext); super::macros::default_sparse_field_implementation_from_field_name!(SparseTagFieldWithEmbedContext); super::macros::default_sparse_field_implementation_from_field_name!(SparseTagFieldWithViewContext); + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + generate, + posts::PostId, + request::endpoint::{ + tests::{fixture_api_base_url, validate_wp_v2_endpoint}, + ApiBaseUrl, + }, + tags::{TagId, WpApiParamTagsOrderBy}, + WpApiParamOrder, + }; + use rstest::*; + use std::sync::Arc; + + #[rstest] + #[case(TagListParams::default(), "")] + #[case(generate!(TagListParams, (page, Some(2))), "page=2")] + #[case(generate!(TagListParams, (per_page, Some(2))), "per_page=2")] + #[case(generate!(TagListParams, (search, Some("foo".to_string()))), "search=foo")] + #[case(generate!(TagListParams, (exclude, vec![TagId(1), TagId(2)])), "exclude=1%2C2")] + #[case(generate!(TagListParams, (include, vec![TagId(1), TagId(2)])), "include=1%2C2")] + #[case(generate!(TagListParams, (offset, Some(2))), "offset=2")] + #[case(generate!(TagListParams, (order, Some(WpApiParamOrder::Asc))), "order=asc")] + #[case(generate!(TagListParams, (order, Some(WpApiParamOrder::Desc))), "order=desc")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Id))), "orderby=id")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Include))), "orderby=include")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Name))), "orderby=name")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Slug))), "orderby=slug")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::IncludeSlugs))), "orderby=include_slugs")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::TermGroup))), "orderby=term_group")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Description))), "orderby=description")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Count))), "orderby=count")] + #[case(generate!(TagListParams, (hide_empty, Some(true))), "hide_empty=true")] + #[case(generate!(TagListParams, (post, Some(PostId(3)))), "post=3")] + #[case(generate!(TagListParams, (slug, vec!["slug_1".to_string(), "slug_2".to_string()])), "slug=slug_1%2Cslug_2")] + // TODO + #[case( + tag_list_params_with_all_fields(), + EXPECTED_QUERY_PAIRS_FOR_TAG_LIST_PARAMS_WITH_ALL_FIELDS + )] + fn list_tags( + endpoint: TagsRequestEndpoint, + #[case] params: TagListParams, + #[case] expected_additional_params: &str, + ) { + let expected_path = |context: &str| { + if expected_additional_params.is_empty() { + format!("/tags?context={}", context) + } else { + format!("/tags?context={}&{}", context, expected_additional_params) + } + }; + validate_wp_v2_endpoint( + endpoint.list_with_edit_context(¶ms), + &expected_path("edit"), + ); + validate_wp_v2_endpoint( + endpoint.list_with_embed_context(¶ms), + &expected_path("embed"), + ); + validate_wp_v2_endpoint( + endpoint.list_with_view_context(¶ms), + &expected_path("view"), + ); + } + + #[rstest] + #[case(TagListParams::default(), &[], "/tags?context=edit&_fields=")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Id))), &[SparseTagFieldWithEditContext::Count], "/tags?context=edit&orderby=id&_fields=count")] + #[case(tag_list_params_with_all_fields(), ALL_SPARSE_TAG_FIELDS_WITH_EDIT_CONTEXT, &format!("/tags?context=edit&{}&{}", EXPECTED_QUERY_PAIRS_FOR_TAG_LIST_PARAMS_WITH_ALL_FIELDS, EXPECTED_QUERY_PAIRS_FOR_ALL_SPARSE_TAG_FIELDS_WITH_EDIT_CONTEXT))] + fn filter_list_tags_with_edit_context( + endpoint: TagsRequestEndpoint, + #[case] params: TagListParams, + #[case] fields: &[SparseTagFieldWithEditContext], + #[case] expected_path: &str, + ) { + validate_wp_v2_endpoint( + endpoint.filter_list_with_edit_context(¶ms, fields), + expected_path, + ); + } + + #[rstest] + #[case(TagListParams::default(), &[], "/tags?context=embed&_fields=")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Slug))), &[SparseTagFieldWithEmbedContext::Link], "/tags?context=embed&orderby=slug&_fields=link")] + #[case(tag_list_params_with_all_fields(), ALL_SPARSE_TAG_FIELDS_WITH_EMBED_CONTEXT, &format!("/tags?context=embed&{}&{}", EXPECTED_QUERY_PAIRS_FOR_TAG_LIST_PARAMS_WITH_ALL_FIELDS, EXPECTED_QUERY_PAIRS_FOR_ALL_SPARSE_TAG_FIELDS_WITH_EMBED_CONTEXT))] + fn filter_list_tags_with_embed_context( + endpoint: TagsRequestEndpoint, + #[case] params: TagListParams, + #[case] fields: &[SparseTagFieldWithEmbedContext], + #[case] expected_path: &str, + ) { + validate_wp_v2_endpoint( + endpoint.filter_list_with_embed_context(¶ms, fields), + expected_path, + ); + } + + #[rstest] + #[case(TagListParams::default(), &[], "/tags?context=view&_fields=")] + #[case(generate!(TagListParams, (orderby, Some(WpApiParamTagsOrderBy::Include))), &[SparseTagFieldWithViewContext::Description], "/tags?context=view&orderby=include&_fields=description")] + #[case(tag_list_params_with_all_fields(), ALL_SPARSE_TAG_FIELDS_WITH_VIEW_CONTEXT, &format!("/tags?context=view&{}&{}", EXPECTED_QUERY_PAIRS_FOR_TAG_LIST_PARAMS_WITH_ALL_FIELDS, EXPECTED_QUERY_PAIRS_FOR_ALL_SPARSE_TAG_FIELDS_WITH_VIEW_CONTEXT))] + fn filter_list_tags_with_view_context( + endpoint: TagsRequestEndpoint, + #[case] params: TagListParams, + #[case] fields: &[SparseTagFieldWithViewContext], + #[case] expected_path: &str, + ) { + validate_wp_v2_endpoint( + endpoint.filter_list_with_view_context(¶ms, fields), + expected_path, + ); + } + + const EXPECTED_QUERY_PAIRS_FOR_TAG_LIST_PARAMS_WITH_ALL_FIELDS: &str = "page=11&per_page=22&search=s_q&exclude=1111%2C1112&include=2111%2C2112&offset=11111&order=desc&orderby=slug&hide_empty=true&post=33333&slug=slug_1%2Cslug_2"; + fn tag_list_params_with_all_fields() -> TagListParams { + TagListParams { + page: Some(11), + per_page: Some(22), + search: Some("s_q".to_string()), + exclude: vec![TagId(1111), TagId(1112)], + include: vec![TagId(2111), TagId(2112)], + offset: Some(11111), + order: Some(WpApiParamOrder::Desc), + orderby: Some(WpApiParamTagsOrderBy::Slug), + hide_empty: Some(true), + post: Some(PostId(33333)), + slug: vec!["slug_1".to_string(), "slug_2".to_string()], + } + } + + #[rstest] + fn retrieve_tag(endpoint: TagsRequestEndpoint) { + let tag_id = TagId(54); + let expected_path = |context: &str| format!("/tags/54?context={}", context); + validate_wp_v2_endpoint( + endpoint.retrieve_with_edit_context(&tag_id), + &expected_path("edit"), + ); + validate_wp_v2_endpoint( + endpoint.retrieve_with_embed_context(&tag_id), + &expected_path("embed"), + ); + validate_wp_v2_endpoint( + endpoint.retrieve_with_view_context(&tag_id), + &expected_path("view"), + ); + } + + #[rstest] + #[case(None, &[], "/tags/54?context=view&_fields=")] + #[case(Some("foo"), &[SparseTagFieldWithViewContext::Count], "/tags/54?context=view&_fields=count")] + #[case(Some("foo"), ALL_SPARSE_TAG_FIELDS_WITH_VIEW_CONTEXT, &format!("/tags/54?context=view&{}", EXPECTED_QUERY_PAIRS_FOR_ALL_SPARSE_TAG_FIELDS_WITH_VIEW_CONTEXT))] + fn filter_retrieve_tag_with_view_context( + endpoint: TagsRequestEndpoint, + #[case] password: Option<&str>, + #[case] fields: &[SparseTagFieldWithViewContext], + #[case] expected_path: &str, + ) { + validate_wp_v2_endpoint( + endpoint.filter_retrieve_with_view_context(&TagId(54), fields), + expected_path, + ); + } + + #[rstest] + fn create_tag(endpoint: TagsRequestEndpoint) { + validate_wp_v2_endpoint(endpoint.create(), "/tags"); + } + + #[rstest] + fn delete_tag(endpoint: TagsRequestEndpoint) { + validate_wp_v2_endpoint(endpoint.delete(&TagId(54)), "/tags/54?force=true"); + } + + #[rstest] + fn update_tag(endpoint: TagsRequestEndpoint) { + validate_wp_v2_endpoint(endpoint.update(&TagId(54)), "/tags/54"); + } + + const EXPECTED_QUERY_PAIRS_FOR_ALL_SPARSE_TAG_FIELDS_WITH_EDIT_CONTEXT: &str = + "_fields=id%2Ccount%2Cdescription%2Clink%2Cname%2Cslug%2Ctaxonomy"; + const ALL_SPARSE_TAG_FIELDS_WITH_EDIT_CONTEXT: &[SparseTagFieldWithEditContext; 7] = &[ + SparseTagFieldWithEditContext::Id, + SparseTagFieldWithEditContext::Count, + SparseTagFieldWithEditContext::Description, + SparseTagFieldWithEditContext::Link, + SparseTagFieldWithEditContext::Name, + SparseTagFieldWithEditContext::Slug, + SparseTagFieldWithEditContext::Taxonomy, + ]; + + const EXPECTED_QUERY_PAIRS_FOR_ALL_SPARSE_TAG_FIELDS_WITH_EMBED_CONTEXT: &str = + "_fields=id%2Clink%2Cname%2Cslug%2Ctaxonomy"; + const ALL_SPARSE_TAG_FIELDS_WITH_EMBED_CONTEXT: &[SparseTagFieldWithEmbedContext; 5] = &[ + SparseTagFieldWithEmbedContext::Id, + SparseTagFieldWithEmbedContext::Link, + SparseTagFieldWithEmbedContext::Name, + SparseTagFieldWithEmbedContext::Slug, + SparseTagFieldWithEmbedContext::Taxonomy, + ]; + + const EXPECTED_QUERY_PAIRS_FOR_ALL_SPARSE_TAG_FIELDS_WITH_VIEW_CONTEXT: &str = + "_fields=id%2Ccount%2Cdescription%2Clink%2Cname%2Cslug%2Ctaxonomy"; + const ALL_SPARSE_TAG_FIELDS_WITH_VIEW_CONTEXT: &[SparseTagFieldWithViewContext; 7] = &[ + SparseTagFieldWithViewContext::Id, + SparseTagFieldWithViewContext::Count, + SparseTagFieldWithViewContext::Description, + SparseTagFieldWithViewContext::Link, + SparseTagFieldWithViewContext::Name, + SparseTagFieldWithViewContext::Slug, + SparseTagFieldWithViewContext::Taxonomy, + ]; + + #[fixture] + fn endpoint(fixture_api_base_url: Arc) -> TagsRequestEndpoint { + TagsRequestEndpoint::new(fixture_api_base_url) + } +} diff --git a/wp_api_integration_tests/src/lib.rs b/wp_api_integration_tests/src/lib.rs index d924a8be..1c8ccd60 100644 --- a/wp_api_integration_tests/src/lib.rs +++ b/wp_api_integration_tests/src/lib.rs @@ -62,6 +62,7 @@ pub const MEDIA_TEST_FILE_PATH: &str = "../test_media.jpg"; pub const MEDIA_TEST_FILE_CONTENT_TYPE: &str = "image/jpeg"; pub const CATEGORY_ID_1: CategoryId = CategoryId(1); pub const TAG_ID_100: TagId = TagId(100); +pub const TAG_ID_INVALID: TagId = TagId(99999999); pub const POST_TEMPLATE_SINGLE_WITH_SIDEBAR: &str = "single-with-sidebar"; pub fn api_client() -> WpApiClient { diff --git a/wp_api_integration_tests/tests/test_tags_err.rs b/wp_api_integration_tests/tests/test_tags_err.rs new file mode 100644 index 00000000..e008659f --- /dev/null +++ b/wp_api_integration_tests/tests/test_tags_err.rs @@ -0,0 +1,76 @@ +use serial_test::parallel; +use wp_api::{ + tags::{TagCreateParams, TagListParams, TagUpdateParams}, + WpErrorCode, +}; +use wp_api_integration_tests::{ + api_client, api_client_as_subscriber, AssertWpError, POST_ID_INVALID, TAG_ID_100, + TAG_ID_INVALID, +}; + +#[tokio::test] +#[parallel] +async fn create_err_cannot_create() { + api_client_as_subscriber() + .tags() + .create(&TagCreateParams { + name: "foo".to_string(), + description: None, + slug: None, + }) + .await + .assert_wp_error(WpErrorCode::CannotCreate); +} + +#[tokio::test] +#[parallel] +async fn delete_err_cannot_delete() { + api_client_as_subscriber() + .tags() + .delete(&TAG_ID_100) + .await + .assert_wp_error(WpErrorCode::CannotDelete); +} + +#[tokio::test] +#[parallel] +async fn list_err_forbidden_context() { + api_client_as_subscriber() + .tags() + .list_with_edit_context(&TagListParams::default()) + .await + .assert_wp_error(WpErrorCode::ForbiddenContext); +} + +#[tokio::test] +#[parallel] +async fn list_err_post_invalid_id() { + api_client() + .tags() + .list_with_edit_context(&TagListParams { + post: Some(POST_ID_INVALID), + ..Default::default() + }) + .await + .assert_wp_error(WpErrorCode::PostInvalidId); +} + +#[tokio::test] +#[parallel] +async fn retrieve_err_term_invalid() { + api_client() + .tags() + .retrieve_with_edit_context(&TAG_ID_INVALID) + .await + .assert_wp_error(WpErrorCode::TermInvalid); +} + +#[tokio::test] +#[parallel] +async fn update_err_cannot_update() { + api_client_as_subscriber() + .tags() + .update(&TAG_ID_100, &TagUpdateParams::default()) + .await + .assert_wp_error(WpErrorCode::CannotUpdate); +}