set('user_id', 1); $s->set('username', 'u'); $s->set('role', $role); $s->set('csrf', 'tok'); return $s; } public function testPolygonValidation(): void { $this->assertTrue(Polygon::validate([[0,0],[10,0],[10,10]])); $this->assertFalse(Polygon::validate([[0,0],[10,0]])); // <3 $this->assertFalse(Polygon::validate([[0,0],[10,0],['x',10]])); // non-numeric } private function saveReq(array $body, array $headers = ['X-Csrf-Token' => 'tok']): Request { return new Request('POST', '/api/zones', $headers, json_encode($body), [], []); } public function testSaveRequiresPermission(): void { $c = new ZoneConfigController(new InMemoryZoneRepository(), new InMemoryAuditRepository(), $this->authed('viewer')); $res = $c->save($this->saveReq(['camera_id' => 'cam-1', 'level' => 'restricted', 'polygon' => [[0,0],[9,0],[9,9]]])); $this->assertSame(403, $res->status()); } public function testSaveValidZone(): void { $repo = new InMemoryZoneRepository(); $c = new ZoneConfigController($repo, new InMemoryAuditRepository(), $this->authed('operator')); $res = $c->save($this->saveReq(['device_id' => 'pi-1', 'camera_id' => 'cam-1', 'level' => 'restricted', 'polygon' => [[0,0],[9,0],[9,9]]])); $this->assertSame(201, $res->status()); $this->assertCount(1, $repo->forCamera('cam-1')); } public function testSaveRejectsBadLevelOrPolygon(): void { $c = new ZoneConfigController(new InMemoryZoneRepository(), new InMemoryAuditRepository(), $this->authed('operator')); $this->assertSame(400, $c->save($this->saveReq(['camera_id' => 'cam-1', 'level' => 'BOOM', 'polygon' => [[0,0],[9,0],[9,9]]]))->status()); $this->assertSame(400, $c->save($this->saveReq(['camera_id' => 'cam-1', 'level' => 'warning', 'polygon' => [[0,0]]]))->status()); } public function testSaveRejectsMissingOrWrongCsrf(): void { $repo = new InMemoryZoneRepository(); $c = new ZoneConfigController($repo, new InMemoryAuditRepository(), $this->authed('operator')); $body = ['device_id' => 'pi-1', 'camera_id' => 'cam-1', 'level' => 'restricted', 'polygon' => [[0,0],[9,0],[9,9]]]; // missing header $this->assertSame(403, $c->save($this->saveReq($body, []))->status()); // wrong token $this->assertSame(403, $c->save($this->saveReq($body, ['X-Csrf-Token' => 'nope']))->status()); // save did not proceed $this->assertCount(0, $repo->forCamera('cam-1')); } public function testDeleteRejectsMissingOrWrongCsrf(): void { $repo = new InMemoryZoneRepository(); $repo->save(['zone_id' => 'z1', 'device_id' => 'pi-1', 'camera_id' => 'cam-1', 'level' => 'restricted', 'polygon_json' => '[[0,0],[9,0],[9,9]]']); $c = new ZoneConfigController($repo, new InMemoryAuditRepository(), $this->authed('operator')); // missing header $missing = new Request('POST', '/api/zones/z1/delete', [], '', [], []); $this->assertSame(403, $c->delete($missing, ['id' => 'z1'])->status()); // wrong token $wrong = new Request('POST', '/api/zones/z1/delete', ['X-Csrf-Token' => 'nope'], '', [], []); $this->assertSame(403, $c->delete($wrong, ['id' => 'z1'])->status()); // delete did not proceed $this->assertCount(1, $repo->forCamera('cam-1')); // valid token proceeds $ok = new Request('POST', '/api/zones/z1/delete', ['X-Csrf-Token' => 'tok'], '', [], []); $this->assertSame(200, $c->delete($ok, ['id' => 'z1'])->status()); $this->assertCount(0, $repo->forCamera('cam-1')); } }