Files
kate522andClaude Opus 5 427968a721 chore(branding): remove leftover upstream theme identifiers
The theme was renamed Zibll/子比 → AKSRT → Jio, but upstream branding
survived the sweeps in CSS classes, the SVG sprite prefix, a persisted
widget field ID, JS events, admin Vue class names, test infrastructure
and docblocks.

Renames:
- zib-widget / zib-widget-wrap / zib-slider → jio-widget-box /
  jio-widget-box-wrap / jio-slider-box (jio-widget is taken by the modern
  widget shell, jio-box-* by the card primitives)
- zib-partners-scroll + zps-* → jio-partners-scroll + jps-*
- zib-combo-info-card + zci-* → jio-combo-info-card + jci-*
- zibsvg- → jiosvg-, including the exported picker helpers and the
  fa-zibsvg- family-prefix guard
- zibpay_type → pay_type; the dual-read fallback in PostQuery collapses
  to a single key
- [zibajax="submit"] dropped (no PHP emitter); zib_ajax.success →
  jio_ajax.success
- aks-* admin Vue classes → jio-*
- zib_palette() → palette()

User-facing vendor traces are gone: the floating "more" button default no
longer ships a zibll.com link or 子比主题 marketing copy, the notice
widget ships no preset promo entries, and the four 官网教程 links are
removed from the post-list admin schema.

Test infrastructure renamed to neutral names (docker-compose.reference.yml,
Caddyfile, article-baseline.json, modern-widgets-contract.test.cjs) with
their DB/volume/env/slug identifiers following.

Kept intentionally, documented in tests/fixtures/README.md: the golden
fixture reference fingerprint (a SHA-256 record of an external directory,
read by validate-widget-golden / generate-widget-golden-fixture /
audit-widget-system), the doesNotMatch guards that prove the old tokens are
gone, and the reference theme's own meta key in the visual fixtures.

No backward-compat or data migration: saved widget configs holding old
icon keys or the old field ID silently lose them, per the project's
rewrite-over-compatibility rule.

Verified: 132/132 node contract tests, php tests/widget-system/run.php
(146 assertions), audit-admin-contracts, audit-psr4-paths and
validate-widget-golden all green; tracked bundles regenerated and in sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-02 00:06:54 +08:00

178 lines
10 KiB
JavaScript

