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\Controllers\InterOp;
7
8use App\Models\Group;
9use App\Models\User;
10use App\Models\UserGroupEvent;
11use Tests\TestCase;
12
13class UserGroupsControllerTest extends TestCase
14{
15 public function testUserGroupAdd()
16 {
17 $user = User::factory()->create();
18 $group = app('groups')->byIdentifier('gmt');
19 $userAddEventCount = $this->eventCount(UserGroupEvent::USER_ADD, $user, $group);
20 $url = route('interop.user-group.update', [
21 'group' => $group->getKey(),
22 'timestamp' => time(),
23 'user' => $user->getKey(),
24 ]);
25
26 $this
27 ->withInterOpHeader($url)
28 ->put($url)
29 ->assertStatus(204);
30
31 $user->refresh();
32
33 $this->assertTrue($user->isGroup($group));
34 $this->assertSame(
35 $this->eventCount(UserGroupEvent::USER_ADD, $user, $group),
36 $userAddEventCount + 1,
37 );
38 }
39
40 public function testUserGroupAddWhenAlreadyMember()
41 {
42 $user = User::factory()->withGroup('gmt')->create();
43 $group = app('groups')->byIdentifier('gmt');
44 $userAddEventCount = $this->eventCount(UserGroupEvent::USER_ADD, $user, $group);
45 $url = route('interop.user-group.update', [
46 'group' => $group->getKey(),
47 'timestamp' => time(),
48 'user' => $user->getKey(),
49 ]);
50
51 $this
52 ->withInterOpHeader($url)
53 ->put($url)
54 ->assertStatus(204);
55
56 $user->refresh();
57
58 $this->assertTrue($user->isGroup($group));
59 $this->assertSame(
60 $this->eventCount(UserGroupEvent::USER_ADD, $user, $group),
61 $userAddEventCount,
62 );
63 }
64
65 public function testUserGroupAddWhenHasSamePlaymodes()
66 {
67 $playmodes = ['osu'];
68 $user = User::factory()->withGroup('nat', $playmodes)->create();
69 $group = app('groups')->byIdentifier('nat');
70 $userAddPlaymodesEventCount = $this->eventCount(UserGroupEvent::USER_ADD_PLAYMODES, $user, $group);
71 $url = route('interop.user-group.update', [
72 'group' => $group->getKey(),
73 'playmodes' => $playmodes,
74 'timestamp' => time(),
75 'user' => $user->getKey(),
76 ]);
77
78 $this
79 ->withInterOpHeader($url)
80 ->put($url)
81 ->assertStatus(204);
82
83 $this->assertSame(
84 $this->eventCount(UserGroupEvent::USER_ADD_PLAYMODES, $user, $group),
85 $userAddPlaymodesEventCount,
86 );
87 }
88
89 public function testUserGroupChangePlaymodes()
90 {
91 $playmodes = ['fruits', 'mania'];
92 $user = User::factory()->withGroup('nat', ['osu', 'taiko'])->create();
93 $group = app('groups')->byIdentifier('nat');
94 $userAddEventCount = $this->eventCount(UserGroupEvent::USER_ADD, $user, $group);
95 $userAddPlaymodesEventCount = $this->eventCount(UserGroupEvent::USER_ADD_PLAYMODES, $user, $group);
96 $userRemovePlaymodesEventCount = $this->eventCount(UserGroupEvent::USER_REMOVE_PLAYMODES, $user, $group);
97 $url = route('interop.user-group.update', [
98 'group' => $group->getKey(),
99 'playmodes' => $playmodes,
100 'timestamp' => time(),
101 'user' => $user->getKey(),
102 ]);
103
104 $this
105 ->withInterOpHeader($url)
106 ->put($url)
107 ->assertStatus(204);
108
109 $user->refresh();
110
111 $actualPlaymodes = $user->findUserGroup($group, true)->playmodes;
112
113 $this->assertCount(count($playmodes), $actualPlaymodes);
114 $this->assertArraySubset($playmodes, $actualPlaymodes);
115 $this->assertSame(
116 $this->eventCount(UserGroupEvent::USER_ADD, $user, $group),
117 $userAddEventCount,
118 );
119 $this->assertSame(
120 $this->eventCount(UserGroupEvent::USER_ADD_PLAYMODES, $user, $group),
121 $userAddPlaymodesEventCount + 1,
122 );
123 $this->assertSame(
124 $this->eventCount(UserGroupEvent::USER_REMOVE_PLAYMODES, $user, $group),
125 $userRemovePlaymodesEventCount + 1,
126 );
127 }
128
129 public function testUserGroupRemove()
130 {
131 $user = User::factory()->withGroup('gmt')->create();
132 $group = app('groups')->byIdentifier('gmt');
133 $userRemoveEventCount = $this->eventCount(UserGroupEvent::USER_REMOVE, $user, $group);
134 $url = route('interop.user-group.destroy', [
135 'group' => $group->getKey(),
136 'timestamp' => time(),
137 'user' => $user->getKey(),
138 ]);
139
140 $this
141 ->withInterOpHeader($url)
142 ->delete($url)
143 ->assertStatus(204);
144
145 $user->refresh();
146
147 $this->assertNotSame($user->group_id, $group->getKey());
148 $this->assertFalse($user->isGroup($group));
149 $this->assertSame(
150 $this->eventCount(UserGroupEvent::USER_REMOVE, $user, $group),
151 $userRemoveEventCount + 1,
152 );
153 }
154
155 public function testUserGroupRemoveWhenNotMember()
156 {
157 $user = User::factory()->create();
158 $group = app('groups')->byIdentifier('gmt');
159 $userRemoveEventCount = $this->eventCount(UserGroupEvent::USER_REMOVE, $user, $group);
160 $url = route('interop.user-group.destroy', [
161 'group' => $group->getKey(),
162 'timestamp' => time(),
163 'user' => $user->getKey(),
164 ]);
165
166 $this
167 ->withInterOpHeader($url)
168 ->delete($url)
169 ->assertStatus(204);
170
171 $user->refresh();
172
173 $this->assertFalse($user->isGroup($group));
174 $this->assertSame(
175 $this->eventCount(UserGroupEvent::USER_REMOVE, $user, $group),
176 $userRemoveEventCount,
177 );
178 }
179
180 public function testUserGroupSetDefault()
181 {
182 $user = User::factory()->withGroup('gmt')->create(['group_id' => app('groups')->byIdentifier('default')]);
183 $group = app('groups')->byIdentifier('gmt');
184 $userAddEventCount = $this->eventCount(UserGroupEvent::USER_ADD, $user, $group);
185 $userSetDefaultEventCount = $this->eventCount(UserGroupEvent::USER_SET_DEFAULT, $user, $group);
186 $url = route('interop.user-group.set-default', [
187 'group' => $group->getKey(),
188 'timestamp' => time(),
189 'user' => $user->getKey(),
190 ]);
191
192 $this
193 ->withInterOpHeader($url)
194 ->post($url)
195 ->assertStatus(204);
196
197 $user->refresh();
198
199 $this->assertSame($user->group_id, $group->getKey());
200 $this->assertTrue($user->isGroup($group));
201 $this->assertSame(
202 $this->eventCount(UserGroupEvent::USER_ADD, $user, $group),
203 $userAddEventCount,
204 );
205 $this->assertSame(
206 $this->eventCount(UserGroupEvent::USER_SET_DEFAULT, $user, $group),
207 $userSetDefaultEventCount + 1,
208 );
209 }
210
211 public function testUserGroupSetDefaultLeavesPlaymodesUnchanged()
212 {
213 $playmodes = ['osu'];
214 $user = User::factory()->withGroup('nat', $playmodes)->create(['group_id' => app('groups')->byIdentifier('default')->getKey()]);
215 $group = app('groups')->byIdentifier('nat');
216 $url = route('interop.user-group.set-default', [
217 'group' => $group->getKey(),
218 'timestamp' => time(),
219 'user' => $user->getKey(),
220 ]);
221
222 $this
223 ->withInterOpHeader($url)
224 ->post($url)
225 ->assertStatus(204);
226
227 $user->refresh();
228
229 $actualPlaymodes = $user->findUserGroup($group, true)->playmodes;
230
231 $this->assertCount(count($playmodes), $actualPlaymodes);
232 $this->assertArraySubset($playmodes, $actualPlaymodes);
233 }
234
235 public function testUserGroupSetDefaultWhenAlreadyDefault()
236 {
237 $user = User::factory()->withGroup('gmt')->create();
238 $group = app('groups')->byIdentifier('gmt');
239 $userSetDefaultEventCount = $this->eventCount(UserGroupEvent::USER_SET_DEFAULT, $user, $group);
240 $url = route('interop.user-group.set-default', [
241 'group' => $group->getKey(),
242 'timestamp' => time(),
243 'user' => $user->getKey(),
244 ]);
245
246 $this
247 ->withInterOpHeader($url)
248 ->post($url)
249 ->assertStatus(204);
250
251 $this->assertSame(
252 $this->eventCount(UserGroupEvent::USER_SET_DEFAULT, $user, $group),
253 $userSetDefaultEventCount,
254 );
255 }
256
257 public function testUserGroupSetDefaultWhenNotMember()
258 {
259 $user = User::factory()->create();
260 $group = app('groups')->byIdentifier('gmt');
261 $userAddEventCount = $this->eventCount(UserGroupEvent::USER_ADD, $user, $group);
262 $userSetDefaultEventCount = $this->eventCount(UserGroupEvent::USER_SET_DEFAULT, $user, $group);
263 $url = route('interop.user-group.set-default', [
264 'group' => $group->getKey(),
265 'timestamp' => time(),
266 'user' => $user->getKey(),
267 ]);
268
269 $this
270 ->withInterOpHeader($url)
271 ->post($url)
272 ->assertStatus(204);
273
274 $user->refresh();
275
276 $this->assertSame($user->group_id, $group->getKey());
277 $this->assertTrue($user->isGroup($group));
278 $this->assertSame(
279 $this->eventCount(UserGroupEvent::USER_ADD, $user, $group),
280 $userAddEventCount + 1,
281 );
282 $this->assertSame(
283 $this->eventCount(UserGroupEvent::USER_SET_DEFAULT, $user, $group),
284 $userSetDefaultEventCount + 1,
285 );
286 }
287
288 public function testInvalidPlaymodes()
289 {
290 $user = User::factory()->create();
291 $group = app('groups')->byIdentifier('nat');
292 $group->update(['has_playmodes' => true]);
293 $url = route('interop.user-group.update', [
294 'group' => $group->getKey(),
295 'playmodes' => ['osu', 'invalid_playmode'],
296 'timestamp' => time(),
297 'user' => $user->getKey(),
298 ]);
299
300 $this
301 ->withInterOpHeader($url)
302 ->put($url)
303 ->assertStatus(422);
304 }
305
306 public function testPlaymodesWithoutGroupPlaymodesSet()
307 {
308 $user = User::factory()->create();
309 $group = app('groups')->byIdentifier('gmt');
310 $url = route('interop.user-group.update', [
311 'group' => $group->getKey(),
312 'playmodes' => ['osu'],
313 'timestamp' => time(),
314 'user' => $user->getKey(),
315 ]);
316
317 $this
318 ->withInterOpHeader($url)
319 ->put($url)
320 ->assertStatus(422);
321 }
322
323 /**
324 * @dataProvider userGroupRoutesDataProvider
325 */
326 public function testWithActor(string $type, string $method, string $route): void
327 {
328 $user = User::factory()->create();
329 $group = app('groups')->byIdentifier('gmt');
330 $actor = User::factory()->create();
331 $url = route($route, [
332 'actor_id' => $actor->getKey(),
333 'group' => $group->getKey(),
334 'timestamp' => time(),
335 'user' => $user->getKey(),
336 ]);
337
338 if ($type === UserGroupEvent::USER_REMOVE) {
339 $user->addToGroup($group);
340 }
341
342 $this->expectCountChange(
343 fn () => $this->eventCount($type, $user, $group, $actor),
344 1,
345 );
346
347 $this
348 ->withInterOpHeader($url)
349 ->$method($url)
350 ->assertStatus(204);
351 }
352
353 /**
354 * @dataProvider userGroupRoutesDataProvider
355 */
356 public function testWithInvalidActor(string $type, string $method, string $route): void
357 {
358 $user = User::factory()->create();
359 $group = app('groups')->byIdentifier('gmt');
360 $url = route($route, [
361 'actor_id' => $user->getKey() + 1,
362 'group' => $group->getKey(),
363 'timestamp' => time(),
364 'user' => $user->getKey(),
365 ]);
366
367 $this
368 ->withInterOpHeader($url)
369 ->$method($url)
370 ->assertStatus(404);
371 }
372
373 public static function userGroupRoutesDataProvider(): array
374 {
375 return [
376 'add' =>
377 [UserGroupEvent::USER_ADD, 'put', 'interop.user-group.update'],
378 'remove' =>
379 [UserGroupEvent::USER_REMOVE, 'delete', 'interop.user-group.destroy'],
380 'set default' =>
381 [UserGroupEvent::USER_SET_DEFAULT, 'post', 'interop.user-group.set-default'],
382 ];
383 }
384
385 private function eventCount(string $type, User $user, Group $group, ?User $actor = null): int
386 {
387 return UserGroupEvent
388 ::where([
389 'actor_id' => $actor?->getKey(),
390 'group_id' => $group->getKey(),
391 'type' => $type,
392 'user_id' => $user->getKey(),
393 ])
394 ->count();
395 }
396}