Files
clinicpro/tests/Blog/BlogBodySanitizerTest.php
T
hamed 47323daa27 feat: add RichTextEditor component for rich text editing in articles
feat: create SanitizeBlogBodiesCommand to clean existing blog bodies according to current HTML sanitization policies

test: add AppointmentTreatmentSessionLinkTest to ensure appointment booking functionality works correctly with treatment session links
2026-08-08 11:40:17 +03:30

136 lines
5.8 KiB
PHP

<?php
namespace App\Tests\Blog;
use App\Blog\Repository\BlogRepository;
use App\Tests\ApiTestCase;
/**
* آدیت ۲۰۲۶-۰۸-۰۷: بدنهٔ مقاله بدون پاک‌سازی ذخیره می‌شد و پنل با
* `dangerouslySetInnerHTML` رندرش می‌کرد. پاک‌سازی حالا در لحظهٔ ذخیره است، پس
* این تست‌ها به‌جای خروجی، **آنچه در DB نشسته** را می‌سنجند.
*/
class BlogBodySanitizerTest extends ApiTestCase
{
private function storedBody(string $uuid): string
{
$this->em->clear();
return self::getContainer()->get(BlogRepository::class)->findByUuid($uuid)->getBody();
}
public function testScriptTagIsStrippedOnCreate(): void
{
$admin = $this->createUser(['ROLE_ADMIN']);
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
'title' => 'مقالهٔ تست',
'body' => '<p>سلام</p><script>alert(1)</script>',
]);
$this->assertSame(201, $this->responseCode());
$body = $this->storedBody($res['data']['data']['uuid'] ?? $res['data']['uuid']);
$this->assertStringNotContainsString('<script', $body);
$this->assertStringNotContainsString('alert(1)', $body);
$this->assertStringContainsString('سلام', $body, 'متن سالم نباید حذف شود');
}
public function testInlineHandlerAndJavascriptUrlAreStripped(): void
{
$admin = $this->createUser(['ROLE_ADMIN']);
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
'title' => 'مقالهٔ تست',
'body' => '<p onclick="steal()">متن</p><a href="javascript:alert(1)">لینک</a>'
. '<img src="x" onerror="alert(2)">',
]);
$this->assertSame(201, $this->responseCode());
$body = $this->storedBody($res['data']['data']['uuid'] ?? $res['data']['uuid']);
$this->assertStringNotContainsString('onclick', $body);
$this->assertStringNotContainsString('onerror', $body);
$this->assertStringNotContainsString('javascript:', $body);
$this->assertStringContainsString('متن', $body);
}
public function testSafeRichTextSurvives(): void
{
$admin = $this->createUser(['ROLE_ADMIN']);
$html = '<h2>عنوان</h2><p><strong>پررنگ</strong> و <em>کج</em></p>'
. '<ul><li>یک</li><li>دو</li></ul>'
. '<a href="https://example.com">پیوند</a>'
. '<table><tbody><tr><td>خانه</td></tr></tbody></table>';
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
'title' => 'مقالهٔ تست',
'body' => $html,
]);
$this->assertSame(201, $this->responseCode());
$body = $this->storedBody($res['data']['data']['uuid'] ?? $res['data']['uuid']);
foreach (['<h2', '<strong', '<em', '<ul', '<li', '<a', '<table', '<td'] as $tag) {
$this->assertStringContainsString($tag, $body, "عنصر مجاز {$tag} نباید حذف شود");
}
$this->assertStringContainsString('https://example.com', $body);
// لینک باید rel امن بگیرد، وگرنه tabnabbing باز می‌ماند.
$this->assertStringContainsString('noopener', $body);
}
public function testUpdatePathIsSanitizedToo(): void
{
$admin = $this->createUser(['ROLE_ADMIN']);
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
'title' => 'مقالهٔ تست',
'body' => '<p>اولیه</p>',
]);
$uuid = $res['data']['data']['uuid'] ?? $res['data']['uuid'];
$this->authJson('PATCH', "/api/v1/blog/{$uuid}", $admin, [
'body' => '<p>ویرایش</p><script>alert(3)</script>',
]);
$this->assertSame(200, $this->responseCode());
$body = $this->storedBody($uuid);
$this->assertStringNotContainsString('<script', $body, 'مسیر ویرایش هم باید پاک‌سازی شود');
$this->assertStringContainsString('ویرایش', $body);
}
/**
* جدول‌های مقاله‌های موجود ظاهرشان را از attributeهای قدیمیِ HTML می‌گیرند.
* این سه غیرقابل‌اجرا هستند و از ۲۰۲۶-۰۸-۰۸ مجازند؛ `style` همچنان می‌رود چون
* تنها attributeِ ظاهریِ جدول است که می‌تواند بارِ اجرایی حمل کند.
*/
public function testTableKeepsInertLayoutAttributesButLosesStyle(): void
{
$admin = $this->createUser(['ROLE_ADMIN']);
$res = $this->authJson('POST', '/api/v1/blog', $admin, [
'title' => 'مقالهٔ جدول',
'body' => '<table border="1" cellpadding="7" cellspacing="0" style="width:100%">'
. '<tr><td>سلول</td></tr></table>',
]);
$uuid = $res['data']['data']['uuid'] ?? $res['data']['uuid'];
$body = $this->storedBody($uuid);
$this->assertStringContainsString('border="1"', $body);
$this->assertStringContainsString('cellpadding="7"', $body);
$this->assertStringContainsString('cellspacing="0"', $body);
$this->assertStringNotContainsString('style=', $body);
$this->assertStringContainsString('سلول', $body);
}
/** بدنه‌ای که چیزی جز markup ناامن ندارد، بعد از پاک‌سازی خالی است → ۴۲۲، نه ذخیره. */
public function testBodyThatIsOnlyUnsafeMarkupIsRejected(): void
{
$admin = $this->createUser(['ROLE_ADMIN']);
$this->authJson('POST', '/api/v1/blog', $admin, [
'title' => 'مقالهٔ تست',
'body' => '<script>alert(1)</script>',
]);
$this->assertSame(422, $this->responseCode());
}
}