文件
Henglianshutong/tests/partner/verify_active_order_source_spec.js

177 行
7.5 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* 自购与直推订单双向保活(收益有效期)专项测试
* 覆盖场景:
* 1. 静态代码结构检查:PartnerRewardService::touchOrderTimes, ActiveService::hasQualifiedOrder & syncActiveTimes, Partner.php 监听调用
* 2. 逻辑模拟:自购订单激活本人保活
* 3. 逻辑模拟:直推订单激活直属上级保活(二级及以上间接团队隔离,不激活)
* 4. 逻辑模拟:防时间倒退(较旧订单不覆盖较新订单时间戳)
* 5. 状态转换模拟:直推订单使 30 天未自购的上级从 inactive 转为 active
*/
const fs = require('fs');
const path = require('path');
const assert = require('assert');
let passCount = 0;
let failCount = 0;
function it(desc, fn) {
try {
fn();
console.log(` [PASS] ${desc}`);
passCount++;
} catch (err) {
console.error(` [FAIL] ${desc}`);
console.error(` Error: ${err.message}`);
failCount++;
}
}
console.log('========================================================');
console.log(' 自购与直推订单双向保活(收益有效期)专项测试');
console.log('========================================================\n');
const rewardServicePath = path.resolve(__dirname, '../../addons/shopro/service/partner/PartnerRewardService.php');
const activeServicePath = path.resolve(__dirname, '../../addons/shopro/service/partner/ActiveService.php');
const listenerPath = path.resolve(__dirname, '../../addons/shopro/listener/Partner.php');
const rewardServiceContent = fs.readFileSync(rewardServicePath, 'utf8');
const activeServiceContent = fs.readFileSync(activeServicePath, 'utf8');
const listenerContent = fs.readFileSync(listenerPath, 'utf8');
// 1. 代码规范与方法存在性检查
it('PartnerRewardService 包含 touchOrderTimes 方法且更新买家本人与一级推荐人', () => {
assert(rewardServiceContent.includes('function touchOrderTimes'), '缺少 touchOrderTimes 方法');
assert(rewardServiceContent.includes('parent_user_id'), 'touchOrderTimes 未查询 parent_user_id');
assert(rewardServiceContent.includes('$this->updateAgentOrderTime($parentUserId'), '未更新直推上级订单时间');
});
it('PartnerRewardService 防时间倒退逻辑(新时间戳不被旧时间戳覆盖)', () => {
assert(rewardServiceContent.includes('$timestamp >= $current'), 'updateAgentOrderTime 缺少防时间倒退判断');
});
it('ActiveService::hasQualifiedOrder 支持自购与直推订单双向判定', () => {
assert(activeServiceContent.includes('parent_user_id'), 'hasQualifiedOrder 未关联直推下级 parent_user_id');
assert(activeServiceContent.includes('$candidateUserIds = array_merge([$userId], $directChildIds)'), '缺少买家本人与直推下级候选集合并');
});
it('ActiveService 包含 syncActiveTimes 全量校准同步方法', () => {
assert(activeServiceContent.includes('function syncActiveTimes'), '缺少 syncActiveTimes 方法');
assert(activeServiceContent.includes('max($selfTime, $directTime)'), 'syncActiveTimes 缺少自购与直推最大值聚合');
});
it('Partner 监听器 orderPaidAfter 调用 touchOrderTimes 触发双向保活', () => {
assert(listenerContent.includes('touchOrderTimes'), 'Partner.php 未调用 touchOrderTimes');
});
// 2. 模拟订单与层级保活流转模拟器
class MockAgentSystem {
constructor() {
this.users = {}; // id -> { id, parent_user_id }
this.agents = {}; // user_id -> { last_self_order_time, level }
}
addUser(id, parentUserId, level = 1) {
this.users[id] = { id, parent_user_id: parentUserId };
this.agents[id] = { last_self_order_time: 0, level };
}
updateAgentOrderTime(userId, timestamp) {
const agent = this.agents[userId];
if (agent) {
if (timestamp >= agent.last_self_order_time) {
agent.last_self_order_time = timestamp;
}
}
}
touchOrderTimes(buyerId, time) {
const timestamp = time || Math.floor(Date.now() / 1000);
// 1. 买家本人(自购)
this.updateAgentOrderTime(buyerId, timestamp);
// 2. 直推一级上级(直推)
const user = this.users[buyerId];
if (user && user.parent_user_id && user.parent_user_id !== buyerId) {
this.updateAgentOrderTime(user.parent_user_id, timestamp);
}
}
isActive(userId, nowSec, windowDays = 30) {
const agent = this.agents[userId];
if (!agent || !agent.last_self_order_time) return false;
const windowStart = nowSec - windowDays * 86400;
return agent.last_self_order_time >= windowStart;
}
}
// 场景一:买家 A 自购,激活本人 30 天保活
it('场景一:买家 A 自购 -> 激活本人保活,状态为 active', () => {
const sys = new MockAgentSystem();
sys.addUser(101, 0); // 无上级
const orderTime = 1700000000;
sys.touchOrderTimes(101, orderTime);
assert.strictEqual(sys.agents[101].last_self_order_time, orderTime, '买家 A 订单时间未更新');
assert.strictEqual(sys.isActive(101, orderTime + 86400), true, '自购后第 1 天应为 active');
assert.strictEqual(sys.isActive(101, orderTime + 31 * 86400), false, '自购后第 31 天应为 inactive');
});
// 场景二:直推下级 B 下单 -> 激活直推上级 A 保活
it('场景二:直推下级 B 下单 -> 同时激活直推上级 A,A 即使无自购也变为 active', () => {
const sys = new MockAgentSystem();
sys.addUser(100, 0); // 上级 A(从未自购)
sys.addUser(200, 100); // 直推下级 B(推荐人是 A)
const orderTime = 1700000000;
sys.touchOrderTimes(200, orderTime);
// B 本人自购更新
assert.strictEqual(sys.agents[200].last_self_order_time, orderTime, '买家 B 未更新');
// A 直推获得保活更新
assert.strictEqual(sys.agents[100].last_self_order_time, orderTime, '直推上级 A 未被直推订单激活');
assert.strictEqual(sys.isActive(100, orderTime + 10 * 86400), true, '直推上级 A 获得 30 天有效分红资格');
});
// 场景三:间接下级 C(二级)下单 -> 仅激活 B,不激活上上级 A(严格限定直推与自购)
it('场景三:二级下级 C 下单 -> 仅激活一级直推人 B,上上级 A 不受影响(层级隔离)', () => {
const sys = new MockAgentSystem();
sys.addUser(10, 0); // 上上级 A
sys.addUser(20, 10); // 上级 B
sys.addUser(30, 20); // 下级 C(买家)
const orderTime = 1700000000;
sys.touchOrderTimes(30, orderTime);
assert.strictEqual(sys.agents[30].last_self_order_time, orderTime, '买家 C 应更新');
assert.strictEqual(sys.agents[20].last_self_order_time, orderTime, '直属上级 B 应更新');
assert.strictEqual(sys.agents[10].last_self_order_time, 0, '上上级 A 绝不应被二级订单更新(严格限定自购与直推)');
});
// 场景四:防时间倒退机制(乱序异步事件不能覆盖更新的时间戳)
it('场景四:防时间倒退 -> 乱序处理时,较旧的订单不会覆盖较新的保活时间戳', () => {
const sys = new MockAgentSystem();
sys.addUser(1, 0);
sys.addUser(2, 1);
const tNew = 1700050000;
const tOld = 1700010000;
sys.touchOrderTimes(2, tNew);
assert.strictEqual(sys.agents[1].last_self_order_time, tNew);
// 此时来了一单较早的补偿结算
sys.touchOrderTimes(2, tOld);
assert.strictEqual(sys.agents[1].last_self_order_time, tNew, '较旧的时间戳被错误覆盖了');
});
console.log(`\n--------------------------------------------------------`);
console.log(`测试结果统计: 通过 ${passCount} 项, 失败 ${failCount} 项`);
console.log(`========================================================\n`);
if (failCount > 0) {
process.exit(1);
} else {
process.exit(0);
}