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\Models;
7
8use App\Models\Beatmap;
9use App\Models\BeatmapDiscussion;
10use App\Models\Beatmapset;
11use App\Models\KudosuHistory;
12use App\Models\User;
13use Illuminate\Database\Eloquent\Builder;
14use Tests\TestCase;
15
16class BeatmapDiscussionTest extends TestCase
17{
18 /**
19 * Valid beatmapset status always maps to a scope method sanity test.
20 *
21 * @dataProvider validBeatmapsetStatuses
22 */
23 public function testBeatmapsetScopesExist($scope)
24 {
25 $this->assertInstanceOf(Builder::class, Beatmapset::$scope());
26 }
27
28 public function testMapperPost()
29 {
30 $mapper = User::factory()->create();
31 $beatmapset = Beatmapset::factory()->create([
32 'user_id' => $mapper,
33 ]);
34 $beatmap = $beatmapset->beatmaps()->save(Beatmap::factory()->make());
35
36 $discussion = $this->newDiscussion($beatmapset);
37 $discussion->fill([
38 'beatmap_id' => $beatmap->beatmap_id,
39 'message_type' => 'mapper_note',
40 'user_id' => $mapper->getKey(),
41 ]);
42
43 $this->assertTrue($discussion->isValid());
44
45 $discussion->message_type = 'problem';
46 $this->assertTrue($discussion->isValid());
47
48 $discussion->message_type = 'suggestion';
49 $this->assertTrue($discussion->isValid());
50
51 $discussion->message_type = 'praise';
52 $this->assertTrue($discussion->isValid());
53
54 $discussion->beatmapset->update(['approved' => Beatmapset::STATES['pending']]);
55 $discussion->beatmap_id = null;
56 $discussion->message_type = 'hype';
57 $this->assertFalse($discussion->isValid());
58 }
59
60 public function testModderPost()
61 {
62 $mapper = User::factory()->create();
63 $beatmapset = Beatmapset::factory()->create([
64 'user_id' => $mapper,
65 ]);
66 $beatmap = $beatmapset->beatmaps()->save(Beatmap::factory()->make());
67 $modder = User::factory()->create();
68
69 $discussion = $this->newDiscussion($beatmapset);
70 $discussion->fill([
71 'beatmap_id' => $beatmap->beatmap_id,
72 'message_type' => 'mapper_note',
73 'user_id' => $modder->getKey(),
74 ]);
75
76 $this->assertTrue($discussion->isValid());
77
78 $discussion->message_type = 'problem';
79 $this->assertTrue($discussion->isValid());
80
81 $discussion->message_type = 'suggestion';
82 $this->assertTrue($discussion->isValid());
83
84 $discussion->message_type = 'praise';
85 $this->assertTrue($discussion->isValid());
86
87 $discussion->beatmapset->update(['approved' => Beatmapset::STATES['ranked']]);
88 $discussion->message_type = 'hype';
89 $this->assertFalse($discussion->isValid());
90
91 $discussion->beatmap_id = null;
92 $this->assertFalse($discussion->isValid());
93
94 $discussion->beatmapset->update(['approved' => Beatmapset::STATES['pending']]);
95 $this->assertTrue($discussion->isValid());
96 }
97
98 public function testIsValid()
99 {
100 $beatmapset = Beatmapset::factory()->create();
101 $beatmap = $beatmapset->beatmaps()->save(Beatmap::factory()->make());
102
103 $otherBeatmapset = Beatmapset::factory()->create();
104 $otherBeatmap = $otherBeatmapset->beatmaps()->save(Beatmap::factory()->make());
105
106 $validTimestamp = ($beatmap->total_length + 10) * 1000;
107 $invalidTimestamp = $validTimestamp + 1;
108
109 // blank everything not fine
110 $discussion = $this->newDiscussion($beatmapset);
111 $this->assertFalse($discussion->isValid());
112
113 // is valid with message_type
114 $discussion = $this->newDiscussion($beatmapset);
115 $discussion->fill(['message_type' => 'problem']);
116 $this->assertTrue($discussion->isValid());
117
118 // just beatmap_id is not fine (per-beatmap general)
119 $discussion = $this->newDiscussion($beatmapset);
120 $discussion->fill(['beatmap_id' => $beatmap->beatmap_id]);
121 $this->assertFalse($discussion->isValid());
122
123 // with beatmap_id and message_type is fine (per-beatmap general)
124 $discussion = $this->newDiscussion($beatmapset);
125 $discussion->fill([
126 'beatmap_id' => $beatmap->beatmap_id,
127 'message_type' => 'problem',
128 ]);
129 $this->assertTrue($discussion->isValid());
130
131 // complete data is fine as well
132 $discussion = $this->newDiscussion($beatmapset);
133 $discussion->fill(['timestamp' => $validTimestamp, 'message_type' => 'praise', 'beatmap_id' => $beatmap->beatmap_id]);
134 $this->assertTrue($discussion->isValid());
135
136 // Including timestamp 0
137 $discussion = $this->newDiscussion($beatmapset);
138 $discussion->fill(['timestamp' => 0, 'message_type' => 'praise', 'beatmap_id' => $beatmap->beatmap_id]);
139 $this->assertTrue($discussion->isValid());
140
141 // just timestamp is not valid
142 $discussion = $this->newDiscussion($beatmapset);
143 $discussion->fill(['timestamp' => $validTimestamp]);
144 $this->assertFalse($discussion->isValid());
145
146 // nor is wrong timestamp
147 $discussion = $this->newDiscussion($beatmapset);
148 $discussion->fill(['timestamp' => $invalidTimestamp, 'message_type' => 'praise', 'beatmap_id' => $beatmap->beatmap_id]);
149 $this->assertFalse($discussion->isValid());
150 }
151
152 public function testSoftDeleteOrExplode()
153 {
154 $beatmapset = Beatmapset::factory()->create();
155 $beatmap = $beatmapset->beatmaps()->save(Beatmap::factory()->make());
156 $user = User::factory()->create();
157 $discussion = BeatmapDiscussion::create([
158 'beatmapset_id' => $beatmapset->getKey(),
159 'beatmap_id' => $beatmap->getKey(),
160 'user_id' => $user->getKey(),
161 'message_type' => 'suggestion',
162 ]);
163
164 $this->assertFalse($discussion->trashed());
165
166 // Soft delete.
167 $discussion->softDeleteOrExplode($user);
168 $discussion = $discussion->fresh();
169 $this->assertTrue($discussion->trashed());
170
171 // Restore.
172 $discussion->restore($user);
173 $discussion = $discussion->fresh();
174 $this->assertFalse($discussion->trashed());
175
176 // Soft delete with deleted beatmap.
177 $beatmap->delete();
178 $discussion->softDeleteOrExplode($user);
179 $discussion = $discussion->fresh();
180 $this->assertTrue($discussion->trashed());
181
182 // Restore with deleted beatmap.
183 $discussion->restore($user);
184 $discussion = $discussion->fresh();
185 $this->assertFalse($discussion->trashed());
186 }
187
188 public function testRefreshKudosu(): void
189 {
190 $discussion = BeatmapDiscussion::factory()->problem()->create([
191 'beatmapset_id' => Beatmapset::factory()->create(),
192 'timestamp' => null,
193 'user_id' => User::factory()->create(),
194 ]);
195 $discussion->beatmapDiscussionVotes()->create([
196 'score' => 1,
197 'user_id' => User::factory()->create()->getKey(),
198 ]);
199 $discussion->beatmapDiscussionVotes()->create([
200 'score' => -1,
201 'user_id' => User::factory()->create()->getKey(),
202 ]);
203
204 $this->expectCountChange(fn () => KudosuHistory::count(), 1);
205 $this->expectCountChange(fn () => $discussion->user->fresh()->osu_kudostotal, 1);
206 $discussion->fresh()->refreshKudosu('recalculate');
207 }
208
209 public static function validBeatmapsetStatuses()
210 {
211 return array_map(function ($status) {
212 return [camel_case($status)];
213 }, BeatmapDiscussion::VALID_BEATMAPSET_STATUSES);
214 }
215
216 private function newDiscussion($beatmapset)
217 {
218 return new BeatmapDiscussion(['beatmapset_id' => $beatmapset->getKey()]);
219 }
220}