fix(2meet-data-optimizer): filter REST listings collection by post_status and post_type

- GET /wpdo/v1/listings rejects unregistered or non-viewable post types (400) and joins posts so Zone A only lists published posts of that type
- WP_Query fallback now returns only show_in_rest hot columns, matching the Zone A path
- Verify: core unit 605 / integration 432 OK (new RestApi tests); 1M bench: page 7ms, total COUNT 0.12s → 1.7s (tracked in PR-030)
- PR: PR-015
This commit is contained in:
2026-09-25 23:10:46 +08:00
parent b407f235ac
commit 9dc4516ca0
5 changed files with 129 additions and 15 deletions
+52 -2
View File
@@ -72,12 +72,29 @@ class RestApiIntegrationTest extends TestCase {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
);
// Seed 5 rows.
for ( $i = 1; $i <= 5; $i++ ) {
// Posts table: listings are filtered by post_status against it.
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->posts}`" );
$wpdb->query(
"CREATE TABLE `{$wpdb->posts}` (
ID BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_type VARCHAR(20) NOT NULL DEFAULT 'post',
post_status VARCHAR(20) NOT NULL DEFAULT 'publish',
PRIMARY KEY (ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
);
// Seed 5 published rows, plus draft/private/trash rows (ids 6-8) and
// a published post of another type (id 9) that must never be listed.
$statuses = array( 6 => 'draft', 7 => 'private', 8 => 'trash' );
for ( $i = 1; $i <= 8; $i++ ) {
$price = $i * 100;
$featured = $i % 2;
$status = $statuses[ $i ] ?? 'publish';
$wpdb->query( "INSERT INTO `{$wpdb->posts}` (ID, post_type, post_status) VALUES ($i, 'restapi', '$status')" );
$wpdb->query( "INSERT INTO `" . self::$hot_table . "` (post_id, rp_price, rp_featured) VALUES ($i, $price, $featured)" );
}
$wpdb->query( "INSERT INTO `{$wpdb->posts}` (ID, post_type, post_status) VALUES (9, 'post', 'publish')" );
$wpdb->query( "INSERT INTO `" . self::$hot_table . "` (post_id, rp_price, rp_featured) VALUES (9, 900, 1)" );
// Set module to cutover so REST API reads from Zone A.
WPDO_Feature_Flags::set( 'hot_restapi', 'cutover' );
@@ -87,6 +104,7 @@ class RestApiIntegrationTest extends TestCase {
global $wpdb;
$wpdb->query( "DROP TABLE IF EXISTS `" . self::$hot_table . "`" );
$wpdb->query( "DROP TABLE IF EXISTS `" . self::$warm_table . "`" );
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->posts}`" );
WPDO_Feature_Flags::reset( 'hot_restapi' );
}
@@ -97,6 +115,7 @@ class RestApiIntegrationTest extends TestCase {
$GLOBALS['_wp_current_user_can'] = [];
$GLOBALS['_wp_valid_nonces'] = [];
$GLOBALS['_wp_transients'] = []; // Reset rate-limit transients between tests.
$GLOBALS['_wp_registered_post_types'] = [ 'restapi' => true ];
// Note: do NOT reset _wp_options here — Feature Flags state is stored there.
// Invalidate Feature Flags static request cache so each test reads fresh.
@@ -232,6 +251,37 @@ class RestApiIntegrationTest extends TestCase {
$this->assertSame( $sorted, $prices );
}
public function test_listings_excludes_unpublished_posts(): void {
$req = new WP_REST_Request( 'GET', '/wpdo/v1/listings' );
$req->set_param( 'post_type', 'restapi' );
$req->set_param( 'per_page', 20 );
$response = self::$api->get_listings( $req );
$ids = array_column( $response->get_data(), 'id' );
sort( $ids );
$this->assertSame( array( 1, 2, 3, 4, 5 ), $ids, 'draft/private/trash and other post types must not be listed.' );
$this->assertSame( '5', $response->get_headers()['X-WP-Total'] );
}
public function test_listings_filters_still_apply_with_status_join(): void {
$req = new WP_REST_Request( 'GET', '/wpdo/v1/listings' );
$req->set_param( 'post_type', 'restapi' );
$req->set_param( 'rp_price_min', 400 );
$req->set_param( 'per_page', 20 );
$ids = array_column( self::$api->get_listings( $req )->get_data(), 'id' );
sort( $ids );
$this->assertSame( array( 4, 5 ), $ids );
}
public function test_listings_rejects_unregistered_post_type(): void {
$req = new WP_REST_Request( 'GET', '/wpdo/v1/listings' );
$req->set_param( 'post_type', 'nope' );
$this->assertSame( 400, self::$api->get_listings( $req )->get_status() );
}
// ── GET /listings/{id} ───────────────────────────────────────────────────
public function test_get_listing_404_for_unknown_post(): void {
+3
View File
@@ -337,6 +337,9 @@ if ( ! function_exists( 'is_post_publicly_viewable' ) ) {
return 'publish' === ( $GLOBALS['_wp_post_status'][ (int) $post_id ] ?? 'publish' );
}
}
// Registered post types: name => publicly viewable (bool).
if ( ! function_exists( 'post_type_exists' ) ) { function post_type_exists( string $post_type ): bool { return isset( $GLOBALS['_wp_registered_post_types'][ $post_type ] ); } }
if ( ! function_exists( 'is_post_type_viewable' ) ) { function is_post_type_viewable( $post_type ): bool { return ! empty( $GLOBALS['_wp_registered_post_types'][ (string) $post_type ] ); } }
if ( ! function_exists( 'current_user_can' ) ) {
function current_user_can( string $cap, ...$args ): bool {
if ( ! empty( $args ) ) { $key = $cap . ':' . implode( ',', array_map( 'strval', $args ) ); if ( isset( $GLOBALS['_wp_current_user_can'][ $key ] ) ) { return (bool) $GLOBALS['_wp_current_user_can'][ $key ]; } }