message; } } /** Canned responses keyed by URL. */ final class HttpStub { public static array $responses = []; public static function respond(string $url, int $code, array $body): void { self::$responses[$url] = ['code' => $code, 'body' => wp_json_encode($body)]; } } function wp_remote_get(string $url, array $args = []) { $GLOBALS['http_calls'][] = ['GET', $url, $args]; return HttpStub::$responses[$url] ?? new WP_Error('no_stub_for_' . $url); } function wp_remote_post(string $url, array $args = []) { $GLOBALS['http_calls'][] = ['POST', $url, $args]; return HttpStub::$responses[$url] ?? new WP_Error('no_stub_for_' . $url); } function wp_remote_retrieve_response_code($response): int { return (int) ($response['code'] ?? 0); } function wp_remote_retrieve_body($response): string { return (string) ($response['body'] ?? ''); } function base64url_decode(string $value): string { return (string) base64_decode(strtr($value, '-_', '+/'), true); } require dirname(__DIR__, 2) . '/src/Auth/OAuthProviderClient.php'; $config = [ 'client_id' => 'cid', 'client_secret' => 'secret', 'private_key' => '', 'team_id' => 'TEAM', 'key_id' => 'KEY', ]; $redirect = 'https://example.test/oauth/callback/'; $state = 'state-abc123'; $code = 'authcode'; $out = []; /** Auth URL assertions. */ foreach (['douyin', 'microsoft', 'facebook', 'twitter', 'apple'] as $provider) { $url = \Jio\Auth\OAuthProviderClient::auth_url($provider, $config, $redirect, $state); $query = []; parse_str((string) parse_url($url, PHP_URL_QUERY), $query); $out['auth'][$provider] = [ 'url' => $url, 'has_redirect' => ($query['redirect_uri'] ?? '') === $redirect, 'has_state' => ($query['state'] ?? '') === $state, 'challenge' => $query['code_challenge'] ?? '', ]; } /** Identity normalisation, one canned response set per provider. */ HttpStub::respond('https://open.douyin.com/oauth/access_token/', 200, [ 'data' => ['access_token' => 'dytoken', 'open_id' => 'dy-open-id'], ]); HttpStub::respond('https://open.douyin.com/oauth/userinfo/', 200, [ 'data' => ['nickname' => '抖音用户', 'avatar_larger' => 'https://p3.douyinpic.com/a.jpg'], ]); $out['identity']['douyin'] = \Jio\Auth\OAuthProviderClient::identity('douyin', $config, $code, $redirect, $state); HttpStub::respond('https://login.microsoftonline.com/common/oauth2/v2.0/token', 200, [ 'access_token' => 'mstoken', ]); HttpStub::respond('https://graph.microsoft.com/oidc/userinfo', 200, [ 'sub' => 'ms-sub', 'email' => 'ms@example.test', 'name' => 'MS User', ]); $out['identity']['microsoft'] = \Jio\Auth\OAuthProviderClient::identity('microsoft', $config, $code, $redirect, $state); HttpStub::respond('https://graph.facebook.com/v21.0/oauth/access_token?' . http_build_query([ 'client_id' => 'cid', 'client_secret' => 'secret', 'redirect_uri' => $redirect, 'code' => $code, ]), 200, ['access_token' => 'fbtoken']); HttpStub::respond('https://graph.facebook.com/v21.0/me?' . http_build_query([ 'fields' => 'id,name,email,picture.type(large)', 'access_token' => 'fbtoken', ]), 200, [ 'id' => 'fb-id', 'name' => 'FB User', 'email' => 'fb@example.test', 'picture' => ['data' => ['url' => 'http://graph.facebook.com/pic.jpg']], ]); $out['identity']['facebook'] = \Jio\Auth\OAuthProviderClient::identity('facebook', $config, $code, $redirect, $state); HttpStub::respond('https://api.twitter.com/2/oauth2/token', 200, ['access_token' => 'twtoken']); HttpStub::respond('https://api.twitter.com/2/users/me?' . http_build_query([ 'user.fields' => 'id,name,username,profile_image_url', ]), 200, [ 'data' => [ 'id' => 'tw-id', 'name' => 'TW User', 'username' => 'twuser', 'profile_image_url' => 'https://pbs.twimg.com/p.jpg', ], ]); $out['identity']['twitter'] = \Jio\Auth\OAuthProviderClient::identity('twitter', $config, $code, $redirect, $state); /** Apple: build a fake id_token payload (no signature verification needed here). */ $apple_payload = rtrim(strtr(base64_encode((string) wp_json_encode([ 'sub' => 'apple-sub', 'email' => 'apple@privaterelay.appleid.com', 'email_verified' => 'true', ])), '+/', '-_'), '='); HttpStub::respond('https://appleid.apple.com/auth/token', 200, [ 'id_token' => 'eyJhbGciOiJub25lIn0.' . $apple_payload . '.', ]); $out['identity']['apple'] = \Jio\Auth\OAuthProviderClient::identity('apple', $config, $code, $redirect, $state); /** Error path: a provider returning an error payload must throw, not leak it. */ HttpStub::respond('https://open.douyin.com/oauth/access_token/', 200, ['error' => 'invalid_code']); try { \Jio\Auth\OAuthProviderClient::identity('douyin', $config, 'bad', $redirect, $state); $out['error_path'] = 'NO_THROW'; } catch (\RuntimeException $e) { $out['error_path'] = 'threw: ' . $e->getMessage(); } echo json_encode($out, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);