withoutMiddleware([ VerifyCsrfToken::class, \Illuminate\Session\Middleware\AuthenticateSession::class, ]); } // ─── Private visibility accepted ───────────────── public function test_create_live_stream_accepts_private_visibility(): void { $u = $this->makeUser(); $this->actingAs($u); $this->post(route('live.store'), [ 'title' => 'My private test stream', 'description' => 'just testing', 'visibility' => 'private', ])->assertRedirect(); $this->assertDatabaseHas('livestreams', [ 'user_id' => $u->id, 'title' => 'My private test stream', 'visibility' => 'private', ]); } // ─── Ended state renders "Stream ended" ────────── public function test_ended_stream_watch_page_shows_stream_ended(): void { $u = $this->makeUser(); $s = $this->makeStream($u, [ 'status' => LiveStream::STATUS_ENDED, 'started_at' => now()->subMinutes(10), 'ended_at' => now(), ]); $html = $this->get(route('live.watch', $s))->assertOk()->getContent(); $this->assertStringContainsString('Stream ended', $html); $this->assertStringNotContainsString('Waiting for the broadcast to start', $html); } // ─── Channel name is a link ────────────────────── public function test_watch_page_channel_name_is_a_real_link(): void { $u = $this->makeUser(); $s = $this->makeStream($u, ['status' => LiveStream::STATUS_LIVE]); $expected = route('channel', $u->channel); $html = $this->get(route('live.watch', $s))->assertOk()->getContent(); $this->assertStringContainsString('href="' . $expected . '"', $html); } // ─── Chat poll ─────────────────────────────────── public function test_chat_poll_returns_messages_newer_than_since(): void { $u = $this->makeUser(); $s = $this->makeStream($u, ['status' => LiveStream::STATUS_LIVE]); $a = LiveChatMessage::create(['livestream_id' => $s->id, 'user_id' => $u->id, 'author_name' => 'A', 'body' => 'hello', 'created_at' => now()]); $b = LiveChatMessage::create(['livestream_id' => $s->id, 'user_id' => $u->id, 'author_name' => 'A', 'body' => 'world', 'created_at' => now()]); $this->getJson(route('live.chat.poll', $s) . '?since=' . $a->id) ->assertOk() ->assertJsonCount(1, 'messages') ->assertJsonPath('messages.0.body', 'world'); } // ─── Chat send ─────────────────────────────────── public function test_chat_send_rejects_guests(): void { $u = $this->makeUser(); $s = $this->makeStream($u, ['status' => LiveStream::STATUS_LIVE]); // postJson sets Accept: application/json so the auth middleware // returns 401 instead of redirecting to login. $this->postJson(route('live.chat.send', $s), ['body' => 'hi']) ->assertStatus(401); } public function test_chat_send_rejects_offline_streams(): void { $u = $this->makeUser(); $s = $this->makeStream($u, ['status' => LiveStream::STATUS_ENDED]); $this->actingAs($u); $this->postJson(route('live.chat.send', $s), ['body' => 'hi']) ->assertStatus(422); } public function test_private_stream_chat_rejects_non_owner(): void { $owner = $this->makeUser(); $viewer = $this->makeUser(); $s = $this->makeStream($owner, ['status' => LiveStream::STATUS_LIVE, 'visibility' => 'private']); $this->actingAs($viewer); $this->postJson(route('live.chat.send', $s), ['body' => 'sneak']) ->assertStatus(404); } // ─── Helpers ───────────────────────────────────── private function makeUser(): User { return User::factory()->create([ 'username' => 'lu'.substr(bin2hex(random_bytes(4)), 0, 6), ]); } private function makeStream(User $u, array $attrs = []): LiveStream { return LiveStream::create(array_merge([ 'user_id' => $u->id, 'public_id' => substr(bin2hex(random_bytes(10)), 0, 16), 'title' => 'Test stream', 'description' => '', 'status' => LiveStream::STATUS_IDLE, 'visibility' => 'public', ], $attrs)); } }