Files
deepseek beaf0e1f37 Toolvana: production-ready media tools platform (CodeIgniter 4)
- 134-tool registry with programmatic SEO (unique titles/H1/descriptions,
  JSON-LD graphs, sitemap index, canonical 301 enforcement via required filter)
- DB-backed job queue (SKIP LOCKED) with drivers: Ffmpeg, Images (GD),
  Pdf (qpdf/gs/poppler), Youtube (thumbnails), Qr (server-side PNG)
- Security: SSRF guard, MIME validation, rate limits, API-key auth,
  bcrypt admin login, security headers
- Admin panel: dashboard, tools/categories/guides CRUD, SEO audit,
  analytics, job inspector with retry, system health, feature flags
- Docker deployment (nginx + web/api FPM pools + scalable workers),
  PHPUnit suite (19 tests / 1139 assertions), PWA manifest + service worker
2026-08-23 07:10:30 +00:00

98 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Libraries\CatalogBuilder;
use App\Libraries\FormatCatalog;
use App\Libraries\Pipeline\Youtube as YoutubeLib;
use App\Libraries\UrlGuard;
use CodeIgniter\Test\CIUnitTestCase;
/**
* Pure unit tests — no database required.
*/
final class CatalogTest extends CIUnitTestCase
{
public function testCatalogBuildsUniqueSlugs(): void
{
$slugs = array_column(CatalogBuilder::build(), 'slug');
$this->assertSame($slugs, array_values(array_unique($slugs)), 'Tool slugs must be unique');
$this->assertGreaterThan(100, count($slugs));
}
public function testEveryToolHasCompleteSeoFields(): void
{
foreach (CatalogBuilder::build() as $tool) {
$this->assertNotSame('', trim((string) $tool['seo_title']), "seo_title empty for {$tool['slug']}");
$this->assertNotSame('', trim((string) $tool['h1']), "h1 empty for {$tool['slug']}");
$this->assertGreaterThanOrEqual(20, mb_strlen((string) $tool['seo_description']), "short description for {$tool['slug']}");
$this->assertNotEmpty($tool['how_to'], "how_to missing for {$tool['slug']}");
$this->assertNotEmpty($tool['faqs'], "faqs missing for {$tool['slug']}");
$this->assertLessThanOrEqual(65, mb_strlen((string) $tool['seo_title']) + 0, "title too long for {$tool['slug']}");
}
}
public function testConverterTitlesAreFormatSpecific(): void
{
$bySlug = [];
foreach (CatalogBuilder::build() as $tool) {
$bySlug[$tool['slug']] = $tool;
}
// each converter page must mention its actual formats
foreach (['jpg-to-png', 'mp4-to-webm', 'flac-to-mp3'] as $slug) {
$this->assertArrayHasKey($slug, $bySlug);
$title = (string) $bySlug[$slug]['seo_title'];
[$from, $to] = explode('-to-', $slug);
$this->assertStringContainsString(FormatCatalog::name($from), $title);
$this->assertStringContainsString(FormatCatalog::name($to), $title);
}
}
public function testYoutubeIdExtraction(): void
{
$cases = [
'https://www.youtube.com/watch?v=dQw4w9WgXcQ' => 'dQw4w9WgXcQ',
'https://youtu.be/dQw4w9WgXcQ?t=42' => 'dQw4w9WgXcQ',
'https://www.youtube.com/shorts/abcdefghijk' => 'abcdefghijk',
'https://www.youtube.com/embed/abcdefghijk' => 'abcdefghijk',
'dQw4w9WgXcQ' => 'dQw4w9WgXcQ',
'https://evil.com/watch?v=dQw4w9WgXcQ' => null,
'not a url' => null,
];
foreach ($cases as $input => $expected) {
$this->assertSame($expected, YoutubeLib::extractId($input), "Failed extracting from: {$input}");
}
}
public function testUrlGuardBlocksPrivateNetworks(): void
{
$guard = new UrlGuard();
$this->expectException(\InvalidArgumentException::class);
$guard->check('http://127.0.0.1/secret');
}
public function testUrlGuardBlocksFileScheme(): void
{
$guard = new UrlGuard();
$this->expectException(\InvalidArgumentException::class);
$guard->check('file:///etc/passwd');
}
public function testIsPublicIp(): void
{
$guard = new UrlGuard();
$this->assertFalse($guard->isPublicIp('10.0.0.5'));
$this->assertFalse($guard->isPublicIp('192.168.1.1'));
$this->assertFalse($guard->isPublicIp('169.254.169.254')); // cloud metadata
$this->assertTrue($guard->isPublicIp('142.250.185.78'));
}
}