Токен-аутентификация Sanctum (login/logout по устройствам), эндпоинты /api/v1 для зон/устройств/правил автоматизации с тем же RBAC (owner/viewer), что и в веб-версии — контроллеры переиспользуют существующие Policy и FormRequest. Устройства отдают живой статус из Device Shadow (Redis) и историю телеметрии из ClickHouse, плюс управление (turn-on/turn-off/set-level) через device-control-service.
125 lines
4.5 KiB
PHP
125 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api\V1;
|
|
|
|
use App\Enums\DeviceCategory;
|
|
use App\Enums\UserRole;
|
|
use App\Models\Device;
|
|
use App\Models\DeviceType;
|
|
use App\Models\User;
|
|
use App\Models\Zone;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class AutomationRuleApiTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function makeZoneWithDevices(User $owner): array
|
|
{
|
|
$zone = Zone::forceCreate(['user_id' => $owner->id, 'name' => 'Гроубокс']);
|
|
|
|
$sensorType = DeviceType::create([
|
|
'code' => 'sensor_temp_humidity',
|
|
'category' => DeviceCategory::Sensor,
|
|
'capabilities' => ['temperature', 'humidity'],
|
|
]);
|
|
$fanType = DeviceType::create([
|
|
'code' => 'fan',
|
|
'category' => DeviceCategory::Actuator,
|
|
'capabilities' => ['turn_on', 'turn_off', 'set_level'],
|
|
]);
|
|
|
|
$sensor = Device::forceCreate([
|
|
'user_id' => $owner->id, 'zone_id' => $zone->id, 'device_type_id' => $sensorType->id,
|
|
'name' => 'Датчик', 'external_id' => 'sensor-1', 'protocol' => 'mqtt',
|
|
]);
|
|
$fan = Device::forceCreate([
|
|
'user_id' => $owner->id, 'zone_id' => $zone->id, 'device_type_id' => $fanType->id,
|
|
'name' => 'Вентилятор', 'external_id' => 'fan-1', 'protocol' => 'mqtt',
|
|
]);
|
|
|
|
return [$zone, $sensor, $fan];
|
|
}
|
|
|
|
public function test_owner_can_create_rule_with_turn_on_action(): void
|
|
{
|
|
$owner = User::factory()->create(['role' => UserRole::Owner]);
|
|
[$zone, $sensor, $fan] = $this->makeZoneWithDevices($owner);
|
|
|
|
$response = $this->actingAs($owner)->postJson('/api/v1/automation-rules', [
|
|
'zone_id' => $zone->id,
|
|
'condition_source_device_id' => $sensor->id,
|
|
'condition_sensor_type' => 'temperature',
|
|
'condition_operator' => '>',
|
|
'condition_value' => 28,
|
|
'target_device_id' => $fan->id,
|
|
'action_type' => 'turn_on',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$response->assertCreated();
|
|
// '{}', not '[]' — same PHP/Go empty-array-vs-object gotcha as the web controller.
|
|
$this->assertDatabaseHas('automation_rules', [
|
|
'target_device_id' => $fan->id,
|
|
'action_type' => 'turn_on',
|
|
'action_params' => '{}',
|
|
]);
|
|
}
|
|
|
|
public function test_set_level_action_requires_and_stores_level_param(): void
|
|
{
|
|
$owner = User::factory()->create(['role' => UserRole::Owner]);
|
|
[$zone, $sensor, $fan] = $this->makeZoneWithDevices($owner);
|
|
|
|
$missingLevel = $this->actingAs($owner)->postJson('/api/v1/automation-rules', [
|
|
'zone_id' => $zone->id,
|
|
'condition_source_device_id' => $sensor->id,
|
|
'condition_sensor_type' => 'temperature',
|
|
'condition_operator' => '>',
|
|
'condition_value' => 28,
|
|
'target_device_id' => $fan->id,
|
|
'action_type' => 'set_level',
|
|
]);
|
|
$missingLevel->assertJsonValidationErrors('level');
|
|
|
|
$response = $this->actingAs($owner)->postJson('/api/v1/automation-rules', [
|
|
'zone_id' => $zone->id,
|
|
'condition_source_device_id' => $sensor->id,
|
|
'condition_sensor_type' => 'temperature',
|
|
'condition_operator' => '>',
|
|
'condition_value' => 28,
|
|
'target_device_id' => $fan->id,
|
|
'action_type' => 'set_level',
|
|
'level' => 42,
|
|
]);
|
|
|
|
$response->assertCreated();
|
|
$this->assertDatabaseHas('automation_rules', [
|
|
'target_device_id' => $fan->id,
|
|
'action_type' => 'set_level',
|
|
'action_params' => '{"level":42}',
|
|
]);
|
|
}
|
|
|
|
public function test_viewer_cannot_create_rule(): void
|
|
{
|
|
$owner = User::factory()->create(['role' => UserRole::Owner]);
|
|
$viewer = User::factory()->create(['role' => UserRole::Viewer]);
|
|
[$zone, $sensor, $fan] = $this->makeZoneWithDevices($owner);
|
|
|
|
$response = $this->actingAs($viewer)->postJson('/api/v1/automation-rules', [
|
|
'zone_id' => $zone->id,
|
|
'condition_source_device_id' => $sensor->id,
|
|
'condition_sensor_type' => 'temperature',
|
|
'condition_operator' => '>',
|
|
'condition_value' => 28,
|
|
'target_device_id' => $fan->id,
|
|
'action_type' => 'turn_on',
|
|
]);
|
|
|
|
$response->assertForbidden();
|
|
$this->assertDatabaseCount('automation_rules', 0);
|
|
}
|
|
}
|