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>
96 lines
5.3 KiB
JavaScript
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/)
|
|
})
|