diff --git a/plugins/performance-lab/includes/site-health/audit-enqueued-assets/helper.php b/plugins/performance-lab/includes/site-health/audit-enqueued-assets/helper.php index cf7ba9d397..ab934e4209 100644 --- a/plugins/performance-lab/includes/site-health/audit-enqueued-assets/helper.php +++ b/plugins/performance-lab/includes/site-health/audit-enqueued-assets/helper.php @@ -522,9 +522,30 @@ function perflab_aea_get_asset_size( string $resource_url ) { ); } - // TODO: A non-cacheable response should also be considered an error. - // TODO: A size of zero could be considered an error too. - return strlen( wp_remote_retrieve_body( $response ) ); + $body = wp_remote_retrieve_body( $response ); + if ( 0 === strlen( $body ) ) { + return new WP_Error( + 'zero_size', + __( 'The asset returned an empty response (0 bytes).', 'performance-lab' ) + ); + } + + $cache_control = wp_remote_retrieve_header( $response, 'Cache-Control' ); + if ( is_string( $cache_control ) && ( false !== stripos( $cache_control, 'no-store' ) || false !== stripos( $cache_control, 'no-cache' ) ) ) { + return new WP_Error( + 'non_cacheable', + wp_kses( + sprintf( + /* translators: %s is the Cache-Control header value */ + __( 'The asset is not cacheable (Cache-Control: %s), which can harm performance.', 'performance-lab' ), + esc_html( $cache_control ) + ), + array( 'code' => array() ) + ) + ); + } + + return strlen( $body ); } /** diff --git a/plugins/performance-lab/tests/includes/site-health/audit-enqueued-assets/test-audit-enqueued-assets-helper.php b/plugins/performance-lab/tests/includes/site-health/audit-enqueued-assets/test-audit-enqueued-assets-helper.php index e3939b8190..4dce8d93f8 100644 --- a/plugins/performance-lab/tests/includes/site-health/audit-enqueued-assets/test-audit-enqueued-assets-helper.php +++ b/plugins/performance-lab/tests/includes/site-health/audit-enqueued-assets/test-audit-enqueued-assets-helper.php @@ -520,10 +520,45 @@ public function test_perflab_aea_get_asset_size(): void { $this->assertWPError( perflab_aea_get_asset_size( 'https://example.com/script1.js' ) ); $this->assertWPError( perflab_aea_get_asset_size( 'https://example.com/script2.js' ) ); - $this->assertEquals( 0, perflab_aea_get_asset_size( 'https://example.com/script3.js' ) ); + + $zero_size_result = perflab_aea_get_asset_size( 'https://example.com/script3.js' ); + $this->assertWPError( $zero_size_result ); + $this->assertSame( 'zero_size', $zero_size_result->get_error_code() ); + $this->assertEquals( 1000, perflab_aea_get_asset_size( 'https://example.com/script4.js' ) ); } + /** + * Tests perflab_aea_get_asset_size() returns error for non-cacheable Cache-Control header. + * + * @covers ::perflab_aea_get_asset_size + */ + public function test_perflab_aea_get_asset_size_non_cacheable(): void { + add_filter( + 'pre_http_request', + static function ( $preempt, $parsed_args, $url ) { + if ( 'https://example.com/nocache.js' !== $url ) { + return $preempt; + } + return array( + 'headers' => new WpOrg\Requests\Utility\CaseInsensitiveDictionary( array( 'Cache-Control' => 'no-store' ) ), + 'body' => 'var x = 1;', + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + ); + }, + 10, + 3 + ); + + $result = perflab_aea_get_asset_size( 'https://example.com/nocache.js' ); + $this->assertWPError( $result ); + $this->assertSame( 'non_cacheable', $result->get_error_code() ); + $this->assertStringContainsString( 'no-store', $result->get_error_message() ); + } + /** * Tests perflab_aea_copy_basic_auth_headers() with various scenarios. *