Newer
Older
smart-home-server / server / tests / ScriptsModelStateTest.php
@Eugene Sukhodolskiy Eugene Sukhodolskiy 4 hours ago 5 KB Fix 10 critical/high issues from Phase 6-7 audit
<?php

use PHPUnit\Framework\TestCase;
use SHServ\Models\Scripts;
use SHServ\Middleware\ControlScripts;

class ScriptsModelStateTest extends TestCase {
	private $tb;
	private $scriptsModel;
	private $originalScopes;

	protected function setUp(): void {
		$this -> tb = app() -> thin_builder;
		$this -> create_scripts_table();
		$this -> scriptsModel = new Scripts();
		$this -> originalScopes = app() -> control_scripts_instances;
		app() -> control_scripts_instances = [];
	}

	protected function tearDown(): void {
		$this -> tb -> query("DROP TABLE IF EXISTS scripts");
		app() -> control_scripts_instances = $this -> originalScopes;
		// Reset static script registries to avoid leakage between tests
		$refRegular = new \ReflectionProperty(ControlScripts::class, 'regular_scripts');
		$refRegular -> setAccessible(true);
		$refRegular -> setValue(null, []);
		$refActions = new \ReflectionProperty(ControlScripts::class, 'actions_scripts');
		$refActions -> setAccessible(true);
		$refActions -> setValue(null, []);
	}

	private function create_scripts_table(): void {
		$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
		)");
	}

	private function makeFakeScope(string $name): object {
		return new class($name) extends ControlScripts {
			private string $name;
			public function __construct(string $name) {
				$this -> name = $name;
				// bypass parent constructor to avoid DB checks
			}
			public function register_sync_map(): void {}
			protected function register_events_handlers(): void {}
			protected function register_regular_scripts(): void {}
			protected function register_actions_scripts(): void {}
		};
	}

	private function registerActionScript(string $alias): void {
		$scope = $this -> makeFakeScope('FakeScope');
		$ref = new \ReflectionMethod(ControlScripts::class, 'add_action_script');
		$ref -> setAccessible(true);
		$ref -> invoke($scope, ['alias' => $alias, 'name' => 'Test Action'], function($params) {
			return true;
		});
	}

	private function registerRegularScript(string $alias): void {
		$scope = $this -> makeFakeScope('FakeScope');
		$ref = new \ReflectionMethod(ControlScripts::class, 'add_regular_script');
		$ref -> setAccessible(true);
		$ref -> invoke($scope, ['alias' => $alias, 'name' => 'Test Regular'], function() {
			return true;
		});
	}

	public function test_get_scopes_list_returns_metadata(): void {
		app() -> control_scripts_instances = [
			'FakeScope' => $this -> makeFakeScope('FakeScope'),
		];

		$list = $this -> scriptsModel -> get_scopes_list();
		$this -> assertCount(1, $list);
		$this -> assertSame('FakeScope', $list[0]['name']);
		$this -> assertNotEmpty($list[0]['filename']);
		$this -> assertNotEmpty($list[0]['path']);
		$this -> assertSame('disabled', $list[0]['state']);
	}

	public function test_script_state_returns_false_for_missing(): void {
		$this -> assertFalse($this -> scriptsModel -> script_state('action', 'nonexistent'));
	}

	public function test_script_state_returns_true_for_enabled(): void {
		$this -> tb -> insert('scripts', [
			'uniq_name' => 'test_script',
			'type' => 'action',
			'state' => 'enabled',
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$this -> assertTrue($this -> scriptsModel -> script_state('action', 'test_script'));
	}

	public function test_enable_script_inserts_row(): void {
		$this -> registerActionScript('enable_me');
		$result = $this -> scriptsModel -> enable_script('action', 'enable_me');
		$this -> assertTrue($result);

		$rows = $this -> tb -> select('scripts', ['state'], [
			['uniq_name', '=', 'enable_me'],
			'AND',
			['type', '=', 'action'],
		]);
		$this -> assertCount(1, $rows);
		$this -> assertSame('enabled', $rows[0]['state']);
	}

	public function test_disable_script_updates_row(): void {
		$this -> registerActionScript('disable_me');
		$this -> tb -> insert('scripts', [
			'uniq_name' => 'disable_me',
			'type' => 'action',
			'state' => 'enabled',
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$result = $this -> scriptsModel -> disable_script('action', 'disable_me');
		$this -> assertTrue($result);

		$rows = $this -> tb -> select('scripts', ['state'], [
			['uniq_name', '=', 'disable_me'],
			'AND',
			['type', '=', 'action'],
		]);
		$this -> assertSame('disabled', $rows[0]['state']);
	}

	public function test_set_script_state_returns_scope_not_found(): void {
		app() -> control_scripts_instances = ['RealScope' => $this -> makeFakeScope('RealScope')];
		$result = $this -> scriptsModel -> set_script_state('scope', 'missing_scope', true);
		$this -> assertSame('scope_not_found', $result);
	}

	public function test_set_script_state_returns_invalid_type(): void {
		$result = $this -> scriptsModel -> set_script_state('invalid_type', 'something', true);
		$this -> assertSame('invalid_type', $result);
	}

	public function test_set_script_state_updates_existing_row(): void {
		$this -> registerRegularScript('regular_one');
		$this -> tb -> insert('scripts', [
			'uniq_name' => 'regular_one',
			'type' => 'regular',
			'state' => 'disabled',
			'create_at' => date('Y-m-d H:i:s'),
		]);

		$result = $this -> scriptsModel -> set_script_state('regular', 'regular_one', true);
		$this -> assertTrue($result);

		$rows = $this -> tb -> select('scripts', ['state'], [
			['uniq_name', '=', 'regular_one'],
			'AND',
			['type', '=', 'regular'],
		]);
		$this -> assertSame('enabled', $rows[0]['state']);
	}

	public function test_select_scripts_by_aliases_types_returns_empty_for_empty_array(): void {
		$result = $this -> scriptsModel -> select_scripts_by_aliases_types('action', []);
		$this -> assertIsArray($result);
		$this -> assertCount(0, $result);
	}
}