'https://ha.test/api/webhook/hado', 'presence_url' => 'https://ha.test/api/hado/presence']; protected function setUp(): void { parent::setUp(); } private function driver(): WebhookChannel { return new WebhookChannel(2); } public function test_presence_from_json(): void { Http::fake([ 'ha.test/api/hado/presence' => Http::sequence() ->push(['present' => true]) ->push(['present' => false]) ->push('oops', 500), ]); $user = new User(['login' => 'n', 'tz' => 'UTC']); $this->assertSame(Presence::Present, $this->driver()->presence($user, $this->config)); $this->assertSame(Presence::Absent, $this->driver()->presence($user, $this->config)); $this->assertSame(Presence::Unknown, $this->driver()->presence($user, $this->config)); } public function test_presence_unknown_on_timeout(): void { Http::fake(fn () => throw new ConnectionException('timeout')); $this->assertSame(Presence::Unknown, $this->driver()->presence(new User(['login' => 'n', 'tz' => 'UTC']), $this->config)); } public function test_deliver_posts_event_and_action_urls(): void { Http::fake(['ha.test/api/webhook/hado' => Http::response('', 200)]); $event = new Event([ 'due_mode' => DueMode::Local, 'due_date' => '2026-08-14', 'due_time' => '24:00:00', 'fire_on' => '2026-08-14', 'after_due' => AfterDue::Expire, 'state' => EventState::Today, 'payload' => ['title' => 'Сегодня ДР — Вася', 'subtitle' => 'исполняется 34', 'deep_link' => '/people/42', 'done_label' => 'Поздравил'], ]); $event->id = 17; $event->setRelation('source', new Source(['name' => 'docs'])); $delivery = new Delivery(['action_token' => 'tok123']); $outcome = $this->driver()->deliver($event, new User(['login' => 'n', 'tz' => 'UTC']), $this->config, $delivery); $this->assertTrue($outcome->ok); Http::assertSent(function (Request $r) { return $r->url() === 'https://ha.test/api/webhook/hado' && $r['event']['id'] === 17 && $r['event']['title'] === 'Сегодня ДР — Вася' && $r['event']['done_label'] === 'Поздравил' && $r['event']['source'] === 'docs' && $r['event']['due_at'] === '2026-08-14' && str_ends_with($r['actions']['ack'], '/a/tok123/ack') && str_ends_with($r['actions']['done'], '/a/tok123/done'); }); } public function test_deliver_reports_failure(): void { Http::fake(['ha.test/api/webhook/hado' => Http::response('nope', 503)]); $event = new Event(['due_mode' => DueMode::Local, 'due_date' => '2026-08-14', 'due_time' => '24:00:00', 'fire_on' => '2026-08-14', 'after_due' => AfterDue::Expire, 'state' => EventState::Today, 'payload' => ['title' => 't']]); $event->setRelation('source', new Source(['name' => 'docs'])); $outcome = $this->driver()->deliver($event, new User(['login' => 'n', 'tz' => 'UTC']), $this->config, new Delivery(['action_token' => 'x'])); $this->assertFalse($outcome->ok); $this->assertStringContainsString('503', $outcome->error); } }