Files
speedBB/tests/Feature/ThreadControllerTest.php
tracer 88e4a70f88
Some checks failed
CI/CD Pipeline / test (push) Successful in 3s
CI/CD Pipeline / deploy (push) Failing after 15s
Add comprehensive test coverage and update notes
2026-02-08 19:04:12 +01:00

273 lines
6.9 KiB
PHP

<?php
use App\Models\Forum;
use App\Models\Role;
use App\Models\Thread;
use App\Models\User;
use Laravel\Sanctum\Sanctum;
beforeEach(function (): void {
$parserProp = new ReflectionProperty(\App\Actions\BbcodeFormatter::class, 'parser');
$parserProp->setAccessible(true);
$parserProp->setValue(
\Mockery::mock(\s9e\TextFormatter\Parser::class)
->shouldReceive('parse')
->andReturn('<r/>')
->getMock()
);
$rendererProp = new ReflectionProperty(\App\Actions\BbcodeFormatter::class, 'renderer');
$rendererProp->setAccessible(true);
$rendererProp->setValue(
\Mockery::mock(\s9e\TextFormatter\Renderer::class)
->shouldReceive('render')
->andReturn('<p></p>')
->getMock()
);
});
afterEach(function (): void {
\Mockery::close();
});
function makeForum(): Forum
{
$category = Forum::create([
'name' => 'Category',
'description' => null,
'type' => 'category',
'parent_id' => null,
'position' => 1,
]);
return Forum::create([
'name' => 'Forum',
'description' => null,
'type' => 'forum',
'parent_id' => $category->id,
'position' => 1,
]);
}
it('creates a thread inside a forum', function (): void {
$user = User::factory()->create();
Sanctum::actingAs($user);
$forum = makeForum();
$response = $this->postJson('/api/threads', [
'title' => 'First Thread',
'body' => 'Hello world',
'forum' => "/api/forums/{$forum->id}",
]);
$response->assertStatus(201);
$response->assertJsonFragment([
'title' => 'First Thread',
'forum' => "/api/forums/{$forum->id}",
]);
$this->assertDatabaseHas('threads', [
'forum_id' => $forum->id,
'user_id' => $user->id,
'title' => 'First Thread',
]);
});
it('rejects creating threads in a category', function (): void {
$user = User::factory()->create();
Sanctum::actingAs($user);
$category = Forum::create([
'name' => 'Category Only',
'description' => null,
'type' => 'category',
'parent_id' => null,
'position' => 1,
]);
$response = $this->postJson('/api/threads', [
'title' => 'Nope',
'body' => 'Not allowed',
'forum' => "/api/forums/{$category->id}",
]);
$response->assertStatus(422);
$response->assertJsonFragment(['message' => 'Threads can only be created inside forums.']);
});
it('requires authentication to update a thread', function (): void {
$forum = makeForum();
$owner = User::factory()->create();
$thread = Thread::create([
'forum_id' => $forum->id,
'user_id' => $owner->id,
'title' => 'Original',
'body' => '',
]);
$response = $this->patchJson("/api/threads/{$thread->id}", [
'title' => 'Updated',
'body' => 'Updated body',
]);
$response->assertStatus(401);
});
it('enforces thread update permissions', function (): void {
$forum = makeForum();
$owner = User::factory()->create();
$other = User::factory()->create();
$thread = Thread::create([
'forum_id' => $forum->id,
'user_id' => $owner->id,
'title' => 'Original',
'body' => '',
]);
Sanctum::actingAs($other);
$response = $this->patchJson("/api/threads/{$thread->id}", [
'title' => 'Updated',
'body' => 'Updated body',
]);
$response->assertStatus(403);
Sanctum::actingAs($owner);
$response = $this->patchJson("/api/threads/{$thread->id}", [
'title' => 'Owner Update',
'body' => 'Owner body',
]);
$response->assertOk();
$this->assertDatabaseHas('threads', [
'id' => $thread->id,
'title' => 'Owner Update',
'body' => 'Owner body',
]);
$admin = User::factory()->create();
$role = Role::create(['name' => 'ROLE_ADMIN', 'color' => '#111111']);
$admin->roles()->attach($role);
Sanctum::actingAs($admin);
$response = $this->patchJson("/api/threads/{$thread->id}", [
'title' => 'Admin Update',
'body' => 'Admin body',
]);
$response->assertOk();
$this->assertDatabaseHas('threads', [
'id' => $thread->id,
'title' => 'Admin Update',
'body' => 'Admin body',
]);
});
it('enforces solved status permissions', function (): void {
$forum = makeForum();
$owner = User::factory()->create();
$other = User::factory()->create();
$thread = Thread::create([
'forum_id' => $forum->id,
'user_id' => $owner->id,
'title' => 'Original',
'body' => '',
'solved' => false,
]);
Sanctum::actingAs($other);
$response = $this->patchJson("/api/threads/{$thread->id}/solved", [
'solved' => true,
]);
$response->assertStatus(403);
Sanctum::actingAs($owner);
$response = $this->patchJson("/api/threads/{$thread->id}/solved", [
'solved' => true,
]);
$response->assertOk();
$this->assertDatabaseHas('threads', [
'id' => $thread->id,
'solved' => 1,
]);
});
it('filters threads by forum', function (): void {
$forumA = makeForum();
$forumB = makeForum();
$threadA = Thread::create([
'forum_id' => $forumA->id,
'user_id' => null,
'title' => 'Thread A',
'body' => '',
]);
Thread::create([
'forum_id' => $forumB->id,
'user_id' => null,
'title' => 'Thread B',
'body' => '',
]);
$response = $this->getJson("/api/threads?forum=/api/forums/{$forumA->id}");
$response->assertOk();
$response->assertJsonCount(1);
$response->assertJsonFragment([
'id' => $threadA->id,
'title' => 'Thread A',
]);
});
it('increments views count when showing a thread', function (): void {
$forum = makeForum();
$thread = Thread::create([
'forum_id' => $forum->id,
'user_id' => null,
'title' => 'Viewed Thread',
'body' => '',
'views_count' => 0,
]);
$response = $this->getJson("/api/threads/{$thread->id}");
$response->assertOk();
$response->assertJsonFragment([
'id' => $thread->id,
'views_count' => 1,
]);
$thread->refresh();
expect($thread->views_count)->toBe(1);
});
it('soft deletes a thread and tracks deleted_by', function (): void {
$forum = makeForum();
$user = User::factory()->create();
$thread = Thread::create([
'forum_id' => $forum->id,
'user_id' => $user->id,
'title' => 'Delete Me',
'body' => 'Body',
]);
Sanctum::actingAs($user);
$response = $this->deleteJson("/api/threads/{$thread->id}");
$response->assertStatus(204);
$this->assertSoftDeleted('threads', [
'id' => $thread->id,
]);
$this->assertDatabaseHas('threads', [
'id' => $thread->id,
'deleted_by' => $user->id,
]);
});