diff --git a/app/Http/Controllers/Appeal/AppealActionController.php b/app/Http/Controllers/Appeal/AppealActionController.php
new file mode 100644
index 00000000..1df15df0
--- /dev/null
+++ b/app/Http/Controllers/Appeal/AppealActionController.php
@@ -0,0 +1,248 @@
+middleware('auth');
+ }
+
+ /**
+ * Process a appeal status change request
+ * @param Request $request Request object to process
+ * @param Appeal $appeal Appeal object to process
+ * @param string $logEntry Log action name, for example 'reserve' or 'sent for CheckUser review'
+ * @param Closure $doAction Closure to take action on the appeal object
+ * @param Closure|null $validate Check if this action can be taken. Return a string to fail, or true to pass.
+ * @param int $logProtection Log protection level. Use a const from LogEntry::LOG_PROTECTION_*
+ * @param string $requiredPermission Permission to take this object
+ * @return object Response object
+ */
+ private function doAction(
+ Request $request,
+ Appeal $appeal,
+ string $logEntry,
+ Closure $doAction,
+ ?Closure $validate = null,
+ int $logProtection = LogEntry::LOG_PROTECTION_NONE,
+ string $requiredPermission = 'update'
+ )
+ {
+ // first off, make sure that we can do the action
+ $this->authorize($requiredPermission, $appeal);
+
+ if ($validate) {
+ $validationResult = $validate($appeal, $request);
+ if ($validationResult && $validationResult !== true) {
+ abort(403, $validationResult);
+ return response($validationResult); // unreachable, in theory
+ }
+ }
+
+ DB::transaction(function () use ($appeal, $request, $doAction, $logEntry, $logProtection) {
+ $ip = $request->ip();
+ $ua = $request->userAgent();
+ $lang = $request->header('Accept-Language');
+
+ $reason = $doAction($appeal, $request);
+ if (!$reason || empty($reason)) {
+ $reason = null;
+ }
+
+ LogEntry::create([
+ 'user_id' => $request->user()->id,
+ 'model_id' => $appeal->id,
+ 'model_type' => Appeal::class,
+ 'reason' => $reason,
+ 'action' => $logEntry,
+ 'ip' => $ip,
+ 'ua' => $ua . " " . $lang,
+ 'protected' => $logProtection,
+ ]);
+ });
+
+ return redirect()->route('appeal.view', [ $appeal ]);
+ }
+
+ public function reserve(Request $request, Appeal $appeal)
+ {
+ return $this->doAction(
+ $request,
+ $appeal,
+ 'reserve',
+ function (Appeal $appeal, Request $request) {
+ $appeal->handlingadmin = $request->user()->id;
+ $appeal->save();
+ },
+ function (Appeal $appeal) {
+ return $appeal->handlingadmin
+ ? 'This appeal has already been reserved.'
+ : true;
+ }
+ );
+ }
+
+ public function close(Request $request, Appeal $appeal, string $status)
+ {
+ if (!in_array($status, [ Appeal::STATUS_ACCEPT, Appeal::STATUS_DECLINE, Appeal::STATUS_EXPIRE ])) {
+ return abort(400, 'Invalid status.');
+ }
+
+ $this->doAction(
+ $request,
+ $appeal,
+ 'closed - ' . strtolower($status),
+ function (Appeal $appeal) use ($status) {
+ $appeal->status = $status;
+ $appeal->save();
+ },
+ );
+
+ return redirect()->route('appeal.list');
+ }
+
+ public function release(Request $request, Appeal $appeal)
+ {
+ return $this->doAction(
+ $request,
+ $appeal,
+ 'release',
+ function (Appeal $appeal) {
+ $appeal->handlingadmin = null;
+ $appeal->save();
+ },
+ function (Appeal $appeal, Request $request) {
+ if ($appeal->handlingadmin) {
+ /** @var User $user */
+ $user = $request->user();
+ if ($appeal->handlingadmin === $user->id) {
+ return true;
+ }
+
+ return $user->hasAnySpecifiedLocalOrGlobalPerms($appeal->wiki, [ 'tooladmin' ])
+ ? true
+ : "Only tool administrators can force release appeals.";
+ }
+
+ return 'No-one has reserved this appeal.';
+ }
+ );
+ }
+
+ public function sendToTooladminReview(Request $request, Appeal $appeal)
+ {
+ return $this->doAction(
+ $request,
+ $appeal,
+ 'sent for tool administrator review',
+ function (Appeal $appeal) {
+ $appeal->status = Appeal::STATUS_ADMIN;
+ $appeal->save();
+ },
+ function (Appeal $appeal) {
+ return $appeal->status === Appeal::STATUS_ADMIN
+ ? 'This appeal is already waiting for tool administrator review.'
+ : true;
+ }
+ );
+ }
+
+ public function sendToCheckUserReview(Request $request, Appeal $appeal)
+ {
+ return $this->doAction(
+ $request,
+ $appeal,
+ 'sent for CheckUser review',
+ function (Appeal $appeal, Request $request) {
+ $reason = $request->validate([
+ 'cu_reason' => 'required|string|min:3|max:190',
+ ])['cu_reason'];
+
+ $appeal->status = Appeal::STATUS_CHECKUSER;
+ $appeal->save();
+
+ return $reason;
+ },
+ function (Appeal $appeal) {
+ return $appeal->status === Appeal::STATUS_CHECKUSER
+ ? 'This appeal is already waiting for CheckUser review.'
+ : true;
+ }
+ );
+ }
+
+ public function reOpen(Request $request, Appeal $appeal)
+ {
+ return $this->doAction(
+ $request,
+ $appeal,
+ 're-open',
+ function (Appeal $appeal) {
+ $appeal->status = Appeal::STATUS_OPEN;
+ $appeal->save();
+ },
+ function (Appeal $appeal, Request $request) {
+ /** @var User $user */
+ $user = $request->user();
+
+ if (in_array($appeal->status,
+ [ Appeal::STATUS_ACCEPT, Appeal::STATUS_EXPIRE, Appeal::STATUS_DECLINE,
+ Appeal::STATUS_CHECKUSER, Appeal::STATUS_ADMIN, ])
+ && $user->hasAnySpecifiedLocalOrGlobalPerms($appeal->wiki,
+ [ 'tooladmin', 'checkuser', 'oversight', 'steward', 'staff', ])) {
+ return true;
+ }
+
+ return $user->hasAnySpecifiedPermsOnAnyWiki([ 'developer' ])
+ ? true
+ : 'This appeal is currently not in a status where you can re-open it.';
+ }
+ );
+ }
+
+ public function invalidate(Request $request, Appeal $appeal)
+ {
+ return $this->doAction(
+ $request,
+ $appeal,
+ 'closed as invalid',
+ function (Appeal $appeal) {
+ $appeal->status = Appeal::STATUS_INVALID;
+ $appeal->save();
+ },
+ null,
+ LogEntry::LOG_PROTECTION_ADMIN,
+ 'performDeveloperActions',
+ );
+ }
+
+ public function reverifyBlockDetails(Request $request, Appeal $appeal)
+ {
+ return $this->doAction(
+ $request,
+ $appeal,
+ 're-verify block details',
+ function (Appeal $appeal) {
+ GetBlockDetailsJob::dispatch($appeal);
+ },
+ function (Appeal $appeal) {
+ return in_array($appeal->status, [ Appeal::STATUS_VERIFY, Appeal::STATUS_NOTFOUND ])
+ ? true
+ : 'Block details for this appeal have already been found.';
+ },
+ LogEntry::LOG_PROTECTION_NONE,
+ 'performDeveloperActions',
+ );
+ }
+}
diff --git a/app/Http/Controllers/AppealController.php b/app/Http/Controllers/AppealController.php
index e114426c..ca1741d3 100644
--- a/app/Http/Controllers/AppealController.php
+++ b/app/Http/Controllers/AppealController.php
@@ -191,7 +191,7 @@ public function search(Request $request)
$number = intval(substr($search, 1), 10);
}
- $wikis = collect(MwApiUrls::getSupportedWikis(true));
+ $wikis = collect(MediaWikiRepository::getSupportedTargets(true));
// For users who aren't developers, stewards or staff, show appeals only for own wikis
if (!$user->hasAnySpecifiedLocalOrGlobalPerms(['*'], ['steward', 'staff', 'developer'])) {
@@ -288,7 +288,7 @@ public function checkuser(Appeal $appeal, Request $request)
LogEntry::create(['user_id' => $user, 'model_id' => $appeal->id, 'model_type' => Appeal::class, 'action' => 'checkuser', 'reason' => $reason, 'ip' => $ip, 'ua' => $ua . " " . $lang, 'protected' => LogEntry::LOG_PROTECTION_FUNCTIONARY]);
return redirect('appeal/' . $appeal->id);
}
-
+
public function comment($id, Request $request)
{
if (!Auth::check()) {
@@ -429,105 +429,6 @@ public function respondCustom(Appeal $appeal)
return view('appeals.custom', ['appeal' => $appeal, 'userlist' => $userlist]);
}
- public function reserve(Appeal $appeal, Request $request)
- {
- $ua = $request->server('HTTP_USER_AGENT');
- $ip = $request->ip();
- $lang = $request->server('HTTP_ACCEPT_LANGUAGE');
- $user = Auth::id();
-
- $admin = Permission::checkAdmin($user, $appeal->wiki);
- abort_unless($admin,403, 'You are not an administrator.');
- abort_if($appeal->handlingadmin, 403, 'This appeal has already been reserved.');
- $appeal->handlingadmin = Auth::id();
- $appeal->save();
- LogEntry::create(['user_id' => $user, 'model_id' => $appeal->id, 'model_type' => Appeal::class, 'action' => 'reserve', 'ip' => $ip, 'ua' => $ua . " " . $lang, 'protected' => LogEntry::LOG_PROTECTION_NONE]);
-
- return redirect('appeal/' . $appeal->id);
- }
-
- public function release($id, Request $request)
- {
- $ua = $request->server('HTTP_USER_AGENT');
- $ip = $request->ip();
- $lang = $request->server('HTTP_ACCEPT_LANGUAGE');
- $user = Auth::id();
- $appeal = Appeal::findOrFail($id);
- $admin = Permission::checkAdmin($user, $appeal->wiki);
- if ($admin) {
- if (isset($appeal->handlingadmin)) {
- $appeal->handlingadmin = null;
- $appeal->save();
- $log = LogEntry::create(array('user_id' => $user, 'model_id' => $id, 'model_type' => Appeal::class, 'action' => 'release', 'ip' => $ip, 'ua' => $ua . " " . $lang, 'protected' => LogEntry::LOG_PROTECTION_NONE));
- } else {
- abort(403);
- }
- return redirect('appeal/' . $id);
- } else {
- abort(403);
- }
- }
-
- public function open($id, Request $request)
- {
- $appeal = Appeal::findOrFail($id);
- $user = Auth::id();
-
- $admin = Permission::checkAdmin($user, $appeal->wiki);
- abort_if(!$admin,403,"You are not an administrator on the wiki this appeal is for");
-
- $ua = $request->server('HTTP_USER_AGENT');
- $ip = $request->ip();
- $lang = $request->server('HTTP_ACCEPT_LANGUAGE');
- $user = Auth::id();
-
- if ($appeal->status == Appeal::STATUS_ACCEPT || $appeal->status == Appeal::STATUS_EXPIRE || $appeal->status == Appeal::STATUS_DECLINE || $appeal->status == Appeal::STATUS_CHECKUSER || $appeal->status == Appeal::STATUS_ADMIN) {
- $appeal->status = Appeal::STATUS_OPEN;
- $appeal->save();
- LogEntry::create(array('user_id' => $user, 'model_id' => $id, 'model_type' => Appeal::class, 'action' => 're-open', 'ip' => $ip, 'ua' => $ua . " " . $lang, 'protected' => LogEntry::LOG_PROTECTION_NONE));
- return redirect('appeal/' . $id);
- } else {
- abort(403);
- }
-}
-
- public function invalidate($id, Request $request)
- {
- $ua = $request->server('HTTP_USER_AGENT');
- $ip = $request->ip();
- $lang = $request->server('HTTP_ACCEPT_LANGUAGE');
- $user = Auth::id();
- $appeal = Appeal::findOrFail($id);
- $dev = Permission::checkSecurity($user, "DEVELOPER", "*");
- if ($dev && $appeal->status !== Appeal::STATUS_INVALID) {
- $appeal->status = Appeal::STATUS_INVALID;
- $appeal->save();
- $log = LogEntry::create(array('user_id' => $user, 'model_id' => $id, 'model_type' => Appeal::class, 'action' => 'closed - invalidate', 'ip' => $ip, 'ua' => $ua . " " . $lang, 'protected' => LogEntry::LOG_PROTECTION_ADMIN));
- return redirect('appeal/' . $id);
- } else {
- abort(403);
- }
- }
-
- public function close($id, $type, Request $request)
- {
- $appeal = Appeal::findOrFail($id);
- $user = Auth::id();
- $admin = Permission::checkAdmin($user, $appeal->wiki);
- abort_if(!$admin,403,"You are not an administrator on the wiki this appeal is for");
- $ua = $request->server('HTTP_USER_AGENT');
- $ip = $request->ip();
- $lang = $request->server('HTTP_ACCEPT_LANGUAGE');
- if ($admin) {
- $appeal->status = strtoupper($type);
- $appeal->save();
- $log = LogEntry::create(array('user_id' => $user, 'model_id' => $id, 'model_type' => Appeal::class, 'action' => 'closed - ' . $type, 'ip' => $ip, 'ua' => $ua . " " . $lang, 'protected' => LogEntry::LOG_PROTECTION_NONE));
- return redirect('/review');
- } else {
- abort(403);
- }
- }
-
public function checkuserreview(Request $request, Appeal $appeal)
{
$ua = $request->header('User-Agent');
@@ -559,56 +460,4 @@ public function checkuserreview(Request $request, Appeal $appeal)
return redirect()->back();
}
-
- public function admin(Request $request, Appeal $appeal)
- {
- $user = Auth::id();
- $admin = Permission::checkAdmin($user, $appeal->wiki);
- abort_unless($admin,403,"You are not an administrator on the wiki this appeal is for");
- abort_if($appeal->status === Appeal::STATUS_ADMIN, 400, 'This appeal is already waiting for tool administrator review.');
-
- $ua = $request->userAgent();
- $ip = $request->ip();
- $lang = $request->header('Accept-Language');
-
- $appeal->status = Appeal::STATUS_ADMIN;
- $appeal->save();
- LogEntry::create([
- 'user_id' => $user,
- 'model_id' => $appeal->id,
- 'model_type' => Appeal::class,
- 'action' => 'sent for admin review',
- 'ip' => $ip,
- 'ua' => $ua . " " . $lang,
- 'protected' => LogEntry::LOG_PROTECTION_NONE
- ]);
-
- return redirect()->back();
- }
-
- public function findagain(Request $request, Appeal $appeal)
- {
- /** @var User $user */
- $user = $request->user();
-
- $ua = $request->server('HTTP_USER_AGENT');
- $ip = $request->ip();
- $lang = $request->server('HTTP_ACCEPT_LANGUAGE');
-
- $dev = $user->hasAnySpecifiedLocalOrGlobalPerms('*', 'developer');
- abort_unless($dev,403,"You are not an UTRS developer");
- abort_if($appeal->status !== Appeal::STATUS_NOTFOUND && $appeal->status !== Appeal::STATUS_VERIFY, 400, 'Appeal details were already found.');
-
- GetBlockDetailsJob::dispatch($appeal);
- LogEntry::create([
- 'user_id' => Auth::id(),
- 'model_id'=> $appeal->id,
- 'model_type'=> Appeal::class,
- 'action'=>'reverify block',
- 'ip' => $ip,
- 'ua' => $ua . " " .$lang
- ]);
-
- return redirect()->back();
- }
}
diff --git a/app/Policies/AppealPolicy.php b/app/Policies/AppealPolicy.php
index f40b7286..85965317 100644
--- a/app/Policies/AppealPolicy.php
+++ b/app/Policies/AppealPolicy.php
@@ -33,7 +33,7 @@ public function viewAny(User $user)
public function view(User $user, Appeal $appeal)
{
if (!$user->hasAnySpecifiedLocalOrGlobalPerms($appeal->wiki, ['admin'])) {
- return false;
+ return $this->deny('Only ' . $appeal->wiki . ' administrators are able to see this appeal.');
}
if ($appeal->status === Appeal::STATUS_INVALID) {
@@ -74,4 +74,17 @@ public function update(User $user, Appeal $appeal)
return $user->hasAnySpecifiedLocalOrGlobalPerms($appeal->wiki, ['admin']) ? true
: $this->deny('Only administrators can take actions on appeals.');
}
+
+ /**
+ * Determine whether the user can take developer actions on this appeal.
+ *
+ * @param User $user
+ * @param Appeal $appeal
+ * @return mixed
+ */
+ public function performDeveloperActions(User $user, Appeal $appeal)
+ {
+ // Handle via Gate::before()
+ return $this->deny('Only developers can take developer actions on appeals.');
+ }
}
diff --git a/resources/views/appeals/appeal.blade.php b/resources/views/appeals/appeal.blade.php
index d2e27cdb..3e8268b8 100644
--- a/resources/views/appeals/appeal.blade.php
+++ b/resources/views/appeals/appeal.blade.php
@@ -158,10 +158,12 @@ class="btn btn-warning">
@if($info->status === Appeal::STATUS_ACCEPT || $info->status === Appeal::STATUS_DECLINE || $info->status === Appeal::STATUS_EXPIRE)
@if($perms['functionary'])
@else
@@ -172,11 +174,14 @@ class="btn btn-warning">
@if($info->handlingadmin == null)
-
- Reserve
-
+
@elseif($info->handlingadmin == Auth::id() || $perms['tooladmin'] || $perms['developer'])
-
@endif
@if($info->status === Appeal::STATUS_OPEN || $info->status === Appeal::STATUS_AWAITING_REPLY)
@@ -226,16 +243,22 @@ class="btn btn-warning">
@endif
@if(($info->status !== Appeal::STATUS_OPEN && $info->status !== Appeal::STATUS_EXPIRE && $info->status !== Appeal::STATUS_AWAITING_REPLY && $info->status !== Appeal::STATUS_DECLINE && $info->status !== Appeal::STATUS_ACCEPT) && ($perms['tooladmin'] || $perms['functionary'] || $perms['developer']))
@endif
@if($perms['developer'] && ($info->status=="NOTFOUND" || $info->status=="VERIFY"))
@endif
@endif
diff --git a/routes/web.php b/routes/web.php
index 2a61782c..15df43f7 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -30,7 +30,7 @@
Route::post('/appeal/verify/{appeal}', 'Appeal\PublicAppealController@verifyAccountOwnership')->name('public.appeal.verifyownership.submit');
});
-Route::get('/appeal/{id}', 'AppealController@appeal');
+Route::get('/appeal/{id}', 'AppealController@appeal')->middleware('auth')->name('appeal.view');
Route::get('/review', 'AppealController@appeallist')->name('appeal.list');
Route::get('/locate', 'AppealController@search')->name('appeal.search');
@@ -38,14 +38,16 @@
Route::post('/appeal/checkuser/{appeal}', 'AppealController@checkuser');
Route::post('/appeal/comment/{id}', 'AppealController@comment');
Route::get('/appeal/respond/{id}', 'AppealController@respond');
-Route::get('/appeal/reserve/{appeal}', 'AppealController@reserve');
-Route::post('/appeal/release/{id}', 'AppealController@release')->name('appeal.action.release');
-Route::get('/appeal/open/{id}', 'AppealController@open');
-Route::get('/appeal/findagain/{appeal}', 'AppealController@findagain');
-Route::get('/appeal/close/{id}/{type}', 'AppealController@close');
-Route::post('/appeal/checkuserreview/{appeal}', 'AppealController@checkuserreview')->name('appeal.action.checkuser');
-Route::post('/appeal/tooladmin/{appeal}', 'AppealController@admin')->name('appeal.action.tooladmin');
-Route::get('/appeal/invalidate/{id}', 'AppealController@invalidate');
+
+Route::post('/appeal/reserve/{appeal}', 'Appeal\AppealActionController@reserve')->name('appeal.action.reserve');
+Route::post('/appeal/release/{appeal}', 'Appeal\AppealActionController@release')->name('appeal.action.release');
+
+Route::post('/appeal/open/{appeal}', 'Appeal\AppealActionController@reOpen')->name('appeal.action.reopen');
+Route::post('/appeal/findagain/{appeal}', 'Appeal\AppealActionController@reverifyBlockDetails')->name('appeal.action.findagain');
+Route::post('/appeal/close/{appeal}/{type}', 'Appeal\AppealActionController@close')->name('appeal.action.close');
+Route::post('/appeal/checkuserreview/{appeal}', 'Appeal\AppealActionController@sendToCheckUserReview')->name('appeal.action.checkuser');
+Route::post('/appeal/tooladmin/{appeal}', 'Appeal\AppealActionController@sendToTooladminReview')->name('appeal.action.tooladmin');
+Route::post('/appeal/invalidate/{appeal}', 'Appeal\AppealActionController@invalidate')->name('appeal.action.invalidate');
Route::get('/appeal/template/{appeal}', 'AppealController@viewtemplates')->name('appeal.template');
Route::post('/appeal/template/{appeal}/{template}', 'AppealController@respond')->name('appeal.template.submit');
diff --git a/tests/Browser/Appeals/AppealCommentsTest.php b/tests/Browser/Appeals/AppealCommentsTest.php
index 9b1c16b2..626347ab 100644
--- a/tests/Browser/Appeals/AppealCommentsTest.php
+++ b/tests/Browser/Appeals/AppealCommentsTest.php
@@ -55,7 +55,7 @@ public function test_using_template()
->assertDontSee(Appeal::STATUS_AWAITING_REPLY)
->assertDontSee('Send a reply to user')
->assertDontSee($targetTemplateTextStart)
- ->clickLink('Reserve')
+ ->press('Reserve')
->clickLink('Send a reply to the user')
->assertSee('On this screen, you will see a list of templates to choose from in responding to a user')
->assertDontSee($targetTemplateTextStart)
@@ -84,7 +84,7 @@ public function test_custom_reply()
->visit('/appeal/' . $appeal->id)
->assertSee(Appeal::STATUS_OPEN)
->assertDontSee('Send a reply to user')
- ->clickLink('Reserve')
+ ->press('Reserve')
->clickLink('Send a reply to the user')
->assertSee('On this screen, you will see a list of templates to choose from in responding to a user')
->clickLink('Reply custom text')
diff --git a/tests/Browser/Appeals/AppealDeferTest.php b/tests/Browser/Appeals/AppealDeferTest.php
index 9dda9b09..cd5349c7 100644
--- a/tests/Browser/Appeals/AppealDeferTest.php
+++ b/tests/Browser/Appeals/AppealDeferTest.php
@@ -28,7 +28,7 @@ public function test_can_defer_to_tooladmin()
$appeal->refresh();
$this->assertEquals(Appeal::STATUS_ADMIN, $appeal->status);
- $this->assertNotNull($appeal->comments()->where('action', 'sent for admin review')->first());
+ $this->assertNotNull($appeal->comments()->where('action', 'sent for tool administrator review')->first());
});
}
}
diff --git a/tests/Feature/Appeal/Action/AppealCloseTest.php b/tests/Feature/Appeal/Action/AppealCloseTest.php
new file mode 100644
index 00000000..577b6ce7
--- /dev/null
+++ b/tests/Feature/Appeal/Action/AppealCloseTest.php
@@ -0,0 +1,64 @@
+getUser();
+ $appeal = Appeal::factory()->create([ 'status' => Appeal::STATUS_OPEN, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.close', [ $appeal, Appeal::STATUS_ACCEPT ]));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_ACCEPT, $appeal->status);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 'closed - accept')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_can_decline_appeal()
+ {
+ $user = $this->getUser();
+ $appeal = Appeal::factory()->create([ 'status' => Appeal::STATUS_OPEN, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.close', [ $appeal, Appeal::STATUS_DECLINE ]));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_DECLINE, $appeal->status);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 'closed - decline')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+
+ public function test_user_cant_use_whatever_statuses_when_closing()
+ {
+ $user = $this->getUser();
+ $appeal = Appeal::factory()->create([ 'status' => Appeal::STATUS_OPEN, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.close', [ $appeal, Appeal::STATUS_INVALID ]));
+ $response->assertStatus(400);
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_OPEN, $appeal->status);
+ $this->assertFalse($appeal->comments()
+ ->where('action', 'closed - accept')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+}
diff --git a/tests/Feature/Appeal/Action/AppealDeveloperActionsTest.php b/tests/Feature/Appeal/Action/AppealDeveloperActionsTest.php
new file mode 100644
index 00000000..4888a9f1
--- /dev/null
+++ b/tests/Feature/Appeal/Action/AppealDeveloperActionsTest.php
@@ -0,0 +1,111 @@
+getUser();
+ $appeal = Appeal::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.invalidate', $appeal));
+ $response->assertStatus(403);
+ $response->assertSee('Only developers can take developer actions on appeals.');
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_OPEN, $appeal->status);
+ $this->assertFalse($appeal->comments()
+ ->where('action', 'closed as invalid')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_developer_can_invalidate_appeal()
+ {
+ $user = $this->getDeveloperUser();
+ $appeal = Appeal::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.invalidate', $appeal));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_INVALID, $appeal->status);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 'closed as invalid')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_cant_reverify_appeal()
+ {
+ Queue::fake();
+
+ $user = $this->getUser();
+ $appeal = Appeal::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.findagain', $appeal));
+ $response->assertStatus(403);
+ $response->assertSee('Only developers can take developer actions on appeals.');
+
+ $appeal->refresh();
+ Queue::assertNothingPushed();
+ $this->assertFalse($appeal->comments()
+ ->where('action', 're-verify block details')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_developer_cant_reverify_open_appeal()
+ {
+ Queue::fake();
+
+ $user = $this->getDeveloperUser();
+ $appeal = Appeal::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.findagain', $appeal));
+ $response->assertStatus(403);
+ $response->assertSee('Block details for this appeal have already been found.');
+
+ $appeal->refresh();
+ Queue::assertNothingPushed();
+ $this->assertFalse($appeal->comments()
+ ->where('action', 're-verify block details')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_developer_can_reverify_appeal()
+ {
+ Queue::fake();
+
+ $user = $this->getDeveloperUser();
+ $appeal = Appeal::factory()->create([ 'status' => Appeal::STATUS_NOTFOUND, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.findagain', $appeal));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ Queue::assertPushed(GetBlockDetailsJob::class);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 're-verify block details')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+
+}
diff --git a/tests/Feature/Appeal/Action/AppealReferTest.php b/tests/Feature/Appeal/Action/AppealReferTest.php
new file mode 100644
index 00000000..4012d805
--- /dev/null
+++ b/tests/Feature/Appeal/Action/AppealReferTest.php
@@ -0,0 +1,83 @@
+getUser();
+ $appeal = Appeal::factory()->create([ 'status' => Appeal::STATUS_OPEN, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.tooladmin', $appeal));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_ADMIN, $appeal->status);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 'sent for tool administrator review')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_can_refer_an_appeal_to_checkusers()
+ {
+ $user = $this->getUser();
+ $appeal = Appeal::factory()->create([ 'status' => Appeal::STATUS_OPEN, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.checkuser', $appeal), [
+ 'cu_reason' => 'Example CheckUser reason',
+ ]);
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_CHECKUSER, $appeal->status);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 'sent for CheckUser review')
+ ->where('reason', 'Example CheckUser reason')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_cant_refer_an_appeal_to_checkusers_without_a_reason()
+ {
+ $user = $this->getUser();
+ $appeal = Appeal::factory()->create([ 'status' => Appeal::STATUS_OPEN, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.checkuser', $appeal));
+ $response->assertSessionHasErrors('cu_reason');
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_OPEN, $appeal->status);
+ $this->assertFalse($appeal->comments()
+ ->where('action', 'sent for CheckUser review')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_tool_admin_can_send_an_appeal_back_to_tool_users()
+ {
+ $user = $this->getTooladminUser();
+ $appeal = Appeal::factory()->create([ 'status' => Appeal::STATUS_ADMIN, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.reopen', $appeal));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertEquals(Appeal::STATUS_OPEN, $appeal->status);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 're-open')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+}
diff --git a/tests/Feature/Appeal/Action/AppealReserveReleaseTest.php b/tests/Feature/Appeal/Action/AppealReserveReleaseTest.php
new file mode 100644
index 00000000..0a286e0b
--- /dev/null
+++ b/tests/Feature/Appeal/Action/AppealReserveReleaseTest.php
@@ -0,0 +1,159 @@
+getUser();
+ $appeal = Appeal::factory()->create([ 'handlingadmin' => null, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.reserve', $appeal));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertEquals($user->id, $appeal->handlingadmin);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 'reserve')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_cant_reserve_already_reserved_appeal()
+ {
+ $user = $this->getUser();
+ $reservedToUser = $this->getUser();
+ $this->assertNotEquals($user->id, $reservedToUser->id);
+
+ $appeal = Appeal::factory()->create([ 'handlingadmin' => $reservedToUser->id, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.reserve', $appeal));
+ $response->assertStatus(403);
+ $response->assertSee("This appeal has already been reserved.");
+
+ $appeal->refresh();
+ $this->assertEquals($reservedToUser->id, $appeal->handlingadmin);
+ $this->assertFalse($appeal->comments()
+ ->where('action', 'reserve')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_cant_reserve_appeal_that_they_cant_see()
+ {
+ $user = $this->getUser();
+ $wiki = MediaWikiRepository::getSupportedTargets()[1];
+
+ $appeal = Appeal::factory()->create([ 'wiki' => $wiki, 'handlingadmin' => null, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.reserve', $appeal));
+ $response->assertStatus(403);
+ $response->assertSee("Only $wiki administrators are able to see this appeal.");
+
+ $appeal->refresh();
+ $this->assertNull($appeal->handlingadmin);
+ $this->assertFalse($appeal->comments()
+ ->where('action', 'reserve')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_can_release_own_appeal()
+ {
+ $user = $this->getUser();
+ $appeal = Appeal::factory()->create([ 'handlingadmin' => $user->id, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.release', $appeal));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertNull($appeal->handlingadmin);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 'release')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_cant_release_other_appeal()
+ {
+ $user = $this->getUser();
+ $reservedToUser = $this->getUser();
+ $this->assertNotEquals($user->id, $reservedToUser->id);
+
+ $appeal = Appeal::factory()->create([ 'handlingadmin' => $reservedToUser->id, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.release', $appeal));
+ $response->assertStatus(403);
+ $response->assertSee("Only tool administrators can force release appeals.");
+
+ $appeal->refresh();
+ $this->assertEquals($reservedToUser->id, $appeal->handlingadmin);
+ $this->assertFalse($appeal->comments()
+ ->where('action', 'release')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_tool_admin_can_release_other_appeal()
+ {
+ $user = $this->getTooladminUser();
+ $reservedToUser = $this->getUser();
+ $this->assertNotEquals($user->id, $reservedToUser->id);
+
+ $appeal = Appeal::factory()->create([ 'handlingadmin' => $reservedToUser->id, ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.release', $appeal));
+ $response->assertRedirect();
+
+ $appeal->refresh();
+ $this->assertNull($appeal->handlingadmin);
+ $this->assertTrue($appeal->comments()
+ ->where('action', 'release')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+
+ public function test_user_cant_release_appeal_they_cant_see()
+ {
+ $user = $this->getUser();
+ $reservedToUser = $this->getUser([ 'ptwiki' => [ 'user', 'admin', ], ]);
+ $this->assertNotEquals($user->id, $reservedToUser->id);
+
+ $wiki = MediaWikiRepository::getSupportedTargets()[1];
+
+ $appeal = Appeal::factory()->create([
+ 'wiki' => $wiki,
+ 'handlingadmin' => $reservedToUser->id,
+ ]);
+
+ $response = $this
+ ->actingAs($user)
+ ->post(route('appeal.action.release', $appeal));
+
+ $response->assertStatus(403);
+ $response->assertSee("Only $wiki administrators are able to see this appeal.");
+
+ $appeal->refresh();
+ $this->assertEquals($reservedToUser->id, $appeal->handlingadmin);
+ $this->assertFalse($appeal->comments()
+ ->where('action', 'reserve')
+ ->where('user_id', $user->id)
+ ->exists());
+ }
+}
diff --git a/tests/Feature/Appeal/Action/BaseAppealActionTest.php b/tests/Feature/Appeal/Action/BaseAppealActionTest.php
new file mode 100644
index 00000000..cc4051a8
--- /dev/null
+++ b/tests/Feature/Appeal/Action/BaseAppealActionTest.php
@@ -0,0 +1,13 @@
+getUser($permissions, $extraData);
}
+
+ protected function getDeveloperUser($extraData = [])
+ {
+ $permissions = $this->userDefaultPermissions;
+ $permissions['*'] = ['developer'];
+ return $this->getUser($permissions, $extraData);
+ }
}