Files
SkinbaseNova/tests/Unit/ContentSanitizerTest.php
2026-02-26 21:12:32 +01:00

145 lines
6.2 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
use App\Services\ContentSanitizer;
// ── Rendering ─────────────────────────────────────────────────────────────────
test('render converts bold markdown to strong tag', function () {
$html = ContentSanitizer::render('**bold text**');
expect($html)->toContain('<strong>bold text</strong>');
});
test('render converts italic markdown to em tag', function () {
$html = ContentSanitizer::render('*italic text*');
expect($html)->toContain('<em>italic text</em>');
});
test('render converts inline code to code tag', function () {
$html = ContentSanitizer::render('use `code`');
expect($html)->toContain('<code>code</code>');
});
test('render auto-links URLs', function () {
$html = ContentSanitizer::render('visit https://example.com for more');
expect($html)->toContain('<a');
expect($html)->toContain('href="https://example.com"');
});
test('render returns empty string for null input', function () {
expect(ContentSanitizer::render(null))->toBe('');
});
test('render returns empty string for whitespace-only input', function () {
expect(ContentSanitizer::render(' '))->toBe('');
});
// ── XSS Prevention ────────────────────────────────────────────────────────────
test('render strips script tags', function () {
$html = ContentSanitizer::render('<script>alert("xss")</script>hello');
// The <script> tag itself must be gone (cannot execute)
expect($html)->not()->toContain('<script>');
// The word "hello" after the script block should appear
expect($html)->toContain('hello');
// Text inside script is rendered as harmless plain text — acceptable
// Critical: no executable script element exists in the output
});
test('render strips iframe tags', function () {
$html = ContentSanitizer::render('<iframe src="evil.com"></iframe>');
expect($html)->not()->toContain('<iframe');
});
test('render strips javascript: links', function () {
$html = ContentSanitizer::render('[click me](javascript:alert(1))');
expect($html)->not()->toContain('javascript:');
});
test('render strips style attributes', function () {
$html = ContentSanitizer::render('<b style="color:red">red</b>');
expect($html)->not()->toContain('style=');
});
test('render strips onclick attributes', function () {
$html = ContentSanitizer::render('<b onclick="evil()">text</b>');
expect($html)->not()->toContain('onclick');
});
test('render adds rel=noopener to external links', function () {
$html = ContentSanitizer::render('[link](https://example.com)');
expect($html)->toContain('rel="noopener noreferrer nofollow"');
});
// ── Legacy HTML conversion ────────────────────────────────────────────────────
test('render converts legacy bold HTML to markdown output', function () {
$html = ContentSanitizer::render('<b>old bold</b>');
expect($html)->toContain('<strong>');
expect($html)->not()->toContain('<script>');
});
test('render converts br tags to line breaks', function () {
$html = ContentSanitizer::render("line one<br>line two");
// Should not contain the raw <br> tag in unexpected ways
expect($html)->toContain('line one');
expect($html)->toContain('line two');
});
// ── Plain text ────────────────────────────────────────────────────────────────
test('stripToPlain removes all HTML', function () {
$plain = ContentSanitizer::stripToPlain('<p>Hello <b>world</b>!</p>');
expect($plain)->toBe('Hello world!');
});
test('stripToPlain converts br to newline', function () {
$plain = ContentSanitizer::stripToPlain("line one<br>line two");
expect($plain)->toContain("\n");
});
// ── Validation ────────────────────────────────────────────────────────────────
test('validate returns error for raw HTML tags', function () {
$errors = ContentSanitizer::validate('<script>evil</script>');
expect($errors)->not()->toBeEmpty();
expect(implode(' ', $errors))->toContain('HTML');
});
test('validate passes for clean markdown', function () {
$errors = ContentSanitizer::validate('**bold** and *italic* and `code`');
expect($errors)->toBeEmpty();
});
test('validate returns error when content is too long', function () {
$errors = ContentSanitizer::validate(str_repeat('a', 10_001));
expect($errors)->not()->toBeEmpty();
});
test('validate returns error when emoji density exceeds threshold', function () {
// 10 fire emoji + 4 spaces = 14 chars; emoji count = 10; density = 10/14 ≈ 0.71 > 0.40
$floodContent = implode(' ', array_fill(0, 10, '🔥'));
$errors = ContentSanitizer::validate($floodContent);
expect($errors)->not()->toBeEmpty();
expect(implode(' ', $errors))->toContain('emoji');
});
test('validate accepts content with reasonable emoji usage', function () {
// 3 emoji in a 50-char string — density ≈ 0.06, well below threshold
$errors = ContentSanitizer::validate('Great work on this piece 🎨 love the colours ❤️ keep it up 👏');
expect($errors)->toBeEmpty();
});
// ── collapseFlood ─────────────────────────────────────────────────────────────
test('collapseFlood delegates to LegacySmileyMapper and collapses runs', function () {
$input = implode(' ', array_fill(0, 8, '🍺'));
$result = ContentSanitizer::collapseFlood($input);
expect($result)->toContain('×8');
expect(substr_count($result, '🍺'))->toBeLessThanOrEqual(5);
});
test('collapseFlood returns unchanged string when no flood present', function () {
$input = 'Nice art 🎨 love it ❤️';
expect(ContentSanitizer::collapseFlood($input))->toBe($input);
});