2025-08-06 21:49:13 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Tests\Unit\Services\Factories;
|
|
|
|
|
|
2025-08-15 16:39:18 +02:00
|
|
|
use Domains\Article\Contracts\ArticleParserInterface;
|
|
|
|
|
use Domains\Article\Parsers\Factories\ArticleParserFactory;
|
|
|
|
|
use Domains\Article\Parsers\Belga\BelgaArticleParser;
|
|
|
|
|
use Domains\Article\Parsers\Vrt\VrtArticleParser;
|
2025-08-06 21:49:13 +02:00
|
|
|
use Exception;
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
|
|
class ArticleParserFactoryTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
public function test_get_parser_returns_vrt_parser_for_vrt_urls(): void
|
|
|
|
|
{
|
|
|
|
|
$vrtUrl = 'https://www.vrt.be/vrtnws/nl/2024/01/01/test-article/';
|
|
|
|
|
|
|
|
|
|
$parser = ArticleParserFactory::getParser($vrtUrl);
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf(VrtArticleParser::class, $parser);
|
|
|
|
|
$this->assertInstanceOf(ArticleParserInterface::class, $parser);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_get_parser_returns_belga_parser_for_belga_urls(): void
|
|
|
|
|
{
|
|
|
|
|
$belgaUrl = 'https://www.belganewsagency.eu/nl/nieuws/binnenland/test-article';
|
|
|
|
|
|
|
|
|
|
$parser = ArticleParserFactory::getParser($belgaUrl);
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf(BelgaArticleParser::class, $parser);
|
|
|
|
|
$this->assertInstanceOf(ArticleParserInterface::class, $parser);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_get_parser_throws_exception_for_unsupported_url(): void
|
|
|
|
|
{
|
|
|
|
|
$unsupportedUrl = 'https://www.example.com/article';
|
|
|
|
|
|
|
|
|
|
$this->expectException(Exception::class);
|
|
|
|
|
$this->expectExceptionMessage("No parser found for URL: {$unsupportedUrl}");
|
|
|
|
|
|
|
|
|
|
ArticleParserFactory::getParser($unsupportedUrl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_get_supported_sources_returns_array_of_source_names(): void
|
|
|
|
|
{
|
|
|
|
|
$sources = ArticleParserFactory::getSupportedSources();
|
|
|
|
|
|
|
|
|
|
$this->assertIsArray($sources);
|
|
|
|
|
$this->assertCount(2, $sources);
|
|
|
|
|
$this->assertContains('VRT News', $sources);
|
|
|
|
|
$this->assertContains('Belga News Agency', $sources);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_get_supported_sources_returns_sources_in_correct_order(): void
|
|
|
|
|
{
|
|
|
|
|
$sources = ArticleParserFactory::getSupportedSources();
|
|
|
|
|
|
|
|
|
|
// Based on the factory's parser registration order
|
|
|
|
|
$this->assertEquals('VRT News', $sources[0]);
|
|
|
|
|
$this->assertEquals('Belga News Agency', $sources[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_register_parser_adds_new_parser_to_list(): void
|
|
|
|
|
{
|
|
|
|
|
// Create a mock parser class
|
|
|
|
|
$mockParserClass = new class implements ArticleParserInterface {
|
|
|
|
|
public function canParse(string $url): bool
|
|
|
|
|
{
|
|
|
|
|
return str_contains($url, 'test-parser.com');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function extractData(string $html): array
|
|
|
|
|
{
|
|
|
|
|
return ['title' => 'Test Title'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getSourceName(): string
|
|
|
|
|
{
|
|
|
|
|
return 'TestParser';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$mockParserClassName = get_class($mockParserClass);
|
|
|
|
|
|
|
|
|
|
// Register the mock parser
|
|
|
|
|
ArticleParserFactory::registerParser($mockParserClassName);
|
|
|
|
|
|
|
|
|
|
// Verify it's now included in supported sources
|
|
|
|
|
$sources = ArticleParserFactory::getSupportedSources();
|
|
|
|
|
$this->assertContains('TestParser', $sources);
|
|
|
|
|
$this->assertCount(3, $sources); // Original 2 + 1 new
|
|
|
|
|
|
|
|
|
|
// Verify it can be used to parse URLs
|
|
|
|
|
$testUrl = 'https://test-parser.com/article';
|
|
|
|
|
$parser = ArticleParserFactory::getParser($testUrl);
|
|
|
|
|
$this->assertInstanceOf($mockParserClassName, $parser);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_register_parser_prevents_duplicate_registration(): void
|
|
|
|
|
{
|
|
|
|
|
// Get initial source count
|
|
|
|
|
$initialSources = ArticleParserFactory::getSupportedSources();
|
|
|
|
|
$initialCount = count($initialSources);
|
|
|
|
|
|
|
|
|
|
// Try to register an existing parser
|
|
|
|
|
ArticleParserFactory::registerParser(VrtArticleParser::class);
|
|
|
|
|
|
|
|
|
|
// Verify count hasn't changed
|
|
|
|
|
$newSources = ArticleParserFactory::getSupportedSources();
|
|
|
|
|
$this->assertCount($initialCount, $newSources);
|
|
|
|
|
$this->assertEquals($initialSources, $newSources);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_get_parser_uses_first_matching_parser(): void
|
|
|
|
|
{
|
|
|
|
|
// Create two mock parsers that can parse the same URL
|
|
|
|
|
$mockParser1 = new class implements ArticleParserInterface {
|
|
|
|
|
public function canParse(string $url): bool
|
|
|
|
|
{
|
|
|
|
|
return str_contains($url, 'shared-domain.com');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function extractData(string $html): array
|
|
|
|
|
{
|
|
|
|
|
return ['parser' => 'first'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getSourceName(): string
|
|
|
|
|
{
|
|
|
|
|
return 'FirstParser';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$mockParser2 = new class implements ArticleParserInterface {
|
|
|
|
|
public function canParse(string $url): bool
|
|
|
|
|
{
|
|
|
|
|
return str_contains($url, 'shared-domain.com');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function extractData(string $html): array
|
|
|
|
|
{
|
|
|
|
|
return ['parser' => 'second'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getSourceName(): string
|
|
|
|
|
{
|
|
|
|
|
return 'SecondParser';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$mockParser1Class = get_class($mockParser1);
|
|
|
|
|
$mockParser2Class = get_class($mockParser2);
|
|
|
|
|
|
|
|
|
|
// Register both parsers
|
|
|
|
|
ArticleParserFactory::registerParser($mockParser1Class);
|
|
|
|
|
ArticleParserFactory::registerParser($mockParser2Class);
|
|
|
|
|
|
|
|
|
|
// The first registered parser should be returned
|
|
|
|
|
$testUrl = 'https://shared-domain.com/article';
|
|
|
|
|
$parser = ArticleParserFactory::getParser($testUrl);
|
|
|
|
|
|
|
|
|
|
// Should return the first parser since it was registered first
|
|
|
|
|
$this->assertInstanceOf($mockParser1Class, $parser);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_factory_maintains_parser_registration_across_calls(): void
|
|
|
|
|
{
|
|
|
|
|
// Create a mock parser
|
|
|
|
|
$mockParser = new class implements ArticleParserInterface {
|
|
|
|
|
public function canParse(string $url): bool
|
|
|
|
|
{
|
|
|
|
|
return str_contains($url, 'persistent-test.com');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function extractData(string $html): array
|
|
|
|
|
{
|
|
|
|
|
return ['title' => 'Persistent Test'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getSourceName(): string
|
|
|
|
|
{
|
|
|
|
|
return 'PersistentTestParser';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$mockParserClass = get_class($mockParser);
|
|
|
|
|
|
|
|
|
|
// Register the parser
|
|
|
|
|
ArticleParserFactory::registerParser($mockParserClass);
|
|
|
|
|
|
|
|
|
|
// Make multiple calls to verify persistence
|
|
|
|
|
$parser1 = ArticleParserFactory::getParser('https://persistent-test.com/article1');
|
|
|
|
|
$parser2 = ArticleParserFactory::getParser('https://persistent-test.com/article2');
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf($mockParserClass, $parser1);
|
|
|
|
|
$this->assertInstanceOf($mockParserClass, $parser2);
|
|
|
|
|
|
|
|
|
|
// Verify both instances are of the same class but different objects
|
|
|
|
|
$this->assertEquals(get_class($parser1), get_class($parser2));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_get_parser_creates_new_instance_each_time(): void
|
|
|
|
|
{
|
|
|
|
|
$vrtUrl = 'https://www.vrt.be/vrtnws/nl/test/';
|
|
|
|
|
|
|
|
|
|
$parser1 = ArticleParserFactory::getParser($vrtUrl);
|
|
|
|
|
$parser2 = ArticleParserFactory::getParser($vrtUrl);
|
|
|
|
|
|
|
|
|
|
// Should be same class but different instances
|
|
|
|
|
$this->assertEquals(get_class($parser1), get_class($parser2));
|
|
|
|
|
$this->assertNotSame($parser1, $parser2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_get_supported_sources_creates_new_instances_for_each_call(): void
|
|
|
|
|
{
|
|
|
|
|
// This test ensures that getSupportedSources doesn't cause issues
|
|
|
|
|
// by creating new instances each time it's called
|
|
|
|
|
|
|
|
|
|
$sources1 = ArticleParserFactory::getSupportedSources();
|
|
|
|
|
$sources2 = ArticleParserFactory::getSupportedSources();
|
|
|
|
|
|
|
|
|
|
$this->assertEquals($sources1, $sources2);
|
|
|
|
|
$this->assertCount(count($sources1), $sources2);
|
|
|
|
|
}
|
|
|
|
|
}
|