<?php
use PHPUnit\Framework\TestCase;
use SHServ\Controllers\CronController;
use SHServ\Middleware\ControlScripts;
use SHServ\Tools\DeviceScanner;
class TestableDeviceScanner extends DeviceScanner {
public $mockResults = [];
public function scan_range(string $start_ip, string $end_ip, int $port = 80, float $timeout = 1): array {
return $this -> mockResults;
}
}
class TestableCronController extends CronController {
public $cliCalls = [];
public $returnCodes = [];
public $mockScannerResults = [];
protected function run_script_cli(String $alias): int {
$this -> cliCalls[] = $alias;
return $this -> returnCodes[$alias] ?? 0;
}
protected function createDeviceScanner(): DeviceScanner {
$scanner = new TestableDeviceScanner();
$scanner -> mockResults = $this -> mockScannerResults;
return $scanner;
}
}
class CronControllerTest extends TestCase {
protected $tb;
protected function setUp(): void {
$this -> tb = app() -> thin_builder;
$this -> tb -> query("CREATE TABLE scripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
area_id INTEGER DEFAULT 0,
uniq_name TEXT,
type TEXT,
state TEXT,
create_at TEXT,
update_at TEXT
)");
$this -> tb -> query("CREATE TABLE devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
area_id INTEGER DEFAULT 0,
alias TEXT,
name TEXT,
device_type TEXT,
device_ip TEXT,
device_mac TEXT,
device_hard_id TEXT,
firmware_version TEXT,
connection_status TEXT,
status TEXT,
description TEXT,
last_contact TEXT,
create_at TEXT,
update_at TEXT
)");
ControlScripts::flush_statics();
}
protected function tearDown(): void {
$this -> tb -> query("DROP TABLE IF EXISTS scripts");
$this -> tb -> query("DROP TABLE IF EXISTS devices");
ControlScripts::flush_statics();
}
public function test_regular_scripts_are_dispatched_to_cli(): void {
$ref = new \ReflectionClass(ControlScripts::class);
$prop = $ref -> getProperty('regular_scripts');
$prop -> setAccessible(true);
$scripts = [
'fail_script' => [
'attributes' => ['alias' => 'fail_script', 'name' => 'Fail'],
'code' => '',
'script' => function() {}
],
'ok_script' => [
'attributes' => ['alias' => 'ok_script', 'name' => 'OK'],
'code' => '',
'script' => function() {}
]
];
$prop -> setValue(null, $scripts);
$this -> tb -> insert('scripts', [
'uniq_name' => 'fail_script',
'type' => 'regular',
'state' => 'enabled',
'create_at' => date('Y-m-d H:i:s'),
]);
$this -> tb -> insert('scripts', [
'uniq_name' => 'ok_script',
'type' => 'regular',
'state' => 'enabled',
'create_at' => date('Y-m-d H:i:s'),
]);
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$controller = new TestableCronController();
$controller -> returnCodes = ['fail_script' => 1, 'ok_script' => 0];
$controller -> run_regular_cron_scripts();
$this -> assertSame(['fail_script', 'ok_script'], $controller -> cliCalls);
}
public function test_disabled_regular_scripts_are_skipped(): void {
$ref = new \ReflectionClass(ControlScripts::class);
$prop = $ref -> getProperty('regular_scripts');
$prop -> setAccessible(true);
$scripts = [
'disabled_script' => [
'attributes' => ['alias' => 'disabled_script', 'name' => 'Disabled'],
'code' => '',
'script' => function() {}
]
];
$prop -> setValue(null, $scripts);
$this -> tb -> insert('scripts', [
'uniq_name' => 'disabled_script',
'type' => 'regular',
'state' => 'disabled',
'create_at' => date('Y-m-d H:i:s'),
]);
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$controller = new TestableCronController();
$controller -> run_regular_cron_scripts();
$this -> assertSame([], $controller -> cliCalls);
}
public function test_marks_stale_devices_as_lost(): void {
$this -> tb -> insert('devices', [
'alias' => 'stale_device',
'name' => 'Stale Device',
'device_type' => 'relay',
'device_ip' => '192.168.1.10',
'device_hard_id' => 'hard_stale_001',
'connection_status' => 'active',
'status' => 'active',
'last_contact' => date('Y-m-d H:i:s', time() - 600), // 10 минут назад
'create_at' => date('Y-m-d H:i:s'),
]);
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$controller = new TestableCronController();
$controller -> mockScannerResults = [];
$controller -> status_update_scanning();
$row = $this -> tb -> query("SELECT connection_status FROM devices WHERE device_hard_id = 'hard_stale_001'") -> fetch(\PDO::FETCH_ASSOC);
$this -> assertSame('lost', $row['connection_status']);
}
public function test_keeps_fresh_devices_active(): void {
$this -> tb -> insert('devices', [
'alias' => 'fresh_device',
'name' => 'Fresh Device',
'device_type' => 'relay',
'device_ip' => '192.168.1.20',
'device_hard_id' => 'hard_fresh_001',
'connection_status' => 'active',
'status' => 'active',
'last_contact' => date('Y-m-d H:i:s', time() - 60), // 1 минуту назад
'create_at' => date('Y-m-d H:i:s'),
]);
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$controller = new TestableCronController();
$controller -> mockScannerResults = [];
$controller -> status_update_scanning();
$row = $this -> tb -> query("SELECT connection_status FROM devices WHERE device_hard_id = 'hard_fresh_001'") -> fetch(\PDO::FETCH_ASSOC);
$this -> assertSame('active', $row['connection_status']);
}
public function test_scanner_recovers_lost_device(): void {
$this -> tb -> insert('devices', [
'alias' => 'lost_device',
'name' => 'Lost Device',
'device_type' => 'relay',
'device_ip' => '192.168.1.30',
'device_hard_id' => 'hard_lost_001',
'connection_status' => 'lost',
'status' => 'active',
'last_contact' => date('Y-m-d H:i:s', time() - 600),
'create_at' => date('Y-m-d H:i:s'),
]);
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$controller = new TestableCronController();
$controller -> mockScannerResults = [
[
'device_id' => 'hard_lost_001',
'ip_address' => '192.168.1.30',
'device_type' => 'relay',
]
];
$controller -> status_update_scanning();
$row = $this -> tb -> query("SELECT connection_status, device_ip FROM devices WHERE device_hard_id = 'hard_lost_001'") -> fetch(\PDO::FETCH_ASSOC);
$this -> assertSame('active', $row['connection_status']);
$this -> assertSame('192.168.1.30', $row['device_ip']);
}
public function test_scanner_updates_ip_on_roaming(): void {
$this -> tb -> insert('devices', [
'alias' => 'roaming_device',
'name' => 'Roaming Device',
'device_type' => 'relay',
'device_ip' => '192.168.1.40',
'device_hard_id' => 'hard_roam_001',
'connection_status' => 'active',
'status' => 'active',
'last_contact' => date('Y-m-d H:i:s', time() - 60),
'create_at' => date('Y-m-d H:i:s'),
]);
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$controller = new TestableCronController();
$controller -> mockScannerResults = [
[
'device_id' => 'hard_roam_001',
'ip_address' => '192.168.1.99',
'device_type' => 'relay',
]
];
$controller -> status_update_scanning();
$row = $this -> tb -> query("SELECT connection_status, device_ip FROM devices WHERE device_hard_id = 'hard_roam_001'") -> fetch(\PDO::FETCH_ASSOC);
$this -> assertSame('active', $row['connection_status']);
$this -> assertSame('192.168.1.99', $row['device_ip']);
}
}