Skip to content

Commit 6b65e29

Browse files
authored
Fix issues with clearing out globals that results in orphaned data. Account for sitemaps, feeds, and robots in testing. (#856)
* Migrating to contract for responses * Adding a test for feeds * Remove mutable from test response class * Adding tests for sitemaps and a fake for sitemap renderer * Docblock * Reduce test * Pass through to the sitemap renderer set instead of creating a new one * Clear out wp_stylesheet_path * Add reset_lazyload_queue from core * Wrapping up with a need to fix locate_template * Adding Makes_Http_Requests_With_Feeds * CHANGELOG * Drop comments * Ensure rewrite is properly preserved and cleared (#857) * Backup query variables between test runs * Wrapping up wp_rewrite backup * Use a deep copy when retrieving value * Ensure rewrite is properly preserved and cleared * CHANGELOG order * Bump jetpack to 14.1 * Drop jetpack * Reduce matrix
1 parent 980f69b commit 6b65e29

22 files changed

+931
-111
lines changed

.github/workflows/tests.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ jobs:
8888
# https://make.wordpress.org/core/handbook/references/php-compatibility-and-wordpress-versions/
8989
php: [8.2, 8.3, 8.4, 8.5]
9090
wordpress: ${{fromJson(needs.get-versions.outputs.versions)}}
91-
vip-mu-plugins: [true, false]
91+
vip-mu-plugins: [false]
9292
exclude:
9393
# WordPress 6.7 added support for PHP 8.4.
9494
- php: 8.4
@@ -108,6 +108,17 @@ jobs:
108108
- php: 8.5
109109
wordpress: "6.5"
110110
include:
111+
# Test with VIP MU Plugins enabled (using latest version of WordPress to prevent Jetpack compatibility issues).
112+
- php: 8.4
113+
wordpress: "latest"
114+
dependencies: "prefer-stable"
115+
multisite: true
116+
vip-mu-plugins: true
117+
- php: 8.4
118+
wordpress: "latest"
119+
dependencies: "prefer-stable"
120+
multisite: false
121+
vip-mu-plugins: true
111122
# Test with prefer-lowest selectively to check for compatibility issues with older dependencies.
112123
- php: 8.3
113124
wordpress: "latest"

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
- Added proper support for testing `robots.txt`, feeds, and sitemaps via the testing
13+
framework's HTTP request methods.
14+
- Added a `Spy_Sitemaps_Renderer` class to replace the sitemap renderer during testing
15+
to allow for spying on sitemap render calls.
16+
1017
### Changed
1118

19+
- **Potentially breaking:** The `$wp_rewrite` global and public query variables in `WP`
20+
are now backed up before any tests are run and restored before each test to prevent
21+
side effects from tests that modify these globals. This will allow tests to properly
22+
modify the rewrite rules and public query variables without affecting other tests.
1223
- Adds PHPStan testing to more traits.
24+
- Clear object metadata lazy load queue when tearing down a test.
1325

1426
## v1.16.0
1527

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"league/uri": "^7.5",
2828
"league/uri-interfaces": "^7.5",
2929
"monolog/monolog": "^2.9.1",
30+
"myclabs/deep-copy": "^1.13",
3031
"nesbot/carbon": "^3.8.4",
3132
"nette/php-generator": "^4.1",
3233
"nunomaduro/termwind": "^1.15.1 || ^2.0",
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* Mutable_Response interface file
4+
*
5+
* @package Mantle
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Mantle\Contracts\Http;
11+
12+
/**
13+
* Mutable Response Interface
14+
*
15+
* Consistent response interface for HTTP responses used across packages for a
16+
* mutable response.
17+
*/
18+
interface Mutable_Response extends Response {
19+
/**
20+
* Set the response status code.
21+
*
22+
* @param int $code The status code to set.
23+
*/
24+
public function set_status_code( int $code ): void;
25+
26+
/**
27+
* Set the response headers.
28+
*
29+
* @param array<string, string> $headers The headers to set.
30+
*/
31+
public function set_headers( array $headers ): void;
32+
33+
/**
34+
* Set a response header.
35+
*
36+
* @param string $name The header name.
37+
* @param string|string[] $value The header value.
38+
*/
39+
public function set_header( string $name, string|array $value ): void;
40+
41+
/**
42+
* Delete a response header.
43+
*
44+
* @param string $name The header name to delete.
45+
*/
46+
public function delete_header( string $name ): void;
47+
48+
/**
49+
* Set the response body.
50+
*
51+
* @param ?string $body The response body to set.
52+
*/
53+
public function set_body( ?string $body ): void;
54+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
/**
3+
* Response interface file
4+
*
5+
* @package Mantle
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Mantle\Contracts\Http;
11+
12+
/**
13+
* Response Interface
14+
*
15+
* Consistent response interface for HTTP responses used across packages.
16+
*/
17+
interface Response {
18+
/**
19+
* Retrieve the response status code.
20+
*/
21+
public function get_status_code(): int;
22+
23+
/**
24+
* Retrieve all response headers.
25+
*
26+
* @return array<string, string> Array of headers.
27+
*/
28+
public function get_headers(): array;
29+
30+
/**
31+
* Retrieve a specific response header.
32+
*
33+
* @param string $name The header name to retrieve.
34+
* @param bool $as_array Whether to return the header as an array.
35+
* @return ($as_array is true ? array<string> : string|null) The header value(s), or null if not found.
36+
*/
37+
public function get_header( string $name, bool $as_array = false ): string|array|null;
38+
39+
/**
40+
* Retrieve the response body.
41+
*
42+
* @return string|null The response body.
43+
*/
44+
public function get_body(): ?string;
45+
}

src/mantle/http-client/class-response.php

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use ArrayAccess;
1313
use LogicException;
14+
use Mantle\Contracts\Http\Response as ResponseContract;
1415
use Mantle\Support\Collection;
1516
use Mantle\Support\HTML;
1617
use Mantle\Support\Mixed_Data;
@@ -53,7 +54,7 @@
5354
* http_response?: \WP_HTTP_Requests_Response,
5455
* }
5556
*/
56-
class Response implements ArrayAccess {
57+
class Response implements ArrayAccess, ResponseContract {
5758
use Concerns\Interacts_With_Feeds;
5859
use Macroable;
5960

@@ -159,21 +160,58 @@ public function response(): array {
159160
/**
160161
* Retrieve all the headers from a response.
161162
*
162-
* @return array<string, string> Headers from the response.
163+
* @return array<string, string|string[]> Headers from the response.
163164
*/
164165
public function headers(): array {
165166
return (array) ( $this->response['headers'] ?? [] );
166167
}
167168

169+
/**
170+
* Alias for headers().
171+
*
172+
* @return array<string, string|string[]> Headers from the response.
173+
*/
174+
public function get_headers(): array {
175+
return $this->headers();
176+
}
177+
168178
/**
169179
* Retrieve a specific header (headers are case-insensitive).
170180
*
181+
* Similar to get_header().
182+
*
183+
* In the event of multiple headers with the same name, the first header will
184+
* be returned. If you need all headers, use `get_headers()`.
185+
*
171186
* @param string $header Header to retrieve.
172-
* @return mixed
187+
* @return string|null Header value(s), or null if not found.
173188
*/
174-
public function header( string $header ) {
175-
$header = strtolower( $header );
176-
return $this->headers()[ $header ] ?? null;
189+
public function header( string $header ): ?string {
190+
return $this->get_header( $header );
191+
}
192+
193+
/**
194+
* Retrieve a specific response header.
195+
*
196+
* @param string $name The header name to retrieve.
197+
* @param bool $as_array Whether to return the header as an array.
198+
* @return ($as_array is true ? array<string> : string|null) The header value(s), or null if not found.
199+
*/
200+
public function get_header( string $name, bool $as_array = false ): string|array|null {
201+
$header = strtolower( $name );
202+
$headers = $this->headers();
203+
204+
if ( ! array_key_exists( $header, $headers ) ) {
205+
return null;
206+
}
207+
208+
$value = $headers[ $header ];
209+
210+
if ( is_array( $value ) ) {
211+
return $as_array ? $value : $value[0] ?? '';
212+
}
213+
214+
return $as_array ? [ $value ] : $value;
177215
}
178216

179217
/**
@@ -183,6 +221,13 @@ public function status(): int {
183221
return (int) ( $this->response['response']['code'] ?? 0 );
184222
}
185223

224+
/**
225+
* Retrieve the response status code.
226+
*/
227+
public function get_status_code(): int {
228+
return $this->status();
229+
}
230+
186231
/**
187232
* Determine if the request was successful.
188233
*/
@@ -304,6 +349,15 @@ public function body(): string {
304349
return (string) ( $this->response['body'] ?? '' );
305350
}
306351

352+
/**
353+
* Alias for body().
354+
*
355+
* @return string|null The response body.
356+
*/
357+
public function get_body(): ?string {
358+
return $this->body();
359+
}
360+
307361
/**
308362
* Retrieve the file path to the downloaded file.
309363
*/

src/mantle/http-client/concerns/trait-interacts-with-feeds.php

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
/**
33
* Interacts_With_Feeds trait file
44
*
5+
* phpcs:disable WordPress.NamingConventions.ValidFunctionName
6+
*
57
* @package Mantle
68
*/
79

@@ -10,22 +12,23 @@
1012
namespace Mantle\Http_Client\Concerns;
1113

1214
use Closure;
15+
use PHPUnit\Framework\Assert;
1316
use WP_SimplePie_Sanitize_KSES;
1417

1518
/**
1619
* Integrations with feed responses.
1720
*
18-
* @mixin \Mantle\Http_Client\Response
21+
* @mixin \Mantle\Contracts\Http\Response
1922
*/
2023
trait Interacts_With_Feeds {
2124
/**
2225
* Check if the response is a feed.
2326
*/
2427
public function is_feed(): bool {
25-
return false !== strpos( (string) $this->header( 'content-type' ), 'application/rss+xml' ) ||
26-
false !== strpos( (string) $this->header( 'content-type' ), 'application/atom+xml' ) ||
27-
false !== strpos( (string) $this->header( 'content-type' ), 'application/xml' ) ||
28-
false !== strpos( (string) $this->header( 'content-type' ), 'text/xml' );
28+
return false !== strpos( (string) $this->get_header( 'content-type' ), 'application/rss+xml' ) ||
29+
false !== strpos( (string) $this->get_header( 'content-type' ), 'application/atom+xml' ) ||
30+
false !== strpos( (string) $this->get_header( 'content-type' ), 'application/xml' ) ||
31+
false !== strpos( (string) $this->get_header( 'content-type' ), 'text/xml' );
2932
}
3033

3134
/**
@@ -59,7 +62,7 @@ public function feed( ?Closure $options = null ): \SimplePie {
5962
*/
6063
$feed->sanitize = new \WP_SimplePie_Sanitize_KSES();
6164

62-
$feed->set_raw_data( $this->body() );
65+
$feed->set_raw_data( (string) $this->get_body() );
6366

6467
if ( $options instanceof Closure ) {
6568
$options( $feed );
@@ -70,4 +73,28 @@ public function feed( ?Closure $options = null ): \SimplePie {
7073

7174
return $feed; // @phpstan-ignore-line return.type
7275
}
76+
77+
/**
78+
* Assert that the response is a feed.
79+
*/
80+
public function assertIsFeed(): static {
81+
Assert::assertTrue(
82+
$this->is_feed(),
83+
'Failed asserting that the response is a feed.',
84+
);
85+
86+
return $this;
87+
}
88+
89+
/**
90+
* Assert that the response is not a feed.
91+
*/
92+
public function assertIsNotFeed(): static {
93+
Assert::assertFalse(
94+
$this->is_feed(),
95+
'Failed asserting that the response is not a feed.',
96+
);
97+
98+
return $this;
99+
}
73100
}

src/mantle/testing/TestCase.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,6 @@ function ( string|object $trait ): void {
270270
'comment_depth',
271271
'comment_thread_alt',
272272

273-
// Sitemap globals.
274-
'wp_sitemaps',
275-
276273
// Template globals.
277274
'wp_stylesheet_path',
278275
'wp_template_path',
@@ -289,6 +286,7 @@ function ( string|object $trait ): void {
289286
remove_filter( 'wp_die_handler', [ WP_Die::class, 'get_handler' ] );
290287
static::restore_hooks();
291288
wp_set_current_user( 0 );
289+
$this->reset_lazyload_queue();
292290
// phpcs:enable
293291

294292
parent::tearDown();
@@ -413,4 +411,14 @@ public function __get( string $name ): mixed {
413411
public static function usesTrait( string $trait ): bool {
414412
return isset( static::$test_uses[ $trait ] );
415413
}
414+
415+
/**
416+
* Reset the lazy load meta queue.
417+
*/
418+
protected function reset_lazyload_queue(): void {
419+
$lazyloader = wp_metadata_lazyloader();
420+
$lazyloader->reset_queue( 'term' );
421+
$lazyloader->reset_queue( 'comment' );
422+
$lazyloader->reset_queue( 'blog' ); // @phpstan-ignore-line argument.type
423+
}
416424
}

0 commit comments

Comments
 (0)