118 lines
4.5 KiB
PHP
118 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Jobs\IngestRingMessage;
|
|
use App\Models\Addon;
|
|
use App\Models\Device;
|
|
use App\Support\Mqtt\RingNormalizer;
|
|
use App\Support\Mqtt\RingTopics;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Tests\TestCase;
|
|
|
|
class RingIngestTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function installRing(): void
|
|
{
|
|
Addon::create(['key' => 'ring', 'installed' => true]);
|
|
Cache::flush();
|
|
}
|
|
|
|
public function test_topic_parser_reads_entity_topics(): void
|
|
{
|
|
$this->assertSame(
|
|
['location' => 'loc1', 'category' => 'camera', 'ring_id' => 'abc123', 'entity' => 'motion'],
|
|
RingTopics::parseState('ring/loc1/camera/abc123/motion/state'),
|
|
);
|
|
$this->assertTrue(RingTopics::isBridgeStatus('ring/loc1/status'));
|
|
$this->assertNull(RingTopics::parseState('ring/loc1/camera/abc123/motion/command'));
|
|
}
|
|
|
|
public function test_normalizer_maps_common_entities(): void
|
|
{
|
|
$this->assertSame(['on' => true], RingNormalizer::normalize('ding', 'ON')[0]['state']);
|
|
$this->assertSame(['on' => false], RingNormalizer::normalize('motion', 'OFF')[0]['state']);
|
|
$this->assertSame(['open' => true], RingNormalizer::normalize('contact', 'open')[0]['state']);
|
|
$this->assertSame(['percent' => 87], RingNormalizer::normalize('info', json_encode(['batteryLevel' => 87]))[0]['state']);
|
|
$this->assertSame([], RingNormalizer::normalize('unknown', 'ON'));
|
|
}
|
|
|
|
public function test_bridge_status_updates_addon(): void
|
|
{
|
|
$this->installRing();
|
|
|
|
(new IngestRingMessage('ring/loc1/status', 'online'))->handle();
|
|
$this->assertSame('connected', Addon::where('key', 'ring')->first()->status);
|
|
|
|
(new IngestRingMessage('ring/loc1/status', 'offline'))->handle();
|
|
$this->assertSame('error', Addon::where('key', 'ring')->first()->status);
|
|
}
|
|
|
|
public function test_doorbell_ding_auto_creates_cloud_device_and_state(): void
|
|
{
|
|
$this->installRing();
|
|
|
|
(new IngestRingMessage('ring/loc1/camera/frontdoor01/ding/state', 'ON'))->handle();
|
|
|
|
$device = Device::where('config->ring_id', 'frontdoor01')->first();
|
|
$this->assertNotNull($device);
|
|
$this->assertSame('Ring', $device->vendor);
|
|
$this->assertTrue($device->isCloud());
|
|
|
|
$ding = $device->entities()->where('key', 'ding')->first();
|
|
$this->assertTrue(data_get($ding->state->state, 'on'));
|
|
}
|
|
|
|
public function test_ingest_is_ignored_when_addon_not_installed(): void
|
|
{
|
|
Cache::flush(); // no ring addon row
|
|
|
|
(new IngestRingMessage('ring/loc1/camera/frontdoor01/motion/state', 'ON'))->handle();
|
|
|
|
$this->assertDatabaseCount('devices', 0);
|
|
}
|
|
|
|
public function test_unknown_entity_does_not_create_a_device(): void
|
|
{
|
|
$this->installRing();
|
|
|
|
(new IngestRingMessage('ring/loc1/camera/frontdoor01/wibble/state', 'ON'))->handle();
|
|
|
|
$this->assertDatabaseCount('devices', 0);
|
|
}
|
|
|
|
public function test_ring_id_is_unique_so_concurrent_creates_cannot_duplicate(): void
|
|
{
|
|
Device::create(['name' => 'A', 'vendor' => 'Ring', 'protocol' => 'mqtt', 'config' => ['ring_id' => 'dup1']]);
|
|
|
|
$this->expectException(\Illuminate\Database\UniqueConstraintViolationException::class);
|
|
Device::create(['name' => 'B', 'vendor' => 'Ring', 'protocol' => 'mqtt', 'config' => ['ring_id' => 'dup1']]);
|
|
}
|
|
|
|
public function test_repeated_messages_reuse_the_same_device(): void
|
|
{
|
|
$this->installRing();
|
|
|
|
(new IngestRingMessage('ring/loc1/camera/cam9/ding/state', 'ON'))->handle();
|
|
(new IngestRingMessage('ring/loc1/camera/cam9/motion/state', 'ON'))->handle();
|
|
(new IngestRingMessage('ring/loc1/camera/cam9/info/state', json_encode(['batteryLevel' => 50])))->handle();
|
|
|
|
$this->assertSame(1, Device::where('config->ring_id', 'cam9')->count());
|
|
$this->assertSame(3, Device::where('config->ring_id', 'cam9')->first()->entities()->count());
|
|
}
|
|
|
|
public function test_out_of_order_messages_do_not_overwrite_newer_state(): void
|
|
{
|
|
$this->installRing();
|
|
|
|
(new IngestRingMessage('ring/loc1/camera/cam1/motion/state', 'ON', 2_000))->handle();
|
|
(new IngestRingMessage('ring/loc1/camera/cam1/motion/state', 'OFF', 1_000))->handle();
|
|
|
|
$device = Device::where('config->ring_id', 'cam1')->first();
|
|
$this->assertTrue(data_get($device->entities()->where('key', 'motion')->first()->state->state, 'on'));
|
|
}
|
|
}
|