更新
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use app\adminapi\lists\qywx\CustomerLists;
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
function qywxChannelExpect(bool $condition, string $message): void
|
||||
{
|
||||
if (!$condition) {
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
}
|
||||
|
||||
final class QywxChannelFilterQueryFake
|
||||
{
|
||||
/** @var array<int, array{field:string,operator:string,value:string,logic:string}> */
|
||||
public array $likes = [];
|
||||
/** @var string[] */
|
||||
public array $raw = [];
|
||||
|
||||
public function where($field, $operator = null, $value = null): self
|
||||
{
|
||||
if ($field instanceof Closure) {
|
||||
$field($this);
|
||||
return $this;
|
||||
}
|
||||
$this->likes[] = [
|
||||
'field' => (string) $field,
|
||||
'operator' => (string) $operator,
|
||||
'value' => (string) $value,
|
||||
'logic' => 'and',
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function whereOr($field, $operator = null, $value = null): self
|
||||
{
|
||||
$this->likes[] = [
|
||||
'field' => (string) $field,
|
||||
'operator' => (string) $operator,
|
||||
'value' => (string) $value,
|
||||
'logic' => 'or',
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function whereRaw(string $condition): self
|
||||
{
|
||||
$this->raw[] = $condition;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
$method = new ReflectionMethod(CustomerLists::class, 'projectAddChannelEvents');
|
||||
$method->setAccessible(true);
|
||||
$addWayLabelMethod = new ReflectionMethod(CustomerLists::class, 'addWayLabel');
|
||||
$addWayLabelMethod->setAccessible(true);
|
||||
$normalizeAddWayMethod = new ReflectionMethod(CustomerLists::class, 'normalizeAddWay');
|
||||
$normalizeAddWayMethod->setAccessible(true);
|
||||
|
||||
$events = [
|
||||
['id' => 12, 'external_userid' => 'ext-a', 'user_id' => 'staff-2', 'state' => 'channel-b', 'event_time' => 300],
|
||||
['id' => 11, 'external_userid' => 'ext-a', 'user_id' => 'staff-1', 'state' => ' ', 'event_time' => 300],
|
||||
['id' => 10, 'external_userid' => 'ext-a', 'user_id' => 'staff-1', 'state' => 'zyt_pool:2', 'event_time' => 250],
|
||||
['id' => 9, 'external_userid' => 'ext-a', 'user_id' => 'staff-1', 'state' => 'channel-b', 'event_time' => 200],
|
||||
['id' => 8, 'external_userid' => 'ext-a', 'user_id' => 'staff-1', 'state' => 'channel-a', 'event_time' => 100],
|
||||
['id' => 7, 'external_userid' => 'ext-zero', 'user_id' => 'staff-1', 'state' => '0', 'event_time' => 100],
|
||||
['id' => 6, 'external_userid' => '', 'user_id' => 'staff-1', 'state' => 'ignored', 'event_time' => 100],
|
||||
];
|
||||
|
||||
/** @var array<string, array<int, array<string, mixed>>> $projected */
|
||||
$projected = $method->invoke(null, $events, [2 => '九月投放方案']);
|
||||
|
||||
qywxChannelExpect(
|
||||
array_column($projected['ext-a'] ?? [], 'state') === ['channel-b', 'zyt_pool:2', 'channel-a'],
|
||||
'渠道必须按最近事件排序、排除空值并按 state 去重'
|
||||
);
|
||||
qywxChannelExpect(($projected['ext-a'][0]['user_id'] ?? '') === 'staff-2', '重复渠道必须保留最近事件的员工');
|
||||
qywxChannelExpect(($projected['ext-a'][0]['event_time'] ?? 0) === 300, '重复渠道必须保留最近事件时间');
|
||||
qywxChannelExpect(($projected['ext-a'][1]['label'] ?? '') === '九月投放方案', '获客助手 state 必须映射方案名称');
|
||||
qywxChannelExpect(($projected['ext-a'][1]['source_type'] ?? '') === 'promotion_pool', '获客助手渠道类型错误');
|
||||
qywxChannelExpect(($projected['ext-a'][1]['pool_id'] ?? 0) === 2, '获客助手方案 ID 解析错误');
|
||||
qywxChannelExpect(array_column($projected['ext-zero'] ?? [], 'state') === ['0'], '字符串 0 是有效渠道,不能被 empty/filter 丢弃');
|
||||
qywxChannelExpect(!isset($projected['']), '空 external_userid 不得生成渠道投影');
|
||||
|
||||
$fallback = $method->invoke(null, [
|
||||
['id' => 1, 'external_userid' => 'ext-b', 'user_id' => '', 'state' => 'zyt_pool:99', 'event_time' => 1],
|
||||
], []);
|
||||
qywxChannelExpect(
|
||||
($fallback['ext-b'][0]['label'] ?? '') === '获客助手方案 #99',
|
||||
'已删除或缺失的获客助手方案应保留可读兜底名称'
|
||||
);
|
||||
|
||||
$knownAddWays = [
|
||||
0 => '未知添加方式',
|
||||
1 => '通过扫描二维码添加',
|
||||
2 => '通过搜索手机号添加',
|
||||
3 => '通过名片分享添加',
|
||||
4 => '通过群聊添加',
|
||||
5 => '通过手机通讯录添加',
|
||||
6 => '通过微信联系人添加',
|
||||
8 => '安装第三方应用时自动添加',
|
||||
9 => '通过搜索邮箱添加',
|
||||
10 => '通过视频号添加',
|
||||
11 => '通过日程参与人添加',
|
||||
12 => '通过会议参与人添加',
|
||||
13 => '通过微信好友添加',
|
||||
14 => '通过智慧硬件专属客服添加',
|
||||
15 => '通过上门服务客服添加',
|
||||
16 => '通过获客链接添加',
|
||||
17 => '通过定制开发添加',
|
||||
18 => '通过需求回复添加',
|
||||
21 => '通过第三方售前客服添加',
|
||||
22 => '通过可能的商务伙伴添加',
|
||||
24 => '通过接受微信好友申请添加',
|
||||
201 => '通过内部成员共享添加',
|
||||
202 => '通过管理员或负责人分配添加',
|
||||
];
|
||||
foreach ($knownAddWays as $addWay => $expectedLabel) {
|
||||
qywxChannelExpect(
|
||||
$addWayLabelMethod->invoke(null, $addWay) === $expectedLabel,
|
||||
"add_way={$addWay} 缺少正确的可读文案"
|
||||
);
|
||||
}
|
||||
qywxChannelExpect(
|
||||
$addWayLabelMethod->invoke(null, 999) === '其他添加方式(999)',
|
||||
'未知的新 add_way 必须保留编号作为可读兜底'
|
||||
);
|
||||
qywxChannelExpect($normalizeAddWayMethod->invoke(null, '16') === 16, '数字字符串 add_way 应被规范化');
|
||||
qywxChannelExpect($normalizeAddWayMethod->invoke(null, '1future') === null, '异常 add_way 不得被强转成错误来源');
|
||||
qywxChannelExpect($normalizeAddWayMethod->invoke(null, null) === null, '缺失 add_way 应保持未记录');
|
||||
|
||||
$filterMethod = new ReflectionMethod(CustomerLists::class, 'applyAddWayFilter');
|
||||
$filterMethod->setAccessible(true);
|
||||
$list = (new ReflectionClass(CustomerLists::class))->newInstanceWithoutConstructor();
|
||||
$paramsProperty = new ReflectionProperty(app\common\lists\BaseDataLists::class, 'params');
|
||||
$paramsProperty->setAccessible(true);
|
||||
|
||||
$paramsProperty->setValue($list, ['add_way' => 1]);
|
||||
$filterQuery = new QywxChannelFilterQueryFake();
|
||||
$filterMethod->invoke($list, $filterQuery);
|
||||
qywxChannelExpect(
|
||||
array_column($filterQuery->likes, 'value') === [
|
||||
'%"add_way":1,%',
|
||||
'%"add_way":1}%',
|
||||
'%"add_way":"1",%',
|
||||
'%"add_way":"1"}%',
|
||||
],
|
||||
'渠道筛选必须精确匹配数字或字符串 add_way,不能让 1 误命中 16'
|
||||
);
|
||||
|
||||
$paramsProperty->setValue($list, ['add_way' => 0]);
|
||||
$zeroFilterQuery = new QywxChannelFilterQueryFake();
|
||||
$filterMethod->invoke($list, $zeroFilterQuery);
|
||||
qywxChannelExpect(count($zeroFilterQuery->likes) === 4, 'add_way=0 是有效渠道筛选,不能按空值忽略');
|
||||
|
||||
$paramsProperty->setValue($list, ['add_way' => 'invalid']);
|
||||
$invalidFilterQuery = new QywxChannelFilterQueryFake();
|
||||
$filterMethod->invoke($list, $invalidFilterQuery);
|
||||
qywxChannelExpect($invalidFilterQuery->raw === ['1=0'], '非法渠道参数必须返回空结果,不能泄露全量客户');
|
||||
|
||||
$paramsProperty->setValue($list, ['add_way' => '']);
|
||||
$emptyFilterQuery = new QywxChannelFilterQueryFake();
|
||||
$filterMethod->invoke($list, $emptyFilterQuery);
|
||||
qywxChannelExpect($emptyFilterQuery->likes === [] && $emptyFilterQuery->raw === [], '空渠道参数应表示不限');
|
||||
|
||||
$source = file_get_contents(__DIR__ . '/../app/adminapi/lists/qywx/CustomerLists.php');
|
||||
qywxChannelExpect(is_string($source), '无法读取 CustomerLists.php');
|
||||
foreach ([
|
||||
"->where('change_type', 'add_external_contact')",
|
||||
"->where('state', '<>', '')",
|
||||
"->whereIn('external_userid', \$ids)",
|
||||
"->order('event_time', 'desc')",
|
||||
"->order('id', 'desc')",
|
||||
"\$item['add_channels']",
|
||||
"\$item['add_channel_states']",
|
||||
"\$fu['add_way_label'] = self::addWayLabel(\$addWay)",
|
||||
'$this->applyAddWayFilter($query)',
|
||||
] as $needle) {
|
||||
qywxChannelExpect(str_contains($source, $needle), "客户渠道投影缺少契约:{$needle}");
|
||||
}
|
||||
|
||||
echo "QYWX_CUSTOMER_CHANNEL_PROJECTION_OK\n";
|
||||
Reference in New Issue
Block a user