feat(dashboard): 从专业财富观角度重构升级后台收益与分成统计仪表盘
这个提交包含在:
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace tests\partner;
|
||||
|
||||
// 引导 ThinkPHP 环境
|
||||
define('APP_PATH', __DIR__ . '/../../application/');
|
||||
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
|
||||
require_once __DIR__ . '/../../thinkphp/base.php';
|
||||
require_once __DIR__ . '/../../thinkphp/helper.php';
|
||||
require_once __DIR__ . '/../../application/common.php';
|
||||
|
||||
\think\Config::set(include APP_PATH . 'database.php', 'database');
|
||||
\think\Loader::addNamespace('app', APP_PATH);
|
||||
require_once __DIR__ . '/../../addons/shopro/helper/helper.php';
|
||||
|
||||
use app\admin\controller\shopro\Dashboard;
|
||||
use think\Request;
|
||||
|
||||
class TestableDashboard extends Dashboard
|
||||
{
|
||||
public $lastOutput = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->request = Request::instance();
|
||||
}
|
||||
|
||||
public function success($msg = '', $url = null, $data = '', $wait = 3, array $header = [])
|
||||
{
|
||||
$this->lastOutput = [
|
||||
'code' => 1,
|
||||
'msg' => $msg,
|
||||
'data' => $data
|
||||
];
|
||||
}
|
||||
|
||||
public function setGetParams(array $params)
|
||||
{
|
||||
$this->request->get($params);
|
||||
}
|
||||
|
||||
public function error($msg = '', $url = null, $data = '', $wait = 3, array $header = [])
|
||||
{
|
||||
$this->lastOutput = [
|
||||
'code' => 0,
|
||||
'msg' => $msg,
|
||||
'data' => $data
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class DashboardWealthTest
|
||||
{
|
||||
private $passed = 0;
|
||||
private $failed = 0;
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "========================================================\n";
|
||||
echo " 仪表盘专业财富观与分成透视专项测试 (Dashboard Wealth) \n";
|
||||
echo "========================================================\n\n";
|
||||
|
||||
$this->testTotalWealthData();
|
||||
$this->testChartNormalData();
|
||||
$this->testChartEmptyDateFallback();
|
||||
$this->testRankingPartnerAndDailyData();
|
||||
$this->testWealthFinancialFormulaIntegrity();
|
||||
|
||||
echo "\n--------------------------------------------------------\n";
|
||||
echo "测试执行结果: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n";
|
||||
echo "========================================================\n";
|
||||
return $this->failed === 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
private function assert($condition, $testName, $detail = '')
|
||||
{
|
||||
if ($condition) {
|
||||
echo " [PASS] {$testName}\n";
|
||||
$this->passed++;
|
||||
} else {
|
||||
echo " [FAIL] {$testName} - {$detail}\n";
|
||||
$this->failed++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 1:全局总览 total() 财富指标与资产池完整性
|
||||
*/
|
||||
public function testTotalWealthData()
|
||||
{
|
||||
$controller = new TestableDashboard();
|
||||
$controller->total();
|
||||
|
||||
$data = $controller->lastOutput['data'] ?? [];
|
||||
$wealth = $data['wealth_data'] ?? null;
|
||||
$madow = $data['madow_data'] ?? null;
|
||||
$voucher = $data['voucher_data'] ?? null;
|
||||
$network = $data['partner_network'] ?? null;
|
||||
|
||||
$this->assert(!empty($wealth) && isset($wealth['total_gmv'], $wealth['total_payout'], $wealth['net_retention'], $wealth['payout_rate']),
|
||||
'total() 返回完整的 wealth_data 资金盘宏观指标');
|
||||
|
||||
$this->assert(!empty($madow) && isset($madow['total_madow'], $madow['current_rate'], $madow['rate_per_wan'], $madow['total_madow_cny']),
|
||||
'total() 返回完整的 madow_data 马豆资产与折算人民币指标');
|
||||
|
||||
$this->assert(!empty($voucher) && isset($voucher['total_voucher'], $voucher['released_voucher'], $voucher['pending_voucher']),
|
||||
'total() 返回完整的 voucher_data 消费兑换券杠杆资金池指标');
|
||||
|
||||
$this->assert(!empty($network) && isset($network['maker_count'], $network['taster_count'], $network['active_rate']),
|
||||
'total() 返回完整的 partner_network 合伙股东活力分布');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 2:核心走势 chart() 正常日期区间返回
|
||||
*/
|
||||
public function testChartNormalData()
|
||||
{
|
||||
$controller = new TestableDashboard();
|
||||
$start = date('Y-m-d 00:00:00', strtotime('-7 days'));
|
||||
$end = date('Y-m-d 23:59:59');
|
||||
$controller->setGetParams(['date' => "{$start} - {$end}"]);
|
||||
|
||||
$controller->chart();
|
||||
$data = $controller->lastOutput['data'] ?? [];
|
||||
|
||||
$this->assert(isset($data['payAmountNum'], $data['payoutNum'], $data['retentionNum'], $data['payoutRate']),
|
||||
'chart() 正常区间包含 payAmountNum, payoutNum, retentionNum, payoutRate 复合指标');
|
||||
|
||||
$this->assert(isset($data['splitStructure']) && is_array($data['splitStructure']) && count($data['splitStructure']) === 4,
|
||||
'chart() splitStructure 包含四大板块(直推、创客、渠道、均分)');
|
||||
|
||||
$this->assert(isset($data['rewardDetails']) && is_array($data['rewardDetails']),
|
||||
'chart() rewardDetails 包含收益明细穿透统计');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 3:边界条件 chart() 空日期/异常传参自动防御
|
||||
*/
|
||||
public function testChartEmptyDateFallback()
|
||||
{
|
||||
$controller = new TestableDashboard();
|
||||
$controller->setGetParams(['date' => '']);
|
||||
$controller->chart();
|
||||
$data = $controller->lastOutput['data'] ?? [];
|
||||
|
||||
$this->assert(!empty($data) && isset($data['payAmountNum']),
|
||||
'chart() 传入空日期时自动降级为当日时间范围并安全返回,不抛异常');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 4:排行与对账 ranking() 合伙股东收益排行与最新日结
|
||||
*/
|
||||
public function testRankingPartnerAndDailyData()
|
||||
{
|
||||
$controller = new TestableDashboard();
|
||||
$controller->ranking();
|
||||
$data = $controller->lastOutput['data'] ?? [];
|
||||
|
||||
$partnerRanking = $data['partner_ranking'] ?? null;
|
||||
$recentReport = $data['daily_report_recent'] ?? null;
|
||||
|
||||
$this->assert(is_array($partnerRanking), 'ranking() 返回 partner_ranking 合伙股东收益榜');
|
||||
if (!empty($partnerRanking)) {
|
||||
$top1 = $partnerRanking[0];
|
||||
$this->assert(isset($top1['user_id'], $top1['nickname'], $top1['total_income'], $top1['identity_text']),
|
||||
'partner_ranking 包含用户ID、昵称、收益金额与身份等级标签');
|
||||
}
|
||||
|
||||
$this->assert($recentReport === null || isset($recentReport['settle_date'], $recentReport['gmv'], $recentReport['rate']),
|
||||
'ranking() 返回 daily_report_recent 最新日结对账概览');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 5:财务公式精算勾稽校验
|
||||
*/
|
||||
public function testWealthFinancialFormulaIntegrity()
|
||||
{
|
||||
$controller = new TestableDashboard();
|
||||
$controller->total();
|
||||
$wealth = $controller->lastOutput['data']['wealth_data'] ?? [];
|
||||
|
||||
$gmv = (float)($wealth['total_gmv'] ?? 0);
|
||||
$payout = (float)($wealth['total_payout'] ?? 0);
|
||||
$retention = (float)($wealth['net_retention'] ?? 0);
|
||||
$payoutRate = (float)($wealth['payout_rate'] ?? 0);
|
||||
|
||||
$expectedRetention = max(0, $gmv - $payout);
|
||||
$retentionDiff = abs($retention - $expectedRetention);
|
||||
|
||||
$this->assert($retentionDiff < 0.01, "资金留存勾稽检验: net_retention({$retention}) == gmv({$gmv}) - payout({$payout})");
|
||||
|
||||
if ($gmv > 0) {
|
||||
$expectedRate = round(($payout / $gmv) * 100, 2);
|
||||
$rateDiff = abs($payoutRate - $expectedRate);
|
||||
$this->assert($rateDiff < 0.1, "全网分成拨比率精算检验: payout_rate({$payoutRate}%) == (payout/gmv)*100%");
|
||||
} else {
|
||||
$this->assert($payoutRate == 0, "GMV 为 0 时分成拨比率恒为 0%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (php_sapi_name() === 'cli' && basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME'])) {
|
||||
$tester = new DashboardWealthTest();
|
||||
exit($tester->run());
|
||||
}
|
||||
241
tests/partner/PartnerFinanceTest.php
普通文件
241
tests/partner/PartnerFinanceTest.php
普通文件
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace tests\partner;
|
||||
|
||||
// 引导 ThinkPHP 环境
|
||||
define('APP_PATH', __DIR__ . '/../../application/');
|
||||
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
|
||||
require_once __DIR__ . '/../../thinkphp/base.php';
|
||||
|
||||
\think\Config::set(\think\Config::load(APP_PATH . 'config.php'));
|
||||
\think\Config::set(\think\Config::load(APP_PATH . 'database.php'));
|
||||
|
||||
use addons\shopro\service\partner\DailySettleService;
|
||||
use addons\shopro\service\partner\PartnerConfig;
|
||||
use addons\shopro\service\partner\VoucherService;
|
||||
use app\admin\controller\shopro\partner\Reward;
|
||||
use app\admin\controller\shopro\partner\Daily;
|
||||
use think\Request;
|
||||
use think\Db;
|
||||
|
||||
class PartnerFinanceTest
|
||||
{
|
||||
private $passed = 0;
|
||||
private $failed = 0;
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "========================================================\n";
|
||||
echo " 合伙股东/收益中心 财务优化专项测试 (Finance Tests) \n";
|
||||
echo "========================================================\n\n";
|
||||
|
||||
$this->testOrderTreeParamError();
|
||||
$this->testOrderTreeNonExist();
|
||||
$this->testReissueRiskLimit();
|
||||
$this->testReissueParamMissing();
|
||||
$this->testDailyDryRunPreview();
|
||||
$this->testVoucherDebtCalculation();
|
||||
|
||||
echo "\n--------------------------------------------------------\n";
|
||||
echo "测试执行结果: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n";
|
||||
echo "========================================================\n";
|
||||
return $this->failed === 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
private function assert($condition, $testName, $detail = '')
|
||||
{
|
||||
if ($condition) {
|
||||
echo " [PASS] {$testName}\n";
|
||||
$this->passed++;
|
||||
} else {
|
||||
echo " [FAIL] {$testName} - {$detail}\n";
|
||||
$this->failed++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 1:订单透视参数校验 (order_id <= 0 抛出异常或错误返回)
|
||||
*/
|
||||
public function testOrderTreeParamError()
|
||||
{
|
||||
$testName = "API: orderTree 参数非法拦截 (order_id=0)";
|
||||
try {
|
||||
$ctrl = new Reward();
|
||||
Request::instance()->withGet(['order_id' => 0]);
|
||||
$output = $this->captureResponse(function () use ($ctrl) {
|
||||
$ctrl->orderTree();
|
||||
});
|
||||
$res = json_decode($output, true);
|
||||
$this->assert(
|
||||
isset($res['code']) && $res['code'] !== 0 && strpos($res['msg'] ?? '', '订单ID不合法') !== false,
|
||||
$testName,
|
||||
"返回内容: {$output}"
|
||||
);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
$res = $e->getResponse()->getData();
|
||||
$this->assert(
|
||||
isset($res['code']) && $res['code'] !== 0,
|
||||
$testName,
|
||||
"HttpResponse 拦截成功"
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(true, $testName, "正确捕获参数异常: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 2:订单透视订单不存在拦截
|
||||
*/
|
||||
public function testOrderTreeNonExist()
|
||||
{
|
||||
$testName = "API: orderTree 订单不存在拦截 (order_id=99999999)";
|
||||
try {
|
||||
$ctrl = new Reward();
|
||||
Request::instance()->withGet(['order_id' => 99999999]);
|
||||
$output = $this->captureResponse(function () use ($ctrl) {
|
||||
$ctrl->orderTree();
|
||||
});
|
||||
$res = json_decode($output, true);
|
||||
$this->assert(
|
||||
isset($res['code']) && $res['code'] !== 0 && strpos($res['msg'] ?? '', '订单不存在') !== false,
|
||||
$testName,
|
||||
"返回内容: {$output}"
|
||||
);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
$res = $e->getResponse()->getData();
|
||||
$this->assert(
|
||||
isset($res['code']) && $res['code'] !== 0 && strpos($res['msg'] ?? '', '订单不存在') !== false,
|
||||
$testName,
|
||||
"正确拦截不存在订单"
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(strpos($e->getMessage(), '订单不存在') !== false, $testName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 3:手工补发 10,000 元风控硬上限拦截
|
||||
*/
|
||||
public function testReissueRiskLimit()
|
||||
{
|
||||
$testName = "风控: reissue 单笔超 10,000 元硬上限拦截 (amount=10001)";
|
||||
try {
|
||||
$ctrl = new Reward();
|
||||
Request::instance()->withPost([
|
||||
'user_id' => 1,
|
||||
'reward_type' => 'push3',
|
||||
'amount' => 10001,
|
||||
'memo' => '超额测试',
|
||||
]);
|
||||
$output = $this->captureResponse(function () use ($ctrl) {
|
||||
$ctrl->reissue();
|
||||
});
|
||||
$res = json_decode($output, true);
|
||||
$this->assert(
|
||||
isset($res['code']) && $res['code'] !== 0 && strpos($res['msg'] ?? '', '10,000') !== false,
|
||||
$testName,
|
||||
"返回内容: {$output}"
|
||||
);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
$res = $e->getResponse()->getData();
|
||||
$this->assert(
|
||||
isset($res['code']) && $res['code'] !== 0 && strpos($res['msg'] ?? '', '10,000') !== false,
|
||||
$testName,
|
||||
"风控硬上限成功拦截"
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(strpos($e->getMessage(), '10,000') !== false, $testName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 4:手工补发必填参数校验
|
||||
*/
|
||||
public function testReissueParamMissing()
|
||||
{
|
||||
$testName = "API: reissue 必填参数缺失拦截 (缺少 memo)";
|
||||
try {
|
||||
$ctrl = new Reward();
|
||||
Request::instance()->withPost([
|
||||
'user_id' => 1,
|
||||
'reward_type' => 'push3',
|
||||
'amount' => 100,
|
||||
'memo' => '',
|
||||
]);
|
||||
$output = $this->captureResponse(function () use ($ctrl) {
|
||||
$ctrl->reissue();
|
||||
});
|
||||
$res = json_decode($output, true);
|
||||
$this->assert(
|
||||
isset($res['code']) && $res['code'] !== 0 && strpos($res['msg'] ?? '', '必填') !== false,
|
||||
$testName,
|
||||
"返回内容: {$output}"
|
||||
);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
$res = $e->getResponse()->getData();
|
||||
$this->assert(
|
||||
isset($res['code']) && $res['code'] !== 0 && strpos($res['msg'] ?? '', '必填') !== false,
|
||||
$testName,
|
||||
"参数缺失成功拦截"
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(strpos($e->getMessage(), '必填') !== false, $testName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 5:日结 Dry Run 预跑试算只读测试
|
||||
*/
|
||||
public function testDailyDryRunPreview()
|
||||
{
|
||||
$testName = "业务: DailySettleService::preview 只读试算结构完整性";
|
||||
$service = new DailySettleService();
|
||||
$testDate = '2026-09-01'; // 模拟日期
|
||||
$preview = $service->preview($testDate);
|
||||
|
||||
$hasKeys = isset($preview['settle_date']) &&
|
||||
isset($preview['order_count']) &&
|
||||
isset($preview['gmv']) &&
|
||||
isset($preview['madow']['pool']) &&
|
||||
isset($preview['venue']['pool']) &&
|
||||
isset($preview['county']['pool']) &&
|
||||
isset($preview['financial']['payout_ratio']);
|
||||
|
||||
$this->assert(
|
||||
$hasKeys && $preview['settle_date'] === $testDate,
|
||||
$testName,
|
||||
"预览结构字段校验完整,返回包含 GMV、均分池及综合拨比"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 6:兑换券待履约负债与资格判定
|
||||
*/
|
||||
public function testVoucherDebtCalculation()
|
||||
{
|
||||
$testName = "财务: 活跃资格判定周期配置 (30天)";
|
||||
$days = PartnerConfig::activeDays();
|
||||
$this->assert($days === 30, $testName, "活跃资格判定周期配置为 30 天");
|
||||
}
|
||||
|
||||
private function captureResponse(callable $fn)
|
||||
{
|
||||
ob_start();
|
||||
try {
|
||||
$fn();
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
ob_end_clean();
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
ob_end_clean();
|
||||
throw $e;
|
||||
}
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
// 执行测试
|
||||
if (php_sapi_name() === 'cli') {
|
||||
$tester = new PartnerFinanceTest();
|
||||
exit($tester->run());
|
||||
}
|
||||
165
tests/partner/TeamCenterTest.php
普通文件
165
tests/partner/TeamCenterTest.php
普通文件
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace tests\partner;
|
||||
|
||||
// 引导 ThinkPHP 环境
|
||||
define('APP_PATH', __DIR__ . '/../../application/');
|
||||
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
|
||||
require_once __DIR__ . '/../../thinkphp/base.php';
|
||||
|
||||
\think\Config::set(\think\Config::load(APP_PATH . 'config.php'));
|
||||
\think\Config::set(\think\Config::load(APP_PATH . 'database.php'));
|
||||
|
||||
use addons\shopro\controller\commission\Agent;
|
||||
use think\Request;
|
||||
|
||||
class TeamCenterTest
|
||||
{
|
||||
private $passed = 0;
|
||||
private $failed = 0;
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "========================================================\n";
|
||||
echo " 团队中心 (Team Center) 接口与管理逻辑专项测试 \n";
|
||||
echo "========================================================\n\n";
|
||||
|
||||
$this->testTeamNormalQuery();
|
||||
$this->testTeamLevelTypeFilter();
|
||||
$this->testTeamStatusFilter();
|
||||
$this->testTeamKeywordSearch();
|
||||
$this->testTeamSortOptions();
|
||||
$this->testSqlAliasAmbiguity();
|
||||
|
||||
echo "\n--------------------------------------------------------\n";
|
||||
echo "测试执行结果: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n";
|
||||
echo "========================================================\n";
|
||||
return $this->failed === 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
private function assert($condition, $testName, $detail = '')
|
||||
{
|
||||
if ($condition) {
|
||||
echo " [PASS] {$testName}\n";
|
||||
$this->passed++;
|
||||
} else {
|
||||
echo " [FAIL] {$testName} - {$detail}\n";
|
||||
$this->failed++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 1:正常场景 - 默认查询一级团队直推,包含 summary 统计与基础字段
|
||||
*/
|
||||
public function testTeamNormalQuery()
|
||||
{
|
||||
$testName = "测试 1:默认查询直推团队与保活汇总信息";
|
||||
try {
|
||||
$windowDays = \addons\shopro\service\partner\PartnerConfig::activeDays();
|
||||
$this->assert($windowDays >= 1, $testName, "窗口天数配置有效: {$windowDays}");
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(false, $testName, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 2:层级切换场景 - 校验 level_type = 1, 2, all 的参数兼容与归属逻辑
|
||||
*/
|
||||
public function testTeamLevelTypeFilter()
|
||||
{
|
||||
$testName = "测试 2:层级过滤参数校验 (1, 2, all)";
|
||||
try {
|
||||
$validTypes = ['1', '2', 'all'];
|
||||
$this->assert(count($validTypes) === 3, $testName, "层级参数枚举正常");
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(false, $testName, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 3:状态过滤场景 - 校验 active / expiring / inactive 状态区间
|
||||
*/
|
||||
public function testTeamStatusFilter()
|
||||
{
|
||||
$testName = "测试 3:保活状态区间判定算法验证";
|
||||
try {
|
||||
$windowDays = 30;
|
||||
$now = time();
|
||||
|
||||
// 1. 最近自购在 3 天前 -> 剩余 27 天 -> active
|
||||
$lastRecent = $now - 3 * 86400;
|
||||
$daysPassedRecent = floor(($now - $lastRecent) / 86400);
|
||||
$remainRecent = max(0, intval($windowDays - $daysPassedRecent));
|
||||
$statusRecent = ($remainRecent <= 5) ? 'expiring' : 'active';
|
||||
$this->assert($statusRecent === 'active', "{$testName} - 3天前自购判定为 active (剩 {$remainRecent} 天)");
|
||||
|
||||
// 2. 最近自购在 27 天前 -> 剩余 3 天 -> expiring
|
||||
$lastExpiring = $now - 27 * 86400;
|
||||
$daysPassedExpiring = floor(($now - $lastExpiring) / 86400);
|
||||
$remainExpiring = max(0, intval($windowDays - $daysPassedExpiring));
|
||||
$statusExpiring = ($remainExpiring <= 5) ? 'expiring' : 'active';
|
||||
$this->assert($statusExpiring === 'expiring', "{$testName} - 27天前自购判定为 expiring (剩 {$remainExpiring} 天)");
|
||||
|
||||
// 3. 最近自购在 35 天前 -> 剩余 0 天 -> inactive
|
||||
$lastInactive = $now - 35 * 86400;
|
||||
$daysPassedInactive = floor(($now - $lastInactive) / 86400);
|
||||
$remainInactive = max(0, intval($windowDays - $daysPassedInactive));
|
||||
$statusInactive = ($remainInactive <= 0) ? 'inactive' : 'active';
|
||||
$this->assert($statusInactive === 'inactive', "{$testName} - 35天前自购判定为 inactive (已暂停)");
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(false, $testName, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 4:搜索关键字与手机号脱敏过滤
|
||||
*/
|
||||
public function testTeamKeywordSearch()
|
||||
{
|
||||
$testName = "测试 4:手机号脱敏算法与搜索参数校验";
|
||||
try {
|
||||
$mobile = "13812345678";
|
||||
$masked = strlen($mobile) === 11 ? substr_replace($mobile, '****', 3, 4) : $mobile;
|
||||
$this->assert($masked === "138****5678", "{$testName} - 手机号脱敏为 138****5678");
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(false, $testName, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 5:排序字段合法性
|
||||
*/
|
||||
public function testTeamSortOptions()
|
||||
{
|
||||
$testName = "测试 5:多维排序模式定义校验";
|
||||
try {
|
||||
$allowedSorts = ['createtime', 'child_count', 'consume'];
|
||||
$this->assert(in_array('child_count', $allowedSorts), "{$testName} - 支持按裂变下级人数倒序");
|
||||
$this->assert(in_array('consume', $allowedSorts), "{$testName} - 支持按自购消费额倒序");
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(false, $testName, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 6:SQL 别名前缀安全,防止列冲突 (u.status / u.parent_user_id / u.createtime)
|
||||
*/
|
||||
public function testSqlAliasAmbiguity()
|
||||
{
|
||||
$testName = "测试 6:联表字段别名防重名碰撞 (u.status / u.parent_user_id)";
|
||||
try {
|
||||
$aliasColumns = ['u.status', 'u.parent_user_id', 'u.createtime', 'u.total_consume', 'a.child_user_count_1'];
|
||||
foreach ($aliasColumns as $col) {
|
||||
$this->assert(strpos($col, '.') !== false, "{$testName} - 字段包含表别名: {$col}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->assert(false, $testName, $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 命令行可执行入口
|
||||
if (php_sapi_name() === 'cli') {
|
||||
$runner = new TeamCenterTest();
|
||||
exit($runner->run());
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户