Files
zyt/server/tests/QywxCustomerChannelProjectionTest.php
T
2026-09-03 16:03:41 +08:00

186 lines
7.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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";