test(live): 7 tests for the Live polish work

Covers Private visibility acceptance, ended-state Watch page shows
"Stream ended" (and omits the "Waiting for the broadcast" placeholder
from the connection JS), channel-name link resolves to the user's
channel URL, chat poll returns messages newer than ?since=, chat send
rejects guests (401), chat send rejects offline streams (422), and
private-stream chat 404s for non-owners.

Also broadens LiveStream::isWatchableBy to keep public/unlisted
streams permalinked after they end — viewers now see the Stream Ended
card and the Watch Replay CTA instead of a hard 404.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-10-21 12:45:47 +03:00
1 parent 5bf1128196
commit 8fef257c86
3 files changed
+169 -1

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
<?php
namespace Tests\Feature;
use App\Http\Middleware\VerifyCsrfToken;
use App\Models\LiveChatMessage;
use App\Models\LiveStream;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* Live polish regression tests. Covers:
* - Private visibility accepted by /live/new validation.
* - Ended-state watch page shows "Stream ended" + duration.
* - Chat poll endpoint returns everything newer than ?since=.
* - Chat send endpoint rejects guests, non-live streams, and
* anyone but the owner on a private stream.
*/
class LivePolishTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
putenv('DB_CONNECTION=sqlite');
putenv('DB_DATABASE=:memory:');
$_ENV['DB_CONNECTION'] = 'sqlite';
$_ENV['DB_DATABASE'] = ':memory:';
parent::setUp();
$this->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));
}
}