<?php
use PHPUnit\Framework\TestCase;
use SHServ\Entities\Area;
class AreaRecursionTest extends TestCase {
protected $tb;
protected function setUp(): void {
$this -> tb = app() -> thin_builder;
$this -> tb -> query("CREATE TABLE areas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alias TEXT,
display_name TEXT,
type TEXT,
parent_id INTEGER DEFAULT 0,
schema TEXT,
create_at TEXT,
update_at TEXT
)");
$this -> tb -> query("CREATE TABLE devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
area_id INTEGER,
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
)");
$this -> tb -> query("CREATE TABLE scripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
area_id INTEGER,
uniq_name TEXT,
type TEXT,
state TEXT,
create_at TEXT,
update_at TEXT
)");
}
protected function tearDown(): void {
$this -> tb -> query("DROP TABLE IF EXISTS areas");
$this -> tb -> query("DROP TABLE IF EXISTS devices");
$this -> tb -> query("DROP TABLE IF EXISTS scripts");
}
protected function createArea(string $alias, int $parentId = 0, string $type = 'room'): int {
return $this -> tb -> insert('areas', [
'alias' => $alias,
'display_name' => ucfirst($alias),
'type' => $type,
'parent_id' => $parentId,
'schema' => '',
'create_at' => date('Y-m-d H:i:s'),
]);
}
protected function createDevice(int $areaId, string $alias): int {
return $this -> tb -> insert('devices', [
'area_id' => $areaId,
'alias' => $alias,
'name' => ucfirst($alias),
'device_type' => 'relay',
'status' => 'active',
'create_at' => date('Y-m-d H:i:s'),
]);
}
protected function createScript(int $areaId, string $uniqName, string $type = 'regular'): int {
return $this -> tb -> insert('scripts', [
'area_id' => $areaId,
'uniq_name' => $uniqName,
'type' => $type,
'state' => 'enable',
'create_at' => date('Y-m-d H:i:s'),
]);
}
public function test_get_inner_areas_non_recursive(): void {
$root = $this -> createArea('root', 0, 'building');
$this -> createArea('floor', $root, 'floor');
$this -> createArea('room', $root, 'room');
$this -> createArea('closet', $root + 1, 'room'); // child of floor
$area = new Area($root);
$inner = $area -> get_inner_areas(false);
$aliases = array_map(fn($a) => $a -> alias, $inner);
$this -> assertSame(['floor', 'room'], $aliases);
}
public function test_get_inner_areas_recursive(): void {
$root = $this -> createArea('root', 0, 'building');
$floor = $this -> createArea('floor', $root, 'floor');
$this -> createArea('room1', $floor, 'room');
$this -> createArea('room2', $floor, 'room');
$area = new Area($root);
$inner = $area -> get_inner_areas(true);
$aliases = array_map(fn($a) => $a -> alias, $inner);
$this -> assertSame(['floor', 'room1', 'room2'], $aliases);
}
public function test_get_recursive_inner_areas_ids_respects_depth_limit(): void {
$ids = [];
$parentId = 0;
for ($i = 0; $i < 13; $i++) {
$parentId = $this -> createArea("area_{$i}", $parentId, 'room');
$ids[] = $parentId;
}
$rootArea = new Area($ids[0]);
$method = new \ReflectionMethod($rootArea, 'get_recursive_inner_areas_ids');
$result = $method -> invoke($rootArea, $ids[0], 0);
// Depth limit 10 means descendants up to level 10 are collected
// Chain: 0 -> 1 -> 2 -> ... -> 12
// Expected IDs: 1..10 (10 items)
$expected = array_slice($ids, 1, 10);
$this -> assertSame($expected, $result);
$this -> assertNotContains($ids[11], $result);
$this -> assertNotContains($ids[12], $result);
}
public function test_remove_clears_child_parent_ids(): void {
$root = $this -> createArea('root', 0, 'building');
$child = $this -> createArea('child', $root, 'floor');
$rootArea = new Area($root);
$rootArea -> remove();
$rows = $this -> tb -> select('areas', ['parent_id'], [['id', '=', $child]]);
$this -> assertSame(0, $rows[0]['parent_id']);
}
public function test_remove_clears_device_area_ids(): void {
$room = $this -> createArea('room', 0, 'room');
$device = $this -> createDevice($room, 'relay_1');
$roomArea = new Area($room);
$roomArea -> remove();
$rows = $this -> tb -> select('devices', ['area_id'], [['id', '=', $device]]);
$this -> assertSame(0, $rows[0]['area_id']);
}
public function test_get_areas_by_type_recursive(): void {
$root = $this -> createArea('root', 0, 'building');
$floor = $this -> createArea('floor', $root, 'floor');
$this -> createArea('room1', $floor, 'room');
$this -> createArea('room2', $floor, 'room');
$area = new Area($root);
$floors = $area -> get_areas_by_type('floor', true);
$rooms = $area -> get_areas_by_type('room', true);
$this -> assertCount(1, $floors);
$this -> assertSame('floor', $floors[0] -> alias);
$this -> assertCount(2, $rooms);
}
public function test_get_area_by_alias_recursive(): void {
$root = $this -> createArea('root', 0, 'building');
$floor = $this -> createArea('floor', $root, 'floor');
$room = $this -> createArea('room1', $floor, 'room');
$area = new Area($root);
$found = $area -> get_area_by_alias('room1', true);
$this -> assertInstanceOf(Area::class, $found);
$this -> assertSame($room, $found -> id());
}
public function test_get_inner_devices_recursive(): void {
$root = $this -> createArea('root', 0, 'building');
$floor = $this -> createArea('floor', $root, 'floor');
$room = $this -> createArea('room', $floor, 'room');
$device = $this -> createDevice($room, 'relay_1');
$area = new Area($root);
$devices = $area -> get_inner_devices(true);
$this -> assertCount(1, $devices);
$this -> assertSame($device, $devices[0] -> id());
}
public function test_get_inner_scripts_recursive(): void {
$root = $this -> createArea('root', 0, 'building');
$floor = $this -> createArea('floor', $root, 'floor');
$room = $this -> createArea('room', $floor, 'room');
$script = $this -> createScript($room, 'script_1');
$area = new Area($root);
$scripts = $area -> get_inner_scripts(true);
$this -> assertCount(1, $scripts);
$this -> assertSame($script, $scripts[0] -> id());
}
public function test_parent_area_returns_parent_or_null(): void {
$root = $this -> createArea('root', 0, 'building');
$child = $this -> createArea('child', $root, 'floor');
$rootArea = new Area($root);
$childArea = new Area($child);
$this -> assertNull($rootArea -> parent_area());
$this -> assertInstanceOf(Area::class, $childArea -> parent_area());
$this -> assertSame($root, $childArea -> parent_area() -> id());
}
}