Make WordPress Core

Opened 6 days ago

Last modified 6 days ago

#66495 new enhancement

Tests: Add unit coverage for the post functions in post.php

Reported by: hmbashar Owned by:
Priority: normal Milestone: Awaiting Review
Component: Posts, Post Types Version:
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses: tests

Description

src/wp-includes/post.php has several functions that sanitize post data, detect changes of posts and keep related data in sync, but they have no direct PHPUnit coverage. A search of tests/phpunit/tests finds no references to:

  • sanitize_post_field(), wp_get_recent_posts() and get_private_posts_cap_sql().
  • wp_check_for_changed_slugs(), wp_check_for_changed_dates(), wp_transition_post_status() and is_local_attachment().
  • wp_trash_post_comments(), wp_untrash_post_comments(), delete_post_thumbnail(), update_post_caches(), update_postmeta_cache(), clean_attachment_cache() and update_attached_file().

Some of these are only exercised indirectly through wp_insert_post() and wp_update_post(), so a change in their behavior is hard to spot and to attribute. Direct tests describe the current behavior of each function.

Proposed scope

This ticket is meant to be handled with several small patches, one for each group of related functions, so each one is easy to review. The tests go in tests/phpunit/tests/post/, one class per function, with data providers where it helps.

  1. Sanitizing and queries. sanitize_post_field() for each context (raw, edit, db, display, attribute and js), the integer fields and the filters. wp_get_recent_posts() for the defaults, the arguments and the output types. get_private_posts_cap_sql() for the users who can read private posts.
  2. Changes of a post. wp_check_for_changed_slugs() and wp_check_for_changed_dates(). wp_transition_post_status() for the actions that fire with and without a change, including the post type. is_local_attachment().
  3. Comments, thumbnails and caches. wp_trash_post_comments() and wp_untrash_post_comments(). delete_post_thumbnail(). update_post_caches(), update_postmeta_cache() and clean_attachment_cache(). update_attached_file().

Out of scope

This ticket only adds tests and does not change behavior. The following are left out on purpose because other tickets are working on them:

  • get_post_statuses() and get_page_statuses(), see #24722 and #58617.
  • get_post_custom(), get_post_custom_keys() and get_post_custom_values(), see #65044, #45505 and #20853.
  • wp_match_mime_types(), see #60003.
  • wp_check_post_hierarchy_for_loops(), which goes with the tests of wp_find_hierarchy_loop() in #59901.

Related: #65819, #59901.

Change History (3)

This ticket was mentioned in ​PR #15532 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#1

  • Keywords has-patch has-unit-tests added; needs-patch removed

Adds unit tests for sanitize_post_field(), wp_get_recent_posts() and get_private_posts_cap_sql() in src/wp-includes/post.php, which had no direct test coverage. This is the first group of the work described in the ticket, and each of the groups is a separate PR. No behavior is changed.

Functions covered, each in its own test class in tests/phpunit/tests/post/:

  • sanitize_post_field()
    • The integer fields (ID, post_parent, menu_order) are cast to integers in every context, also after escaping.
    • ancestors is returned as a list of positive integers.
    • The raw context returns the value unchanged, and runs no filter.
    • The edit context: the order of the filters of fields with and without the post_ prefix, the text of the title, excerpt and password formatted for a text area, and the other fields escaped as attributes.
    • The db context: the order of the filters for both kinds of fields, and no escaping.
    • The display, attribute, js and other contexts: the filter and its arguments, and the escaping of the attribute and js contexts.
  • wp_get_recent_posts()
    • The defaults (ten newest posts, as arrays), OBJECT output, and the empty results (array() or false).
    • The number of posts, the offset, the order, the order by, include, exclude, the category, the custom field, the post type and the post status.
    • The default post statuses, and that the query filters are suppressed by default.
    • A number as the arguments, which is deprecated.
  • get_private_posts_cap_sql()
    • The conditions for a visitor, for a user who can read private posts, and for a user who can only read their own.
    • The capability of the post type, a list of post types, and a post type that does not exist.
    • It is the same as get_posts_by_author_sql() without the full query.

Testing:

  • vendor/bin/phpunit --filter 'Tests_Post_(SanitizePostField|WpGetRecentPosts|GetPrivatePostsCapSql)'
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter 'Tests_Post_(SanitizePostField|WpGetRecentPosts|GetPrivatePostsCapSql)'
  • vendor/bin/phpunit --group post
  • vendor/bin/phpcs tests/phpunit/tests/post/

I also temporarily broke the related lines in src/wp-includes/post.php (the integer casts, absint of the ancestors, the raw early return, each of the edit, db and display filters, the formatting and escaping of the fields, and the defaults, output and deprecated argument of wp_get_recent_posts()) and confirmed that a new test fails for each one.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Written with AI and manually reviewed by me.

This ticket was mentioned in ​PR #15533 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#2

Adds unit tests for the functions that track the changes of a post in src/wp-includes/post.php, which had no direct test coverage. This is the second group of the work described in the ticket, and is independent from the other groups. No behavior is changed.

