Skip to content

Commit 135a187

Browse files
committed
Tests: Cover five more admin AJAX handlers.
Add tests for `wp_ajax_imgedit_preview()`, `wp_ajax_get_community_events()`, `wp_ajax_hidden_columns()`, `wp_ajax_save_widget()`, and `wp_ajax_save_user_color_scheme()`. Each test dispatches the action core registers through `get_ajax_response()` and checks the response and the resulting state. The community events tests mock the Events API, so they make no network requests. The save-widget tests use a per-test Text widget and restore the widget globals afterward. This consolidates five earlier handler test proposals. Developed in: #15421 Props mindctrl, pbearne. See #65252. git-svn-id: https://develop.svn.wordpress.org/trunk@65267 602fd350-edb4-49c9-b593-d223f7449a82
1 parent a7bb8a9 commit 135a187

5 files changed

Lines changed: 1010 additions & 0 deletions

File tree

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
<?php
2+
3+
/**
4+
* Admin Ajax functions to be tested.
5+
*/
6+
require_once ABSPATH . 'wp-admin/includes/ajax-actions.php';
7+
8+
/**
9+
* Testing wp_ajax_get_community_events() functionality.
10+
*
11+
* @package WordPress
12+
* @subpackage UnitTests
13+
*
14+
* @group ajax
15+
*
16+
* @covers ::wp_ajax_get_community_events
17+
*/
18+
class Tests_Admin_Includes_AjaxActions_getCommunityEvents extends WP_Ajax_UnitTestCase {
19+
20+
/**
21+
* Administrator user ID.
22+
*
23+
* @var int
24+
*/
25+
protected static $admin_id;
26+
27+
/**
28+
* Original $_SERVER values.
29+
*
30+
* @var array
31+
*/
32+
private $server_backup;
33+
34+
/**
35+
* Requests made to the Events API.
36+
*
37+
* @var array[]
38+
*/
39+
private $requests = array();
40+
41+
/**
42+
* Mocked Events API response, or a WP_Error.
43+
*
44+
* @var array|WP_Error
45+
*/
46+
private $api_response;
47+
48+
/**
49+
* Setup test fixtures.
50+
*
51+
* @param WP_UnitTest_Factory $factory
52+
*/
53+
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ): void {
54+
self::$admin_id = $factory->user->create( array( 'role' => 'administrator' ) );
55+
}
56+
57+
/**
58+
* Setup before each test method.
59+
*/
60+
public function set_up(): void {
61+
parent::set_up();
62+
63+
// The test harness does not register this action.
64+
add_action( 'wp_ajax_get-community-events', 'wp_ajax_get_community_events', 1 );
65+
add_filter( 'pre_http_request', array( $this, 'mock_events_api' ), 10, 3 );
66+
67+
// WP_Community_Events sends the anonymized client IP, so make it predictable.
68+
$this->server_backup = $_SERVER;
69+
foreach ( array( 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED' ) as $header ) {
70+
unset( $_SERVER[ $header ] );
71+
}
72+
$_SERVER['REMOTE_ADDR'] = '203.0.113.5';
73+
74+
$this->api_response = $this->get_api_response_body();
75+
76+
wp_set_current_user( self::$admin_id );
77+
}
78+
79+
/**
80+
* Tear down after each test method.
81+
*/
82+
public function tear_down(): void {
83+
$_SERVER = $this->server_backup;
84+
parent::tear_down();
85+
}
86+
87+
/**
88+
* Mocks requests to the Events API.
89+
*
90+
* @param false|array|WP_Error $response Preemptive response.
91+
* @param array $args Request arguments.
92+
* @param string $url Request URL.
93+
* @return array|WP_Error Mocked response.
94+
*/
95+
public function mock_events_api( $response, $args, $url ) {
96+
$this->requests[] = array(
97+
'url' => $url,
98+
'args' => $args,
99+
);
100+
101+
if ( is_wp_error( $this->api_response ) ) {
102+
return $this->api_response;
103+
}
104+
105+
return array(
106+
'headers' => array(),
107+
'body' => wp_json_encode( $this->api_response['body'] ),
108+
'response' => array(
109+
'code' => $this->api_response['code'],
110+
'message' => '',
111+
),
112+
'cookies' => array(),
113+
'filename' => null,
114+
);
115+
}
116+
117+
/**
118+
* Returns a successful Events API response with one expired and two upcoming events.
119+
*
120+
* @return array
121+
*/
122+
private function get_api_response_body(): array {
123+
return array(
124+
'code' => 200,
125+
'body' => array(
126+
'location' => array(
127+
'description' => 'Paris',
128+
'latitude' => '48.857',
129+
'longitude' => '2.352',
130+
'country' => 'FR',
131+
'ip' => '198.51.100.99',
132+
),
133+
'events' => array(
134+
array(
135+
'type' => 'meetup',
136+
'title' => 'Past Meetup',
137+
'end_unix_timestamp' => time() - DAY_IN_SECONDS,
138+
),
139+
array(
140+
'type' => 'meetup',
141+
'title' => 'Paris Meetup',
142+
'end_unix_timestamp' => time() + DAY_IN_SECONDS,
143+
),
144+
array(
145+
'type' => 'wordcamp',
146+
'title' => 'WordCamp Paris',
147+
'end_unix_timestamp' => time() + WEEK_IN_SECONDS,
148+
),
149+
),
150+
'ttl' => HOUR_IN_SECONDS,
151+
),
152+
);
153+
}
154+
155+
/**
156+
* Returns the location the handler saves for the default API response.
157+
*
158+
* The API's IP is replaced with the anonymized IP sent in the request.
159+
*
160+
* @return array
161+
*/
162+
private function get_expected_location(): array {
163+
$location = $this->api_response['body']['location'];
164+
$location['ip'] = '203.0.113.0';
165+
166+
return $location;
167+
}
168+
169+
/**
170+
* @ticket 65252
171+
*/
172+
public function test_get_community_events_searches_location(): void {
173+
// The client IP is unchanged, so only the search should replace the saved location.
174+
update_user_meta(
175+
self::$admin_id,
176+
'community-events-location',
177+
array(
178+
'description' => 'Lyon',
179+
'ip' => '203.0.113.0',
180+
)
181+
);
182+
183+
$_POST = array(
184+
'_ajax_nonce' => wp_create_nonce( 'community_events' ),
185+
'location' => 'Paris',
186+
'timezone' => 'Europe/Paris',
187+
);
188+
189+
$response = json_decode( $this->get_ajax_response( 'get-community-events' ), true );
190+
191+
$this->assertTrue( $response['success'] );
192+
$this->assertSame( $this->get_expected_location(), $response['data']['location'] );
193+
$this->assertSame( array( 'Paris Meetup', 'WordCamp Paris' ), wp_list_pluck( $response['data']['events'], 'title' ), 'Expired events should be removed.' );
194+
$this->assertSame( $this->get_expected_location(), get_user_meta( self::$admin_id, 'community-events-location', true ) );
195+
196+
$this->assertCount( 1, $this->requests );
197+
$this->assertSame( 'api.wordpress.org', wp_parse_url( $this->requests[0]['url'], PHP_URL_HOST ) );
198+
$this->assertSame( '/events/1.0/', wp_parse_url( $this->requests[0]['url'], PHP_URL_PATH ) );
199+
$this->assertSame( 'Paris', $this->requests[0]['args']['body']['location'] );
200+
$this->assertSame( 'Europe/Paris', $this->requests[0]['args']['body']['timezone'] );
201+
$this->assertSame( '203.0.113.0', $this->requests[0]['args']['body']['ip'] );
202+
}
203+
204+
/**
205+
* Without a location search, the saved location is only replaced when the client IP changes.
206+
*
207+
* @ticket 65252
208+
*
209+
* @dataProvider data_saved_location_without_search
210+
*
211+
* @param array|null $saved_location Location saved before the request, or null for none.
212+
* @param bool $response_ip Whether the API response includes an IP.
213+
* @param bool $should_update Whether the saved location should be replaced.
214+
*/
215+
public function test_get_community_events_updates_saved_location_when_ip_changes( ?array $saved_location, bool $response_ip, bool $should_update ): void {
216+
if ( null !== $saved_location ) {
217+
update_user_meta( self::$admin_id, 'community-events-location', $saved_location );
218+
}
219+
220+
if ( ! $response_ip ) {
221+
unset( $this->api_response['body']['location']['ip'] );
222+
}
223+
224+
$_POST = array(
225+
'_ajax_nonce' => wp_create_nonce( 'community_events' ),
226+
);
227+
228+
$response = json_decode( $this->get_ajax_response( 'get-community-events' ), true );
229+
230+
$this->assertTrue( $response['success'] );
231+
$this->assertCount( 1, $this->requests );
232+
233+
if ( $should_update ) {
234+
$expected = $this->get_expected_location();
235+
} else {
236+
$expected = $saved_location ?? '';
237+
}
238+
239+
$this->assertSame( $expected, get_user_meta( self::$admin_id, 'community-events-location', true ) );
240+
}
241+
242+
/**
243+
* Data provider.
244+
*
245+
* @return array<string, array{ 0: array|null, 1: bool, 2: bool }>
246+
*/
247+
public static function data_saved_location_without_search(): array {
248+
$saved_location = array(
249+
'description' => 'Lyon',
250+
'latitude' => '45.764',
251+
'longitude' => '4.836',
252+
'country' => 'FR',
253+
);
254+
255+
return array(
256+
'no saved location' => array( null, true, true ),
257+
'saved location without an IP' => array( $saved_location, true, true ),
258+
'saved IP differs from client IP' => array( array_merge( $saved_location, array( 'ip' => '198.51.100.0' ) ), true, true ),
259+
'saved IP matches client IP' => array( array_merge( $saved_location, array( 'ip' => '203.0.113.0' ) ), true, false ),
260+
'API response has no IP' => array( null, false, false ),
261+
);
262+
}
263+
264+
/**
265+
* @ticket 65252
266+
*/
267+
public function test_get_community_events_returns_error_for_api_error_code(): void {
268+
$this->api_response = array(
269+
'code' => 500,
270+
'body' => array(),
271+
);
272+
273+
$_POST = array(
274+
'_ajax_nonce' => wp_create_nonce( 'community_events' ),
275+
'location' => 'Paris',
276+
);
277+
278+
$this->assertSame(
279+
array(
280+
'success' => false,
281+
'data' => array( 'error' => 'Invalid API response code (500).' ),
282+
),
283+
json_decode( $this->get_ajax_response( 'get-community-events' ), true )
284+
);
285+
$this->assertSame( '', get_user_meta( self::$admin_id, 'community-events-location', true ) );
286+
}
287+
288+
/**
289+
* @ticket 65252
290+
*/
291+
public function test_get_community_events_returns_error_for_failed_request(): void {
292+
$this->api_response = new WP_Error( 'http_request_failed', 'Mock connection failure.' );
293+
294+
$_POST = array(
295+
'_ajax_nonce' => wp_create_nonce( 'community_events' ),
296+
'location' => 'Paris',
297+
);
298+
299+
$this->assertSame(
300+
array(
301+
'success' => false,
302+
'data' => array( 'error' => 'Mock connection failure.' ),
303+
),
304+
json_decode( $this->get_ajax_response( 'get-community-events' ), true )
305+
);
306+
$this->assertSame( '', get_user_meta( self::$admin_id, 'community-events-location', true ) );
307+
}
308+
309+
/**
310+
* @ticket 65252
311+
*/
312+
public function test_get_community_events_rejects_invalid_nonce(): void {
313+
$_POST = array(
314+
'_ajax_nonce' => 'invalid-nonce',
315+
'location' => 'Paris',
316+
);
317+
318+
$this->assertSame( '-1', $this->get_ajax_response( 'get-community-events' ) );
319+
$this->assertCount( 0, $this->requests, 'No API request should be made.' );
320+
$this->assertSame( '', get_user_meta( self::$admin_id, 'community-events-location', true ) );
321+
}
322+
}

0 commit comments

Comments
 (0)