Skip to content

Commit 290778b

Browse files
pattonwebzclaude
andcommitted
Add standalone tests for Synced_Capability
Uses a throwaway capability/option pair, independent of edac_ignore_issues, to cover sync/register/manage_options-bypass/ permission_callback plus the migration behavior that had no direct test coverage before this class existed: initial migration on an unset option, no re-run at the same version, and a version bump forcing re-sync. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent f12e49d commit 290778b

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
<?php
2+
/**
3+
* Tests for Synced_Capability, independent of any specific feature's use of it.
4+
*
5+
* @package Accessibility_Checker
6+
*/
7+
8+
use EqualizeDigital\AccessibilityChecker\Capabilities\Synced_Capability;
9+
10+
/**
11+
* Covers the reusable sync/bypass/migration/REST-callback behavior in
12+
* isolation, using a capability string not used by any real feature so
13+
* these tests can't collide with edac_ignore_issues's own test coverage
14+
* (IgnoreCapabilityTest) or leftover role state from it.
15+
*/
16+
class SyncedCapabilityTest extends WP_UnitTestCase {
17+
18+
/**
19+
* Capability string used only by this test class.
20+
*
21+
* @var string
22+
*/
23+
private const TEST_CAP = 'edac_test_synced_capability';
24+
25+
/**
26+
* Option name used only by this test class.
27+
*
28+
* @var string
29+
*/
30+
private const TEST_OPTION = 'edac_test_synced_capability_roles';
31+
32+
/**
33+
* Remove the capability from every role and the option/migration
34+
* markers after each test so they don't leak into each other.
35+
*
36+
* @return void
37+
*/
38+
public function tearDown(): void {
39+
foreach ( wp_roles()->role_objects as $role ) {
40+
$role->remove_cap( self::TEST_CAP );
41+
}
42+
delete_option( self::TEST_OPTION );
43+
delete_option( 'edac_capability_version_' . self::TEST_CAP );
44+
wp_set_current_user( 0 );
45+
parent::tearDown();
46+
}
47+
48+
/**
49+
* Sync() should add the capability only to the roles passed in, and
50+
* remove it from roles not included.
51+
*
52+
* @return void
53+
*/
54+
public function test_sync_adds_and_removes_capability_by_role() {
55+
$capability = new Synced_Capability( self::TEST_CAP, self::TEST_OPTION );
56+
57+
wp_roles()->get_role( 'editor' )->add_cap( self::TEST_CAP );
58+
59+
$capability->sync( [ 'author' ] );
60+
61+
$this->assertFalse( wp_roles()->get_role( 'editor' )->has_cap( self::TEST_CAP ) );
62+
$this->assertTrue( wp_roles()->get_role( 'author' )->has_cap( self::TEST_CAP ) );
63+
}
64+
65+
/**
66+
* Register() should wire live sync to the option's add/update hooks.
67+
*
68+
* @return void
69+
*/
70+
public function test_register_syncs_on_option_save() {
71+
$capability = new Synced_Capability( self::TEST_CAP, self::TEST_OPTION );
72+
$capability->register();
73+
74+
add_option( self::TEST_OPTION, [ 'author' ] );
75+
$this->assertTrue( wp_roles()->get_role( 'author' )->has_cap( self::TEST_CAP ) );
76+
77+
update_option( self::TEST_OPTION, [ 'editor' ] );
78+
$this->assertFalse( wp_roles()->get_role( 'author' )->has_cap( self::TEST_CAP ) );
79+
$this->assertTrue( wp_roles()->get_role( 'editor' )->has_cap( self::TEST_CAP ) );
80+
}
81+
82+
/**
83+
* Manage_options users must always pass user_can(), regardless of
84+
* whether their role was synced.
85+
*
86+
* @return void
87+
*/
88+
public function test_manage_options_bypasses_sync() {
89+
$capability = new Synced_Capability( self::TEST_CAP, self::TEST_OPTION );
90+
$capability->register();
91+
$capability->sync( [ 'author' ] );
92+
93+
$admin_id = self::factory()->user->create( [ 'role' => 'administrator' ] );
94+
wp_set_current_user( $admin_id );
95+
96+
$this->assertFalse( wp_roles()->get_role( 'administrator' )->has_cap( self::TEST_CAP ), 'Precondition: administrator role itself was not synced.' );
97+
$this->assertTrue( $capability->user_can() );
98+
}
99+
100+
/**
101+
* Permission_callback() should return a callable proxying user_can(),
102+
* suitable for a REST route's permission_callback directly.
103+
*
104+
* @return void
105+
*/
106+
public function test_permission_callback_proxies_user_can() {
107+
$capability = new Synced_Capability( self::TEST_CAP, self::TEST_OPTION );
108+
$capability->sync( [ 'author' ] );
109+
110+
$callback = $capability->permission_callback();
111+
$this->assertIsCallable( $callback );
112+
113+
$author_id = self::factory()->user->create( [ 'role' => 'author' ] );
114+
wp_set_current_user( $author_id );
115+
$this->assertTrue( $callback() );
116+
117+
$subscriber_id = self::factory()->user->create( [ 'role' => 'subscriber' ] );
118+
wp_set_current_user( $subscriber_id );
119+
$this->assertFalse( $callback() );
120+
}
121+
122+
/**
123+
* Maybe_migrate() should run the initial sync from default_roles when
124+
* the option was never set and no migration has run yet.
125+
*
126+
* @return void
127+
*/
128+
public function test_migration_runs_once_for_unset_option() {
129+
$capability = new Synced_Capability( self::TEST_CAP, self::TEST_OPTION, [ 'editor' ] );
130+
131+
$capability->maybe_migrate();
132+
133+
$this->assertTrue( wp_roles()->get_role( 'editor' )->has_cap( self::TEST_CAP ) );
134+
}
135+
136+
/**
137+
* Maybe_migrate() should not re-run (and shouldn't clobber roles synced
138+
* some other way since) once it has already run for the current version.
139+
*
140+
* @return void
141+
*/
142+
public function test_migration_does_not_rerun_for_same_version() {
143+
$capability = new Synced_Capability( self::TEST_CAP, self::TEST_OPTION, [ 'editor' ] );
144+
$capability->maybe_migrate();
145+
146+
// Simulate the site's config changing after the one-time migration ran.
147+
$capability->sync( [ 'author' ] );
148+
149+
$capability->maybe_migrate();
150+
151+
$this->assertFalse( wp_roles()->get_role( 'editor' )->has_cap( self::TEST_CAP ), 'Migration should not have re-applied default_roles.' );
152+
$this->assertTrue( wp_roles()->get_role( 'author' )->has_cap( self::TEST_CAP ) );
153+
}
154+
155+
/**
156+
* Bumping the version should force maybe_migrate() to re-sync even
157+
* though an earlier version's migration already ran once.
158+
*
159+
* @return void
160+
*/
161+
public function test_version_bump_forces_remigration() {
162+
$v1 = new Synced_Capability( self::TEST_CAP, self::TEST_OPTION, [ 'editor' ], 1 );
163+
$v1->maybe_migrate();
164+
165+
// Site never saved the option, so it's still on default_roles from v1.
166+
$this->assertTrue( wp_roles()->get_role( 'editor' )->has_cap( self::TEST_CAP ) );
167+
168+
$v2 = new Synced_Capability( self::TEST_CAP, self::TEST_OPTION, [ 'author' ], 2 );
169+
$v2->maybe_migrate();
170+
171+
$this->assertTrue( wp_roles()->get_role( 'author' )->has_cap( self::TEST_CAP ), 'v2 default_roles should have been applied.' );
172+
$this->assertFalse( wp_roles()->get_role( 'editor' )->has_cap( self::TEST_CAP ), 'v1 default_roles should no longer apply after the v2 re-sync.' );
173+
}
174+
}

0 commit comments

Comments
 (0)