Files
zyt/server/tests/WecomPromotionDeletePoolBehaviorTest.php
2026-08-25 14:16:31 +08:00

169 lines
7.0 KiB
PHP
Raw Permalink 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\logic\firstvisit\WecomPromotionLogic;
use app\common\service\qywx\QywxCustomerAcquisitionApiService;
use think\facade\Db;
require dirname(__DIR__) . '/vendor/autoload.php';
$app = new think\App();
$app->initialize();
final class PromotionDeleteApiFake extends QywxCustomerAcquisitionApiService
{
/** @var list<string> */
public array $deleted = [];
public function __construct(
private string $failLinkId = '',
private string $failMessage = 'mock remote deletion failed',
private int $failCode = 0
) {
}
public function deleteLink(string $linkId): void
{
$this->deleted[] = $linkId;
if ($linkId === $this->failLinkId) {
throw new RuntimeException($this->failMessage, $this->failCode);
}
}
}
$assert = static function (bool $condition, string $message): void {
if (!$condition) {
throw new RuntimeException($message);
}
};
$admin = Db::name('admin')->where('root', 1)->whereNull('delete_time')->find();
if (!$admin) {
throw new RuntimeException('未找到 root 管理员,无法执行删除行为测试');
}
$now = time();
$suffix = bin2hex(random_bytes(6));
$firstRemoteId = 'contract_first_' . $suffix;
$secondRemoteId = 'contract_second_' . $suffix;
Db::startTrans();
try {
$poolId = (int) Db::name('qywx_promotion_pool')->insertGetId([
'name' => '删除契约测试',
'public_key' => bin2hex(random_bytes(16)),
'status' => 1,
'owner_admin_id' => (int) $admin['id'],
'create_time' => $now,
'update_time' => $now,
]);
$firstLinkId = (int) Db::name('qywx_promotion_link')->insertGetId([
'pool_id' => $poolId,
'name' => '当前官方链接',
'wecom_url' => 'https://work.weixin.qq.com/ca/' . $firstRemoteId,
'remote_link_id' => $firstRemoteId,
'remote_status' => 1,
'status' => 1,
'owner_admin_id' => (int) $admin['id'],
'create_time' => $now,
'update_time' => $now,
]);
$secondLinkId = (int) Db::name('qywx_promotion_link')->insertGetId([
'pool_id' => $poolId,
'name' => '历史已软删官方链接',
'wecom_url' => 'https://work.weixin.qq.com/ca/' . $secondRemoteId,
'remote_link_id' => $secondRemoteId,
'remote_status' => 1,
'status' => 0,
'owner_admin_id' => (int) $admin['id'],
'create_time' => $now,
'update_time' => $now,
'delete_time' => $now - 10,
]);
$memberId = (int) Db::name('qywx_promotion_pool_member')->insertGetId([
'pool_id' => $poolId,
'admin_id' => (int) $admin['id'],
'userid' => 'contract_user_' . $suffix,
'enabled' => 1,
'create_time' => $now,
'update_time' => $now,
]);
Db::name('qywx_promotion_range_sync')->insert([
'pool_id' => $poolId,
'promotion_link_id' => $firstLinkId,
'desired_version' => 1,
'applied_version' => 1,
'status' => 0,
'create_time' => $now,
'update_time' => $now,
]);
$firstAttempt = new PromotionDeleteApiFake(
$secondRemoteId,
'企业微信获客助手接口失败[40058]invalid range',
40058
);
$message = '';
try {
WecomPromotionLogic::deletePool($poolId, (int) $admin['id'], $admin, $firstAttempt);
} catch (RuntimeException $e) {
$message = $e->getMessage();
}
$assert(str_contains($message, '本地方案已保留'), '远端部分删除失败没有明确保留本地方案');
$assert($firstAttempt->deleted === [$firstRemoteId, $secondRemoteId], '首次删除没有按顺序处理全部远端链接');
$assert(Db::name('qywx_promotion_pool')->where('id', $poolId)->whereNull('delete_time')->count() === 1, '远端失败时错误删除了本地方案');
$assert((int) Db::name('qywx_promotion_link')->where('id', $firstLinkId)->value('remote_status') === 2, '部分成功的远端删除进度没有保存');
$assert((int) Db::name('qywx_promotion_link')->where('id', $secondLinkId)->value('remote_status') === 1, '失败的远端链接被错误标记为已删除');
$assert((int) Db::name('qywx_promotion_pool_member')->where('id', $memberId)->value('enabled') === 1, '远端失败时错误禁用了方案成员');
$retry = new PromotionDeleteApiFake();
WecomPromotionLogic::deletePool($poolId, (int) $admin['id'], $admin, $retry);
$assert($retry->deleted === [$secondRemoteId], '重试时重复删除了已经成功的远端链接');
$assert(Db::name('qywx_promotion_pool')->where('id', $poolId)->whereNotNull('delete_time')->count() === 1, '远端全部成功后没有软删除本地方案');
$assert((int) Db::name('qywx_promotion_link')->where('id', $secondLinkId)->value('remote_status') === 2, '历史软删链接没有同步删除企业微信端');
$assert((int) Db::name('qywx_promotion_pool_member')->where('id', $memberId)->value('enabled') === 0, '方案删除后成员仍处于启用状态');
$sync = Db::name('qywx_promotion_range_sync')->where('pool_id', $poolId)->find();
$assert((int) ($sync['status'] ?? 0) === 4 && (string) ($sync['lock_token'] ?? '') === '', '方案删除后同步租约没有终止');
// 企业微信明确返回 invalid link_id,说明远端目标已不存在或当前应用已无法操作;
// 删除应保持幂等并继续清理本地方案,不能让陈旧 link_id 永久阻塞用户。
$staleRemoteId = 'contract_stale_' . $suffix;
$stalePoolId = (int) Db::name('qywx_promotion_pool')->insertGetId([
'name' => '失效链接删除测试',
'public_key' => bin2hex(random_bytes(16)),
'status' => 1,
'owner_admin_id' => (int) $admin['id'],
'create_time' => $now,
'update_time' => $now,
]);
$staleLinkId = (int) Db::name('qywx_promotion_link')->insertGetId([
'pool_id' => $stalePoolId,
'name' => '远端已失效链接',
'wecom_url' => 'https://work.weixin.qq.com/ca/' . $staleRemoteId,
'remote_link_id' => $staleRemoteId,
'remote_status' => 1,
'status' => 1,
'owner_admin_id' => (int) $admin['id'],
'create_time' => $now,
'update_time' => $now,
]);
$staleApi = new PromotionDeleteApiFake(
$staleRemoteId,
'企业微信获客助手接口失败[40058]invalid link_id',
40058
);
WecomPromotionLogic::deletePool($stalePoolId, (int) $admin['id'], $admin, $staleApi);
$assert($staleApi->deleted === [$staleRemoteId], '失效链接没有请求企业微信删除接口');
$assert(
Db::name('qywx_promotion_pool')->where('id', $stalePoolId)->whereNotNull('delete_time')->count() === 1,
'invalid link_id 阻塞了本地方案删除'
);
$assert(
(int) Db::name('qywx_promotion_link')->where('id', $staleLinkId)->value('remote_status') === 2,
'invalid link_id 没有按远端已删除状态收敛'
);
} finally {
Db::rollback();
}
echo "WECOM_PROMOTION_DELETE_POOL_BEHAVIOR_OK\n";