<?php

namespace App\Tests\Unit\Service\Security;

use App\Service\Security\InternalRequestsService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class TestableInternalRequestsService extends InternalRequestsService {
    public function isInternalIp(string $ip): bool { return parent::isInternalIp($ip); }
    public function tokenIsValid(string $token): bool { return parent::tokenIsValid($token); }
}

class InternalRequestsServiceTest extends TestCase
{
    const internalRequestsToken = 'azerty';

    public function setUp(): void {
    }

    private function getInternalRequestsServiceMockFor(string $methodName, string $token = null): TestableInternalRequestsService | MockObject {
        return $this->getMockBuilder(TestableInternalRequestsService::class)
            ->setConstructorArgs([$token ?? self::internalRequestsToken])
            ->setMethodsExcept([$methodName])
            ->getMock();
    }

    public function testIsInternalIp(): void {
        $internalRequestsService = $this->getInternalRequestsServiceMockFor('isInternalIp');

        $this->assertTrue($internalRequestsService->isInternalIp('127.0.0.0'));
        $this->assertTrue($internalRequestsService->isInternalIp('127.0.0.1'));

        $this->assertTrue($internalRequestsService->isInternalIp('10.8.0.1'));
        $this->assertTrue($internalRequestsService->isInternalIp('10.8.0.255'));

        $this->assertFalse($internalRequestsService->isInternalIp('141.94.117.32'));
        $this->assertTrue($internalRequestsService->isInternalIp('141.94.117.33'));
        $this->assertTrue($internalRequestsService->isInternalIp('141.94.117.50'));
        $this->assertTrue($internalRequestsService->isInternalIp('141.94.117.61'));
        $this->assertFalse($internalRequestsService->isInternalIp('141.94.117.62'));

        $this->assertTrue($internalRequestsService->isInternalIp('172.20.0.0'));
        $this->assertTrue($internalRequestsService->isInternalIp('172.20.255.255'));
    }

    public function testTokenIsValid(): void {
        $internalRequestsService = $this->getInternalRequestsServiceMockFor('tokenIsValid');

        $this->assertTrue($internalRequestsService->tokenIsValid(self::internalRequestsToken));
        $this->assertFalse($internalRequestsService->tokenIsValid('foo'));
    }

    public function testTokenIsValidWithEmptyToken(): void {
        $internalRequestsService = $this->getInternalRequestsServiceMockFor('tokenIsValid', '');

        // A token can not be valid if it is an empty string, even if it's equal to the internal token
        $this->assertFalse($internalRequestsService->tokenIsValid(''));
    }

    public function testIsAllowed(): void {
        $internalRequestsService = $this->getInternalRequestsServiceMockFor('isAllowed');

        $internalRequestsService->expects(self::once())->method('isInternalIp')->with('128.0.0.1')->willReturn(True);
        $internalRequestsService->expects(self::once())->method('tokenIsValid')->with('azerty')->willReturn(True);

        $result = $internalRequestsService->isAllowed('128.0.0.1', 'azerty');

        $this->assertTrue($result);
    }

    public function testIsAllowedInvalidIp(): void {
        $internalRequestsService = $this->getInternalRequestsServiceMockFor('isAllowed');

        $internalRequestsService->expects(self::once())->method('isInternalIp')->with('128.0.0.1')->willReturn(False);
        $internalRequestsService->expects(self::never())->method('tokenIsValid');

        $result = $internalRequestsService->isAllowed('128.0.0.1', 'azerty');

        $this->assertFalse($result);
    }

    public function testIsAllowedInvalidToken(): void {
        $internalRequestsService = $this->getInternalRequestsServiceMockFor('isAllowed');

        $internalRequestsService->expects(self::once())->method('isInternalIp')->with('128.0.0.1')->willReturn(True);
        $internalRequestsService->expects(self::once())->method('tokenIsValid')->with('azerty')->willReturn(False);

        $result = $internalRequestsService->isAllowed('128.0.0.1', 'azerty');

        $this->assertFalse($result);
    }
}
