Files
wordpress-theme-aksrt/tests/widget-system/referral-policy-contract.test.cjs
T
kate522andClaude Fable 5 ea16f9c2fe perf(revenue): server-render the promotion link and stop settling on reads
Opening the 推广收益 tab was slow because every read path ran settlement
and two COUNT aggregates before the browser could show the link.

- profile-revenue.php: the link input now carries a server-rendered value from
  ReferralAttribution::create_link(), so 复制链接 works on first paint instead
  of after a REST round trip
- CommissionLedger::overview() no longer calls lazy_settle_due(); the
  lazy-settlement hook and its static flag are gone. Settlement runs only on
  the existing hourly aksrt_affiliate_settle_due cron, which uses a larger
  batch (100 vs 20) and would otherwise re-run up to 20 locked
  SELECT..FOR UPDATE + write cycles inside a GET request
- ReferralAttribution::rest_me() drops the lazy_settle_due() call and the
  stats() result; stats() itself is deleted — its two COUNTs (one of them
  COUNT(DISTINCT source_user_id)) had no consumer left once the 推广人数 row
  was removed
- credits.js: reads the link from the already-rendered input and only fetches
  /revenue/me for poster config and commission ratio. A failed fetch no longer
  blocks copying — it silently skips the poster enhancement
- tests: the lazy-settlement contract is inverted to assert the read path stays
  read-only and cron stays wired; new assertions pin the server-rendered value
  and the no-COUNT REST payload

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-27 23:17:32 +08:00

96 lines
5.3 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('referral attribution model is a single runtime source with schema surface', () => {
const runtime = read('src', 'Settings', 'CreditsRuntime.php')
const attribution = read('src', 'Payment', 'ReferralAttribution.php')
const phpSchema = read('src', 'Admin', 'Schema', 'SchemaPayment.php')
const jsSchema = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'affiliate.js')
// 运行时:唯一枚举与默认值。
assert.match(runtime, /referral_attribution_model/)
assert.match(runtime, /\['first_touch', 'last_touch'\]/)
assert.match(runtime, /'attribution_model' => \$attribution/)
// 归因行为:capture 末触覆盖、首触保持;注册仲裁按模式取优先方。
assert.match(attribution, /\$this->attribution_model\(\) !== 'last_touch'/)
assert.match(attribution, /!?\(\$last_touch && \$submitted_valid\)/)
assert.match(attribution, /已按最近访问的推广关系绑定/)
assert.match(attribution, /已按首次访问的推广关系绑定/)
// 管理端双 schema 同步暴露该字段。
for (const schema of [phpSchema, jsSchema]) {
assert.match(schema, /referral_attribution_model/)
assert.match(schema, /first_touch/)
assert.match(schema, /last_touch/)
}
})
test('disabled-code stop-commission switch is wired end to end', () => {
const runtime = read('src', 'Settings', 'PaymentRuntime.php')
const ledger = read('src', 'Payment', 'CommissionLedger.php')
const attribution = read('src', 'Payment', 'ReferralAttribution.php')
const phpSchema = read('src', 'Admin', 'Schema', 'SchemaPayment.php')
const jsSchema = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'affiliate.js')
assert.match(runtime, /'disabled_code_stops_commission' => !empty\(\$settings\['affiliate_disabled_code_stops_commission'\]\)/)
// 记账入口必须在 resolve 推广人之后、写 ledger 之前校验推广码状态。
assert.match(ledger, /ReferralAttribution::get_instance\(\)->resolve\(\$buyer\)/)
assert.match(ledger, /is_promoter_code_active\(\$promoter\)/)
assert.ok(
ledger.indexOf('is_promoter_code_active($promoter)') < ledger.indexOf('$wpdb->insert($table'),
'disabled-code gate must run before the ledger insert'
)
assert.match(attribution, /public function is_promoter_code_active\(int \$promoter_id\): bool/)
for (const schema of [phpSchema, jsSchema]) {
assert.match(schema, /affiliate_disabled_code_stops_commission/)
}
})
test('commission base is the discounted payable amount, not the list price', () => {
// 下单侧:content 订单金额取优惠链(推广让利 + 优惠券)后的应付净额。
const payOrders = read('src', 'Content', 'PayOrders.php')
assert.match(payOrders, /\$amount_minor = max\(0, \(int\) \(\$discounted\['payable_minor'\] \?\? 0\)\)/)
// 记账侧:佣金基数直接取订单实付金额。
const ledger = read('src', 'Payment', 'CommissionLedger.php')
assert.match(ledger, /AssetAmount::balance\(is_string\(\$order\['amount'\] \?\? null\) \? \$order\['amount'\] : ''\)/)
})
test('settlement stays off the read path and runs on the cron schedule', () => {
const ledger = read('src', 'Payment', 'CommissionLedger.php')
const attribution = read('src', 'Payment', 'ReferralAttribution.php')
// 懒结算兜底已移除:读路径不得顺带写库(收益页打开慢的主因)。
assert.doesNotMatch(ledger, /lazy_settle_due|\$lazy_settled/)
// overview() 与 rest_me() 都不再触发结算。
assert.doesNotMatch(ledger, /\$this->lazy_settle_due\(\)/)
assert.doesNotMatch(attribution, /lazy_settle_due/)
assert.doesNotMatch(attribution, /->stats\(/)
// 结算只由 hourly 的 wp-cron 事件驱动。
assert.match(ledger, /add_action\('aksrt_affiliate_settle_due', \[\$this, 'settle_due'\]\)/)
assert.match(ledger, /wp_schedule_event\(time\(\) \+ HOUR_IN_SECONDS, 'hourly', 'aksrt_affiliate_settle_due'\)/)
})
test('referral codes are self-serve: no admin ops page, granted at registration', () => {
const service = read('src', 'Payment', 'ReferralAttribution.php')
// 管理端代码已清理:无 /revenue/codes 路由、无后台增删改接口、无管理列表。
assert.doesNotMatch(service, /revenue\/codes/)
assert.doesNotMatch(service, /rest_mutate_code|rest_codes|can_manage_codes|private function list_codes/)
// 注册即得唯一推广码:bind_registered_user 内调用 generate_code,不依赖归因开关。
assert.match(service, /public function bind_registered_user\(int \$invitee_id\): void \{\s*\n\s*\/\/ 每个新用户注册即拥有唯一推广码[\s\S]*?\$this->generate_code\(\$invitee_id, false\)/)
// 用户菜单不再挂"推广码运营"页,trait 文件已删除。
const menus = read('src', 'Admin', 'Native', 'UsersMenus.php')
assert.doesNotMatch(menus, /UsersMenusReferral|aksrt_referral_codes|render_referral_codes_page/)
assert.equal(fs.existsSync(path.join(root, 'src', 'Admin', 'Native', 'UsersMenusReferral.php')), false)
// admin SPA 不再保留跳转路由。
const router = read('assets', 'admin', 'src', 'router', 'index.js')
assert.doesNotMatch(router, /referral-codes/)
})