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]>
159 lines
5.6 KiB
PHP
159 lines
5.6 KiB
PHP
<?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));
|
|
}
|
|
}
|