Functions covered, each in its own test class in tests/phpunit/tests/post/:

  • wp_check_for_changed_slugs()
    • The old slug of a published post, or of an attachment with the status inherit, is stored.
    • Nothing is stored when the slug does not change, for posts that are not published, or for hierarchical post types.
    • An empty slug is not stored, an old slug is not stored twice, and a new slug that was used before is removed from the old slugs.
    • It is hooked to post_updated and attachment_updated, and stores the old slug when a post is updated.
  • wp_check_for_changed_dates()
    • The same rules for the old date, which is stored as a date without the time. A change of the time only is not stored.
  • wp_transition_post_status()
    • The transition_post_status, {old}_to_{new} and {new}_{post_type} actions fire in order with their arguments, also when the status does not change.
    • The status new is allowed as the old status.
    • Only the first action fires for a new status that is not registered, the transition action does not fire for an old status that is not registered, and the last action does not fire for a post type that is not registered.
    • It is used when a post is published.
  • is_local_attachment()
    • A URL of the form ?attachment_id= of this site, and the permalink of an attachment, are attachments.
    • The URL of a post, a URL that matches nothing, and a URL of another site, are not.

Removing the check for the host from is_local_attachment() cannot be detected by a test, because url_to_postid() already ignores the URLs of other sites. The test with a URL of another site that names a real attachment is kept for the behavior.

Testing:

  • vendor/bin/phpunit --filter 'Tests_Post_(WpCheckForChangedSlugs|WpCheckForChangedDates|WpTransitionPostStatus|IsLocalAttachment)'
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter 'Tests_Post_(WpCheckForChangedSlugs|WpCheckForChangedDates|WpTransitionPostStatus|IsLocalAttachment)'
  • vendor/bin/phpunit --group post
  • vendor/bin/phpcs tests/phpunit/tests/post/

I also temporarily broke the related lines in src/wp-includes/post.php (the change check, the status and hierarchical checks, the attachment case, the empty and duplicate old values, the removal of a new value that was used before, each of the conditions and arguments of the actions, and the checks of is_local_attachment()) and confirmed that a new test fails for each one, except the host check described above.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Written with AI and manually reviewed by me.

This ticket was mentioned in ​PR #15534 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#3

Adds unit tests for the functions of src/wp-includes/post.php that handle the comments, the thumbnail, the caches and the attached file of a post, which had no direct test coverage. This is the third and last group of the work described in the ticket, and is independent from the other groups. No behavior is changed.

Functions covered, each in its own test class in tests/phpunit/tests/post/:

  • wp_trash_post_comments() and wp_untrash_post_comments()
    • Nothing is done for a post that does not exist, and the trashed_post_comments action does not fire for a post without comments.
    • The comments are marked as post-trashed, only the comments of that post are changed, and the status of each comment is stored.
    • Untrashing restores the status of each comment, deletes the stored statuses, and a comment that was stored as post-trashed becomes unapproved.
    • The four actions and their arguments, and that both functions are used when a post is trashed and restored.
  • delete_post_thumbnail()
    • The thumbnail is removed and the attachment is kept, false is returned for a post without a thumbnail or one that does not exist, and the other custom fields are kept.
  • update_post_caches() and update_postmeta_cache()
    • The posts, their custom fields and their terms are cached by default, each of the last two can be turned off, and the post type argument decides which terms are cached.
    • update_postmeta_cache() caches the custom fields, returns the cache of each post, and returns false without IDs.
  • clean_attachment_cache()
    • The cache of the attachment and of its custom fields is emptied, the ID is cast to an integer, the terms are cleaned only when asked, and nothing is done while the invalidation of the cache is suspended.
  • update_attached_file()
    • The path is stored relative to the uploads directory, a relative path or a path outside the directory is stored as it is, an empty path deletes the stored file, and the update_attached_file filter is used.

The check for an empty list of posts in update_post_caches() is not tested, because the function does nothing for an empty list with or without it.

Testing:

  • vendor/bin/phpunit --filter 'Tests_Post_(WpTrashPostComments|WpUntrashPostComments|DeletePostThumbnail|UpdatePostCaches|UpdatePostmetaCache|CleanAttachmentCache|UpdateAttachedFile)'
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter 'Tests_Post_(WpTrashPostComments|WpUntrashPostComments|DeletePostThumbnail|UpdatePostCaches|UpdatePostmetaCache|CleanAttachmentCache|UpdateAttachedFile)'
  • vendor/bin/phpunit --group post
  • vendor/bin/phpcs tests/phpunit/tests/post/

I also temporarily broke the related lines in src/wp-includes/post.php (the checks, the actions, the stored and restored statuses, the return values, the flags and post type handling of the caches, the cache deletions, the ID cast, the suspension check, and each step of update_attached_file()) and confirmed that a new test fails for each one, except the check described above.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Written with AI and manually reviewed by me.

Note: See TracTickets for help on using tickets.