security = $this->createStub(Security::class); $this->security ->method('isGranted') ->willReturnCallback(static fn (string $role): bool => 'ROLE_TEAMER' === $role) ; } public function testATeamerMayDownloadASeasonDocumentIssuedToThem(): void { $teamer = new Teamer(); $this->assertSame( VoterInterface::ACCESS_GRANTED, $this->vote(UploadVoter::DOWNLOAD, $this->seasonDocument($teamer), $teamer) ); } public function testATeamerMayNotDownloadAnotherTeamersSeasonDocument(): void { $this->assertSame( VoterInterface::ACCESS_DENIED, $this->vote(UploadVoter::DOWNLOAD, $this->seasonDocument(new Teamer()), new Teamer()) ); } public function testASeasonDocumentOnAnUnstaffedAssignmentReachesNoTeamer(): void { $upload = (new Upload()) ->setType(Upload::TYPE_SEASON_DOCUMENT_OUT) ->setOwner(new User()) ; $upload->setSeasonAssignment(new SeasonAssignment()); $this->assertSame( VoterInterface::ACCESS_DENIED, $this->vote(UploadVoter::DOWNLOAD, $upload, new Teamer()) ); } /** * Downloading is all the new rule grants: the file stays the office's, so the teamer it was * issued to may not delete it or reach it through any other attribute. */ #[\PHPUnit\Framework\Attributes\DataProvider('otherAttributes')] public function testTheNewRuleGrantsNothingButDownloading(string $attribute): void { $teamer = new Teamer(); $this->assertSame( VoterInterface::ACCESS_DENIED, $this->vote($attribute, $this->seasonDocument($teamer), $teamer) ); } /** * @return array */ public static function otherAttributes(): array { return [ 'viewing' => [UploadVoter::VIEW], 'deleting' => [UploadVoter::DELETE], ]; } /** * The library is deliberately open to all staff, which is exactly why an individually issued * document must never carry its type - so the blanket rule has to stay untouched. */ public function testTheLibraryStaysReadableByEveryTeamer(): void { $upload = (new Upload()) ->setType(Upload::TYPE_DOCUMENT) ->setOwner(new User()) ; $this->assertSame( VoterInterface::ACCESS_GRANTED, $this->vote(UploadVoter::DOWNLOAD, $upload, new Teamer()) ); } private function seasonDocument(Teamer $teamer): Upload { $seasonAssignment = (new SeasonAssignment())->setTeamer($teamer); $upload = (new Upload()) ->setType(Upload::TYPE_SEASON_DOCUMENT_OUT) ->setOwner(new User()) ; $upload->setSeasonAssignment($seasonAssignment); return $upload; } private function vote(string $attribute, Upload $upload, Teamer $teamer): int { $token = $this->createStub(TokenInterface::class); $token->method('getUser')->willReturn((new User())->setTeamer($teamer)); return (new UploadVoter($this->security))->vote($token, $upload, [$attribute]); } }