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
6namespace Tests\Libraries;
7
8use App\Events\NewPrivateNotificationEvent;
9use App\Exceptions\AuthorizationException;
10use App\Exceptions\VerificationRequiredException;
11use App\Jobs\Notifications\ChannelAnnouncement;
12use App\Libraries\Chat;
13use App\Mail\UserNotificationDigest;
14use App\Models\Chat\Channel;
15use App\Models\Chat\Message;
16use App\Models\OAuth\Client;
17use App\Models\User;
18use Event;
19use Exception;
20use Mail;
21use Queue;
22use Tests\TestCase;
23
24class ChatTest extends TestCase
25{
26 /**
27 * @dataProvider createAnnouncementApiDataProvider
28 */
29 public function testCreateAnnouncementApi(?string $group, bool $isApiRequest, bool $isAllowed)
30 {
31 $sender = User::factory()->withGroup($group)->create()->markSessionVerified();
32 $users = User::factory()->count(2)->create();
33
34 if ($isApiRequest) {
35 $client = Client::factory()->create(['user_id' => $sender]);
36 $token = $this->createToken($sender, ['chat.write'], $client);
37 $sender->withAccessToken($token);
38 }
39
40 if (!$isAllowed) {
41 $this->expectException(AuthorizationException::class);
42 }
43
44 $channel = $this->createAnnouncement($sender, $users->pluck('user_id')->toArray());
45
46 if ($isAllowed) {
47 $this->assertTrue($channel->fresh()->exists());
48 }
49 }
50
51 public function testCreateAnnouncementIncludesSender()
52 {
53 $sender = User::factory()->withGroup('announce')->create()->markSessionVerified();
54 $user = User::factory()->create();
55
56 $channel = $this->createAnnouncement($sender, [$user->getKey()]);
57
58 $this->assertTrue($channel->fresh()->users()->contains('user_id', $sender->getKey()));
59 }
60
61 public function testCreateAnnouncementSendsNotification()
62 {
63 Queue::fake();
64 Event::fake();
65 Mail::fake();
66
67 $sender = User::factory()->withGroup('announce')->create()->markSessionVerified();
68 $user = User::factory()->create();
69
70 $this->createAnnouncement($sender, [$user->getKey()]);
71
72 Queue::assertPushed(ChannelAnnouncement::class);
73 $this->runFakeQueue();
74
75 Event::assertDispatched(NewPrivateNotificationEvent::class);
76
77 $this->artisan('notifications:send-mail');
78 $this->runFakeQueue();
79
80 Mail::assertSent(UserNotificationDigest::class);
81 }
82
83 /**
84 * @dataProvider minPlaysDataProvider
85 */
86 public function testMinPlaysSendMessage(?string $groupIdentifier, bool $hasMinPlays, bool $successful)
87 {
88 config_set('osu.user.min_plays_allow_verified_bypass', false);
89 config_set('osu.user.min_plays_for_posting', 2);
90
91 $playCount = $hasMinPlays ? null : 1;
92
93 $sender = User::factory()->withGroup($groupIdentifier)->withPlays($playCount)->create()->markSessionVerified();
94 $channel = Channel::factory()->type('public')->create();
95 $channel->addUser($sender);
96
97 $countChange = $successful ? 1 : 0;
98
99 $this->expectCountChange(fn () => Message::count(), $countChange);
100
101 if (!$successful) {
102 $this->expectException(AuthorizationException::class);
103 }
104
105 Chat::sendMessage($sender, $channel, 'test', false);
106 }
107
108 /**
109 * @dataProvider minPlaysDataProvider
110 */
111 public function testMinPlaysSendPM(?string $groupIdentifier, bool $hasMinPlays, bool $successful)
112 {
113 config_set('osu.user.min_plays_allow_verified_bypass', false);
114 config_set('osu.user.min_plays_for_posting', 2);
115
116 $playCount = $hasMinPlays ? null : 1;
117
118 $sender = User::factory()->withGroup($groupIdentifier)->withPlays($playCount)->create()->markSessionVerified();
119 $target = User::factory()->create(['pm_friends_only' => false]);
120
121 $countChange = $successful ? 1 : 0;
122
123 $this->expectCountChange(fn () => Channel::count(), $countChange);
124 $this->expectCountChange(fn () => Message::count(), $countChange);
125
126 if (!$successful) {
127 $this->expectException(AuthorizationException::class);
128 }
129
130 Chat::sendPrivateMessage($sender, $target, 'test message', false);
131
132 if ($successful) {
133 $this->assertInstanceOf(Channel::class, Channel::findPM($sender, $target));
134 }
135 }
136
137 /**
138 * @dataProvider verifiedDataProvider
139 */
140 public function testSendMessage(bool $verified, ?string $expectedException)
141 {
142 $sender = User::factory()->create();
143 $channel = Channel::factory()->type('public')->create();
144 $channel->addUser($sender);
145
146 if ($verified) {
147 $sender->markSessionVerified();
148 }
149
150 if ($expectedException === null) {
151 $this->expectNotToPerformAssertions();
152 } else {
153 $this->expectException($expectedException);
154 }
155
156 Chat::sendMessage($sender, $channel, 'test', false);
157 }
158
159 public function testSendPM()
160 {
161 $sender = User::factory()->create();
162 $sender->markSessionVerified();
163 $target = User::factory()->create(['pm_friends_only' => false]);
164
165 $this->expectCountChange(fn () => Channel::count(), 1);
166 $this->expectCountChange(fn () => Message::count(), 1);
167
168 Chat::sendPrivateMessage($sender, $target, 'test message', false);
169
170 $this->assertInstanceOf(Channel::class, Channel::findPM($sender, $target));
171 }
172
173 /**
174 * @dataProvider sendPmFriendsOnlyGroupsDataProvider
175 */
176 public function testSendPMFriendsOnly(?string $groupIdentifier, bool $successful)
177 {
178 $sender = User::factory()->withGroup($groupIdentifier)->create();
179 $sender->markSessionVerified();
180 $target = User::factory()->create(['pm_friends_only' => true]);
181
182 $countChange = $successful ? 1 : 0;
183 $this->expectCountChange(fn () => Channel::count(), $countChange);
184 $this->expectCountChange(fn () => Message::count(), $countChange);
185
186 try {
187 Chat::sendPrivateMessage($sender, $target, 'test message', false);
188 } catch (Exception $e) {
189 $savedException = $e;
190 }
191
192 $channel = Channel::findPM($sender, $target);
193
194 if ($successful) {
195 $this->assertNotNull($channel);
196 } else {
197 $this->assertNull($channel);
198 $this->assertSame(
199 osu_trans('authorization.chat.friends_only'),
200 $savedException->getMessage()
201 );
202 }
203 }
204
205 /**
206 * @dataProvider sendPmSenderFriendsOnlyGroupsDataProvider
207 */
208 public function testSendPmSenderFriendsOnly(?string $groupIdentifier)
209 {
210 $sender = User::factory()->withGroup($groupIdentifier)->create(['pm_friends_only' => true]);
211 $sender->markSessionVerified();
212 $target = User::factory()->create(['pm_friends_only' => false]);
213
214 $this->expectCountChange(fn () => Channel::count(), 0);
215 $this->expectCountChange(fn () => Message::count(), 0);
216
217 try {
218 Chat::sendPrivateMessage($sender, $target, 'test message', false);
219 } catch (Exception $e) {
220 $savedException = $e;
221 }
222
223 $this->assertNull(Channel::findPM($sender, $target));
224 $this->assertSame(
225 osu_trans('authorization.chat.receive_friends_only'),
226 $savedException->getMessage()
227 );
228 }
229
230 public function testSendPMTooLongNotCreatingNewChannel()
231 {
232 $sender = User::factory()->create();
233 $sender->markSessionVerified();
234 $target = User::factory()->create(['pm_friends_only' => false]);
235
236 $this->expectCountChange(fn () => Channel::count(), 0);
237 $this->expectCountChange(fn () => Message::count(), 0);
238
239 $longMessage = str_repeat('a', $GLOBALS['cfg']['osu']['chat']['message_length_limit'] + 1);
240
241 try {
242 Chat::sendPrivateMessage($sender, $target, $longMessage, false);
243 } catch (Exception $e) {
244 $savedException = $e;
245 }
246
247 $this->assertNull(Channel::findPM($sender, $target));
248 $this->assertSame(
249 osu_trans('api.error.chat.too_long'),
250 $savedException->getMessage()
251 );
252 }
253
254 public function testSendPMSecondTime()
255 {
256 $sender = User::factory()->create();
257 $sender->markSessionVerified();
258 $target = User::factory()->create(['pm_friends_only' => false]);
259
260 Chat::sendPrivateMessage($sender, $target, 'test message', false);
261
262 $this->expectCountChange(fn () => Channel::count(), 0);
263 $this->expectCountChange(fn () => Message::count(), 1);
264
265 Chat::sendPrivateMessage($sender, $target, 'test message again', false);
266 }
267
268 public static function createAnnouncementApiDataProvider()
269 {
270 return [
271 [null, false, false],
272 [null, true, false],
273 ['admin', false, true],
274 ['admin', true, false],
275 ['announce', false, true],
276 ['announce', true, true], // announce group retains its group permission with OAuth.
277 ['bng', false, false],
278 ['bng', true, false],
279 ['bot', false, false],
280 ['bot', true, false],
281 ['gmt', false, true],
282 ['gmt', true, false],
283 ['nat', false, true],
284 ['nat', true, false],
285 ];
286 }
287
288 public static function minPlaysDataProvider()
289 {
290 return [
291 'bot group with minplays' => ['bot', true, true],
292 'bot group without minplays' => ['bot', false, true],
293 'default group with minplays' => [null, true, true],
294 'default group without minplays' => [null, false, false],
295 ];
296 }
297
298 public static function sendPmFriendsOnlyGroupsDataProvider()
299 {
300 return [
301 ['admin', true],
302 ['bng', false],
303 ['bot', true],
304 ['gmt', true],
305 ['nat', true],
306 [null, false],
307 ];
308 }
309
310 public static function sendPmSenderFriendsOnlyGroupsDataProvider()
311 {
312 return [
313 // admin skip because OsuAuthorize skips the check when admin.
314 ['bng'],
315 ['bot'],
316 ['gmt'],
317 ['nat'],
318 [null],
319 ];
320 }
321
322 public static function verifiedDataProvider()
323 {
324 return [
325 [false, VerificationRequiredException::class],
326 [true, null],
327 ];
328 }
329
330 private function createAnnouncement(User $sender, array $targetIds): Channel
331 {
332 return Chat::createAnnouncement($sender, [
333 'channel' => [
334 'description' => 'best',
335 'name' => 'announcements',
336 ],
337 'message' => 'test',
338 'target_ids' => $targetIds,
339 ]);
340 }
341}