1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6declare(strict_types=1);
7
8namespace Tests\Controllers;
9
10use App\Models\Forum\Authorize;
11use App\Models\Forum\Forum;
12use App\Models\Forum\Post;
13use App\Models\Forum\Topic;
14use App\Models\Forum\TopicTrack;
15use App\Models\User;
16use Tests\TestCase;
17
18class ForumTopicsControllerTest extends TestCase
19{
20 public function testDestroy(): void
21 {
22 $user = User::factory()->create();
23 $topic = Topic::factory()->withPost()->create(['topic_poster' => $user]);
24
25 $this->expectCountChange(fn () => Topic::count(), -1);
26
27 $this
28 ->actingAsVerified($user)
29 ->delete(route('forum.topics.destroy', $topic))
30 ->assertRedirect(route('forum.forums.show', $topic->forum_id));
31 }
32
33 public function testDestroyAsDifferentUser(): void
34 {
35 $user = User::factory()->create();
36 $topic = Topic::factory()->withPost()->create();
37
38 $this->expectCountChange(fn () => Topic::count(), 0);
39
40 $this
41 ->actingAsVerified($user)
42 ->delete(route('forum.topics.destroy', $topic))
43 ->assertStatus(403);
44 }
45
46 public function testDestroyAsGuest(): void
47 {
48 $topic = Topic::factory()->withPost()->create();
49
50 $this->expectCountChange(fn () => Topic::count(), 0);
51
52 $this
53 ->delete(route('forum.topics.destroy', $topic))
54 ->assertStatus(401);
55 }
56
57 public function testDestroyAsModerator(): void
58 {
59 $topic = Topic::factory()->withPost()->create();
60 $user = User::factory()->withGroup('gmt')->create();
61
62 $this->expectCountChange(fn () => Topic::count(), -1);
63
64 $this
65 ->actingAsVerified($user)
66 ->delete(route('forum.topics.destroy', $topic))
67 ->assertSuccessful();
68 }
69
70 public function testPin(): void
71 {
72 $moderator = User::factory()->withGroup('gmt')->create();
73 $topic = Topic::factory()->create();
74 $typeInt = Topic::TYPES['sticky'];
75
76 $this
77 ->actingAsVerified($moderator)
78 ->post(route('forum.topics.pin', $topic), ['pin' => $typeInt])
79 ->assertSuccessful();
80
81 $this->assertSame($typeInt, $topic->fresh()->topic_type);
82 }
83
84 public function testReply(): void
85 {
86 $topic = Topic::factory()->create();
87 $user = User::factory()->withPlays($GLOBALS['cfg']['osu']['forum']['minimum_plays'])->create();
88 Authorize::factory()->reply()->create([
89 'forum_id' => $topic->forum_id,
90 'group_id' => app('groups')->byIdentifier('default'),
91 ]);
92
93 $this->expectCountChange(fn () => Post::count(), 1);
94 $this->expectCountChange(fn () => Topic::count(), 0);
95 $this->expectCountChange(fn () => $topic->fresh()->postCount(), 1);
96
97 $this
98 ->actingAsVerified($user)
99 ->post(route('forum.topics.reply', $topic), [
100 'body' => 'This is test reply',
101 ])
102 ->assertSuccessful();
103 }
104
105 public function testReplyWithoutPlays(): void
106 {
107 $topic = Topic::factory()->create();
108 $user = User::factory()->create();
109 Authorize::factory()->reply()->create([
110 'forum_id' => $topic->forum_id,
111 'group_id' => app('groups')->byIdentifier('default'),
112 ]);
113
114 $this->expectCountChange(fn () => Post::count(), 0);
115 $this->expectCountChange(fn () => Topic::count(), 0);
116 $this->expectCountChange(fn () => $topic->fresh()->postCount(), 0);
117
118 $this
119 ->actingAsVerified($user)
120 ->post(route('forum.topics.reply', $topic), [
121 'body' => 'This is test reply',
122 ])
123 ->assertStatus(403);
124 }
125
126 public function testRestore(): void
127 {
128 $moderator = User::factory()->withGroup('gmt')->create();
129 $topic = Topic::factory()->withPost()->create();
130 $topic->delete();
131
132 $this->expectCountChange(fn () => Topic::count(), 1);
133
134 $this
135 ->actingAsVerified($moderator)
136 ->post(route('forum.topics.restore', $topic))
137 ->assertSuccessful();
138 }
139
140 public function testShow(): void
141 {
142 $topic = Topic::factory()->withPost()->create();
143
144 $this
145 ->get(route('forum.topics.show', $topic))
146 ->assertSuccessful();
147 }
148
149 public function testShowMissingFirstPost(): void
150 {
151 $topic = Topic::factory()->withPost()->create();
152 $topic->update(['topic_first_post_id' => 0]);
153
154 $this
155 ->get(route('forum.topics.show', $topic))
156 ->assertStatus(404);
157 }
158
159 public function testShowNoMorePosts(): void
160 {
161 $topic = Topic::factory()->withPost()->create();
162
163 $this
164 ->get(route('forum.topics.show', [
165 'start' => $topic->topic_first_post_id + 1,
166 'topic' => $topic,
167 ]))
168 ->assertStatus(302);
169 }
170
171 public function testShowNoMorePostsWithSkipLayout(): void
172 {
173 $topic = Topic::factory()->withPost()->create();
174
175 $this
176 ->get(route('forum.topics.show', [
177 'skip_layout' => 1,
178 'start' => $topic->topic_first_post_id + 1,
179 'topic' => $topic,
180 ]))
181 ->assertStatus(204);
182 }
183
184 public function testShowMissingPosts(): void
185 {
186 $topic = Topic::factory()->create();
187
188 $this
189 ->get(route('forum.topics.show', $topic))
190 ->assertStatus(404);
191 }
192
193 public function testShowNewUser(): void
194 {
195 $topic = Topic::factory()->withPost()->create();
196 $user = User::factory()->create();
197
198 $this
199 ->be($user)
200 ->get(route('forum.topics.show', $topic))
201 ->assertSuccessful();
202 }
203
204 public function testStore(): void
205 {
206 $forum = Forum::factory()->create();
207 $user = User::factory()->withPlays($GLOBALS['cfg']['osu']['forum']['minimum_plays'])->create();
208 Authorize::factory()->post()->create([
209 'forum_id' => $forum,
210 'group_id' => app('groups')->byIdentifier('default'),
211 ]);
212
213 $this->expectCountChange(fn () => Post::count(), 1);
214 $this->expectCountChange(fn () => Topic::count(), 1);
215 $this->expectCountChange(fn () => TopicTrack::count(), 1);
216
217 $this
218 ->actingAsVerified($user)
219 ->post(route('forum.topics.store', ['forum_id' => $forum]), [
220 'title' => 'Test post',
221 'body' => 'This is test post',
222 ])
223 ->assertRedirect(route(
224 'forum.topics.show',
225 Topic::orderBy('topic_id', 'DESC')->first(),
226 ));
227 }
228
229 public function testStoreWithoutPlays(): void
230 {
231 $forum = Forum::factory()->create();
232 $user = User::factory()->create();
233 Authorize::factory()->post()->create([
234 'forum_id' => $forum,
235 'group_id' => app('groups')->byIdentifier('default'),
236 ]);
237
238 $this->expectCountChange(fn () => Post::count(), 0);
239 $this->expectCountChange(fn () => Topic::count(), 0);
240 $this->expectCountChange(fn () => TopicTrack::count(), 0);
241
242 $this
243 ->actingAsVerified($user)
244 ->post(route('forum.topics.store', ['forum_id' => $forum]), [
245 'title' => 'Test post',
246 'body' => 'This is test post',
247 ])
248 ->assertStatus(403);
249 }
250
251 public function testUpdateTitle(): void
252 {
253 $user = User::factory()->create();
254 $topic = Topic::factory()->withPost()->create([
255 'topic_poster' => $user,
256 'topic_title' => 'Initial title',
257 ]);
258 $newTitle = 'A different title';
259
260 $this
261 ->actingAsVerified($user)
262 ->put(route('forum.topics.update', $topic), [
263 'forum_topic' => [
264 'topic_title' => $newTitle,
265 ],
266 ])
267 ->assertSuccessful();
268
269 $this->assertSame($newTitle, $topic->fresh()->topic_title);
270 }
271
272 public function testUpdateTitleBlank(): void
273 {
274 $user = User::factory()->create();
275 $topic = Topic::factory()->withPost()->create(['topic_poster' => $user]);
276 $title = $topic->topic_title;
277
278 $this
279 ->actingAsVerified($user)
280 ->put(route('forum.topics.update', $topic), [
281 'forum_topic' => [
282 'topic_title' => null,
283 ],
284 ])
285 ->assertStatus(422);
286
287 $this->assertSame($title, $topic->fresh()->topic_title);
288 }
289}