setAccessible(true); $parserProp->setValue( \Mockery::mock(\s9e\TextFormatter\Parser::class) ->shouldReceive('parse') ->andReturn('') ->getMock() ); $rendererProp = new ReflectionProperty(\App\Actions\BbcodeFormatter::class, 'renderer'); $rendererProp->setAccessible(true); $rendererProp->setValue( \Mockery::mock(\s9e\TextFormatter\Renderer::class) ->shouldReceive('render') ->andReturn('

') ->getMock() ); }); afterEach(function (): void { \Mockery::close(); }); function makeForumForThreadController(): 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('returns unauthorized for update when no user', function (): void { $controller = new ThreadController(); $forum = makeForumForThreadController(); $thread = Thread::create([ 'forum_id' => $forum->id, 'user_id' => null, 'title' => 'Thread', 'body' => 'Body', ]); $request = Request::create("/api/threads/{$thread->id}", 'PATCH', [ 'title' => 'New', ]); $request->setUserResolver(fn () => null); $response = $controller->update($request, $thread); expect($response->getStatusCode())->toBe(401); }); it('returns unauthorized for updateSolved when no user', function (): void { $controller = new ThreadController(); $forum = makeForumForThreadController(); $thread = Thread::create([ 'forum_id' => $forum->id, 'user_id' => null, 'title' => 'Thread', 'body' => 'Body', ]); $request = Request::create("/api/threads/{$thread->id}/solved", 'PATCH', [ 'solved' => true, ]); $request->setUserResolver(fn () => null); $response = $controller->updateSolved($request, $thread); expect($response->getStatusCode())->toBe(401); }); it('serializes threads with attachments, group colors, and inline images', function (): void { Storage::fake('public'); $forum = makeForumForThreadController(); $role = Role::create(['name' => 'ROLE_HELPER', 'color' => '#ff0000']); $user = User::factory()->create([ 'avatar_path' => 'avatars/u.png', 'location' => 'Somewhere', ]); $user->roles()->attach($role); Setting::updateOrCreate(['key' => 'attachments.display_images_inline'], ['value' => '1']); $thread = Thread::create([ 'forum_id' => $forum->id, 'user_id' => $user->id, 'title' => 'Thread', 'body' => 'See [attachment]image.png[/attachment]', ]); $post = Post::create([ 'thread_id' => $thread->id, 'user_id' => $user->id, 'body' => 'Reply', ]); $group = AttachmentGroup::create([ 'name' => 'Images', 'max_size_kb' => 100, 'is_active' => true, ]); $attachment = Attachment::create([ 'thread_id' => $thread->id, 'post_id' => null, 'attachment_extension_id' => null, 'attachment_group_id' => $group->id, 'user_id' => $user->id, 'disk' => 'local', 'path' => 'attachments/threads/'.$thread->id.'/image.png', 'thumbnail_path' => 'attachments/threads/'.$thread->id.'/thumbs/image.png', 'original_name' => 'image.png', 'extension' => 'png', 'mime_type' => 'image/png', 'size_bytes' => 10, ]); $thread->load(['user.roles', 'attachments.group', 'latestPost']); $controller = new ThreadController(); $ref = new ReflectionMethod($controller, 'serializeThread'); $ref->setAccessible(true); $payload = $ref->invoke($controller, $thread); expect($payload['user_avatar_url'])->not->toBeNull(); expect($payload['user_group_color'])->toBe('#ff0000'); expect($payload['attachments'][0]['group']['name'])->toBe('Images'); expect($payload['attachments'][0]['thumbnail_url'])->toContain('/thumbnail'); expect($payload['body_html'])->toContain('toBe($post->id); }); it('replaces attachment tags with links when inline images disabled', function (): void { Setting::updateOrCreate(['key' => 'attachments.display_images_inline'], ['value' => '0']); $forum = makeForumForThreadController(); $thread = Thread::create([ 'forum_id' => $forum->id, 'user_id' => null, 'title' => 'Thread', 'body' => 'See [attachment]doc.pdf[/attachment]', ]); $attachment = Attachment::create([ 'thread_id' => $thread->id, 'post_id' => null, 'attachment_extension_id' => null, 'attachment_group_id' => null, 'user_id' => null, 'disk' => 'local', 'path' => 'attachments/threads/'.$thread->id.'/doc.pdf', 'original_name' => 'doc.pdf', 'extension' => 'pdf', 'mime_type' => 'application/pdf', 'size_bytes' => 10, ]); $controller = new ThreadController(); $ref = new ReflectionMethod($controller, 'replaceAttachmentTags'); $ref->setAccessible(true); $result = $ref->invoke($controller, $thread->body, collect([$attachment])); expect($result)->toContain('[url='); expect($result)->toContain('doc.pdf'); }); it('returns body unchanged when no attachments are present', function (): void { $controller = new ThreadController(); $ref = new ReflectionMethod($controller, 'replaceAttachmentTags'); $ref->setAccessible(true); $result = $ref->invoke($controller, 'No attachments', []); expect($result)->toBe('No attachments'); }); it('returns original tag when attachment name does not match', function (): void { $forum = makeForumForThreadController(); $thread = Thread::create([ 'forum_id' => $forum->id, 'user_id' => null, 'title' => 'Thread', 'body' => 'See [attachment]missing.pdf[/attachment]', ]); $attachment = Attachment::create([ 'thread_id' => $thread->id, 'post_id' => null, 'attachment_extension_id' => null, 'attachment_group_id' => null, 'user_id' => null, 'disk' => 'local', 'path' => 'attachments/threads/'.$thread->id.'/doc.pdf', 'original_name' => 'doc.pdf', 'extension' => 'pdf', 'mime_type' => 'application/pdf', 'size_bytes' => 10, ]); $controller = new ThreadController(); $ref = new ReflectionMethod($controller, 'replaceAttachmentTags'); $ref->setAccessible(true); $result = $ref->invoke($controller, $thread->body, collect([$attachment])); expect($result)->toContain('[attachment]missing.pdf[/attachment]'); }); it('returns body unchanged when attachment map is empty', function (): void { $controller = new ThreadController(); $ref = new ReflectionMethod($controller, 'replaceAttachmentTags'); $ref->setAccessible(true); $attachment = new Attachment([ 'original_name' => '', ]); $result = $ref->invoke($controller, 'Body', collect([$attachment])); expect($result)->toBe('Body'); });