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('Oscar'); $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 testCanonicalMapGroupsRelatedAwards(): 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('SAG', $newType->getName()); $this->assertSame('SAG', $newType->getPattern()); $award = $persisted[1]; $this->assertInstanceOf(Award::class, $award); $this->assertSame($newType, $award->getAwardType()); } public function testFallsBackToExtractPrefixWhenNotInCanonicalMap(): void { $actor = $this->createActorWithFlag('Test Actor', awardsImported: false); $this->wikidataGateway->method('getAwardsForActors')->willReturn([ 'Test Actor' => [ ['name' => 'Bambi for Best Film', 'year' => 2019], ], ]); $this->awardTypeRepository->method('findAll')->willReturn([]); $persisted = []; $this->em->method('persist')->willReturnCallback(function ($entity) use (&$persisted) { $persisted[] = $entity; }); $this->importer->importForActors([$actor]); $newType = $persisted[0]; $this->assertInstanceOf(AwardType::class, $newType); $this->assertSame('Bambi', $newType->getName()); } public function testExcludesNonEntertainmentAwards(): void { $actor = $this->createActorWithFlag('Test Actor', awardsImported: false); $this->wikidataGateway->method('getAwardsForActors')->willReturn([ 'Test Actor' => [ ['name' => 'chevalier de la Légion d\'honneur', 'year' => 2015], ['name' => 'docteur honoris causa', 'year' => 2018], ['name' => 'bourse Rhodes', 'year' => 2010], ['name' => 'Oscar du meilleur acteur', 'year' => 2020], ], ]); $this->awardTypeRepository->method('findAll')->willReturn([]); $persisted = []; $this->em->method('persist')->willReturnCallback(function ($entity) use (&$persisted) { $persisted[] = $entity; }); $this->importer->importForActors([$actor]); // Only the Oscar should be persisted (1 AwardType + 1 Award) $this->assertCount(2, $persisted); $this->assertInstanceOf(AwardType::class, $persisted[0]); $this->assertSame('Oscar', $persisted[0]->getName()); $this->assertInstanceOf(Award::class, $persisted[1]); } 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('Oscar'); $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); } public function testExtractPrefixHandlesFrenchPatterns(): void { $actor = $this->createActorWithFlag('Test Actor', awardsImported: false); $this->wikidataGateway->method('getAwardsForActors')->willReturn([ 'Test Actor' => [ ['name' => 'Bodil du meilleur acteur', 'year' => 2019], ], ]); $this->awardTypeRepository->method('findAll')->willReturn([]); $persisted = []; $this->em->method('persist')->willReturnCallback(function ($entity) use (&$persisted) { $persisted[] = $entity; }); $this->importer->importForActors([$actor]); $newType = $persisted[0]; $this->assertInstanceOf(AwardType::class, $newType); $this->assertSame('Bodil', $newType->getName()); } public function testCanonicalMapReusesExistingType(): void { $actor = $this->createActorWithFlag('Test Actor', awardsImported: false); $this->wikidataGateway->method('getAwardsForActors')->willReturn([ 'Test Actor' => [ ['name' => 'oscar du meilleur acteur', 'year' => 2020], ['name' => 'Oscar de la meilleure actrice', 'year' => 2021], ], ]); $existingOscar = new AwardType(); $existingOscar->setName('Oscar')->setPattern('Oscar'); $this->awardTypeRepository->method('findAll')->willReturn([$existingOscar]); $persisted = []; $this->em->method('persist')->willReturnCallback(function ($entity) use (&$persisted) { $persisted[] = $entity; }); $this->importer->importForActors([$actor]); // Both awards should reuse the same "Oscar" type — only 2 Awards persisted, no new AwardType $this->assertCount(2, $persisted); $this->assertContainsOnlyInstancesOf(Award::class, $persisted); $this->assertSame($existingOscar, $persisted[0]->getAwardType()); $this->assertSame($existingOscar, $persisted[1]->getAwardType()); } private function createActorWithFlag(string $name, bool $awardsImported): Actor { $actor = new Actor(); $actor->setName($name); $actor->setAwardsImported($awardsImported); return $actor; } }