117 lines
3.7 KiB
PHP
117 lines
3.7 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace HyperfTest\Cases\Unit\Service;
|
||
|
|
|
||
|
|
use App\Model\OperationLog;
|
||
|
|
use App\Service\OperationLogService;
|
||
|
|
use HyperfTest\TestCase;
|
||
|
|
use HyperfTest\Traits\AuthenticatedTestTrait;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* OperationLogService 单元测试
|
||
|
|
*
|
||
|
|
* 验证操作日志的异步写入和辅助方法
|
||
|
|
*
|
||
|
|
* @internal
|
||
|
|
* @coversNothing
|
||
|
|
*/
|
||
|
|
class OperationLogServiceTest extends TestCase
|
||
|
|
{
|
||
|
|
use AuthenticatedTestTrait;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 在子协程中执行回调,等待其退出(含 defer 执行)
|
||
|
|
*
|
||
|
|
* co-phpunit 下所有测试共享同一顶层协程,
|
||
|
|
* Coroutine::defer 的回调要到协程退出才执行。
|
||
|
|
* 此方法创建子协程并 join 等待,确保 defer 回调完成后再继续。
|
||
|
|
*/
|
||
|
|
protected function runInChildCoroutineAndWait(callable $callback): void
|
||
|
|
{
|
||
|
|
$cid = \Swoole\Coroutine::create($callback);
|
||
|
|
if ($cid !== false) {
|
||
|
|
\Swoole\Coroutine::join([$cid]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_log_creates_record_in_coroutine(): void
|
||
|
|
{
|
||
|
|
$action = 'test.unit_' . uniqid();
|
||
|
|
|
||
|
|
$this->runInChildCoroutineAndWait(static function () use ($action): void {
|
||
|
|
OperationLogService::log(
|
||
|
|
user_id: 1,
|
||
|
|
action: $action,
|
||
|
|
target_type: 'user',
|
||
|
|
target_id: 99,
|
||
|
|
description: '单元测试操作日志',
|
||
|
|
detail: ['key' => 'value'],
|
||
|
|
ip: '127.0.0.1',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
// join 等待子协程退出后 defer 已执行,记录已写入
|
||
|
|
$record = OperationLog::query()->where('action', $action)->first();
|
||
|
|
|
||
|
|
$this->assertNotNull($record);
|
||
|
|
$this->assertSame(1, $record->user_id);
|
||
|
|
$this->assertSame($action, $record->action);
|
||
|
|
$this->assertSame('user', $record->target_type);
|
||
|
|
$this->assertSame(99, $record->target_id);
|
||
|
|
$this->assertSame('单元测试操作日志', $record->description);
|
||
|
|
$this->assertSame(['key' => 'value'], $record->detail);
|
||
|
|
$this->assertSame('127.0.0.1', $record->ip);
|
||
|
|
|
||
|
|
// 清理测试数据
|
||
|
|
OperationLog::query()->where('id', $record->id)->delete();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_log_with_null_detail_and_ip(): void
|
||
|
|
{
|
||
|
|
$action = 'test.null_detail_' . uniqid();
|
||
|
|
|
||
|
|
$this->runInChildCoroutineAndWait(static function () use ($action): void {
|
||
|
|
OperationLogService::log(
|
||
|
|
user_id: 2,
|
||
|
|
action: $action,
|
||
|
|
target_type: 'auth',
|
||
|
|
target_id: null,
|
||
|
|
description: '测试空 detail 和 ip',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
$record = OperationLog::query()->where('action', $action)->first();
|
||
|
|
|
||
|
|
$this->assertNotNull($record);
|
||
|
|
$this->assertSame(2, $record->user_id);
|
||
|
|
$this->assertNull($record->target_id);
|
||
|
|
$this->assertNull($record->detail);
|
||
|
|
$this->assertNull($record->ip);
|
||
|
|
|
||
|
|
// 清理
|
||
|
|
OperationLog::query()->where('id', $record->id)->delete();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_log_does_not_throw_on_failure(): void
|
||
|
|
{
|
||
|
|
// 验证 log 方法在写入失败时不抛异常(仅记录错误日志)
|
||
|
|
// 使用超长 action 触发数据库约束失败
|
||
|
|
$long_action = str_repeat('a', 100); // 超过 VARCHAR(50) 限制
|
||
|
|
|
||
|
|
$this->runInChildCoroutineAndWait(static function () use ($long_action): void {
|
||
|
|
OperationLogService::log(
|
||
|
|
user_id: 1,
|
||
|
|
action: $long_action,
|
||
|
|
target_type: 'test',
|
||
|
|
target_id: null,
|
||
|
|
description: '不应抛异常',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
// 如果到达这里说明没有抛异常
|
||
|
|
$this->assertTrue(true);
|
||
|
|
}
|
||
|
|
}
|