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\Models\Chat;
9
10use App\Events\ChatChannelEvent;
11use App\Jobs\Notifications\ChannelAnnouncement;
12use App\Libraries\User\AvatarHelper;
13use App\Models\Chat\Channel;
14use App\Models\User;
15use App\Models\UserRelation;
16use Event;
17use Illuminate\Filesystem\Filesystem;
18use Queue;
19use SplFileInfo;
20use Storage;
21use Tests\TestCase;
22
23class ChannelTest extends TestCase
24{
25 public function testAnnouncementSendMessage()
26 {
27 Queue::fake();
28
29 $user = User::factory()->withGroup('announce')->create();
30 $otherUser = User::factory()->create();
31 $channel = Channel::factory()->type('announce', [$user, $otherUser])->create();
32
33 $channel->receiveMessage($user, 'test');
34
35 Queue::assertPushed(ChannelAnnouncement::class);
36 }
37
38 public function testAnnouncementSendToRestrictedUsers()
39 {
40 Queue::fake();
41
42 $sender = User::factory()->withGroup('announce')->create();
43 $user = User::factory()->create();
44 $channel = Channel::factory()->type('announce', [$sender, $user])->create();
45
46 $user->update(['user_warnings' => 1]);
47 $channel = $channel->fresh();
48
49 // ChatMessageEvent uses activeUserIds to broadcast.
50 $this->assertContains($user->getKey(), $channel->activeUserIds());
51
52 $channel->receiveMessage($sender, 'message');
53
54 Queue::assertPushed(
55 ChannelAnnouncement::class,
56 fn (ChannelAnnouncement $job) => in_array($user->getKey(), $job->getReceiverIds(), true)
57 );
58 }
59
60 public function testCreatePM()
61 {
62 $user1 = User::factory()->create();
63 $user2 = User::factory()->create();
64
65 $channel = Channel::createPM($user1, $user2);
66 $channel->refresh();
67
68 $users = $channel->users();
69 $this->assertContains($user1, $users);
70 $this->assertContains($user2, $users);
71 $this->assertCount(2, $users);
72 }
73
74 /**
75 * For testing the factory creates the channel in the expected form.
76 */
77 public function testCreatePMWithFactory()
78 {
79 $channel1 = Channel::factory()->type('pm')->create();
80 $users = $channel1->users();
81
82 // multiple PM channels can be created; existing channel check is handled by sendPrivateMessage;
83 // createPM is typically not called directly.
84 $channel2 = Channel::createPM(...$users);
85
86 $this->assertCount(2, $users);
87 $this->assertSame($channel2->name, $channel1->name);
88 $this->assertEquals($channel2->users(), $users);
89 }
90
91 /**
92 * @dataProvider channelWithBlockedUserVisibilityDataProvider
93 */
94 public function testChannelWithBlockedUserVisibility(?string $otherUserGroup, bool $expectVisible)
95 {
96 $user = User::factory()->create();
97 $otherUser = User::factory()->withGroup($otherUserGroup)->create();
98 $channel = Channel::factory()->type('pm', [$user, $otherUser])->create();
99
100 UserRelation::create([
101 'user_id' => $user->getKey(),
102 'zebra_id' => $otherUser->getKey(),
103 'foe' => true,
104 ]);
105
106 $this->assertSame($expectVisible, $channel->isVisibleFor($user));
107 }
108
109 /**
110 * @dataProvider channelCanMessageModeratedChannelDataProvider
111 */
112 public function testChannelCanMessageModeratedPmChannel(?string $group, bool $canMessage)
113 {
114 $user = User::factory()->withGroup($group)->create();
115 $otherUser = User::factory()->create();
116 $channel = Channel::factory()->type('pm', [$user, $otherUser])->moderated()->create();
117
118 $this->assertSame($canMessage, $channel->checkCanMessage($user)->can());
119 }
120
121 /**
122 * @dataProvider channelCanMessageModeratedChannelDataProvider
123 */
124 public function testChannelCanMessageModeratedPublicChannel(?string $group, bool $canMessage)
125 {
126 $user = User::factory()->withGroup($group)->create();
127 $channel = Channel::factory()->type('public', [$user])->moderated()->create();
128
129 $this->assertSame($canMessage, $channel->checkCanMessage($user)->can());
130 }
131
132 /**
133 * @dataProvider channelCanMessageWhenBlockedDataProvider
134 */
135 public function testChannelCanMessagePmChannelWhenBlocked(?string $group, bool $canMessage)
136 {
137 $user = User::factory()->withGroup($group)->create();
138 $otherUser = User::factory()->create();
139 $channel = Channel::factory()->type('pm', [$user, $otherUser])->create();
140
141 UserRelation::create([
142 'user_id' => $user->getKey(),
143 'zebra_id' => $otherUser->getKey(),
144 'foe' => true,
145 ]);
146
147 // reset caches from previous steps.
148 $user->refresh();
149 $otherUser->refresh();
150 app('OsuAuthorize')->resetCache();
151
152 // this assertion to make sure the correct block direction is being tested.
153 $this->assertTrue($user->hasBlocked($otherUser));
154 $this->assertSame($canMessage, $channel->checkCanMessage($user)->can());
155 }
156
157 /**
158 * @dataProvider channelCanMessageWhenBlockedDataProvider
159 */
160 public function testChannelCanMessagePmChannelWhenBlocking(?string $group, bool $canMessage)
161 {
162 $user = User::factory()->withGroup($group)->create();
163 $otherUser = User::factory()->create();
164 $channel = Channel::factory()->type('pm', [$user, $otherUser])->create();
165
166 UserRelation::create([
167 'user_id' => $otherUser->getKey(),
168 'zebra_id' => $user->getKey(),
169 'foe' => true,
170 ]);
171
172 // reset caches from previous steps.
173 $user->refresh();
174 $otherUser->refresh();
175 app('OsuAuthorize')->resetCache();
176
177 // this assertion to make sure the correct block direction is being tested.
178 $this->assertTrue($otherUser->hasBlocked($user));
179 $this->assertSame($canMessage, $channel->checkCanMessage($user)->can());
180 }
181
182 /**
183 * @dataProvider channelCanMessageWhenBlockedDataProvider
184 */
185 public function testChannelCanMessagePmChannelWhenFriendsOnly(?string $group, bool $canMessage)
186 {
187 $user = User::factory()->withGroup($group)->create();
188 $otherUser = User::factory()->create(['pm_friends_only' => true]);
189 $channel = Channel::factory()->type('pm', [$user, $otherUser])->create();
190
191 app('OsuAuthorize')->resetCache();
192
193 $this->assertSame($canMessage, $channel->checkCanMessage($user)->can());
194 }
195
196 public function testCreateAnnouncement()
197 {
198 Event::fake(ChatChannelEvent::class);
199
200 $users = User::factory()->count(2)->create();
201
202 $channel = Channel::createAnnouncement($users, ['description' => 'channel', 'name' => 'the best']);
203
204 $channel = $channel->fresh();
205
206 $this->assertEmpty($users->diff($channel->users()), 'created channel has too many users.');
207 $this->assertEmpty($channel->users()->diff($users), 'created channel is missing users.');
208 $this->assertSame(Channel::TYPES['announce'], $channel->type);
209
210 Event::assertDispatched(fn (ChatChannelEvent $event) => $event->action === 'join', 2);
211 Event::assertNotDispatched(fn (ChatChannelEvent $event) => $event->action !== 'join');
212 }
213
214 public function testGetPMChannelName()
215 {
216 $user1 = User::factory()->create();
217 $user2 = User::factory()->create();
218
219 $this->assertSame(
220 Channel::getPMChannelName($user1, $user2),
221 Channel::getPMChannelName($user2, $user1)
222 );
223 }
224
225 /**
226 * @dataProvider leaveChannelDataProvider
227 */
228 public function testLeaveChannel(string $type, bool $inChannel)
229 {
230 $users = User::factory()->count(2)->create();
231 $user = $users[0];
232 $channel = Channel::factory()->type($type, [...$users])->create();
233 $channel->refresh();
234
235 $channel->removeUser($user);
236 $channel->refresh();
237
238 if ($inChannel) {
239 $this->assertContains($user->getKey(), $channel->userIds());
240 $this->assertTrue($channel->hasUser($user));
241 } else {
242 $this->assertNotContains($user->getKey(), $channel->userIds());
243 $this->assertFalse($channel->hasUser($user));
244 }
245 }
246
247 public function testPmChannelIcon()
248 {
249 Storage::fake('local-avatar');
250 $this->beforeApplicationDestroyed(function () {
251 (new Filesystem())->deleteDirectory(storage_path('framework/testing/disks/local-avatar'));
252 });
253
254 $user = User::factory()->create();
255 $otherUser = User::factory()->create();
256
257 $testFile = new SplFileInfo(public_path('images/layout/avatar-guest.png'));
258 AvatarHelper::set($user, $testFile);
259 AvatarHelper::set($otherUser, $testFile);
260
261 $channel = Channel::factory()->type('pm', [$user, $otherUser])->create();
262 $this->assertSame($otherUser->user_avatar, $channel->displayIconFor($user));
263 $this->assertSame($user->user_avatar, $channel->displayIconFor($otherUser));
264 }
265
266 public function testPmChannelName()
267 {
268 $user = User::factory()->create();
269 $otherUser = User::factory()->create();
270
271 $channel = Channel::factory()->type('pm', [$user, $otherUser])->create();
272 $this->assertSame($otherUser->username, $channel->displayNameFor($user));
273 $this->assertSame($user->username, $channel->displayNameFor($otherUser));
274 }
275
276 public function testPublicChannelDoesNotShowUsers()
277 {
278 $user = User::factory()->create();
279 $channel = Channel::factory()->type('public', [$user])->create();
280
281 $this->assertSame(1, $channel->users()->count());
282 $this->assertEmpty($channel->visibleUsers());
283 }
284
285 // test add/removeUser resets any memoized values
286 public function testResetMemoized()
287 {
288 $user = User::factory()->create();
289 $channel = Channel::factory()->create();
290
291 $channel->addUser($user); // removeUser doesn't trigger resetMemoized if the user isn't in the channel.
292 $memoized = $this->invokeProperty($channel, 'memoized');
293 $this->assertEmpty($memoized); // addUser calls resetMemoized at the end so it should be empty.
294
295 $this->invokeMethod($channel, 'userChannelFor', [$user]);
296 $memoized = $this->invokeProperty($channel, 'memoized');
297 $this->assertNotEmpty($memoized);
298
299 $channel->removeUser($user);
300 $memoized = $this->invokeProperty($channel, 'memoized');
301 $this->assertEmpty($memoized);
302 }
303
304 public static function channelCanMessageModeratedChannelDataProvider()
305 {
306 return [
307 [null, false],
308 ['admin', true],
309 ['bng', false],
310 ['bot', false],
311 ['gmt', true],
312 ['nat', true],
313 ];
314 }
315
316 public static function channelCanMessageWhenBlockedDataProvider()
317 {
318 return [
319 [null, false],
320 ['admin', true],
321 ['bng', false],
322 ['bot', true],
323 ['gmt', true],
324 ['nat', true],
325 ];
326 }
327
328 public static function channelWithBlockedUserVisibilityDataProvider()
329 {
330 return [
331 [null, false],
332 ['admin', true],
333 ['bng', false],
334 ['bot', true],
335 ['gmt', true],
336 ['nat', true],
337 ];
338 }
339
340 public static function leaveChannelDataProvider()
341 {
342 return [
343 ['announce', true],
344 ['pm', true],
345 ['public', false],
346 ];
347 }
348}