Files
zyt/server/tests/FirstVisitConversionFanDetailTest.php
2026-08-25 11:40:58 +08:00

84 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
use app\adminapi\logic\firstvisit\FirstVisitConversionLogic;
use app\adminapi\logic\stats\ConversionLogic;
require dirname(__DIR__) . '/vendor/autoload.php';
function conversionFanDetailExpect(bool $condition, string $message): void
{
if (!$condition) {
throw new RuntimeException($message);
}
}
$firstVisitReflection = new ReflectionClass(FirstVisitConversionLogic::class);
$conversionSource = file_get_contents((new ReflectionClass(ConversionLogic::class))->getFileName());
conversionFanDetailExpect(is_string($conversionSource), 'Unable to read ConversionLogic source');
$buildDetailRows = (new ReflectionClass(ConversionLogic::class))->getMethod('buildFanDetailRows');
$buildDetailRows->setAccessible(true);
$detailRows = $buildDetailRows->invoke(null, [
['user_id' => 'staff-a', 'external_userid' => 'readded', 'add_time' => 100],
['user_id' => 'staff-a', 'external_userid' => 'readded', 'add_time' => 200],
['user_id' => 'staff-b', 'external_userid' => 'deleted', 'add_time' => 300],
], [
['user_id' => 'staff-a', 'external_userid' => 'readded', 'add_time' => 200],
]);
conversionFanDetailExpect(
count($detailRows) === 2
&& (int) ($detailRows[0]['add_time'] ?? 0) === 100
&& empty($detailRows[0]['is_deleted'])
&& !empty($detailRows[1]['is_deleted']),
'Delete/re-add details must retain the first counted add time and final deletion state'
);
conversionFanDetailExpect(
str_contains($conversionSource, 'self::loadFanDetailRows($startTimestamp, $endTimestamp, $mediaChannel, $adminIds)')
&& str_contains($conversionSource, 'MIN(e.event_time) AS add_time')
&& str_contains($conversionSource, "->where('e.event_time', 'between', [\$startTimestamp, \$endTimestamp])")
&& str_contains($conversionSource, 'EXISTS (SELECT 1 FROM `')
&& str_contains($conversionSource, 'MAX(event_time) AS delete_time'),
'Aggregate and detail results must share the same distinct fan-pair loader and expose event times'
);
conversionFanDetailExpect(
str_contains($conversionSource, "'external_userid' => \$externalUserId")
&& str_contains($conversionSource, "'customer_name'")
&& str_contains($conversionSource, "'wecom_staff_name'")
&& str_contains($conversionSource, "'is_deleted'")
&& str_contains($conversionSource, "'delete_time'"),
'Fan detail response must expose customer, employee, add and deletion fields'
);
$firstVisitSource = file_get_contents($firstVisitReflection->getFileName());
conversionFanDetailExpect(
is_string($firstVisitSource)
&& !str_contains($firstVisitSource, '$dashboard = self::overview($params, $adminId, $adminInfo);')
&& str_contains($firstVisitSource, 'resolveFanDetailEntity(')
&& str_contains($firstVisitSource, 'resolveFanDetailMemberDeptId(')
&& str_contains($firstVisitSource, "['channel_dept_ids']")
&& str_contains($firstVisitSource, "preg_match('/^M([1-9]\\d*)_(-?\\d+)$/', \$entityId")
&& str_contains($firstVisitSource, "MediaChannelService::getCurrentTagChannelByCode"),
'Fan detail must authorize the exact structural entity without recomputing overview and retain current-tag channel scope'
);
conversionFanDetailExpect(
str_contains($conversionSource, '$targetWorkWechatUserIds')
&& str_contains($conversionSource, 'fanDetailChannelDeptIds($mediaChannel)')
&& str_contains($conversionSource, 'filterEntitiesByChannelDeptScope(')
&& str_contains($conversionSource, 'NOT EXISTS (SELECT 1 FROM `')
&& str_contains($conversionSource, "':wecom:'")
&& str_contains($conversionSource, '$matched = $pairs;'),
'Fan detail must narrow the event query to the clicked employees/WeCom user before sorting and pagination'
);
$controllerSource = file_get_contents(dirname(__DIR__) . '/app/adminapi/controller/firstvisit/ConversionController.php');
conversionFanDetailExpect(
is_string($controllerSource)
&& str_contains($controllerSource, 'public function fansDetail()')
&& str_contains($controllerSource, 'FirstVisitConversionLogic::fansDetail('),
'First-visit conversion controller must expose the fansDetail endpoint'
);
echo "FirstVisitConversionFanDetailTest passed\n";