Files
api/services/nginx/app/tests/Unit/Selfserve/ShellyRelayInventoryTest.php
T
Jeppe Bundgaard e93d3f30d2 Add support for Shelly device generation detection and update related tests
- Implement generation detection logic for Shelly devices using model codes, metadata, and type inference.
- Extend relay switch and inventory handling to include generation capabilities.
- Ensure compatibility with Gen1, Gen2, and Gen3 devices for relay control and diagnostics.
- Update unit tests to validate generation inference, fallback behavior, and API compatibility.
2026-04-27 17:40:39 +02:00

277 lines
10 KiB
PHP

<?php
app_require('classes/shelly_relay_inventory.php');
use classes\shelly_relay_inventory;
it('normalizes owned Shelly devices into relay select options', function (): void {
$inventory = (new shelly_relay_inventory())
->setInventoryFetcher(static function (): array {
return [
'isok' => true,
'data' => [
'devices_status' => [
'device-key-1' => [
'_dev_info' => [
'id' => 'shelly-plus-01',
'code' => 'SPSW-001PE16EU',
'model' => 'Shelly Plus 1PM',
'online' => 1,
],
'name' => 'Entry Gate',
'wifi_sta' => [
'ip' => '10.32.0.11',
],
'status' => [
'switch:0' => [
'output' => false,
'name' => 'Entrance Relay',
],
],
],
'device-key-2' => [
'_dev_info' => [
'id' => 'shelly-pro-03',
'code' => 'SPSW-201XE16EU',
'model' => 'Shelly Pro 2PM',
],
'name' => 'Machine Cabinet',
'status' => [
'eth' => [
'ip' => '10.32.0.12',
],
],
'relays' => [
['ison' => false, 'name' => 'Program Picker'],
],
],
'device-key-3' => [
'_dev_info' => [
'id' => 'shelly-legacy-02',
'code' => 'SHSW-1',
'model' => 'Shelly 1',
'online' => 0,
],
'relays' => [
['ison' => false],
],
],
'device-key-4' => [
'_dev_info' => [
'id' => 'shelly-sensor-01',
'code' => 'SHHT-1',
'model' => 'Shelly H&T',
'online' => 1,
],
'sensor' => [
'temperature' => 21.5,
],
],
],
],
];
})
->setDeviceListFetcher(static function (): array {
return [
'isok' => true,
'data' => [
'devices' => [
'shelly-plus-01' => [
'id' => 'shelly-plus-01',
'name' => 'Gate From Shelly Cloud',
'type' => 'SPSW-001PE16EU',
'cloud_online' => true,
],
'shelly-pro-03' => [
'id' => 'shelly-pro-03',
'name' => 'Program Picker From Shelly Cloud',
'type' => 'SPSW-201XE16EU',
'cloud_online' => false,
],
],
],
];
});
expect($inventory->listRelayOptions())->toBe([
[
'id' => 'shelly-plus-01',
'name' => 'Gate From Shelly Cloud (Shelly Plus 1PM)',
'device_id' => 'shelly-plus-01',
'device_name' => 'Gate From Shelly Cloud',
'cloud_name' => 'Gate From Shelly Cloud',
'device_type' => 'Shelly Plus 1PM',
'code' => 'SPSW-001PE16EU',
'device_model' => 'SPSW-001PE16EU',
'device_generation' => 2,
'control_type' => 'Switch',
'control_name' => 'Entrance Relay',
'local_ip' => '10.32.0.11',
'status_color' => 'Green',
'online' => true,
],
[
'id' => 'shelly-pro-03',
'name' => 'Program Picker From Shelly Cloud (Shelly Pro 2PM)',
'device_id' => 'shelly-pro-03',
'device_name' => 'Program Picker From Shelly Cloud',
'cloud_name' => 'Program Picker From Shelly Cloud',
'device_type' => 'Shelly Pro 2PM',
'code' => 'SPSW-201XE16EU',
'device_model' => 'SPSW-201XE16EU',
'device_generation' => 2,
'control_type' => 'Relay',
'control_name' => 'Program Picker',
'local_ip' => '10.32.0.12',
'status_color' => 'Red',
'online' => false,
],
[
'id' => 'shelly-legacy-02',
'name' => 'shelly-legacy-02 (Shelly 1)',
'device_id' => 'shelly-legacy-02',
'device_name' => null,
'cloud_name' => null,
'device_type' => 'Shelly 1',
'code' => 'SHSW-1',
'device_model' => 'SHSW-1',
'device_generation' => 1,
'control_type' => 'Relay',
'control_name' => null,
'local_ip' => null,
'status_color' => 'Red',
'online' => false,
],
]);
});
it('falls back to local device and control names when Shelly cloud list metadata is unavailable', function (): void {
$inventory = (new shelly_relay_inventory())
->setInventoryFetcher(static function (): array {
return [
'isok' => true,
'data' => [
'devices_status' => [
'device-key-1' => [
'_dev_info' => [
'id' => 'shelly-plus-01',
'code' => 'SPSW-001PE16EU',
'model' => 'Shelly Plus 1PM',
'online' => 1,
],
'ip' => '10.32.0.21',
'name' => 'Entry Gate',
'status' => [
'switch:0' => [
'output' => false,
'name' => 'Entrance Relay',
],
],
],
],
],
];
})
->setDeviceListFetcher(static function (): array {
return [
'isok' => false,
'errors' => [
'404' => 'Requested method was not found',
],
];
});
expect($inventory->listRelayOptions())->toBe([
[
'id' => 'shelly-plus-01',
'name' => 'Entry Gate / Entrance Relay (Shelly Plus 1PM)',
'device_id' => 'shelly-plus-01',
'device_name' => 'Entry Gate',
'cloud_name' => null,
'device_type' => 'Shelly Plus 1PM',
'code' => 'SPSW-001PE16EU',
'device_model' => 'SPSW-001PE16EU',
'device_generation' => 2,
'control_type' => 'Switch',
'control_name' => 'Entrance Relay',
'local_ip' => '10.32.0.21',
'status_color' => 'Green',
'online' => true,
],
]);
});
it('keeps Shelly 1 Mini Gen3 type, model, and generation aligned with Shelly metadata', function (): void {
$inventory = (new shelly_relay_inventory())
->setInventoryFetcher(static function (): array {
return [
'isok' => true,
'data' => [
'devices_status' => [
'device-key-1' => [
'_dev_info' => [
'id' => 'e4b3231f6410',
'model' => 'S3SW-001X8EU',
'type' => 'Shelly 1 Mini Gen3',
'online' => 1,
],
'name' => 'Roskilde indkørselsport',
'wifi_sta' => [
'ip' => '192.168.1.2',
],
'status' => [
'switch:0' => [
'output' => false,
],
],
],
],
],
];
})
->setDeviceListFetcher(static function (): array {
return [
'isok' => true,
'data' => [
'devices' => [
'e4b3231f6410' => [
'id' => 'e4b3231f6410',
'name' => 'Roskilde indkørselsport',
'type' => 'S3SW-001X8EU',
'cloud_online' => true,
],
],
],
];
});
expect($inventory->listRelayOptions())->toBe([[
'id' => 'e4b3231f6410',
'name' => 'Roskilde indkørselsport (Shelly 1 Mini Gen3)',
'device_id' => 'e4b3231f6410',
'device_name' => 'Roskilde indkørselsport',
'cloud_name' => 'Roskilde indkørselsport',
'device_type' => 'Shelly 1 Mini Gen3',
'code' => 'S3SW-001X8EU',
'device_model' => 'S3SW-001X8EU',
'device_generation' => 3,
'control_type' => 'Switch',
'control_name' => null,
'local_ip' => '192.168.1.2',
'status_color' => 'Green',
'online' => true,
]]);
});
it('fails fast when Shelly inventory does not include owned devices status', function (): void {
$inventory = (new shelly_relay_inventory())->setInventoryFetcher(static function (): array {
return [
'isok' => true,
'data' => [],
];
});
expect(fn() => $inventory->listRelayOptions())
->toThrow(Exception::class, 'Shelly relay inventory response was missing devices_status');
});