artisan('lottery:admin-auth-sync')->assertExitCode(0); }); test('audit log presenter maps business action and entity target', function (): void { $row = new AuditLog([ 'operator_type' => 'admin', 'operator_id' => 1, 'module_code' => 'agent', 'action_code' => 'agent_role.sync_permissions', 'target_type' => 'admin_role', 'target_id' => '5', ]); $row->id = 1; $payload = AuditLogApiPresenter::row($row); expect($payload['module_label'])->toBe('代理') ->and($payload['action_label'])->toBe('同步代理角色权限') ->and($payload['target_label'])->toBe('角色 #5'); }); test('audit log presenter uses admin api resource name for middleware style rows', function (): void { $resourceName = (string) DB::table('admin_api_resources') ->where('code', 'admin.agent-roles.permissions.sync') ->value('name'); expect($resourceName)->not->toBe(''); $row = new AuditLog([ 'operator_type' => 'admin', 'operator_id' => 1, 'module_code' => 'agent', 'action_code' => 'sync', 'target_type' => 'admin.agent-roles.permissions.sync', 'target_id' => '5', ]); $row->id = 2; $resourceNames = ['admin.agent-roles.permissions.sync' => $resourceName]; $payload = AuditLogApiPresenter::row($row, $resourceNames); expect($payload['action_label'])->toBe($resourceName) ->and($payload['target_label'])->toBe($resourceName.' #5'); }); test('agent role permission sync records one business audit and skips middleware duplicate', function (): void { $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id'); $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'audit_dedup_super', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken; $branch = $service->createChild($super, [ 'parent_id' => $rootId, 'code' => 'audit-branch', 'name' => 'Audit Branch', ]); $create = $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/agent-nodes/'.$branch->id.'/roles', [ 'slug' => 'audit_role', 'name' => 'Audit Role', 'permission_slugs' => ['prd.agent.view'], ]) ->assertOk(); $roleId = (int) $create->json('data.id'); $maxIdBeforeSync = (int) AuditLog::query()->max('id'); $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/agent-roles/'.$roleId.'/permissions', [ 'permission_slugs' => ['prd.agent.view', 'prd.agent.role.view'], ]) ->assertOk(); $newRows = AuditLog::query() ->where('id', '>', $maxIdBeforeSync) ->orderBy('id') ->get(); expect($newRows)->toHaveCount(1) ->and($newRows[0]->action_code)->toBe('agent_role.sync_permissions') ->and($newRows[0]->target_type)->toBe('admin_role') ->and($newRows[0]->target_id)->toBe((string) $roleId); }); test('audit log index returns chinese display labels', function (): void { AuditLogger::record( AuditLogger::OPERATOR_ADMIN, 1, 'agent', 'agent_role.sync_permissions', 'admin_role', '9', ); $admin = AdminUser::query()->create([ 'username' => 'audit_list_super', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($admin); $token = $admin->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/audit-logs?per_page=5') ->assertOk() ->assertJsonPath('code', ErrorCode::Success->value) ->assertJsonPath('data.items.0.module_label', '代理') ->assertJsonPath('data.items.0.action_label', '同步代理角色权限') ->assertJsonPath('data.items.0.target_label', '角色 #9'); });