Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `unspam` and `untrash` comment endpoints, mirroring wp-admin's "Not Spam" and "Restore" actions. They restore the comment's saved pre-spam/pre-trash status, which a plain `hold` status write would not do. Replaces the `CommentStatus.custom("unspam")` / `custom("untrash")` workaround.
- WordPress.com `POST /me/transactions` endpoint for redeeming a shopping cart with the account's WordPress.com credits, completing a domain purchase
- WordPress.com `GET /sites/<site_id>/purchases` endpoint for listing a site's purchases (plans, domains, and other subscriptions)
- Publish the Kotlin bindings' per-endpoint Markdown API reference as an `ai-docs` Maven classifier zip on `rs.wordpress.api:kotlin`, generated from the UniFFI bindings for agent/tooling consumption
Expand Down
28 changes: 28 additions & 0 deletions wp_api/src/request/endpoint/comments_endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,25 @@ enum CommentsRequest {
Trash,
#[post(url = "/comments/<comment_id>", params = &CommentUpdateParams, output = crate::comments::CommentWithEditContext)]
Update,
// Unspam and Untrash mirror wp-admin's "Not Spam" and "Restore" actions
// via the dedicated `unspam`/`untrash` values of the REST `status` param.
// They restore the comment's saved pre-spam/pre-trash status from comment
// meta and fire the `unspam_comment`/`untrash_comment` hooks, which a
// plain status write (e.g. `hold`) would not do. The server rejects them
// (HTTP 500) when the comment is not currently spam/trash.
#[post(url = "/comments/<comment_id>", output = crate::comments::CommentWithEditContext)]
Unspam,
#[post(url = "/comments/<comment_id>", output = crate::comments::CommentWithEditContext)]
Untrash,
}

impl DerivedRequest for CommentsRequest {
fn additional_query_pairs(&self) -> Vec<(&str, String)> {
match self {
CommentsRequest::Delete => vec![("force", true.to_string())],
CommentsRequest::Trash => vec![("force", false.to_string())],
CommentsRequest::Unspam => vec![("status", "unspam".to_string())],
CommentsRequest::Untrash => vec![("status", "untrash".to_string())],
_ => vec![],
}
}
Expand Down Expand Up @@ -288,6 +300,22 @@ mod tests {
);
}

#[rstest]
fn unspam_comment(endpoint: CommentsRequestEndpoint) {
validate_wp_v2_endpoint(
endpoint.unspam(&CommentId(54)),
"/comments/54?status=unspam",
);
}

#[rstest]
fn untrash_comment(endpoint: CommentsRequestEndpoint) {
validate_wp_v2_endpoint(
endpoint.untrash(&CommentId(54)),
"/comments/54?status=untrash",
);
}

const EXPECTED_QUERY_PAIRS_FOR_ALL_SPARSE_COMMENT_FIELDS_WITH_EDIT_CONTEXT: &str = "_fields=id%2Cauthor%2Cauthor_email%2Cauthor_ip%2Cauthor_name%2Cauthor_url%2Cauthor_user_agent%2Ccontent%2Cdate%2Cdate_gmt%2Clink%2Cparent%2Cpost%2Cstatus%2Ctype%2Cauthor_avatar_urls";
const ALL_SPARSE_COMMENT_FIELDS_WITH_EDIT_CONTEXT: &[SparseCommentFieldWithEditContext; 16] = &[
SparseCommentFieldWithEditContext::Id,
Expand Down
52 changes: 52 additions & 0 deletions wp_api_integration_tests/tests/test_comments_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,58 @@ async fn trash_comment() {
RestoreServer::db().await;
}

#[tokio::test]
#[serial]
async fn unspam_comment_restores_previous_status() {
// FIRST_COMMENT_ID is approved in the seed data. Mark it as spam first.
let spammed_comment = api_client()
.comments()
.update(
&FIRST_COMMENT_ID,
&CommentUpdateParams {
status: Some(CommentStatus::Spam),
..Default::default()
},
)
.await
.assert_response()
.data;
assert_eq!(spammed_comment.status, CommentStatus::Spam);

// Unspam must restore the saved pre-spam status (approved), not `hold`.
let unspammed_comment = api_client()
.comments()
.unspam(&FIRST_COMMENT_ID)
.await
.assert_response()
.data;
assert_eq!(unspammed_comment.status, CommentStatus::Approved);

RestoreServer::db().await;
}

#[tokio::test]
#[serial]
async fn untrash_comment_restores_previous_status() {
// FIRST_COMMENT_ID is approved in the seed data. Trash it first.
api_client()
.comments()
.trash(&FIRST_COMMENT_ID, &CommentDeleteParams::default())
.await
.assert_response();

// Untrash must restore the saved pre-trash status (approved), not `hold`.
let untrashed_comment = api_client()
.comments()
.untrash(&FIRST_COMMENT_ID)
.await
.assert_response()
.data;
assert_eq!(untrashed_comment.status, CommentStatus::Approved);

RestoreServer::db().await;
}

generate_update_test!(
update_author,
author,
Expand Down