Use a VALUES clause to fetch awards for all actors of a film in a single SPARQL request, reducing Wikidata API calls from ~20 per film to 1 and avoiding idle timeout errors from rate limiting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
175 lines
6.2 KiB
PHP
175 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Import;
|
|
|
|
use App\Entity\Actor;
|
|
use App\Entity\Award;
|
|
use App\Entity\AwardType;
|
|
use App\Gateway\WikidataGateway;
|
|
use App\Repository\AwardTypeRepository;
|
|
use App\Import\AwardImporter;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class AwardImporterTest extends TestCase
|
|
{
|
|
private AwardImporter $importer;
|
|
private WikidataGateway&\PHPUnit\Framework\MockObject\MockObject $wikidataGateway;
|
|
private AwardTypeRepository&\PHPUnit\Framework\MockObject\MockObject $awardTypeRepository;
|
|
private EntityManagerInterface&\PHPUnit\Framework\MockObject\MockObject $em;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->wikidataGateway = $this->createMock(WikidataGateway::class);
|
|
$this->awardTypeRepository = $this->createMock(AwardTypeRepository::class);
|
|
$this->em = $this->createMock(EntityManagerInterface::class);
|
|
|
|
$this->importer = new AwardImporter(
|
|
$this->wikidataGateway,
|
|
$this->awardTypeRepository,
|
|
$this->em,
|
|
);
|
|
}
|
|
|
|
public function testSkipsActorWithAwardsAlreadyImported(): void
|
|
{
|
|
$actor = $this->createActorWithFlag('Already Imported', awardsImported: true);
|
|
|
|
$this->wikidataGateway->expects($this->never())->method('getAwardsForActors');
|
|
|
|
$this->importer->importForActors([$actor]);
|
|
}
|
|
|
|
public function testImportsAwardsAndSetsFlag(): void
|
|
{
|
|
$actor = $this->createActorWithFlag('Test Actor', awardsImported: false);
|
|
|
|
$this->wikidataGateway->method('getAwardsForActors')->willReturn([
|
|
'Test Actor' => [
|
|
['name' => 'Academy Award for Best Actor', 'year' => 2020],
|
|
],
|
|
]);
|
|
|
|
$existingType = new AwardType();
|
|
$existingType->setName('Oscar')->setPattern('Academy Award');
|
|
|
|
$this->awardTypeRepository->method('findAll')->willReturn([$existingType]);
|
|
|
|
$persisted = [];
|
|
$this->em->method('persist')->willReturnCallback(function ($entity) use (&$persisted) {
|
|
$persisted[] = $entity;
|
|
});
|
|
|
|
$this->importer->importForActors([$actor]);
|
|
|
|
$this->assertTrue($actor->isAwardsImported());
|
|
$this->assertCount(1, $persisted);
|
|
$this->assertInstanceOf(Award::class, $persisted[0]);
|
|
$this->assertSame('Academy Award for Best Actor', $persisted[0]->getName());
|
|
$this->assertSame(2020, $persisted[0]->getYear());
|
|
$this->assertSame($existingType, $persisted[0]->getAwardType());
|
|
$this->assertSame($actor, $persisted[0]->getActor());
|
|
}
|
|
|
|
public function testCreatesNewAwardTypeWhenNoPatternMatches(): void
|
|
{
|
|
$actor = $this->createActorWithFlag('Test Actor', awardsImported: false);
|
|
|
|
$this->wikidataGateway->method('getAwardsForActors')->willReturn([
|
|
'Test Actor' => [
|
|
['name' => 'Screen Actors Guild Award for Outstanding Performance', 'year' => 2019],
|
|
],
|
|
]);
|
|
|
|
$this->awardTypeRepository->method('findAll')->willReturn([]);
|
|
|
|
$persisted = [];
|
|
$this->em->method('persist')->willReturnCallback(function ($entity) use (&$persisted) {
|
|
$persisted[] = $entity;
|
|
});
|
|
|
|
$this->importer->importForActors([$actor]);
|
|
|
|
$this->assertTrue($actor->isAwardsImported());
|
|
// Should persist both a new AwardType and the Award
|
|
$this->assertCount(2, $persisted);
|
|
|
|
$newType = $persisted[0];
|
|
$this->assertInstanceOf(AwardType::class, $newType);
|
|
$this->assertSame('Screen Actors Guild Award', $newType->getName());
|
|
$this->assertSame('Screen Actors Guild Award', $newType->getPattern());
|
|
|
|
$award = $persisted[1];
|
|
$this->assertInstanceOf(Award::class, $award);
|
|
$this->assertSame($newType, $award->getAwardType());
|
|
}
|
|
|
|
public function testDoesNotSetFlagOnWikidataError(): void
|
|
{
|
|
$actor = $this->createActorWithFlag('Test Actor', awardsImported: false);
|
|
|
|
$this->wikidataGateway->method('getAwardsForActors')
|
|
->willThrowException(new \RuntimeException('Wikidata timeout'));
|
|
|
|
$this->importer->importForActors([$actor]);
|
|
|
|
$this->assertFalse($actor->isAwardsImported());
|
|
}
|
|
|
|
public function testHandlesActorWithNoAwards(): void
|
|
{
|
|
$actor = $this->createActorWithFlag('Test Actor', awardsImported: false);
|
|
|
|
$this->wikidataGateway->method('getAwardsForActors')->willReturn([]);
|
|
$this->awardTypeRepository->method('findAll')->willReturn([]);
|
|
|
|
$this->em->expects($this->never())->method('persist');
|
|
|
|
$this->importer->importForActors([$actor]);
|
|
|
|
$this->assertTrue($actor->isAwardsImported());
|
|
}
|
|
|
|
public function testBatchImportsMultipleActors(): void
|
|
{
|
|
$actor1 = $this->createActorWithFlag('Actor One', awardsImported: false);
|
|
$actor2 = $this->createActorWithFlag('Actor Two', awardsImported: false);
|
|
$alreadyImported = $this->createActorWithFlag('Actor Three', awardsImported: true);
|
|
|
|
$this->wikidataGateway->expects($this->once())->method('getAwardsForActors')
|
|
->with($this->callback(fn (array $actors) => 2 === \count($actors)))
|
|
->willReturn([
|
|
'Actor One' => [['name' => 'Academy Award for Best Actor', 'year' => 2020]],
|
|
'Actor Two' => [['name' => 'Golden Globe for Best Actor', 'year' => 2021]],
|
|
]);
|
|
|
|
$existingType = new AwardType();
|
|
$existingType->setName('Oscar')->setPattern('Academy Award');
|
|
|
|
$this->awardTypeRepository->method('findAll')->willReturn([$existingType]);
|
|
|
|
$persisted = [];
|
|
$this->em->method('persist')->willReturnCallback(function ($entity) use (&$persisted) {
|
|
$persisted[] = $entity;
|
|
});
|
|
|
|
$this->importer->importForActors([$actor1, $actor2, $alreadyImported]);
|
|
|
|
$this->assertTrue($actor1->isAwardsImported());
|
|
$this->assertTrue($actor2->isAwardsImported());
|
|
// 2 Awards + 1 new AwardType (Golden Globe)
|
|
$this->assertCount(3, $persisted);
|
|
}
|
|
|
|
private function createActorWithFlag(string $name, bool $awardsImported): Actor
|
|
{
|
|
$actor = new Actor();
|
|
$actor->setName($name);
|
|
$actor->setAwardsImported($awardsImported);
|
|
|
|
return $actor;
|
|
}
|
|
}
|