91 lines
3.5 KiB
PHP
91 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Jobs\IngestShellyMessage;
|
|
use App\Models\Device;
|
|
use App\Models\DeviceState;
|
|
use App\Models\Entity;
|
|
use App\Services\DeviceCommandService;
|
|
use App\Support\Drivers\DeviceDriver;
|
|
use App\Support\Drivers\ShellyMqttDriver;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class MqttTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_ingesting_a_shelly_status_upserts_entity_state(): void
|
|
{
|
|
$device = Device::create([
|
|
'name' => 'Lampe', 'vendor' => 'Shelly', 'protocol' => 'mqtt',
|
|
'config' => ['mqtt_prefix' => 'shelly-lampe'], 'status' => 'active',
|
|
'last_seen_at' => now()->subHour(),
|
|
]);
|
|
|
|
(new IngestShellyMessage('shelly-lampe/status/switch:0', json_encode(['output' => true, 'apower' => 12.7])))->handle();
|
|
|
|
$switch = $device->entities()->where('key', 'switch:0')->first();
|
|
$power = $device->entities()->where('key', 'power:0')->first();
|
|
|
|
$this->assertTrue(data_get($switch->state->state, 'on'));
|
|
$this->assertSame(13, data_get($power->state->state, 'watts'));
|
|
$this->assertTrue($device->refresh()->last_seen_at->gt(now()->subMinute()));
|
|
}
|
|
|
|
public function test_out_of_order_messages_do_not_overwrite_newer_state(): void
|
|
{
|
|
$device = Device::create([
|
|
'name' => 'Lampe', 'vendor' => 'Shelly', 'protocol' => 'mqtt',
|
|
'config' => ['mqtt_prefix' => 'shelly-lampe'], 'status' => 'active',
|
|
]);
|
|
|
|
// Newer message (higher observed_at) says on=true …
|
|
(new IngestShellyMessage('shelly-lampe/status/switch:0', json_encode(['output' => true]), 2_000))->handle();
|
|
// … an older one arriving late must NOT overwrite it.
|
|
(new IngestShellyMessage('shelly-lampe/status/switch:0', json_encode(['output' => false]), 1_000))->handle();
|
|
|
|
$switch = $device->entities()->where('key', 'switch:0')->first();
|
|
$this->assertTrue(data_get($switch->state->state, 'on'));
|
|
}
|
|
|
|
public function test_ingest_ignores_unknown_device(): void
|
|
{
|
|
(new IngestShellyMessage('shelly-nope/status/switch:0', json_encode(['output' => true])))->handle();
|
|
|
|
$this->assertDatabaseCount('entities', 0);
|
|
$this->assertDatabaseCount('device_states', 0);
|
|
}
|
|
|
|
public function test_command_service_audits_every_command(): void
|
|
{
|
|
// Fake driver so the test never touches a real broker.
|
|
$this->app->bind(ShellyMqttDriver::class, fn () => new class implements DeviceDriver
|
|
{
|
|
public function turnOn(Entity $entity): void {}
|
|
|
|
public function turnOff(Entity $entity): void {}
|
|
|
|
public function setState(Entity $entity, array $state): void {}
|
|
|
|
public function reboot(Device $device): void {}
|
|
|
|
public function capabilities(): array
|
|
{
|
|
return [];
|
|
}
|
|
});
|
|
|
|
$device = Device::create(['name' => 'Lampe', 'vendor' => 'Shelly', 'protocol' => 'mqtt', 'status' => 'active']);
|
|
$entity = Entity::create(['device_id' => $device->id, 'type' => 'switch', 'key' => 'switch:0']);
|
|
DeviceState::create(['entity_id' => $entity->id, 'state' => ['on' => false]]);
|
|
|
|
$command = app(DeviceCommandService::class)->toggle($entity->load('state'));
|
|
|
|
$this->assertSame('turn_on', $command->command);
|
|
$this->assertSame('ok', $command->result);
|
|
$this->assertDatabaseHas('commands', ['command' => 'turn_on', 'source' => 'user', 'result' => 'ok']);
|
|
}
|
|
}
|