- Correct test cases to ensure proper relay IDs are switched. - Add robust local IP resolution for relay-device bindings, including caching and inventory backfill. - Introduce fast-path options for relay status and switch dispatch. - Validate PHP extensions (`curl`, `sqlite3`) in edge agent images.
186 lines
6.5 KiB
PHP
186 lines
6.5 KiB
PHP
<?php
|
|
|
|
app_require('classes/edge_gateway_manager.php');
|
|
app_require('classes/gateway_shelly_transport.php');
|
|
|
|
use classes\edge_gateway_manager;
|
|
use classes\gateway_shelly_transport;
|
|
|
|
class GatewayShellyTransportManagerFake extends edge_gateway_manager
|
|
{
|
|
/** @var array<int,array<string,mixed>> */
|
|
public array $statusCalls = [];
|
|
/** @var array<int,array<string,mixed>> */
|
|
public array $switchCalls = [];
|
|
/** @var array<int,array<string,mixed>> */
|
|
public array $localOnlyStatusCalls = [];
|
|
/** @var array<int,array<string,mixed>> */
|
|
public array $localOnlySwitchCalls = [];
|
|
public ?Exception $statusException = null;
|
|
public ?Exception $switchException = null;
|
|
|
|
public function __construct()
|
|
{
|
|
}
|
|
|
|
public function dispatchRelayStatus(int $departmentId, string $logicalRelayId): array
|
|
{
|
|
if ($this->statusException instanceof Exception) {
|
|
throw $this->statusException;
|
|
}
|
|
|
|
$this->statusCalls[] = [
|
|
'department_id' => $departmentId,
|
|
'relay_id' => $logicalRelayId,
|
|
];
|
|
|
|
return [
|
|
'online' => true,
|
|
'on' => $logicalRelayId === 'relay-machine',
|
|
'raw' => ['source' => 'status'],
|
|
];
|
|
}
|
|
|
|
public function dispatchRelaySwitch(int $departmentId, string $logicalRelayId, bool $on): array
|
|
{
|
|
if ($this->switchException instanceof Exception) {
|
|
throw $this->switchException;
|
|
}
|
|
|
|
$this->switchCalls[] = [
|
|
'department_id' => $departmentId,
|
|
'relay_id' => $logicalRelayId,
|
|
'on' => $on,
|
|
];
|
|
|
|
return [
|
|
'online' => true,
|
|
'on' => $on,
|
|
'raw' => ['source' => 'switch'],
|
|
];
|
|
}
|
|
|
|
public function dispatchRelayStatusLocalOnly(int $departmentId, string $logicalRelayId): array
|
|
{
|
|
$this->localOnlyStatusCalls[] = [
|
|
'department_id' => $departmentId,
|
|
'relay_id' => $logicalRelayId,
|
|
];
|
|
|
|
return [
|
|
'online' => true,
|
|
'on' => true,
|
|
'raw' => ['source' => 'local-status'],
|
|
];
|
|
}
|
|
|
|
public function dispatchRelaySwitchLocalOnly(int $departmentId, string $logicalRelayId, bool $on): array
|
|
{
|
|
$this->localOnlySwitchCalls[] = [
|
|
'department_id' => $departmentId,
|
|
'relay_id' => $logicalRelayId,
|
|
'on' => $on,
|
|
];
|
|
|
|
return [
|
|
'online' => true,
|
|
'on' => $on,
|
|
'raw' => ['source' => 'local-switch'],
|
|
];
|
|
}
|
|
}
|
|
|
|
it('maps gateway relay status responses into the Shelly cloud payload shape', function (): void {
|
|
$manager = new GatewayShellyTransportManagerFake();
|
|
$transport = new gateway_shelly_transport($manager);
|
|
|
|
$result = $transport->sendPostRequest('/v2/devices/api/get', [
|
|
'ids' => ['relay-machine', 'relay-cleaner'],
|
|
], 17);
|
|
|
|
expect($manager->statusCalls)->toBe([
|
|
['department_id' => 17, 'relay_id' => 'relay-machine'],
|
|
['department_id' => 17, 'relay_id' => 'relay-cleaner'],
|
|
]);
|
|
expect($result)->toBeArray();
|
|
expect($result[0]['id'])->toBe('relay-machine');
|
|
expect($result[0]['status']['switch:0']['output'])->toBeTrue();
|
|
expect($result[1]['status']['switch:0']['output'])->toBeFalse();
|
|
});
|
|
|
|
it('maps gateway relay switch responses into the Shelly cloud payload shape', function (): void {
|
|
$manager = new GatewayShellyTransportManagerFake();
|
|
$transport = new gateway_shelly_transport($manager);
|
|
|
|
$result = $transport->sendPostRequest('/v2/devices/api/set/switch', [
|
|
'id' => 'relay-machine',
|
|
'on' => false,
|
|
], 17);
|
|
|
|
expect($manager->switchCalls)->toBe([
|
|
['department_id' => 17, 'relay_id' => 'relay-machine', 'on' => false],
|
|
]);
|
|
expect($result)->toBeArray();
|
|
expect($result[0]['id'])->toBe('relay-machine');
|
|
expect($result[0]['status']['switch:0']['output'])->toBeFalse();
|
|
});
|
|
|
|
it('uses local-only gateway dispatch for explicit local transport overrides', function (): void {
|
|
$manager = new GatewayShellyTransportManagerFake();
|
|
$transport = new gateway_shelly_transport($manager, true);
|
|
|
|
$status = $transport->sendPostRequest('/v2/devices/api/get', [
|
|
'ids' => ['relay-entry'],
|
|
], 17);
|
|
$switch = $transport->sendPostRequest('/v2/devices/api/set/switch', [
|
|
'id' => 'relay-entry',
|
|
'on' => true,
|
|
], 17);
|
|
|
|
expect($manager->localOnlyStatusCalls)->toBe([
|
|
['department_id' => 17, 'relay_id' => 'relay-entry'],
|
|
]);
|
|
expect($manager->localOnlySwitchCalls)->toBe([
|
|
['department_id' => 17, 'relay_id' => 'relay-entry', 'on' => true],
|
|
]);
|
|
expect($manager->statusCalls)->toBe([]);
|
|
expect($manager->switchCalls)->toBe([]);
|
|
expect($status[0]['raw']['source'])->toBe('local-status');
|
|
expect($switch[0]['raw']['source'])->toBe('local-switch');
|
|
});
|
|
|
|
it('requires a valid department id for gateway transport requests', function (): void {
|
|
$transport = new gateway_shelly_transport(new GatewayShellyTransportManagerFake());
|
|
|
|
expect(fn() => $transport->sendPostRequest('/v2/devices/api/get', ['ids' => ['relay-machine']], null))
|
|
->toThrow(Exception::class, 'A department_id is required for gateway Shelly transport');
|
|
});
|
|
|
|
it('rejects unsupported gateway transport endpoints', function (): void {
|
|
$transport = new gateway_shelly_transport(new GatewayShellyTransportManagerFake());
|
|
|
|
expect(fn() => $transport->sendPostRequest('/v2/devices/api/unknown', [], 17))
|
|
->toThrow(Exception::class, 'Unsupported gateway Shelly transport endpoint');
|
|
});
|
|
|
|
it('surfaces binding lookup failures while resolving relay state through the gateway transport', function (): void {
|
|
$manager = new GatewayShellyTransportManagerFake();
|
|
$manager->statusException = new Exception('Relay binding missing');
|
|
$transport = new gateway_shelly_transport($manager);
|
|
|
|
expect(fn() => $transport->sendPostRequest('/v2/devices/api/get', [
|
|
'ids' => ['relay-machine'],
|
|
], 17))->toThrow(Exception::class, 'Relay binding missing');
|
|
});
|
|
|
|
it('surfaces offline gateway failures while dispatching relay switch commands', function (): void {
|
|
$manager = new GatewayShellyTransportManagerFake();
|
|
$manager->switchException = new Exception('Gateway agent is offline');
|
|
$transport = new gateway_shelly_transport($manager);
|
|
|
|
expect(fn() => $transport->sendPostRequest('/v2/devices/api/set/switch', [
|
|
'id' => 'relay-machine',
|
|
'on' => true,
|
|
], 17))->toThrow(Exception::class, 'Gateway agent is offline');
|
|
});
|