Files
home_automatization/laravel-app/tests/Feature/ZoneControllerTest.php
T
cacto 467a05542c CRUD зон/устройств/правил автоматизации + RBAC owner/viewer
Policies (ZonePolicy/DevicePolicy/AutomationRulePolicy): не multi-tenant
изоляция по user_id, а household-wide RBAC — все авторизованные видят одни
и те же данные, только owner может создавать/менять/удалять. user_id на
записи — это created_by, а не граница видимости.

Form Requests с authorize() через политики; DeviceRequest учитывает unique
external_id с ignore на update. AutomationRuleRequest: action_type остаётся
строкой (не enum), т.к. это открытый список из device_types.capabilities;
для действия set_level в форме есть отдельное поле "level" (Alpine.js
переключает его видимость), которое контроллер мапит в action_params —
без сырого JSON-редактора для MVP.

Контроллеры без show (index+create/edit достаточно), Blade-формы на базе
Breeze-компонентов. Навигация и дашборд (счётчики зон/устройств/активных
правил) обновлены. Добавлен DemoGrowboxSeeder (демо-зона с датчиком,
вентилятором и правилом) и viewer-пользователь в сидер.

Восстановлен трейт AuthorizesRequests в базовом Controller (Laravel 11+
убрал его по умолчанию) — нужен для $this->authorize().

Проверено: 33/33 теста (включая новые ZoneControllerTest,
AutomationRuleControllerTest — маппинг level→action_params, 403 для
viewer), полный CRUD-цикл вживую через браузер под owner (создание зоны,
правила с set_level, Alpine-переключение поля "Уровень"), доступ viewer
подтверждён как read-only (нет кнопок изменения, прямой заход на
/zones/create отдаёт 403).
2026-08-11 16:54:26 +05:00

69 lines
2.0 KiB
PHP

<?php
namespace Tests\Feature;
use App\Enums\UserRole;
use App\Models\User;
use App\Models\Zone;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ZoneControllerTest extends TestCase
{
use RefreshDatabase;
public function test_owner_can_create_zone(): void
{
$owner = User::factory()->create(['role' => UserRole::Owner]);
$response = $this->actingAs($owner)->post(route('zones.store'), [
'name' => 'Гостиная',
'description' => 'Тест',
]);
$response->assertRedirect(route('zones.index'));
$this->assertDatabaseHas('zones', ['name' => 'Гостиная', 'user_id' => $owner->id]);
}
public function test_viewer_cannot_create_zone(): void
{
$viewer = User::factory()->create(['role' => UserRole::Viewer]);
$response = $this->actingAs($viewer)->post(route('zones.store'), [
'name' => 'Гостиная',
]);
$response->assertForbidden();
$this->assertDatabaseMissing('zones', ['name' => 'Гостиная']);
}
public function test_viewer_can_view_zones_index(): void
{
$viewer = User::factory()->create(['role' => UserRole::Viewer]);
Zone::forceCreate(['user_id' => $viewer->id, 'name' => 'Гроубокс']);
$response = $this->actingAs($viewer)->get(route('zones.index'));
$response->assertOk();
$response->assertSee('Гроубокс');
}
public function test_viewer_cannot_delete_zone(): void
{
$viewer = User::factory()->create(['role' => UserRole::Viewer]);
$zone = Zone::forceCreate(['user_id' => $viewer->id, 'name' => 'Гроубокс']);
$response = $this->actingAs($viewer)->delete(route('zones.destroy', $zone));
$response->assertForbidden();
$this->assertDatabaseHas('zones', ['id' => $zone->id]);
}
public function test_guest_is_redirected_to_login(): void
{
$response = $this->get(route('zones.index'));
$response->assertRedirect(route('login'));
}
}