Skip to content

Commit 059e463

Browse files
committed
Tests: Cover plugin paused-state and recovery functions.
Add coverage for `is_plugin_paused()`, `wp_get_plugin_error()`, and `resume_plugin()`, including successful removal of paused state and failure to resume. Restore the original recovery-mode state after each test. Developed in: #14156 Props huzaifaalmesbah. See #65819. git-svn-id: https://develop.svn.wordpress.org/trunk@64307 602fd350-edb4-49c9-b593-d223f7449a82
1 parent d467743 commit 059e463

1 file changed

Lines changed: 297 additions & 0 deletions

File tree

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
<?php
2+
3+
/**
4+
* Tests for is_plugin_paused(), wp_get_plugin_error(), and resume_plugin().
5+
*
6+
* @group admin
7+
* @group plugins
8+
* @group error-protection
9+
*/
10+
class Tests_Admin_Includes_Plugin_PluginPausedState extends WP_UnitTestCase {
11+
12+
const TEST_SESSION_ID = 'test_recovery_session_123';
13+
14+
/**
15+
* Original $_paused_plugins global value.
16+
*
17+
* @var array|null
18+
*/
19+
private $orig_paused_plugins;
20+
21+
/**
22+
* Original recovery mode is_active property.
23+
*
24+
* @var bool
25+
*/
26+
private $orig_recovery_mode_is_active;
27+
28+
/**
29+
* Original recovery mode session_id property.
30+
*
31+
* @var string
32+
*/
33+
private $orig_recovery_mode_session_id;
34+
35+
/**
36+
* Sets up the environment before each test.
37+
*/
38+
public function set_up() {
39+
parent::set_up();
40+
41+
require_once ABSPATH . 'wp-admin/includes/plugin.php';
42+
43+
$this->orig_paused_plugins = isset( $GLOBALS['_paused_plugins'] ) ? $GLOBALS['_paused_plugins'] : null;
44+
45+
$GLOBALS['_paused_plugins'] = array();
46+
47+
$this->orig_recovery_mode_is_active = $this->get_recovery_mode_property( 'is_active' );
48+
$this->orig_recovery_mode_session_id = $this->get_recovery_mode_property( 'session_id' );
49+
}
50+
51+
/**
52+
* Cleans up the environment after each test.
53+
*/
54+
public function tear_down() {
55+
if ( null !== $this->orig_paused_plugins ) {
56+
$GLOBALS['_paused_plugins'] = $this->orig_paused_plugins;
57+
} else {
58+
unset( $GLOBALS['_paused_plugins'] );
59+
}
60+
61+
delete_option( self::TEST_SESSION_ID . '_paused_extensions' );
62+
$this->set_recovery_mode_state( $this->orig_recovery_mode_is_active, $this->orig_recovery_mode_session_id );
63+
64+
parent::tear_down();
65+
}
66+
67+
/**
68+
* Gets WP_Recovery_Mode property value using reflection.
69+
*
70+
* @param string $property Property name.
71+
* @return mixed Property value.
72+
*/
73+
private function get_recovery_mode_property( $property ) {
74+
$recovery_mode = wp_recovery_mode();
75+
$prop = new ReflectionProperty( $recovery_mode, $property );
76+
if ( PHP_VERSION_ID < 80100 ) {
77+
$prop->setAccessible( true );
78+
}
79+
return $prop->getValue( $recovery_mode );
80+
}
81+
82+
/**
83+
* Sets WP_Recovery_Mode protected state using reflection.
84+
*
85+
* @param bool $is_active Whether recovery mode is active.
86+
* @param string $session_id Recovery mode session ID.
87+
*/
88+
private function set_recovery_mode_state( $is_active, $session_id ) {
89+
$recovery_mode = wp_recovery_mode();
90+
91+
$active_prop = new ReflectionProperty( $recovery_mode, 'is_active' );
92+
if ( PHP_VERSION_ID < 80100 ) {
93+
$active_prop->setAccessible( true );
94+
}
95+
$active_prop->setValue( $recovery_mode, $is_active );
96+
97+
$session_prop = new ReflectionProperty( $recovery_mode, 'session_id' );
98+
if ( PHP_VERSION_ID < 80100 ) {
99+
$session_prop->setAccessible( true );
100+
}
101+
$session_prop->setValue( $recovery_mode, $session_id );
102+
}
103+
104+
/**
105+
* Tests that is_plugin_paused() returns false when $_paused_plugins global is not set.
106+
*
107+
* @ticket 65819
108+
*
109+
* @covers ::is_plugin_paused
110+
*/
111+
public function test_is_plugin_paused_returns_false_when_global_not_set() {
112+
unset( $GLOBALS['_paused_plugins'] );
113+
114+
$this->assertFalse( is_plugin_paused( 'test-plugin/test-plugin.php' ) );
115+
}
116+
117+
/**
118+
* Tests that is_plugin_paused() returns false when the plugin is not active.
119+
*
120+
* @ticket 65819
121+
*
122+
* @covers ::is_plugin_paused
123+
*/
124+
public function test_is_plugin_paused_returns_false_when_plugin_inactive() {
125+
$plugin = 'test-plugin/test-plugin.php';
126+
127+
$GLOBALS['_paused_plugins'] = array(
128+
'test-plugin' => array(
129+
'type' => E_ERROR,
130+
'message' => 'Fatal error',
131+
),
132+
);
133+
134+
update_option( 'active_plugins', array() );
135+
136+
$this->assertFalse( is_plugin_paused( $plugin ), 'is_plugin_paused() should return false when plugin is not active.' );
137+
}
138+
139+
/**
140+
* Tests that is_plugin_paused() returns false when the plugin is active but not in the paused list.
141+
*
142+
* @ticket 65819
143+
*
144+
* @covers ::is_plugin_paused
145+
*/
146+
public function test_is_plugin_paused_returns_false_when_active_but_not_paused() {
147+
$plugin = 'active-plugin/active-plugin.php';
148+
update_option( 'active_plugins', array( $plugin ) );
149+
150+
$GLOBALS['_paused_plugins'] = array(
151+
'other-plugin' => array(
152+
'type' => E_ERROR,
153+
'message' => 'Fatal error',
154+
),
155+
);
156+
157+
$this->assertFalse( is_plugin_paused( $plugin ) );
158+
}
159+
160+
/**
161+
* Tests that is_plugin_paused() returns true when the plugin is active and paused.
162+
*
163+
* @ticket 65819
164+
*
165+
* @covers ::is_plugin_paused
166+
*/
167+
public function test_is_plugin_paused_returns_true_when_active_and_paused() {
168+
$plugin = 'broken-plugin/broken-plugin.php';
169+
update_option( 'active_plugins', array( $plugin ) );
170+
171+
$GLOBALS['_paused_plugins'] = array(
172+
'broken-plugin' => array(
173+
'type' => E_ERROR,
174+
'message' => 'Fatal error',
175+
),
176+
);
177+
178+
$this->assertTrue( is_plugin_paused( $plugin ) );
179+
}
180+
181+
/**
182+
* Tests that is_plugin_paused() handles single-file plugins without a directory.
183+
*
184+
* @ticket 65819
185+
*
186+
* @covers ::is_plugin_paused
187+
*/
188+
public function test_is_plugin_paused_handles_single_file_plugin() {
189+
$plugin = 'single-file-plugin.php';
190+
update_option( 'active_plugins', array( $plugin ) );
191+
192+
$GLOBALS['_paused_plugins'] = array(
193+
'single-file-plugin.php' => array(
194+
'type' => E_ERROR,
195+
'message' => 'Fatal error',
196+
),
197+
);
198+
199+
$this->assertTrue( is_plugin_paused( $plugin ) );
200+
}
201+
202+
/**
203+
* Tests that wp_get_plugin_error() returns false when $_paused_plugins global is not set.
204+
*
205+
* @ticket 65819
206+
*
207+
* @covers ::wp_get_plugin_error
208+
*/
209+
public function test_wp_get_plugin_error_returns_false_when_global_not_set() {
210+
unset( $GLOBALS['_paused_plugins'] );
211+
212+
$this->assertFalse( wp_get_plugin_error( 'test-plugin/test-plugin.php' ) );
213+
}
214+
215+
/**
216+
* Tests that wp_get_plugin_error() returns false when plugin has no recorded error.
217+
*
218+
* @ticket 65819
219+
*
220+
* @covers ::wp_get_plugin_error
221+
*/
222+
public function test_wp_get_plugin_error_returns_false_when_not_paused() {
223+
$GLOBALS['_paused_plugins'] = array(
224+
'other-plugin' => array(
225+
'type' => E_ERROR,
226+
'message' => 'Fatal error',
227+
),
228+
);
229+
230+
$this->assertFalse( wp_get_plugin_error( 'unrecorded-plugin/unrecorded-plugin.php' ) );
231+
}
232+
233+
/**
234+
* Tests that wp_get_plugin_error() returns the recorded error array.
235+
*
236+
* @ticket 65819
237+
*
238+
* @covers ::wp_get_plugin_error
239+
*/
240+
public function test_wp_get_plugin_error_returns_recorded_error() {
241+
$error = array(
242+
'type' => E_ERROR,
243+
'message' => 'Call to undefined function fatal_function()',
244+
'file' => '/wp-content/plugins/failing-plugin/failing.php',
245+
'line' => 25,
246+
);
247+
248+
$GLOBALS['_paused_plugins'] = array(
249+
'failing-plugin' => $error,
250+
);
251+
252+
$this->assertSame( $error, wp_get_plugin_error( 'failing-plugin/failing.php' ) );
253+
}
254+
255+
/**
256+
* Tests that resume_plugin() successfully deletes paused state and returns true.
257+
*
258+
* @ticket 65819
259+
*
260+
* @covers ::resume_plugin
261+
*/
262+
public function test_resume_plugin_success() {
263+
$this->set_recovery_mode_state( true, self::TEST_SESSION_ID );
264+
265+
$error = array(
266+
'type' => E_ERROR,
267+
'message' => 'Fatal error occurred',
268+
'file' => '/wp-content/plugins/sample-plugin/sample.php',
269+
'line' => 10,
270+
);
271+
272+
$this->assertTrue( wp_paused_plugins()->set( 'sample-plugin', $error ) );
273+
$this->assertArrayHasKey( 'sample-plugin', wp_paused_plugins()->get_all() );
274+
275+
$result = resume_plugin( 'sample-plugin/sample.php' );
276+
277+
$this->assertTrue( $result, 'resume_plugin() should return true upon successful removal from paused list.' );
278+
$this->assertArrayNotHasKey( 'sample-plugin', wp_paused_plugins()->get_all() );
279+
}
280+
281+
/**
282+
* Tests that resume_plugin() returns WP_Error when deletion fails.
283+
*
284+
* @ticket 65819
285+
*
286+
* @covers ::resume_plugin
287+
*/
288+
public function test_resume_plugin_returns_wp_error_when_deletion_fails() {
289+
// When recovery mode is inactive, storage has no active session option, causing delete() to return false.
290+
$this->set_recovery_mode_state( false, '' );
291+
292+
$result = resume_plugin( 'non-paused-plugin/non-paused-plugin.php' );
293+
294+
$this->assertWPError( $result );
295+
$this->assertSame( 'could_not_resume_plugin', $result->get_error_code() );
296+
}
297+
}

0 commit comments

Comments
 (0)