| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Integration\Admin;
- use App\Domain\Auth\Role;
- use App\Domain\Auth\TokenKind;
- use App\Domain\Ip\IpAddress;
- use App\Infrastructure\Reputation\CidrEvaluatorFactory;
- use App\Tests\Integration\Support\AppTestCase;
- use Monolog\Handler\TestHandler;
- use Monolog\Level;
- use Monolog\Logger;
- use Psr\Log\LoggerInterface;
- /**
- * Covers SPEC §6 allowlist CRUD plus the allowlist-precedence warning
- * logged when an IP appears on both lists. Mirrors the manual-blocks
- * suite minus `expires_at`.
- */
- final class AllowlistControllerTest extends AppTestCase
- {
- public function testOperatorCanCreateIpAllowlistEntry(): void
- {
- $token = $this->createToken(TokenKind::Admin, role: Role::Operator);
- $response = $this->request(
- 'POST',
- '/api/v1/admin/allowlist',
- ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
- json_encode(['kind' => 'ip', 'ip' => '198.51.100.5', 'reason' => 'monitor']) ?: null,
- );
- self::assertSame(201, $response->getStatusCode());
- $body = $this->decode($response);
- self::assertSame('198.51.100.5', $body['ip']);
- }
- public function testOperatorCanCreateSubnetAllowlistEntry(): void
- {
- $token = $this->createToken(TokenKind::Admin, role: Role::Operator);
- $response = $this->request(
- 'POST',
- '/api/v1/admin/allowlist',
- ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
- json_encode(['kind' => 'subnet', 'cidr' => '10.0.0.0/8', 'reason' => 'private']) ?: null,
- );
- self::assertSame(201, $response->getStatusCode());
- $body = $this->decode($response);
- self::assertSame('10.0.0.0/8', $body['cidr']);
- self::assertSame(8, $body['prefix_length']);
- }
- public function testViewerCannotCreate(): void
- {
- $token = $this->createToken(TokenKind::Admin, role: Role::Viewer);
- $response = $this->request(
- 'POST',
- '/api/v1/admin/allowlist',
- ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
- json_encode(['kind' => 'ip', 'ip' => '1.2.3.4']) ?: null,
- );
- self::assertSame(403, $response->getStatusCode());
- }
- public function testAllowlistedIpIsAllowlistedRegardlessOfManualBlock(): void
- {
- $token = $this->createToken(TokenKind::Admin, role: Role::Admin);
- // Same IP on both lists — log should warn; evaluator reports both
- // memberships independently; SPEC precedence is the
- // EffectiveStatusService's job.
- $this->request(
- 'POST',
- '/api/v1/admin/manual-blocks',
- ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
- json_encode(['kind' => 'ip', 'ip' => '198.51.100.5']) ?: null,
- );
- $this->request(
- 'POST',
- '/api/v1/admin/allowlist',
- ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
- json_encode(['kind' => 'ip', 'ip' => '198.51.100.5']) ?: null,
- );
- // Inject a TestHandler-backed logger before the evaluator builds.
- $logger = new Logger('test');
- $logger->pushHandler($handler = new TestHandler(Level::Warning));
- /** @var \DI\Container $container */
- $container = $this->container;
- $container->set(LoggerInterface::class, $logger);
- // Rebuild the factory with the new logger so the warning is captured.
- $container->set(CidrEvaluatorFactory::class, new CidrEvaluatorFactory(
- $container->get(\App\Infrastructure\ManualBlock\ManualBlockRepository::class),
- $container->get(\App\Infrastructure\Allowlist\AllowlistRepository::class),
- $container->get(\App\Domain\Time\Clock::class),
- $logger,
- 0,
- ));
- /** @var CidrEvaluatorFactory $factory */
- $factory = $this->container->get(CidrEvaluatorFactory::class);
- $evaluator = $factory->get();
- $ip = IpAddress::fromString('198.51.100.5');
- self::assertTrue($evaluator->isAllowlisted($ip));
- self::assertTrue($evaluator->isManuallyBlocked($ip));
- $found = false;
- foreach ($handler->getRecords() as $record) {
- if (str_contains($record->message, 'allowlist takes precedence')) {
- $found = true;
- break;
- }
- }
- self::assertTrue($found, 'expected overlap warning to be logged');
- }
- public function testDeleteInvalidatesEvaluator(): void
- {
- $token = $this->createToken(TokenKind::Admin, role: Role::Admin);
- $created = $this->request(
- 'POST',
- '/api/v1/admin/allowlist',
- ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
- json_encode(['kind' => 'subnet', 'cidr' => '10.0.0.0/8']) ?: null,
- );
- $id = (int) $this->decode($created)['id'];
- /** @var CidrEvaluatorFactory $factory */
- $factory = $this->container->get(CidrEvaluatorFactory::class);
- self::assertTrue($factory->get()->isAllowlisted(IpAddress::fromString('10.0.0.1')));
- $delete = $this->request(
- 'DELETE',
- "/api/v1/admin/allowlist/{$id}",
- ['Authorization' => 'Bearer ' . $token],
- );
- self::assertSame(204, $delete->getStatusCode());
- self::assertFalse($factory->get()->isAllowlisted(IpAddress::fromString('10.0.0.1')));
- }
- }
|