Stabilize discovery workflow
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Tests\Repositories;
|
||||
|
||||
use Domovoy\Repositories\DiscoveredHostRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class DiscoveredHostFilterTest extends TestCase
|
||||
{
|
||||
public function testFindFilteredAppliesRangeScanJobAndStatusFilters(): void
|
||||
{
|
||||
$pdo = new FilterCapturePdo();
|
||||
$repository = new DiscoveredHostRepository($pdo);
|
||||
|
||||
$repository->findFiltered(7, 3, 'new', 50);
|
||||
|
||||
self::assertStringContainsString('LEFT JOIN scan_jobs', $pdo->query);
|
||||
self::assertStringContainsString('LEFT JOIN network_ranges', $pdo->query);
|
||||
self::assertStringContainsString('LEFT JOIN devices', $pdo->query);
|
||||
self::assertStringContainsString('sj.network_range_id = :range_id', $pdo->query);
|
||||
self::assertStringContainsString('dh.scan_job_id = :scan_job_id', $pdo->query);
|
||||
self::assertStringContainsString('dh.status = :status', $pdo->query);
|
||||
self::assertSame(7, $pdo->bound['range_id']);
|
||||
self::assertSame(3, $pdo->bound['scan_job_id']);
|
||||
self::assertSame('new', $pdo->bound['status']);
|
||||
self::assertSame(50, $pdo->bound['limit']);
|
||||
}
|
||||
|
||||
public function testFindFilteredSortsByIpByDefault(): void
|
||||
{
|
||||
$pdo = new FilterCapturePdo();
|
||||
$repository = new DiscoveredHostRepository($pdo);
|
||||
|
||||
$repository->findFiltered(null, null, 'all', 50);
|
||||
|
||||
self::assertStringContainsString('ORDER BY INET_ATON(dh.ip_address) ASC', $pdo->query);
|
||||
}
|
||||
|
||||
public function testFindFilteredAppliesAllowedSortColumnAndDirection(): void
|
||||
{
|
||||
$pdo = new FilterCapturePdo();
|
||||
$repository = new DiscoveredHostRepository($pdo);
|
||||
|
||||
$repository->findFiltered(null, null, 'all', 50, 'hostname', 'desc');
|
||||
|
||||
self::assertStringContainsString('ORDER BY dh.hostname DESC', $pdo->query);
|
||||
}
|
||||
}
|
||||
|
||||
final class FilterCapturePdo extends PDO
|
||||
{
|
||||
public string $query = '';
|
||||
/** @var array<string, mixed> */
|
||||
public array $bound = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function prepare(string $query, array $options = []): PDOStatement|false
|
||||
{
|
||||
$this->query = $query;
|
||||
return new FilterCaptureStatement($this);
|
||||
}
|
||||
}
|
||||
|
||||
final class FilterCaptureStatement extends PDOStatement
|
||||
{
|
||||
public function __construct(private FilterCapturePdo $pdo)
|
||||
{
|
||||
}
|
||||
|
||||
public function bindValue(string|int $param, mixed $value, int $type = PDO::PARAM_STR): bool
|
||||
{
|
||||
$this->pdo->bound[ltrim((string)$param, ':')] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function execute(?array $params = null): bool
|
||||
{
|
||||
foreach (($params ?? []) as $key => $value) {
|
||||
$this->pdo->bound[ltrim((string)$key, ':')] = $value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function fetch(int $mode = PDO::FETCH_DEFAULT, int $cursorOrientation = PDO::FETCH_ORI_NEXT, int $cursorOffset = 0): mixed
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Tests\Repositories;
|
||||
|
||||
use Domovoy\Models\DiscoveredHost;
|
||||
use Domovoy\Repositories\DiscoveredHostRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class DiscoveredHostRepositoryTest extends TestCase
|
||||
{
|
||||
public function testSaveUpdatesExistingNewHostByMacInsteadOfCreatingDuplicate(): void
|
||||
{
|
||||
$pdo = new FakeDiscoveredHostPdo();
|
||||
$repository = new DiscoveredHostRepository($pdo);
|
||||
|
||||
$first = new DiscoveredHost();
|
||||
$first->scanJobId = 1;
|
||||
$first->ipAddress = '192.168.1.10';
|
||||
$first->macAddress = 'AA:BB:CC:DD:EE:FF';
|
||||
$first->hostname = 'old-host';
|
||||
$first->openPorts = [22];
|
||||
$repository->save($first);
|
||||
|
||||
$second = new DiscoveredHost();
|
||||
$second->scanJobId = 2;
|
||||
$second->ipAddress = '192.168.1.20';
|
||||
$second->macAddress = 'aa:bb:cc:dd:ee:ff';
|
||||
$second->hostname = 'new-host';
|
||||
$second->openPorts = [22, 80];
|
||||
$repository->save($second);
|
||||
|
||||
self::assertCount(1, $pdo->rows);
|
||||
self::assertSame(1, $first->id);
|
||||
self::assertSame(1, $second->id);
|
||||
self::assertSame(2, $pdo->rows[1]['scan_job_id']);
|
||||
self::assertSame('192.168.1.20', $pdo->rows[1]['ip_address']);
|
||||
self::assertSame('new-host', $pdo->rows[1]['hostname']);
|
||||
self::assertSame('[22,80]', $pdo->rows[1]['open_ports_json']);
|
||||
}
|
||||
}
|
||||
|
||||
final class FakeDiscoveredHostPdo extends PDO
|
||||
{
|
||||
/** @var array<int, array<string, mixed>> */
|
||||
public array $rows = [];
|
||||
public int $lastId = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function prepare(string $query, array $options = []): PDOStatement|false
|
||||
{
|
||||
return new FakeDiscoveredHostStatement($this, $query);
|
||||
}
|
||||
|
||||
public function lastInsertId(?string $name = null): string|false
|
||||
{
|
||||
return (string)$this->lastId;
|
||||
}
|
||||
}
|
||||
|
||||
final class FakeDiscoveredHostStatement extends PDOStatement
|
||||
{
|
||||
/** @var array<string, mixed>|false */
|
||||
private array|false $result = false;
|
||||
|
||||
public function __construct(
|
||||
private FakeDiscoveredHostPdo $pdo,
|
||||
private string $query
|
||||
) {
|
||||
}
|
||||
|
||||
public function execute(?array $params = null): bool
|
||||
{
|
||||
$params ??= [];
|
||||
|
||||
if (str_contains($this->query, 'SELECT * FROM discovered_hosts WHERE id = :id')) {
|
||||
$this->result = $this->pdo->rows[(int)$params['id']] ?? false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($this->query, 'WHERE mac_address = :mac_address')) {
|
||||
$this->result = false;
|
||||
foreach ($this->pdo->rows as $row) {
|
||||
if ($row['mac_address'] === $params['mac_address'] && $row['status'] === 'new') {
|
||||
$this->result = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($this->query, 'WHERE ip_address = :ip_address')) {
|
||||
$this->result = false;
|
||||
foreach ($this->pdo->rows as $row) {
|
||||
if ($row['ip_address'] === $params['ip_address'] && $row['status'] === 'new') {
|
||||
$this->result = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($this->query, 'INSERT INTO discovered_hosts')) {
|
||||
$this->pdo->lastId++;
|
||||
$params['id'] = $this->pdo->lastId;
|
||||
$this->pdo->rows[$this->pdo->lastId] = $params;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($this->query, 'UPDATE discovered_hosts SET')) {
|
||||
$id = (int)$params['id'];
|
||||
$this->pdo->rows[$id] = array_replace($this->pdo->rows[$id], $params);
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Unexpected query: ' . $this->query);
|
||||
}
|
||||
|
||||
public function fetch(int $mode = PDO::FETCH_DEFAULT, int $cursorOrientation = PDO::FETCH_ORI_NEXT, int $cursorOffset = 0): mixed
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user