const test = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const root = path.resolve(__dirname, '..', '..')
const read = (...segments) => fs.readFileSync(path.join(root, ...segments), 'utf8')
test('invite code table has a unique code key and a usage log table', () => {
const schema = read('src', 'Core', 'DBMigratorPart2.php')
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_invite_code \([\s\S]*?UNIQUE KEY uniq_invite_code \(code\)/)
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_invite_code \([\s\S]*?\) ENGINE=InnoDB/)
// 使用记录表:运营需要知道"哪个用户用了哪个码",且 (code_id, user_id) 唯一防重复记账。
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_invite_code_usage \([\s\S]*?UNIQUE KEY uniq_invite_usage \(code_id, user_id\)/)
const registry = read('src', 'Core', 'DBMigratorPart3.php')
assert.match(registry, /'invite_code'\s+=>\s+'jio_invite_code'/)
assert.match(registry, /'invite_code_usage'\s+=>\s+'jio_invite_code_usage'/)
})
test('consumption is a single conditional UPDATE, not select-then-update', () => {
const service = read('src', 'Payment', 'InviteCode.php')
// TOCTOU 防线:消费必须是一条带条件的 UPDATE,且以 affected_rows === 1 判定;
// 同语句维护 updated_at(列表"更新时间"列与排序依据)。
assert.match(service, /SET used_count = used_count \+ 1, updated_at = %s WHERE code = %s AND status = 'active' AND \(max_uses = 0 OR used_count < max_uses\)/)
assert.match(service, /\$updated !== 1/)
// 消费成功后写入使用记录(使用者视图)。
assert.match(service, /invite_code_usage/)
assert.match(service, /recent_users_map/)
// 码生成必须使用 CSPRNG。
assert.match(service, /random_int\(/)
assert.doesNotMatch(service, /str_shuffle|mt_rand|rand\(/)
// 奖励幂等:确定性操作键。
assert.match(service, /OperationKey::deterministic\('invite-reward'/)
})
test('registration services gate on the invite-code field mode', () => {
for (const file of ['src/Auth/ServicePart1a.php', 'src/Auth/ServicePart1b.php']) {
const service = read(...file.split('/'))
assert.match(service, /InviteCode::get_instance\(\)/)
assert.match(service, /请填写邀请码/)
assert.match(service, /邀请码无效或已用完,请检查后重试/)
// 校验必须发生在 wp_create_user 之前。
assert.ok(
service.indexOf('is_valid($submitted_invite)') < service.indexOf('wp_create_user('),
`${file}: invite-code validation must run before user creation`
)
}
})
test('rewards defer to email verification when signup verification is on', () => {
const service = read('src', 'Payment', 'InviteCode.php')
assert.match(service, /signup_verification_enabled\(\)/)
assert.match(service, /jio_email_verified/)
assert.match(service, /META_PENDING/)
})
test('invite-code surfaces: form field, admin page, menus and both schemas', () => {
const template = read('templates', 'parts', 'auth-content.php')
assert.match(template, /name="invite_code"/)
assert.match(template, /get_invite_code_registration_settings/)
const page = read('src', 'Admin', 'Native', 'UsersMenusInviteCode.php')
const forms = read('src', 'Admin', 'Native', 'UsersMenusInviteForms.php')
assert.match(page, /render_invite_codes_page/)
assert.match(page, /use UsersMenusInviteForms;/)
assert.match(page, /render_invite_codes_list\(\$service, \$this_url, \$nonce\)/)
assert.match(page, /render_invite_codes_add\(\$nonce\)/)
assert.match(page, /render_invite_codes_export\(\$nonce\)/)
assert.match(page, /subsubsub/)
assert.match(page, /data-clipboard-text/)
assert.match(page, /orderby-but/)
assert.match(page, /bulk_action/)
assert.match(page, /paginate_links/)
assert.match(page, /jio_invite_delete/)
assert.match(forms, /jio_invite_add/)
assert.match(forms, /jio_invite_export/)
// 列表内不再有列表/添加/导出三视图路由;状态筛选仍是独立的列表筛选器。
assert.doesNotMatch(page + forms, /page-title-action|[?&](?:tab|view)=(?:add|export)|tab=(?:list|add|export)/)
// 管理页不再依赖 REST 与 wp_rest nonce。
assert.doesNotMatch(page + forms, /wp_rest|\$\{endpoint\}/)
const menus = read('src', 'Admin', 'Native', 'UsersMenus.php')
assert.match(menus, /use UsersMenusInviteCode/)
assert.match(menus, /jio_invite_codes/)
const bootstrap = read('functions.php')
assert.match(bootstrap, /InviteCode::get_instance\(\)->init\(\)/)
const phpSchema = read('src', 'Admin', 'Schema', 'SchemaUsers.php')
const jsSchema = read('assets', 'admin', 'src', 'schema', 'sections', 'users', 'auth.js')
for (const schema of [phpSchema, jsSchema]) {
assert.match(schema, /invite_code_s/)
assert.match(schema, /invite_code_open_desc/)
assert.match(schema, /invite_code_must_desc/)
}
})
test('admin operations run through admin_post handlers with capability and nonce guards', () => {
const service = read('src', 'Payment', 'InviteCode.php')
assert.match(service, /admin_post_jio_invite_add/)
assert.match(service, /admin_post_jio_invite_delete/)
assert.match(service, /admin_post_jio_invite_export/)
assert.match(service, /check_admin_referer\('jio_invite_admin', 'jio_nonce'\)/)
assert.match(service, /current_user_can\('edit_users'\)/)
// 管理端 REST 已移除:除公开 verify 外不再注册邀请码路由(导出文件名除外)。
assert.doesNotMatch(service, /register_rest_route\('jio\/v1', '\/invite-codes/)
assert.match(service, /register_rest_route\('jio\/v1', '\/invite-code\/verify'/)
// 列表/导出共用状态过滤;批量删除连使用记录一起清。
assert.match(service, /private function status_where\(string \$status\): array/)
assert.match(service, /'unused' => \['used_count = 0', \[\]\]/)
assert.match(service, /public function delete_codes\(array \$ids\): int/)
assert.match(service, /DELETE FROM \{\$usage_table\} WHERE code_id IN/)
// 列表支持状态/搜索/标识定位/排序/分页,默认按更新时间倒序。
assert.match(service, /\$orderby_map = \['code' => 'code', 'created_at' => 'created_at', 'updated_at' => 'updated_at', 'used_count' => 'used_count', 'remark' => 'remark'\]/)
assert.match(service, /\?\? 'updated_at'/)
assert.match(service, /'remark' => 'remark'/)
// 生成支持自定义位数(高级选项),单次上限 1000。
assert.match(service, /min\(1000, max\(1, \(int\) \(\$args\['count'\] \?\? 1\)\)\)/)
assert.match(service, /min\(50, max\(4, \(int\) \(\$args\['code_length'\] \?\? self::CODE_LENGTH\)\)\)/)
})
test('import accepts the documented line format with per-code rewards', () => {
const service = read('src', 'Payment', 'InviteCode.php')
assert.match(service, /public function import_codes\(array \$args\)/)
// 行格式:`邀请码<分隔符>奖励<分隔符>标识`,奖励参数串键位兼容。
assert.match(service, /level_integral/)
assert.match(service, /vip_time/)
assert.match(service, /in_array\(\$reward, \['无', 'none', '0'\]/)
// 空行先剔除,超过 1000 个非空行时拒绝,而不是静默截断。
assert.match(service, /array_filter\(array_map\('trim', \$lines\), static fn\(\$line\) => \$line !== ''\)/)
assert.match(service, /count\(\$lines\) > 1000/)
assert.doesNotMatch(service, /array_slice\(preg_split\('\/\\r\\n\|\\r\|\\n\/', \$raw\) \?: \[\], 0, 1000\)/)
const adminScript = read('assets', 'js', 'admin-invite-codes.js')
assert.match(adminScript, /split\(\/\\r\\n\|\\r\|\\n\/\)\.filter\(function \(line\) \{ return line\.trim\(\) !== ''; \}\)/)
assert.match(service, /\$exists !== null/)
assert.match(service, /\$failed_codes\[\] = \$code/)
// 导入表单由独立 Forms trait 渲染,并走添加操作的 add_type=import。
const forms = read('src', 'Admin', 'Native', 'UsersMenusInviteForms.php')
assert.match(forms, /name="import_data"/)
assert.match(forms, /name="import_division"/)
assert.match(forms, /name="add_type"/)
})
test('per-code remark persists through generate/import and both exports round-trip', () => {
const schema = read('src', 'Core', 'DBMigratorPart2.php')
assert.match(schema, /remark\s+VARCHAR\(191\) NOT NULL DEFAULT ''/)
assert.match(schema, /updated_at\s+DATETIME NOT NULL DEFAULT/)
const service = read('src', 'Payment', 'InviteCode.php')
// 生成与导入都落 remark;导入第三段"标识"→备注。
assert.match(service, /\$remark = sanitize_text_field\(\(string\) \(\$args\['remark'\] \?\? ''\)\)/)
assert.match(service, /\$remark = sanitize_text_field\(trim\(\(string\) \(\$parts\[2\] \?\? ''\)\)\)/)
assert.match(service, /'remark'\s+=>\s+\$remark/)
// 导出奖励参数串与 parse_reward_string 互逆(balance 分→元,零奖励"无")。
assert.match(service, /private function reward_param_string\(string \$rewards_json\): string/)
assert.match(service, /'balance=' \. /)
assert.match(service, /\$parts === \[\] \? '无'/)
// 文本导出与导入格式互通;CSV(Excel)含中文表头与状态列。
assert.match(service, /\$row\['code'\] \. \$division \. \$this->reward_param_string/)
assert.match(service, /__\('邀请码', 'jio'\), __\('使用奖励', 'jio'\)/)
assert.match(service, /\$used > 0 \? __\('已使用', 'jio'\) : __\('未使用', 'jio'\)/)
const forms = read('src', 'Admin', 'Native', 'UsersMenusInviteForms.php')
// 自动生成标识字段默认 invita_YmdHis;列表标识列可点击定位。
assert.match(forms, /name="auto_remarks"/)
assert.match(forms, /invita_' \. current_time\('YmdHis'\)/)
const page = read('src', 'Admin', 'Native', 'UsersMenusInviteCode.php')
assert.match(page, /add_query_arg\('remark', \$item\['remark'\], \$this_url\)/)
})
test('settings reader exposes normalized invite-code registration mode', () => {
const runtime = read('src', 'Settings', 'CreditsRuntime.php')
assert.match(runtime, /get_invite_code_registration_settings\(\)/)
assert.match(runtime, /\['close', 'open', 'must'\]/)
const groups = read('src', 'Settings', 'CreditsPaymentGroups.php')
assert.match(groups, /CreditsRuntime::get_invite_code_registration_settings\(\)/)
})