<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Tests\Security\Voter;

use Ferienpass\CoreBundle\Security\Voter\ApiTokenVoter;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

final class ApiTokenVoterTest extends TestCase
{
    private ApiTokenVoter $voter;

    protected function setUp(): void
    {
        $this->voter = new ApiTokenVoter('api:');
    }

    public function testSupportsAttributeReturnsTrueForApiPrefix(): void
    {
        self::assertTrue($this->voter->supportsAttribute('api:offers:read'));
    }

    public function testSupportsAttributeReturnsFalseForNonApiPrefix(): void
    {
        self::assertFalse($this->voter->supportsAttribute('ROLE_USER'));
    }

    public function testSupportsTypeReturnsTrueAlways(): void
    {
        self::assertTrue($this->voter->supportsType('anything'));
    }

    public function testVoteAbstainsWhenNoApiAttributes(): void
    {
        $token = $this->createStub(TokenInterface::class);
        $token->method('getRoleNames')->willReturn(['ROLE_USER']);

        $result = $this->voter->vote($token, null, ['ROLE_USER']);

        self::assertSame(VoterInterface::ACCESS_ABSTAIN, $result);
    }

    public function testVoteDeniesWhenRoleNotPresent(): void
    {
        $token = $this->createStub(TokenInterface::class);
        $token->method('getRoleNames')->willReturn(['api:offers:write']);

        $result = $this->voter->vote($token, null, ['api:offers:read']);

        self::assertSame(VoterInterface::ACCESS_DENIED, $result);
    }

    public function testVoteGrantsWhenRoleMatches(): void
    {
        $token = $this->createStub(TokenInterface::class);
        $token->method('getRoleNames')->willReturn(['api:offers:read']);

        $result = $this->voter->vote($token, null, ['api:offers:read']);

        self::assertSame(VoterInterface::ACCESS_GRANTED, $result);
    }

    public function testVoteGrantsWhenOneOfMultipleAttributesMatches(): void
    {
        $token = $this->createStub(TokenInterface::class);
        $token->method('getRoleNames')->willReturn(['api:offers:read']);

        $result = $this->voter->vote($token, null, ['api:offers:write', 'api:offers:read']);

        self::assertSame(VoterInterface::ACCESS_GRANTED, $result);
    }

    public function testVoteAbstainsWithMixedAttributesAndNoApiOnes(): void
    {
        $token = $this->createStub(TokenInterface::class);
        $token->method('getRoleNames')->willReturn([]);

        $result = $this->voter->vote($token, null, ['ROLE_ADMIN', 'IS_AUTHENTICATED']);

        self::assertSame(VoterInterface::ACCESS_ABSTAIN, $result);
    }

    public function testCustomPrefix(): void
    {
        $voter = new ApiTokenVoter('custom:');

        self::assertTrue($voter->supportsAttribute('custom:read'));
        self::assertFalse($voter->supportsAttribute('api:read'));
    }
}
