feat(cloud): event payload validator with enum whitelist

This commit is contained in:
유창욱 2026-07-11 23:40:53 +09:00
parent 4bbd28f95f
commit b5388b2a68
2 changed files with 74 additions and 0 deletions

View file

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Ingest;
final class EventValidator
{
private const TYPES = ['fall','no_motion','immobility','ghost','zone_warning',
'zone_restricted','pinch','monitoring_degraded','device_fault'];
private const SEVERITIES = ['critical','high','warning','info'];
private const REQUIRED = ['device_id','camera_id','event_type','severity','occurred_at'];
public static function validate(array $p): array
{
$errors = [];
foreach (self::REQUIRED as $k) {
if (!array_key_exists($k, $p) || $p[$k] === '' || $p[$k] === null) $errors[] = "missing:$k";
}
if (isset($p['event_type']) && !in_array($p['event_type'], self::TYPES, true)) $errors[] = 'bad:event_type';
if (isset($p['severity']) && !in_array($p['severity'], self::SEVERITIES, true)) $errors[] = 'bad:severity';
$snaps = $p['snapshots'] ?? [];
if (!is_array($snaps)) $errors[] = 'bad:snapshots';
elseif (count($snaps) > 3) $errors[] = 'too_many:snapshots';
else foreach ($snaps as $s) if (!is_string($s)) { $errors[] = 'bad:snapshot_item'; break; }
if ($errors) return ['ok' => false, 'errors' => $errors, 'clean' => []];
$clean = [
'device_id' => (string)$p['device_id'],
'camera_id' => (string)$p['camera_id'],
'event_type' => (string)$p['event_type'],
'severity' => (string)$p['severity'],
'track_id' => isset($p['track_id']) ? (int)$p['track_id'] : null,
'zone_id' => isset($p['zone_id']) ? (string)$p['zone_id'] : null,
'occurred_at' => (float)$p['occurred_at'],
'duration_s' => (float)($p['duration_s'] ?? 0.0),
'metrics' => is_array($p['metrics'] ?? null) ? $p['metrics'] : [],
];
return ['ok' => true, 'errors' => [], 'clean' => $clean];
}
}

View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Tests;
use App\Ingest\EventValidator;
use PHPUnit\Framework\TestCase;
final class EventValidatorTest extends TestCase
{
private function valid(): array {
return ['device_id' => 'pi-1', 'camera_id' => 'cam-1', 'event_type' => 'fall',
'severity' => 'high', 'occurred_at' => 1000.0, 'snapshots' => ['aGk=']];
}
public function testAcceptsValid(): void {
$r = EventValidator::validate($this->valid());
$this->assertTrue($r['ok'], implode(',', $r['errors']));
$this->assertSame('fall', $r['clean']['event_type']);
}
public function testRejectsUnknownEventType(): void {
$p = $this->valid(); $p['event_type'] = 'explode';
$this->assertFalse(EventValidator::validate($p)['ok']);
}
public function testRejectsUnknownSeverity(): void {
$p = $this->valid(); $p['severity'] = 'ULTRA';
$this->assertFalse(EventValidator::validate($p)['ok']);
}
public function testRejectsMissingRequired(): void {
$p = $this->valid(); unset($p['device_id']);
$this->assertFalse(EventValidator::validate($p)['ok']);
}
public function testRejectsTooManySnapshots(): void {
$p = $this->valid(); $p['snapshots'] = ['a', 'b', 'c', 'd'];
$this->assertFalse(EventValidator::validate($p)['ok']);
}
}