44// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
55namespace Yoast \WP \SEO \Tests \Unit \AI \Consent \Application \Consent_Handler ;
66
7+ use Mockery ;
8+ use RuntimeException ;
9+ use Yoast \WP \SEO \AI \HTTP_Request \Domain \Exceptions \Forbidden_Exception ;
10+ use Yoast \WP \SEO \AI \HTTP_Request \Domain \Exceptions \Internal_Server_Error_Exception ;
11+ use Yoast \WP \SEO \AI \HTTP_Request \Domain \Exceptions \WP_Request_Exception ;
12+ use Yoast \WP \SEO \AI \HTTP_Request \Domain \Request ;
13+
714/**
815 * Tests the Consent_Handler's revoke_consent method.
916 *
1421final class Revoke_Consent_Test extends Abstract_Consent_Handler_Test {
1522
1623 /**
17- * Tests revoking the consent.
24+ * Tests revoking the consent on the happy path: token fetched, DELETE succeeds, local meta deleted .
1825 *
1926 * @return void
2027 */
21- public function test_revoke_consent () {
22- // Current user ID is used for the consent permission.
28+ public function test_revoke_consent_success () {
2329 $ user_id = 1 ;
30+ $ user = $ this ->stub_get_user_by ( $ user_id );
31+
32+ $ this ->token_manager ->expects ( 'get_or_request_access_token ' )
33+ ->once ()
34+ ->with ( $ user )
35+ ->andReturn ( 'jwt-token ' );
36+
37+ $ this ->request_handler ->expects ( 'handle ' )
38+ ->once ()
39+ ->with (
40+ Mockery::on (
41+ static function ( $ request ) {
42+ return $ request instanceof Request
43+ && $ request ->get_action_path () === '/user/consent '
44+ && $ request ->get_http_method () === Request::METHOD_DELETE
45+ && $ request ->get_headers () === [ 'Authorization ' => 'Bearer jwt-token ' ]
46+ && $ request ->get_body () === [];
47+ },
48+ ),
49+ );
50+
51+ $ this ->logger ->shouldNotReceive ( 'warning ' );
2452
2553 $ this ->user_helper ->expects ( 'delete_meta ' )
2654 ->once ()
@@ -29,4 +57,119 @@ public function test_revoke_consent() {
2957
3058 $ this ->instance ->revoke_consent ( $ user_id );
3159 }
60+
61+ /**
62+ * Tests that revoke_consent catches a Remote_Request_Exception thrown by the DELETE call,
63+ * logs a warning, and still clears the local meta (security-first).
64+ *
65+ * @return void
66+ */
67+ public function test_revoke_consent_swallows_remote_exception_on_delete () {
68+ $ user_id = 1 ;
69+ $ user = $ this ->stub_get_user_by ( $ user_id );
70+
71+ $ this ->token_manager ->expects ( 'get_or_request_access_token ' )
72+ ->once ()
73+ ->with ( $ user )
74+ ->andReturn ( 'jwt-token ' );
75+
76+ $ this ->request_handler ->expects ( 'handle ' )
77+ ->once ()
78+ ->andThrow ( new Internal_Server_Error_Exception ( 'Internal Server Error ' , 500 ) );
79+
80+ $ this ->logger ->expects ( 'warning ' )
81+ ->once ()
82+ ->with ( Mockery::type ( 'string ' ), Mockery::type ( 'array ' ) );
83+
84+ $ this ->user_helper ->expects ( 'delete_meta ' )
85+ ->once ()
86+ ->with ( $ user_id , '_yoast_wpseo_ai_consent ' )
87+ ->andReturn ( true );
88+
89+ $ this ->instance ->revoke_consent ( $ user_id );
90+ }
91+
92+ /**
93+ * Tests that revoke_consent catches a Remote_Request_Exception thrown while fetching the access
94+ * token, logs a warning, and still clears the local meta.
95+ *
96+ * @return void
97+ */
98+ public function test_revoke_consent_swallows_remote_exception_on_token_fetch () {
99+ $ user_id = 1 ;
100+ $ user = $ this ->stub_get_user_by ( $ user_id );
101+
102+ $ this ->token_manager ->expects ( 'get_or_request_access_token ' )
103+ ->once ()
104+ ->with ( $ user )
105+ ->andThrow ( new Forbidden_Exception ( 'Forbidden ' , 403 ) );
106+
107+ // DELETE should not be attempted when the token fetch fails.
108+ $ this ->request_handler ->shouldNotReceive ( 'handle ' );
109+
110+ $ this ->logger ->expects ( 'warning ' )
111+ ->once ()
112+ ->with ( Mockery::type ( 'string ' ), Mockery::type ( 'array ' ) );
113+
114+ $ this ->user_helper ->expects ( 'delete_meta ' )
115+ ->once ()
116+ ->with ( $ user_id , '_yoast_wpseo_ai_consent ' )
117+ ->andReturn ( true );
118+
119+ $ this ->instance ->revoke_consent ( $ user_id );
120+ }
121+
122+ /**
123+ * Tests that revoke_consent catches a WP_Request_Exception (transport-level error) and still
124+ * clears the local meta.
125+ *
126+ * @return void
127+ */
128+ public function test_revoke_consent_swallows_wp_request_exception () {
129+ $ user_id = 1 ;
130+ $ user = $ this ->stub_get_user_by ( $ user_id );
131+
132+ $ this ->token_manager ->expects ( 'get_or_request_access_token ' )
133+ ->once ()
134+ ->with ( $ user )
135+ ->andReturn ( 'jwt-token ' );
136+
137+ $ this ->request_handler ->expects ( 'handle ' )
138+ ->once ()
139+ ->andThrow ( new WP_Request_Exception ( 'WP_HTTP_REQUEST_ERROR ' ) );
140+
141+ $ this ->logger ->expects ( 'warning ' )
142+ ->once ();
143+
144+ $ this ->user_helper ->expects ( 'delete_meta ' )
145+ ->once ()
146+ ->with ( $ user_id , '_yoast_wpseo_ai_consent ' )
147+ ->andReturn ( true );
148+
149+ $ this ->instance ->revoke_consent ( $ user_id );
150+ }
151+
152+ /**
153+ * Tests that revoke_consent does NOT swallow non-HTTP-layer exceptions: a RuntimeException from
154+ * the token manager must propagate out so that programmer errors stay visible.
155+ *
156+ * @return void
157+ */
158+ public function test_revoke_consent_does_not_swallow_runtime_exception () {
159+ $ user_id = 1 ;
160+ $ user = $ this ->stub_get_user_by ( $ user_id );
161+
162+ $ this ->token_manager ->expects ( 'get_or_request_access_token ' )
163+ ->once ()
164+ ->with ( $ user )
165+ ->andThrow ( new RuntimeException ( 'unexpected programmer error ' ) );
166+
167+ $ this ->request_handler ->shouldNotReceive ( 'handle ' );
168+ $ this ->logger ->shouldNotReceive ( 'warning ' );
169+ $ this ->user_helper ->shouldNotReceive ( 'delete_meta ' );
170+
171+ $ this ->expectException ( RuntimeException::class );
172+
173+ $ this ->instance ->revoke_consent ( $ user_id );
174+ }
32175}
0 commit comments