From 3dc75261aa0b3cf41269bd106496f56aba9b3c78 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:06:21 -0600 Subject: [PATCH] Add failing precondition tests for unspam/untrash on non-spam/trash comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit unspam()/untrash() blindly POST status=unspam|untrash. WordPress core (wp_unspam_comment/wp_untrash_comment) restores the status saved in _wp_trash_meta_status and defaults to hold when that meta is absent — so calling either endpoint on a comment that is not currently spam/trash returns HTTP 200 and silently demotes an approved comment to pending (verified against WordPress 6.8.1), rather than the HTTP 500 the endpoint's doc comment claims. These two integration tests encode the desired contract — refuse the call and surface an error, leaving the comment untouched — and fail against the current implementation. Stacked on #1564. --- .../tests/test_comments_mut.rs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/wp_api_integration_tests/tests/test_comments_mut.rs b/wp_api_integration_tests/tests/test_comments_mut.rs index 8f90d1ba4..527d2e58c 100644 --- a/wp_api_integration_tests/tests/test_comments_mut.rs +++ b/wp_api_integration_tests/tests/test_comments_mut.rs @@ -136,6 +136,81 @@ async fn untrash_comment_restores_previous_status() { RestoreServer::db().await; } +// ASPIRATIONAL / currently FAILING — the proposed contract for the not-spam/not-trash path. +// +// Today `unspam()`/`untrash()` blindly POST `status=unspam|untrash`. WordPress core +// (`wp_unspam_comment`/`wp_untrash_comment`) restores the status saved in +// `_wp_trash_meta_status`; when the comment is NOT currently spam/trash that meta is absent, +// core falls back to `hold` and returns HTTP 200 — silently demoting an approved comment +// into the moderation queue (NOT the HTTP 500 the endpoint's doc comment claims). The desired +// contract: the client refuses to unspam/untrash a comment that isn't spam/trash, surfaces an +// error, and leaves the comment untouched. These fail today and should pass once a +// precondition guard is added. + +#[tokio::test] +#[serial] +async fn unspam_on_non_spam_comment_should_error_without_demoting() { + // FIRST_COMMENT_ID is approved in the seed data — it is NOT spam. + let result = api_client().comments().unspam(&FIRST_COMMENT_ID).await; + + // Read the resulting server-side status, then restore before asserting. + let status = api_client() + .comments() + .retrieve_with_edit_context( + &FIRST_COMMENT_ID, + &wp_api::comments::CommentRetrieveParams::default(), + ) + .await + .assert_response() + .data + .status; + RestoreServer::db().await; + + // The approved comment must never be demoted... + assert_eq!( + status, + CommentStatus::Approved, + "unspam silently demoted an approved comment" + ); + // ...and the caller must be told, not handed a bogus success. + assert!( + result.is_err(), + "unspam on a non-spam comment should return an error, not silently succeed" + ); +} + +#[tokio::test] +#[serial] +async fn untrash_on_non_trashed_comment_should_error_without_demoting() { + // FIRST_COMMENT_ID is approved in the seed data — it is NOT trash. + let result = api_client().comments().untrash(&FIRST_COMMENT_ID).await; + + // Read the resulting server-side status, then restore before asserting. + let status = api_client() + .comments() + .retrieve_with_edit_context( + &FIRST_COMMENT_ID, + &wp_api::comments::CommentRetrieveParams::default(), + ) + .await + .assert_response() + .data + .status; + RestoreServer::db().await; + + // The approved comment must never be demoted... + assert_eq!( + status, + CommentStatus::Approved, + "untrash silently demoted an approved comment" + ); + // ...and the caller must be told, not handed a bogus success. + assert!( + result.is_err(), + "untrash on a non-trashed comment should return an error, not silently succeed" + ); +} + generate_update_test!( update_author, author,