Управление типами устройств из UI (список + создание)

Раньше device_types заводились только через сидер — теперь owner может
посмотреть все типы (карточки: иконка по категории, capabilities
тегами, число устройств этого типа) и добавить новый прямо из
интерфейса: код, категория, возможности вводятся тегами (можно
вписать вручную и добавить по Enter/кнопке). Ссылка на список — со
страницы устройств.

DeviceTypePolicy зеркалит остальные Policy (view — всем, create —
только owner). DeviceTypeResource получил devices_count через
whenCounted — опционально, не ломает существующий API-эндпоинт
(там withCount не вызывается, поле просто не появляется).
This commit is contained in:
2026-08-18 03:07:07 +05:00
parent e452217cc7
commit 2c542fe097
10 changed files with 382 additions and 7 deletions
@@ -0,0 +1,76 @@
<?php
namespace Tests\Feature;
use App\Enums\UserRole;
use App\Models\DeviceType;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class DeviceTypeControllerTest extends TestCase
{
use RefreshDatabase;
public function test_owner_can_create_device_type(): void
{
$owner = User::factory()->create(['role' => UserRole::Owner]);
$response = $this->actingAs($owner)->post(route('device-types.store'), [
'code' => 'smart_socket',
'category' => 'actuator',
'capabilities' => ['turn_on', 'turn_off'],
]);
$response->assertRedirect(route('device-types.index'));
$this->assertDatabaseHas('device_types', [
'code' => 'smart_socket',
'category' => 'actuator',
]);
$this->assertSame(
['turn_on', 'turn_off'],
DeviceType::where('code', 'smart_socket')->first()->capabilities,
);
}
public function test_viewer_cannot_create_device_type(): void
{
$viewer = User::factory()->create(['role' => UserRole::Viewer]);
$response = $this->actingAs($viewer)->post(route('device-types.store'), [
'code' => 'smart_socket',
'category' => 'actuator',
]);
$response->assertForbidden();
$this->assertDatabaseMissing('device_types', ['code' => 'smart_socket']);
}
public function test_code_must_be_unique(): void
{
$owner = User::factory()->create(['role' => UserRole::Owner]);
DeviceType::create(['code' => 'fan', 'category' => 'actuator', 'capabilities' => []]);
$response = $this->actingAs($owner)->post(route('device-types.store'), [
'code' => 'fan',
'category' => 'actuator',
]);
$response->assertSessionHasErrors('code');
}
public function test_viewer_can_view_device_types_index(): void
{
$viewer = User::factory()->create(['role' => UserRole::Viewer]);
DeviceType::create(['code' => 'fan', 'category' => 'actuator', 'capabilities' => ['turn_on']]);
$response = $this->actingAs($viewer)->get(route('device-types.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('DeviceTypes/Index')
->where('deviceTypes.0.code', 'fan')
);
}
}