<?php

/**
 * 文件功能：后台更新日志安全测试
 * 验证广播 payload 与系统消息在发布时会对危险标题进行转义。
 */

namespace Tests\Feature\Feature;

use App\Events\ChangelogPublished;
use App\Jobs\SaveMessageJob;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;

/**
 * 后台更新日志安全测试
 * 负责回归标题进入广播和系统公告前会被安全处理。
 */
class AdminChangelogControllerSecurityTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 测试发布更新日志时会对广播字段与系统消息正文做 XSS 防护。
     */
    public function test_published_changelog_escapes_title_for_broadcast_and_system_message(): void
    {
        Event::fake([ChangelogPublished::class]);
        Queue::fake();

        $admin = User::factory()->create([
            'id' => 1,
            'user_level' => 100,
        ]);

        $dangerousTitle = '<img src=x onerror=alert(1)>';
        $dangerousVersion = '2026-04-" onclick="alert(2)';

        $response = $this->actingAs($admin)->post(route('admin.changelogs.store'), [
            'version' => $dangerousVersion,
            'title' => $dangerousTitle,
            'type' => 'fix',
            'content' => '修复说明',
            'is_published' => '1',
            'notify_chat' => '1',
        ]);

        $response->assertRedirect(route('admin.changelogs.index'));
        $this->assertDatabaseHas('dev_changelogs', [
            'title' => $dangerousTitle,
            'version' => $dangerousVersion,
            'is_published' => true,
        ]);

        Event::assertDispatched(ChangelogPublished::class, function (ChangelogPublished $event) use ($dangerousTitle, $dangerousVersion) {
            $payload = $event->broadcastWith();

            $this->assertSame($dangerousTitle, $payload['title']);
            $this->assertSame(e($dangerousTitle), $payload['safe_title']);
            $this->assertSame(e($dangerousVersion), $payload['safe_version']);
            $this->assertStringContainsString(rawurlencode($dangerousVersion), $payload['url']);
            $this->assertStringNotContainsString('" onclick="', $payload['url']);

            return true;
        });

        Queue::assertPushed(SaveMessageJob::class, function (SaveMessageJob $job) use ($dangerousTitle, $dangerousVersion) {
            $content = $job->messageData['content'] ?? '';

            $this->assertStringContainsString(e($dangerousTitle), $content);
            $this->assertStringContainsString(e($dangerousVersion), $content);
            $this->assertStringContainsString('rel="noopener"', $content);
            $this->assertStringContainsString(rawurlencode($dangerousVersion), $content);
            $this->assertStringNotContainsString($dangerousTitle, $content);

            return true;
        });
    }
}
