domovoy/tests/Discovery/NetworkScannerTest.php

153 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Domovoy\Tests\Discovery;
use Domovoy\Models\DiscoveredHost;
use Domovoy\Models\NetworkRange;
use Domovoy\Repositories\DiscoveredHostRepository;
use Domovoy\Services\Discovery\ArpTableReader;
use Domovoy\Services\Discovery\HostFingerprintService;
use Domovoy\Services\Discovery\NetworkScanner;
use Domovoy\Services\Discovery\PingScanner;
use Domovoy\Services\Discovery\TcpPortScanner;
use PHPUnit\Framework\TestCase;
final class NetworkScannerTest extends TestCase
{
public function testScanEnumeratesUsableHostsForThirtyBitCidr(): void
{
$repository = new InMemoryDiscoveredHostRepository();
$scanner = new NetworkScanner(
new AlwaysAlivePingScanner(),
new EmptyTcpPortScanner(),
new EmptyArpTableReader(),
new StableFingerprintService(),
$repository
);
$range = new NetworkRange();
$range->cidr = '192.168.1.0/30';
$hosts = $scanner->scan($range, 123);
self::assertSame(['192.168.1.1', '192.168.1.2'], array_map(
static fn (DiscoveredHost $host): string => $host->ipAddress,
$hosts
));
self::assertSame([123, 123], array_map(
static fn (DiscoveredHost $host): ?int => $host->scanJobId,
$repository->saved
));
}
public function testScanRejectsRangesLargerThanTwentyFourForMvp(): void
{
$scanner = new NetworkScanner(
new AlwaysAlivePingScanner(),
new EmptyTcpPortScanner(),
new EmptyArpTableReader(),
new StableFingerprintService(),
new InMemoryDiscoveredHostRepository()
);
$range = new NetworkRange();
$range->cidr = '192.168.0.0/23';
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('larger than /24');
$scanner->scan($range, 321);
}
public function testResumableScanStartsAtSavedIndexAndReportsProgress(): void
{
$repository = new InMemoryDiscoveredHostRepository();
$scanner = new NetworkScanner(
new AlwaysAlivePingScanner(),
new EmptyTcpPortScanner(),
new EmptyArpTableReader(),
new StableFingerprintService(),
$repository
);
$range = new NetworkRange();
$range->cidr = '192.168.1.0/30';
$progress = [];
$result = $scanner->scanResumable(
$range,
456,
1,
static fn (): bool => false,
static function (array $state) use (&$progress): void {
$progress[] = $state;
}
);
self::assertSame(['192.168.1.2'], array_map(
static fn (DiscoveredHost $host): string => $host->ipAddress,
$result['hosts']
));
self::assertTrue($result['completed']);
self::assertSame(2, $result['next_ip_index']);
self::assertSame(2, $result['total_ips']);
self::assertSame(1, $result['scanned_count']);
self::assertSame(1, $progress[0]['next_ip_index']);
self::assertSame(2, $progress[1]['next_ip_index']);
}
}
final class AlwaysAlivePingScanner extends PingScanner
{
public function ping(string $ip, int $timeoutMs = 500): bool
{
return true;
}
}
final class EmptyTcpPortScanner extends TcpPortScanner
{
public function scan(string $ip, array $ports, int $timeoutMs = 200, int $maxConcurrency = 50): array
{
return [];
}
}
final class EmptyArpTableReader extends ArpTableReader
{
public function read(): array
{
return [];
}
}
final class StableFingerprintService extends HostFingerprintService
{
public function guessVendor(array $openPorts, ?string $hostname = null): ?string
{
return null;
}
public function calculateConfidence(int $openPortCount, bool $hasHostname): int
{
return 50;
}
}
final class InMemoryDiscoveredHostRepository extends DiscoveredHostRepository
{
/** @var DiscoveredHost[] */
public array $saved = [];
public function __construct()
{
}
public function save(DiscoveredHost $host): void
{
$this->saved[] = $host;
}
}