Files
wordpress-theme-aksrt/tests/widget-system/oauth-avatar-contract.test.cjs
T
kate522 6f29e3d865 fix(oauth): actually use the third-party avatar and seed it only once
The OAuth flow wrote the provider avatar to `jio_oauth_avatar`, but nothing
ever read it — every user who signed up through GitHub/QQ saw a Gravatar hash
or the default avatar instead of their own picture. The reference seeds its
`custom_avatar` from the OAuth profile and its resolver reads it back, so both
halves were missing here.

jio_get_custom_avatar_url() now falls back to the stored OAuth avatar when no
uploaded one exists, which puts the chain at uploaded → third-party → external
service → default. The theme already swaps WordPress's get_avatar_data, so one
change covers every avatar surface.

Writes go through a single seed_avatar() guard: it no-ops when an uploaded
avatar or an existing OAuth avatar is present, matching the reference's
`if ($args['avatar'] && !$custom_avatar)`. Previously both the bind path and
the login refresh overwrote the meta unconditionally, which would have wiped a
user's own picture on their next social login.

Because the OAuth avatar now shows through once the upload is gone, both
"restore default" flows (the REST endpoint and the admin profile box) delete
it too; the admin checkbox also appears when only an OAuth avatar is set,
since $avatar_url is now non-empty in that case.

Baidu serves avatars from tb.himg.baidu.com, which 403s — rewritten to
himg.bdimg.com the way the reference does. On top of that the URL is upgraded
to https when the site runs over SSL, because the reference's scheme-relative
form still yields a plain http:// when copied verbatim, and browsers block it
as mixed content.

WeChatShare was reading `custom_avatar` — a reference-theme key this codebase
never writes — so its author branch always fell through to get_avatar_url();
it now uses the shared resolver like every other surface.

Verification: drove the resolver through five states with a stub (none /
OAuth only / both / upload only / Baidu legacy host) and confirmed the
expected URL each time. Added oauth-avatar-contract.test.cjs to pin the
resolution order, the seed guard, and the reset cleanup. 868 widget and
navigation tests pass; the PSR-4 path audit reports 0 errors.
2026-11-04 17:41:48 +08:00

62 lines
3.0 KiB
JavaScript

/**
* 第三方登录头像契约(参考主题 1:1)。
*
* 硬约束:
* 1) 第三方头像只在建档/首次绑定时补齐,之后不覆盖用户自己的选择
* (参考主题:`if ($args['avatar'] && !$custom_avatar)`)。
* 2) 解析顺序:上传头像 → 第三方头像 → 外部服务 → 默认头像。
* 3) 「恢复默认头像」必须同时清掉第三方头像,否则点了也换不掉。
* 4) 百度头像域名需要换成可访问的 himg.bdimg.com。
*/
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const root = path.resolve(__dirname, '..', '..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
test('第三方头像在解析链里位于上传头像之后、外部服务之前', () => {
const helpers = read('inc/helpers/jio-avatar.php');
// 上传头像分支必须排在第三方头像之前。
const uploadAt = helpers.indexOf("get_user_meta($user_id, 'jio_avatar_attachment_id', true)");
const oauthAt = helpers.indexOf('jio_get_oauth_avatar_url($user_id)');
assert.ok(uploadAt >= 0 && oauthAt > uploadAt, 'uploaded avatar must win over the OAuth one');
// 第三方头像整体仍在外部头像服务之前。
const chain = helpers.slice(helpers.indexOf('function jio_get_avatar_url'));
const customAt = chain.indexOf('jio_get_custom_avatar_url');
const externalAt = chain.indexOf('jio_get_external_avatar_url');
assert.ok(customAt >= 0 && customAt < externalAt);
});
test('百度头像域名为可访问地址', () => {
const helpers = read('inc/helpers/jio-avatar.php');
assert.match(helpers, /str_replace\('tb\.himg\.baidu\.com', 'himg\.bdimg\.com', \$url\)/);
});
test('第三方头像只在空缺时写入,不覆盖用户头像', () => {
const service = read('src/Auth/OAuthAccountService.php');
assert.match(service, /private static function seed_avatar\(int \$user_id, array \$identity\): void/);
// 已有上传头像或已存第三方头像时直接返回,不写 meta。
assert.match(service, /\(int\) get_user_meta\(\$user_id, 'jio_avatar_attachment_id', true\) > 0\) \{\s*return;/);
assert.match(service, /get_user_meta\(\$user_id, 'jio_oauth_avatar', true\)\) !== ''\) \{\s*return;/);
// 两条写入路径(绑定 / 登录刷新)都改走同一守卫,不再直接写 meta。
const directWrites = service.match(/update_user_meta\(\$user_id, 'jio_oauth_avatar'/g) || [];
assert.equal(directWrites.length, 1, 'only seed_avatar may write the OAuth avatar');
assert.equal((service.match(/self::seed_avatar\(/g) || []).length, 2);
});
test('恢复默认头像会一并清除第三方头像', () => {
const rest = read('src/Admin/UserControllerProfileMediaOperations.php');
const admin = read('src/User/ProfileAdmin.php');
assert.match(rest, /delete_user_meta\(\$user_id, 'jio_oauth_avatar'\)/);
assert.match(admin, /delete_user_meta\(\$user_id, 'jio_oauth_avatar'\)/);
});